aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/confview.go
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2019-03-11 01:51:47 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2019-03-12 03:00:46 -0600
commit72ffcd0a79fac3112f436c3841c3ca8b3815391d (patch)
treec084e1ea8396e21706c71ef7565c98f96e09cd55 /ui/confview.go
parentbuild: allow make to skip hidden directory to reduce stats (diff)
downloadwireguard-windows-72ffcd0a79fac3112f436c3841c3ca8b3815391d.tar.xz
wireguard-windows-72ffcd0a79fac3112f436c3841c3ca8b3815391d.zip
ui: initial stab at a better confview
Diffstat (limited to 'ui/confview.go')
-rw-r--r--ui/confview.go315
1 files changed, 315 insertions, 0 deletions
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)
+ }
+}