aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--ui/confview.go315
-rw-r--r--ui/syntax/confview.c177
-rw-r--r--ui/syntax/confview.go155
-rw-r--r--ui/syntax/confview.h20
-rw-r--r--ui/tests/Makefile30
-rw-r--r--ui/tests/confview.go70
-rw-r--r--ui/ui.go2
7 files changed, 416 insertions, 353 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)
+ }
+}
diff --git a/ui/syntax/confview.c b/ui/syntax/confview.c
deleted file mode 100644
index b7e6657b..00000000
--- a/ui/syntax/confview.c
+++ /dev/null
@@ -1,177 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <windows.h>
-#include <richedit.h>
-#include <richole.h>
-#include <tom.h>
-
-#include "confview.h"
-#include "highlighter.h"
-
-static WNDPROC parent_proc;
-
-static void set_rtf(HWND hWnd, const char *str)
-{
- SETTEXTEX settextex = {
- .flags = ST_DEFAULT,
- .codepage = CP_ACP
- };
- CHARRANGE orig_selection;
- POINT original_scroll;
-
- SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_GETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_HIDESELECTION, TRUE, 0);
- SendMessage(hWnd, EM_SETTEXTEX, (WPARAM)&settextex, (LPARAM)str);
- SendMessage(hWnd, EM_SETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_HIDESELECTION, FALSE, 0);
- SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
- HideCaret(hWnd);
- RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
-}
-
-static void context_menu(HWND hWnd, INT x, INT y)
-{
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_DEFAULT,
- .codepage = CP_ACP
- };
- /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
- HMENU popup, menu = LoadMenuW(GetModuleHandleW(L"comctl32.dll"), MAKEINTRESOURCEW(1));
- CHARRANGE selection = { 0 };
- bool has_selection, can_selectall;
- UINT cmd;
-
- if (!menu)
- return;
-
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selection);
- has_selection = selection.cpMax - selection.cpMin;
- can_selectall = selection.cpMin || (selection.cpMax < SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0));
-
- popup = GetSubMenu(menu, 0);
- EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, EM_SETSEL, MF_BYCOMMAND | (can_selectall ? MF_ENABLED : MF_GRAYED));
-
- /* Delete items that we don't handle. */
- for (int ctl = GetMenuItemCount(popup) - 1; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE | MIIM_ID
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (menu_item.fType & MFT_SEPARATOR)
- continue;
- switch (menu_item.wID) {
- case WM_COPY:
- case EM_SETSEL:
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
- /* Delete trailing and adjacent separators. */
- for (int ctl = GetMenuItemCount(popup) - 1, end = true; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR)) {
- end = false;
- continue;
- }
- if (!end && ctl) {
- if (!GetMenuItemInfoW(popup, ctl - 1, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR))
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
-
- if (x == -1 && y == -1) {
- RECT rect;
- GetWindowRect(hWnd, &rect);
- x = rect.left + (rect.right - rect.left) / 2;
- y = rect.top + (rect.bottom - rect.top) / 2;
- }
-
- if (GetFocus() != hWnd)
- SetFocus(hWnd);
-
- cmd = TrackPopupMenu(popup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hWnd, NULL);
- if (cmd)
- SendMessage(hWnd, cmd, 0, cmd == EM_SETSEL ? -1 : 0);
-
- DestroyMenu(menu);
-}
-
-static LRESULT CALLBACK child_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
-{
- switch (Msg) {
- case WM_CREATE:
- HideCaret(hWnd);
- break;
- case WM_LBUTTONDOWN:
- case WM_SETFOCUS: {
- LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam);
- HideCaret(hWnd);
- return ret;
- }
- case WM_SETCURSOR:
- return 0;
- case PV_NEWRTF:
- set_rtf(hWnd, (const char *)wParam);
- return 0;
- case WM_CONTEXTMENU:
- context_menu(hWnd, LOWORD(lParam), HIWORD(lParam));
- return 0;
- }
- return parent_proc(hWnd, Msg, wParam, lParam);
-}
-
-static long has_loaded = 0;
-
-bool register_conf_view(void)
-{
- WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) };
- WNDPROC pp;
- HANDLE lib;
-
- if (InterlockedCompareExchange(&has_loaded, 1, 0) != 0)
- return !!parent_proc;
-
- lib = LoadLibraryExW(L"msftedit.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
- if (!lib)
- return false;
-
- if (!GetClassInfoExW(NULL, L"RICHEDIT50W", &class))
- goto err;
- pp = class.lpfnWndProc;
- if (!pp)
- goto err;
- class.cbSize = sizeof(WNDCLASSEXW);
- class.hInstance = GetModuleHandleW(NULL);
- class.lpszClassName = L"WgConfView";
- class.lpfnWndProc = child_proc;
- if (!RegisterClassExW(&class))
- goto err;
- parent_proc = pp;
- return true;
-
-err:
- FreeLibrary(lib);
- return false;
-}
diff --git a/ui/syntax/confview.go b/ui/syntax/confview.go
deleted file mode 100644
index ef1c4530..00000000
--- a/ui/syntax/confview.go
+++ /dev/null
@@ -1,155 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package syntax
-
-import (
- "fmt"
- "strconv"
- "strings"
- "unsafe"
-
- "github.com/lxn/walk"
- "golang.zx2c4.com/wireguard/windows/conf"
-)
-
-// #include "confview.h"
-import "C"
-
-type ConfView struct {
- walk.WidgetBase
- lastRtf string
-}
-
-func (cv *ConfView) LayoutFlags() walk.LayoutFlags {
- return walk.GrowableHorz | walk.GrowableVert | walk.GreedyHorz | walk.GreedyVert
-}
-
-func (cv *ConfView) MinSizeHint() walk.Size {
- return walk.Size{20, 12}
-}
-
-func (cv *ConfView) SizeHint() walk.Size {
- return walk.Size{200, 100}
-}
-
-func (cv *ConfView) SetConfiguration(conf *conf.Config) {
- var output strings.Builder
-
- if conf == nil {
- t := byte(0)
- cv.SendMessage(C.PV_NEWRTF, uintptr(unsafe.Pointer(&t)), 0)
- return
- }
-
- escape := func(s string) string {
- var o strings.Builder
- for i := 0; i < len(s); i++ {
- if s[i] > 127 || s[i] == '}' || s[i] == '{' || s[i] == '\\' {
- o.WriteString(fmt.Sprintf("\\'%d", s[i]))
- continue
- }
- o.WriteByte(s[i])
- }
- return o.String()
- }
- field := func(key, value string) {
- output.WriteString(fmt.Sprintf("{\\b %s:} %s\\par", escape(key), escape(value)))
- }
-
- output.WriteString("{\\rtf1\\ansi\\fs20")
-
- field("Interface", conf.Name)
- field("Public Key", conf.Interface.PrivateKey.Public().String())
- if conf.Interface.ListenPort > 0 {
- field("Listen Port", strconv.Itoa(int(conf.Interface.ListenPort)))
- }
-
- if conf.Interface.Mtu > 0 {
- field("MTU", strconv.Itoa(int(conf.Interface.Mtu)))
- }
-
- if len(conf.Interface.Addresses) > 0 {
- addrStrings := make([]string, len(conf.Interface.Addresses))
- for i, address := range conf.Interface.Addresses {
- addrStrings[i] = address.String()
- }
- field("Address", strings.Join(addrStrings[:], ", "))
- }
-
- if len(conf.Interface.Dns) > 0 {
- addrStrings := make([]string, len(conf.Interface.Dns))
- for i, address := range conf.Interface.Dns {
- addrStrings[i] = address.String()
- }
- field("DNS", strings.Join(addrStrings[:], ", "))
- }
-
- for _, peer := range conf.Peers {
- output.WriteString("\\par")
- field("Peer", peer.PublicKey.String())
-
- if !peer.PresharedKey.IsZero() {
- output.WriteString("{\\b Preshared Key:} {\\i enabled}\\par")
- }
-
- if len(peer.AllowedIPs) > 0 {
- addrStrings := make([]string, len(peer.AllowedIPs))
- for i, address := range peer.AllowedIPs {
- addrStrings[i] = address.String()
- }
- field("Allowed IPs", strings.Join(addrStrings[:], ", "))
- }
-
- if !peer.Endpoint.IsEmpty() {
- field("Endpoint", peer.Endpoint.String())
- }
-
- if peer.PersistentKeepalive > 0 {
- field("Persistent Keepalive", strconv.Itoa(int(peer.PersistentKeepalive)))
- }
-
- if !peer.LastHandshakeTime.IsEmpty() {
- field("Latest Handshake", peer.LastHandshakeTime.String())
- }
-
- if peer.RxBytes > 0 || peer.TxBytes > 0 {
- field("Transfer", fmt.Sprintf("%s received, %s sent", peer.RxBytes.String(), peer.TxBytes.String()))
- }
- }
-
- output.WriteString("}")
-
- text := output.String()
- if text == cv.lastRtf {
- return
- }
- cv.lastRtf = text
-
- t := C.CString(text)
- cv.SendMessage(C.PV_NEWRTF, uintptr(unsafe.Pointer(t)), 0)
- C.free(unsafe.Pointer(t))
-}
-
-func NewConfView(parent walk.Container) (*ConfView, error) {
- C.register_conf_view()
- cv := &ConfView{
- lastRtf: "",
- }
- err := walk.InitWidget(
- cv,
- parent,
- "WgConfView",
- C.CONFVIEW_STYLE,
- C.CONFVIEW_EXTSTYLE,
- )
- if err != nil {
- return nil, err
- }
-
- cv.GraphicsEffects().Add(walk.InteractionEffect)
- cv.GraphicsEffects().Add(walk.FocusEffect)
- return cv, nil
-}
diff --git a/ui/syntax/confview.h b/ui/syntax/confview.h
deleted file mode 100644
index d415dfe8..00000000
--- a/ui/syntax/confview.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#ifndef CONFVIEW_H
-#define CONFVIEW_H
-
-#include <stdbool.h>
-#include <windows.h>
-#include <richedit.h>
-
-#define CONFVIEW_STYLE (WS_CHILD | WS_CLIPSIBLINGS | ES_MULTILINE | WS_VISIBLE | WS_VSCROLL | ES_READONLY | WS_TABSTOP | ES_WANTRETURN | ES_NOOLEDRAGDROP)
-#define CONFVIEW_EXTSTYLE (WS_EX_TRANSPARENT)
-
-#define PV_NEWRTF (WM_USER + 0x3200)
-
-extern bool register_conf_view(void);
-
-#endif
diff --git a/ui/tests/Makefile b/ui/tests/Makefile
new file mode 100644
index 00000000..c71af21d
--- /dev/null
+++ b/ui/tests/Makefile
@@ -0,0 +1,30 @@
+export CFLAGS := -O3 -Wall -std=gnu11
+export CC := x86_64-w64-mingw32-gcc
+WINDRES := x86_64-w64-mingw32-windres
+export CGO_ENABLED := 1
+export GOOS := windows
+export GOARCH := amd64
+
+DEPLOYMENT_HOST ?= winvm
+DEPLOYMENT_PATH ?= Desktop
+
+all: tests.exe
+
+resources.syso: ../../resources.rc ../../manifest.xml ../icon/icon.ico
+ $(WINDRES) -i $< -o $@ -O coff
+
+rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
+tests.exe: resources.syso $(call rwildcard,..,*.go *.c *.h)
+ go build -ldflags="-s -w" -v -o $@
+
+deploy: tests.exe
+ -ssh $(DEPLOYMENT_HOST) -- 'taskkill /im tests.exe /f'
+ scp tests.exe $(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH)
+
+run: tests.exe
+ wine tests.exe
+
+clean:
+ rm -rf resources.syso tests.exe
+
+.PHONY: deploy run clean all
diff --git a/ui/tests/confview.go b/ui/tests/confview.go
new file mode 100644
index 00000000..6907d83d
--- /dev/null
+++ b/ui/tests/confview.go
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package main
+
+import (
+ "github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/ui"
+ "log"
+ "runtime"
+)
+
+
+const demoConfig = `[Interface]
+PrivateKey = 6KpcbNFK4tKBciKBT2Rj6Z/sHBqxdV+p+nuNA5AlWGI=
+Address = 192.168.4.84/24
+DNS = 8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1
+
+[Peer]
+PublicKey = JRI8Xc0zKP9kXk8qP84NdUQA04h6DLfFbwJn4g+/PFs=
+Endpoint = demo.wireguard.com:12912
+AllowedIPs = 0.0.0.0/0
+`
+
+func main(){
+ mw, _ := walk.NewMainWindowWithName("Test ConfView")
+ mw.SetSize(walk.Size{600, 800})
+ mw.SetLayout(walk.NewVBoxLayout())
+ cv, err := ui.NewConfView(mw)
+ if err != nil {
+ log.Fatal(err)
+ }
+ config, _ := conf.FromWgQuick(demoConfig, "demo")
+ peer := config.Peers[0]
+ config.Peers = make([]conf.Peer, 0)
+
+ pb1, _ := walk.NewPushButton(mw)
+ pb1.SetText("Add and increment")
+ pb1.Clicked().Attach(func() {
+ config.Interface.ListenPort++
+ config.Peers = append(config.Peers, peer)
+ k,_ := conf.NewPrivateKey()
+ config.Peers[len(config.Peers) - 1].PublicKey = *k
+ cv.SetConfiguration(config)
+ })
+ pb2, _ := walk.NewPushButton(mw)
+ pb2.SetText("Remove first peer")
+ pb2.Clicked().Attach(func() {
+ if len(config.Peers) < 1 {
+ return
+ }
+ config.Interface.ListenPort--
+ config.Peers = config.Peers[1:]
+ cv.SetConfiguration(config)
+ })
+ pb3, _ := walk.NewPushButton(mw)
+ pb3.SetText("Toggle MTU")
+ pb3.Clicked().Attach(func() {
+ config.Interface.Mtu = (config.Interface.Mtu + 1) % 2
+ cv.SetConfiguration(config)
+ })
+ mw.SetVisible(true)
+ mw.Show()
+ mw.Activate()
+ mw.Run()
+ runtime.KeepAlive(cv)
+} \ No newline at end of file
diff --git a/ui/ui.go b/ui/ui.go
index db97823d..66c5fc12 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -97,7 +97,7 @@ func RunUI() {
se.SetText(demoConfig)
}
- cv, _ := syntax.NewConfView(mw)
+ cv, _ := NewConfView(mw)
cv.SetVisible(false)
cv.SetEnabled(false)