From 72ffcd0a79fac3112f436c3841c3ca8b3815391d Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 11 Mar 2019 01:51:47 +0100 Subject: ui: initial stab at a better confview --- ui/confview.go | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 ui/confview.go (limited to 'ui/confview.go') diff --git a/ui/confview.go b/ui/confview.go new file mode 100644 index 00000000..18723a0b --- /dev/null +++ b/ui/confview.go @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package ui + +import ( + "fmt" + "github.com/lxn/walk" + "github.com/lxn/win" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/conf" + "reflect" + "strconv" + "strings" + "unsafe" +) + +type labelTextLine struct { + label *walk.TextLabel + text *walk.LineEdit +} + +type interfaceView struct { + publicKey *labelTextLine + listenPort *labelTextLine + mtu *labelTextLine + addresses *labelTextLine + dns *labelTextLine +} + +type peerView struct { + publicKey *labelTextLine + presharedKey *labelTextLine + allowedIPs *labelTextLine + endpoint *labelTextLine + persistentKeepalive *labelTextLine + latestHandshake *labelTextLine + transfer *labelTextLine +} + +type ConfView struct { + *walk.ScrollView + name *walk.GroupBox + interfaze *interfaceView + peers map[conf.Key]*peerView + spacer *walk.Spacer + + originalWndProc uintptr + creatingThread uint32 +} + +func (lt *labelTextLine) show(text string) { + s, e := lt.text.TextSelection() + lt.text.SetText(text) + lt.label.SetVisible(true) + lt.text.SetVisible(true) + lt.text.SetTextSelection(s, e) +} + +func (lt *labelTextLine) hide() { + lt.text.SetText("") + lt.label.SetVisible(false) + lt.text.SetVisible(false) +} + +func newLabelTextLine(fieldName string, parent walk.Container) *labelTextLine { + lt := new(labelTextLine) + lt.label, _ = walk.NewTextLabel(parent) + lt.label.SetText(fieldName + ":") + lt.label.SetTextAlignment(walk.AlignHFarVNear) + lt.label.SetVisible(false) + + lt.text, _ = walk.NewLineEdit(parent) + win.SetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE, win.GetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE) + lt.text.SetReadOnly(true) + lt.text.SetBackground(walk.NullBrush()) + lt.text.SetVisible(false) + lt.text.FocusedChanged().Attach(func() { + lt.text.SetTextSelection(0, 0) + }) + return lt +} + +func newInterfaceView(parent walk.Container) *interfaceView { + iv := &interfaceView{ + newLabelTextLine("Public key", parent), + newLabelTextLine("Listen port", parent), + newLabelTextLine("MTU", parent), + newLabelTextLine("Addresses", parent), + newLabelTextLine("DNS", parent), + } + layoutInGrid(iv, parent.Layout().(*walk.GridLayout)) + return iv +} + +func newPeerView(parent walk.Container) *peerView { + pv := &peerView{ + newLabelTextLine("Public key", parent), + newLabelTextLine("Preshared key", parent), + newLabelTextLine("Allowed IPs", parent), + newLabelTextLine("Endpoint", parent), + newLabelTextLine("Persistent keepalive", parent), + newLabelTextLine("Latest handshake", parent), + newLabelTextLine("Transfer", parent), + } + layoutInGrid(pv, parent.Layout().(*walk.GridLayout)) + return pv +} + +func layoutInGrid(view interface{}, layout *walk.GridLayout) { + v := reflect.ValueOf(view).Elem() + for i := 0; i < v.NumField(); i++ { + lt := (*labelTextLine)(unsafe.Pointer(v.Field(i).Pointer())) + layout.SetRange(lt.label, walk.Rectangle{0, i, 1, 1}) + layout.SetRange(lt.text, walk.Rectangle{2, i, 1, 1}) + } +} + +func (iv *interfaceView) apply(c *conf.Interface) { + iv.publicKey.show(c.PrivateKey.Public().String()) + + if c.ListenPort > 0 { + iv.listenPort.show(strconv.Itoa(int(c.ListenPort))) + } else { + iv.listenPort.hide() + } + + if c.Mtu > 0 { + iv.mtu.show(strconv.Itoa(int(c.Mtu))) + } else { + iv.mtu.hide() + } + + if len(c.Addresses) > 0 { + addrStrings := make([]string, len(c.Addresses)) + for i, address := range c.Addresses { + addrStrings[i] = address.String() + } + iv.addresses.show(strings.Join(addrStrings[:], ", ")) + } else { + iv.addresses.hide() + } + + if len(c.Dns) > 0 { + addrStrings := make([]string, len(c.Dns)) + for i, address := range c.Dns { + addrStrings[i] = address.String() + } + iv.dns.show(strings.Join(addrStrings[:], ", ")) + } else { + iv.dns.hide() + } +} + +func (pv *peerView) apply(c *conf.Peer) { + pv.publicKey.show(c.PublicKey.String()) + + if !c.PresharedKey.IsZero() { + pv.presharedKey.show("enabled") + } else { + pv.presharedKey.hide() + } + + if len(c.AllowedIPs) > 0 { + addrStrings := make([]string, len(c.AllowedIPs)) + for i, address := range c.AllowedIPs { + addrStrings[i] = address.String() + } + pv.allowedIPs.show(strings.Join(addrStrings[:], ", ")) + } else { + pv.allowedIPs.hide() + } + + if !c.Endpoint.IsEmpty() { + pv.endpoint.show(c.Endpoint.String()) + } else { + pv.endpoint.hide() + } + + if c.PersistentKeepalive > 0 { + pv.persistentKeepalive.show(strconv.Itoa(int(c.PersistentKeepalive))) + } else { + pv.persistentKeepalive.hide() + } + + if !c.LastHandshakeTime.IsEmpty() { + pv.latestHandshake.show(c.LastHandshakeTime.String()) + } else { + pv.latestHandshake.hide() + } + + if c.RxBytes > 0 || c.TxBytes > 0 { + pv.transfer.show(fmt.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String())) + } else { + pv.transfer.hide() + } +} + +func newPaddedGroupGrid(parent walk.Container) (group *walk.GroupBox, err error) { + group, err = walk.NewGroupBox(parent) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + group.Dispose() + } + }() + layout := walk.NewGridLayout() + layout.SetMargins(walk.Margins{10, 15, 10, 5}) + err = group.SetLayout(layout) + if err != nil { + return nil, err + } + spacer, err := walk.NewHSpacerFixed(group, 10) + if err != nil { + return nil, err + } + layout.SetRange(spacer, walk.Rectangle{1, 0, 1, 1}) + return group, nil +} + +func NewConfView(parent walk.Container) (*ConfView, error) { + cv := new(ConfView) + cv.ScrollView, _ = walk.NewScrollView(parent) + cv.SetLayout(walk.NewVBoxLayout()) + cv.name, _ = newPaddedGroupGrid(cv) + cv.interfaze = newInterfaceView(cv.name) + cv.peers = make(map[conf.Key]*peerView) + cv.spacer, _ = walk.NewVSpacer(cv) + cv.creatingThread = windows.GetCurrentThreadId() + win.SetWindowLongPtr(cv.Handle(), win.GWLP_USERDATA, uintptr(unsafe.Pointer(cv))) + cv.originalWndProc = win.SetWindowLongPtr(cv.Handle(), win.GWL_WNDPROC, crossThreadMessageHijack) + return cv, nil +} + +//TODO: choose actual good value for this +const crossThreadUpdate = win.WM_APP + 17 + +var crossThreadMessageHijack = windows.NewCallback(func(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { + cv := (*ConfView)(unsafe.Pointer(win.GetWindowLongPtr(hwnd, win.GWLP_USERDATA))) + if msg == crossThreadUpdate { + cv.setConfiguration((*conf.Config)(unsafe.Pointer(wParam))) + return 0 + } + return win.CallWindowProc(cv.originalWndProc, hwnd, msg, wParam, lParam) +}) + +func (cv *ConfView) SetConfiguration(c *conf.Config) { + if cv.creatingThread == windows.GetCurrentThreadId() { + cv.setConfiguration(c) + } else { + cv.SendMessage(crossThreadUpdate, uintptr(unsafe.Pointer(c)), 0) + } +} + +func (cv *ConfView) setConfiguration(c *conf.Config) { + hasSuspended := false + suspend := func() { + if !hasSuspended { + cv.SetSuspended(true) + hasSuspended = true + } + } + defer func() { + if hasSuspended { + cv.SetSuspended(false) + cv.SendMessage(win.WM_SIZING, 0, 0) //TODO: FILTHY HACK! And doesn't work when items disappear. + } + }() + title := "Interface: " + c.Name + if cv.name.Title() != title { + cv.name.SetTitle(title) + } + cv.interfaze.apply(&c.Interface) + inverse := make(map[*peerView]bool, len(cv.peers)) + for _, pv := range cv.peers { + inverse[pv] = true + } + didAddPeer := false + for _, peer := range c.Peers { + if pv := cv.peers[peer.PublicKey]; pv != nil { + pv.apply(&peer) + inverse[pv] = false + } else { + didAddPeer = true + suspend() + group, _ := newPaddedGroupGrid(cv) + group.SetTitle("Peer") + pv := newPeerView(group) + pv.apply(&peer) + cv.peers[peer.PublicKey] = pv + } + } + for pv, remove := range inverse { + if !remove { + continue + } + k, e := conf.NewPrivateKeyFromString(pv.publicKey.text.Text()) + if e != nil { + continue + } + suspend() + delete(cv.peers, *k) + groupBox := pv.publicKey.label.Parent().AsContainerBase().Parent().(*walk.GroupBox) + groupBox.Parent().Children().Remove(groupBox) + groupBox.Dispose() + } + if didAddPeer { + cv.Children().Remove(cv.spacer) + cv.Children().Add(cv.spacer) + } +} -- cgit v1.2.3-59-g8ed1b