aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--ui/syntax/confview.c175
-rw-r--r--ui/syntax/confview.go158
-rw-r--r--ui/syntax/confview.h20
3 files changed, 353 insertions, 0 deletions
diff --git a/ui/syntax/confview.c b/ui/syntax/confview.c
new file mode 100644
index 00000000..9083c252
--- /dev/null
+++ b/ui/syntax/confview.c
@@ -0,0 +1,175 @@
+/* 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);
+}
+
+bool register_conf_view(void)
+{
+ WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) };
+ WNDPROC pp;
+ HANDLE lib;
+
+ if (parent_proc)
+ return true;
+
+ lib = LoadLibraryW(L"msftedit.dll");
+ 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
new file mode 100644
index 00000000..b88a7702
--- /dev/null
+++ b/ui/syntax/confview.go
@@ -0,0 +1,158 @@
+/* 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 init() {
+ C.register_conf_view()
+}
+
+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) {
+ 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
new file mode 100644
index 00000000..d415dfe8
--- /dev/null
+++ b/ui/syntax/confview.h
@@ -0,0 +1,20 @@
+/* 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