aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/syntax/syntaxedit.go
diff options
context:
space:
mode:
authorSimon Rozman <simon@rozman.si>2020-10-28 17:30:58 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-11-13 14:42:54 +0100
commitea200f82c3e855fcb2b5fd56fc5800374b80514f (patch)
tree3b9e7b8ff1792c4bb4f9b67d6e7edebba6201cea /ui/syntax/syntaxedit.go
parentbuild: remove duplicated ld flags (diff)
downloadwireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.tar.xz
wireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.zip
syntax: port to go
Arm has no CGo support, so port the syntax editor C code to Go and hope that it's fast enough. This is a pretty literal/unsafe translation from the C. Signed-off-by: Simon Rozman <simon@rozman.si> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/syntax/syntaxedit.go')
-rw-r--r--ui/syntax/syntaxedit.go422
1 files changed, 390 insertions, 32 deletions
diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go
index 257bdbd2..3bbcfd9c 100644
--- a/ui/syntax/syntaxedit.go
+++ b/ui/syntax/syntaxedit.go
@@ -1,35 +1,41 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2020 WireGuard LLC. All Rights Reserved.
*/
package syntax
import (
"errors"
+ "fmt"
"strings"
+ "sync/atomic"
"syscall"
"unsafe"
"github.com/lxn/walk"
"github.com/lxn/win"
+ "golang.org/x/sys/windows"
)
-// #cgo LDFLAGS: -lgdi32
-// #include "syntaxedit.h"
-import "C"
-
type SyntaxEdit struct {
walk.WidgetBase
+ irich *win.IRichEditOle
+ idoc *win.ITextDocument
+ lastBlockState BlockState
+ yheight int
+ highlightGuard uint32
textChangedPublisher walk.EventPublisher
privateKeyPublisher walk.StringEventPublisher
blockUntunneledTrafficPublisher walk.IntEventPublisher
}
+type BlockState int
+
const (
- InevaluableBlockingUntunneledTraffic = C.InevaluableBlockingUntunneledTraffic
- BlockingUntunneledTraffic = C.BlockingUntunneledTraffic
- NotBlockingUntunneledTraffic = C.NotBlockingUntunneledTraffic
+ InevaluableBlockingUntunneledTraffic BlockState = iota
+ BlockingUntunneledTraffic
+ NotBlockingUntunneledTraffic
)
func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags {
@@ -63,7 +69,6 @@ func (se *SyntaxEdit) SetText(text string) (err error) {
if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
err = errors.New("WM_SETTEXT failed")
}
- se.textChangedPublisher.Publish()
return
}
@@ -79,42 +84,388 @@ func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent {
return se.blockUntunneledTrafficPublisher.Event()
}
-func (se *SyntaxEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
+type spanStyle struct {
+ color win.COLORREF
+ effects uint32
+}
+
+var stylemap = map[highlight]spanStyle{
+ highlightSection: spanStyle{color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD},
+ highlightField: spanStyle{color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD},
+ highlightPrivateKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPublicKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPresharedKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightIP: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightCidr: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightHost: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightPort: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightMTU: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightKeepalive: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightComment: spanStyle{color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC},
+ highlightDelimiter: spanStyle{color: win.RGB(0x00, 0x00, 0x00)},
+ highlightCmd: spanStyle{color: win.RGB(0x63, 0x75, 0x89)},
+ highlightError: spanStyle{color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE},
+}
+
+func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {
+ state := InevaluableBlockingUntunneledTraffic
+ var onAllowedIPs,
+ seenPeer,
+ seen00v6,
+ seen00v4,
+ seen01v6,
+ seen80001v6,
+ seen01v4,
+ seen1281v4 bool
+
+ for i := range spans {
+ span := &spans[i]
+ switch span.t {
+ case highlightError:
+ goto done
+ case highlightSection:
+ if !strings.EqualFold(cfg[span.s:span.s+span.len], "[Peer]") {
+ break
+ }
+ if !seenPeer {
+ seenPeer = true
+ } else {
+ goto done
+ }
+ break
+ case highlightField:
+ onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs")
+ break
+ case highlightIP:
+ if !onAllowedIPs || !seenPeer {
+ break
+ }
+ if i+2 >= len(spans) || spans[i+1].t != highlightDelimiter || spans[i+2].t != highlightCidr {
+ break
+ }
+ if spans[i+2].len != 1 {
+ break
+ }
+ switch cfg[spans[i+2].s] {
+ case '0':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen00v4 = true
+ case "::":
+ seen00v6 = true
+ }
+ case '1':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen01v4 = true
+ case "128.0.0.0":
+ seen1281v4 = true
+ case "::":
+ seen01v6 = true
+ case "8000::":
+ seen80001v6 = true
+ }
+ }
+ break
+ }
+ }
+
+ if seen00v4 || seen00v6 {
+ state = BlockingUntunneledTraffic
+ } else if (seen01v4 && seen1281v4) || (seen01v6 && seen80001v6) {
+ state = NotBlockingUntunneledTraffic
+ }
+
+done:
+ if state != se.lastBlockState {
+ se.blockUntunneledTrafficPublisher.Publish(int(state))
+ se.lastBlockState = state
+ }
+}
+
+func (se *SyntaxEdit) highlightText() error {
+ if !atomic.CompareAndSwapUint32(&se.highlightGuard, 0, 1) {
+ return nil
+ }
+ defer atomic.StoreUint32(&se.highlightGuard, 0)
+
+ hWnd := se.Handle()
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_NUMBYTES,
+ Codepage: win.CP_ACP, // Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes.
+ }
+ msgSize := uint32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))
+ if msgSize == win.E_INVALIDARG {
+ return errors.New("Failed to get text length")
+ }
+
+ gettextex := win.GETTEXTEX{
+ Flags: win.GT_NOHIDDENTEXT,
+ Codepage: gettextlengthex.Codepage,
+ Cb: msgSize + 1,
+ }
+ msg := make([]byte, msgSize+1)
+ msgCount := win.SendMessage(hWnd, win.EM_GETTEXTEX, uintptr(unsafe.Pointer(&gettextex)), uintptr(unsafe.Pointer(&msg[0])))
+ if msgCount < 0 {
+ return errors.New("Failed to get text")
+ }
+ cfg := strings.Replace(string(msg[:msgCount]), "\r", "\n", -1)
+
+ spans := highlightConfig(cfg)
+ se.evaluateUntunneledBlocking(cfg, spans)
+
+ se.idoc.Undo(win.TomSuspend, nil)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.FALSE, 0)
+ var origSelection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ var origScroll win.POINT
+ win.SendMessage(hWnd, win.EM_GETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.TRUE, 0)
+ format := win.CHARFORMAT2{
+ CHARFORMAT: win.CHARFORMAT{
+ CbSize: uint32(unsafe.Sizeof(win.CHARFORMAT2{})),
+ DwMask: win.CFM_COLOR | win.CFM_CHARSET | win.CFM_SIZE | win.CFM_BOLD | win.CFM_ITALIC | win.CFM_UNDERLINE,
+ DwEffects: win.CFE_AUTOCOLOR,
+ BCharSet: win.ANSI_CHARSET,
+ },
+ }
+ if se.yheight != 0 {
+ format.YHeight = 20 * 10
+ }
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_ALL, uintptr(unsafe.Pointer(&format)))
+ bgColor := win.COLORREF(win.GetSysColor(win.COLOR_WINDOW))
+ bgInversion := (bgColor & win.RGB(0xFF, 0xFF, 0xFF)) ^ win.RGB(0xFF, 0xFF, 0xFF)
+ win.SendMessage(hWnd, win.EM_SETBKGNDCOLOR, 0, uintptr(bgColor))
+ numSpans := len(spans)
+ foundPrivateKey := false
+ for i := range spans {
+ span := &spans[i]
+ if numSpans <= 2048 {
+ selection := win.CHARRANGE{int32(span.s), int32(span.s + span.len)}
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ format.CrTextColor = stylemap[span.t].color ^ bgInversion
+ format.DwEffects = stylemap[span.t].effects
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_SELECTION, uintptr(unsafe.Pointer(&format)))
+ }
+ if span.t == highlightPrivateKey && !foundPrivateKey {
+ privateKey := cfg[span.s : span.s+span.len]
+ se.privateKeyPublisher.Publish(privateKey)
+ foundPrivateKey = true
+ }
+ }
+ win.SendMessage(hWnd, win.EM_SETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.FALSE, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.TRUE, 0)
+ win.RedrawWindow(hWnd, nil, 0, win.RDW_ERASE|win.RDW_FRAME|win.RDW_INVALIDATE|win.RDW_ALLCHILDREN)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ se.idoc.Undo(win.TomResume, nil)
+ if !foundPrivateKey {
+ se.privateKeyPublisher.Publish("")
+ }
+ return nil
+}
+
+func (se *SyntaxEdit) contextMenu(x, y int32) error {
+ /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
+ comctl32UTF16, err := windows.UTF16PtrFromString("comctl32.dll")
+ if err != nil {
+ return err
+ }
+ comctl32Handle := win.GetModuleHandle(comctl32UTF16)
+ if comctl32Handle == 0 {
+ return errors.New("Failed to get comctl32.dll handle")
+ }
+ menu := win.LoadMenu(comctl32Handle, win.MAKEINTRESOURCE(1))
+ if menu == 0 {
+ return errors.New("Failed to load menu")
+ }
+ defer win.DestroyMenu(menu)
+
+ hWnd := se.Handle()
+ enableWhenSelected := uint32(win.MF_GRAYED)
+ var selection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ if selection.CpMin < selection.CpMax {
+ enableWhenSelected = win.MF_ENABLED
+ }
+ enableSelectAll := uint32(win.MF_GRAYED)
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_DEFAULT,
+ Codepage: win.CP_ACP,
+ }
+ if selection.CpMin != 0 || (selection.CpMax < int32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))) {
+ enableSelectAll = win.MF_ENABLED
+ }
+ enableUndo := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANUNDO, 0, 0) != 0 {
+ enableUndo = win.MF_ENABLED
+ }
+ enablePaste := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANPASTE, win.CF_TEXT, 0) != 0 {
+ enablePaste = win.MF_ENABLED
+ }
+
+ popup := win.GetSubMenu(menu, 0)
+ win.EnableMenuItem(popup, win.WM_UNDO, win.MF_BYCOMMAND|enableUndo)
+ win.EnableMenuItem(popup, win.WM_CUT, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_COPY, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_PASTE, win.MF_BYCOMMAND|enablePaste)
+ win.EnableMenuItem(popup, win.WM_CLEAR, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.EM_SETSEL, win.MF_BYCOMMAND|enableSelectAll)
+
+ // Delete items that we don't handle.
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE | win.MIIM_ID,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) != 0 {
+ continue
+ }
+ switch menuItem.WID {
+ case win.WM_UNDO, win.WM_CUT, win.WM_COPY, win.WM_PASTE, win.WM_CLEAR, win.EM_SETSEL:
+ continue
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+ // Delete trailing and adjacent separators.
+ end := true
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ end = false
+ continue
+ }
+ if !end && ctl > 0 {
+ if !win.GetMenuItemInfo(popup, uint32(ctl-1), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ continue
+ }
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+
+ if x == -1 && y == -1 {
+ var rect win.RECT
+ win.GetWindowRect(hWnd, &rect)
+ x = (rect.Left + rect.Right) / 2
+ y = (rect.Top + rect.Bottom) / 2
+ }
+
+ if win.GetFocus() != hWnd {
+ win.SetFocus(hWnd)
+ }
+
+ cmd := win.TrackPopupMenu(popup, win.TPM_LEFTALIGN|win.TPM_RIGHTBUTTON|win.TPM_RETURNCMD|win.TPM_NONOTIFY, x, y, 0, hWnd, nil)
+ if cmd != 0 {
+ lParam := uintptr(0)
+ if cmd == win.EM_SETSEL {
+ lParam = ^uintptr(0)
+ }
+ win.SendMessage(hWnd, cmd, 0, lParam)
+ }
+
+ return nil
+}
+
+func (*SyntaxEdit) NeedsWmSize() bool {
+ return true
+}
+
+func (se *SyntaxEdit) WndProc(hWnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
- case win.WM_NOTIFY, win.WM_COMMAND:
+ case win.WM_DESTROY:
+ if se.idoc != nil {
+ se.idoc.Release()
+ }
+ if se.irich != nil {
+ se.irich.Release()
+ }
+
+ case win.WM_SETTEXT:
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ se.highlightText()
+ win.SendMessage(hWnd, win.EM_EMPTYUNDOBUFFER, 0, 0)
+ se.textChangedPublisher.Publish()
+ return ret
+
+ case win.WM_COMMAND, win.WM_NOTIFY:
switch win.HIWORD(uint32(wParam)) {
case win.EN_CHANGE:
+ se.highlightText()
se.textChangedPublisher.Publish()
}
- // This is a horrible trick from MFC where we reflect the event back to the child.
- se.SendMessage(msg+C.WM_REFLECT, wParam, lParam)
- case C.SE_PRIVATE_KEY:
- if lParam == 0 {
- se.privateKeyPublisher.Publish("")
- } else {
- se.privateKeyPublisher.Publish(C.GoString((*C.char)(unsafe.Pointer(lParam))))
+
+ case win.WM_PASTE:
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
+
+ case win.WM_KEYDOWN:
+ key := win.LOWORD(uint32(wParam))
+ if key == 'V' && win.GetKeyState(win.VK_CONTROL) < 0 ||
+ key == win.VK_INSERT && win.GetKeyState(win.VK_SHIFT) < 0 {
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
}
- case C.SE_TRAFFIC_BLOCK:
- se.blockUntunneledTrafficPublisher.Publish(int(lParam))
+
+ case win.WM_CONTEXTMENU:
+ se.contextMenu(win.GET_X_LPARAM(lParam), win.GET_Y_LPARAM(lParam))
+ return 0
+
+ case win.WM_THEMECHANGED:
+ se.highlightText()
+
+ case win.WM_GETDLGCODE:
+ m := (*win.MSG)(unsafe.Pointer(lParam))
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ ret &^= win.DLGC_WANTTAB
+ if m != nil && m.Message == win.WM_KEYDOWN && m.WParam == win.VK_TAB && win.GetKeyState(win.VK_CONTROL) >= 0 {
+ ret &^= win.DLGC_WANTMESSAGE
+ }
+ return ret
}
- return se.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
+
+ return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
}
func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
- C.register_syntax_edit()
+ const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+ _, err := windows.LoadLibraryEx("msftedit.dll", 0, LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to load msftedit.dll: %v", err)
+ }
+
se := &SyntaxEdit{}
- err := walk.InitWidget(
+ if err := walk.InitWidget(
se,
parent,
- "WgQuickSyntaxEdit",
- C.SYNTAXEDIT_STYLE,
- C.SYNTAXEDIT_EXTSTYLE,
- )
- if err != nil {
+ win.MSFTEDIT_CLASS,
+ win.WS_CHILD|win.ES_MULTILINE|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_BORDER|win.WS_HSCROLL|win.WS_TABSTOP|win.ES_WANTRETURN|win.ES_NOOLEDRAGDROP,
+ 0); err != nil {
return nil, err
}
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(parent.DPI()), 0)
-
+ hWnd := se.Handle()
+ win.SetWindowLong(hWnd, win.GWL_EXSTYLE, win.GetWindowLong(hWnd, win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
+ win.SendMessage(hWnd, win.EM_GETOLEINTERFACE, 0, uintptr(unsafe.Pointer(&se.irich)))
+ var idoc unsafe.Pointer
+ se.irich.QueryInterface(&win.IID_ITextDocument, &idoc)
+ se.idoc = (*win.ITextDocument)(idoc)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ win.SendMessage(hWnd, win.EM_SETTEXTMODE, win.TM_SINGLECODEPAGE, 0)
+ se.ApplyDPI(parent.DPI())
se.GraphicsEffects().Add(walk.InteractionEffect)
se.GraphicsEffects().Add(walk.FocusEffect)
se.MustRegisterProperty("Text", walk.NewProperty(
@@ -128,10 +479,17 @@ func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
return se.SetText("")
},
se.textChangedPublisher.Event()))
-
return se, nil
}
func (se *SyntaxEdit) ApplyDPI(dpi int) {
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(dpi), 0)
+ hWnd := se.Handle()
+ hdc := win.GetDC(hWnd)
+ logPixels := win.GetDeviceCaps(hdc, win.LOGPIXELSY)
+ if se.yheight != 0 {
+ win.SendMessage(hWnd, win.EM_SETZOOM, uintptr(logPixels), uintptr(dpi))
+ }
+ se.yheight = 20 * 10 * dpi / int(logPixels)
+ win.ReleaseDC(hWnd, hdc)
+ se.highlightText()
}