aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/aboutdialog.go15
-rw-r--r--ui/confview.go53
-rw-r--r--ui/editdialog.go65
-rw-r--r--ui/filesave.go4
-rw-r--r--ui/icon/dot.svg (renamed from ui/icon/dot-gray.svg)0
-rw-r--r--ui/iconprovider.go18
-rw-r--r--ui/listview.go6
-rw-r--r--ui/logpage.go6
-rw-r--r--ui/managewindow.go8
-rw-r--r--ui/raise.go4
-rw-r--r--ui/syntax/highlighter.c641
-rw-r--r--ui/syntax/highlighter.go631
-rw-r--r--ui/syntax/highlighter.h39
-rw-r--r--ui/syntax/syntaxedit.c415
-rw-r--r--ui/syntax/syntaxedit.go434
-rw-r--r--ui/syntax/syntaxedit.h31
-rw-r--r--ui/tray.go185
-rw-r--r--ui/tunnelspage.go45
-rw-r--r--ui/ui.go13
-rw-r--r--ui/updatepage.go12
20 files changed, 1306 insertions, 1319 deletions
diff --git a/ui/aboutdialog.go b/ui/aboutdialog.go
index 007ddea6..0f939b15 100644
--- a/ui/aboutdialog.go
+++ b/ui/aboutdialog.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -12,13 +12,16 @@ import (
"github.com/lxn/walk"
"github.com/lxn/win"
"golang.org/x/sys/windows"
- "golang.zx2c4.com/wireguard/device"
+ "golang.zx2c4.com/wireguard/windows/driver"
+
"golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/version"
)
-var easterEggIndex = -1
-var showingAboutDialog *walk.Dialog
+var (
+ easterEggIndex = -1
+ showingAboutDialog *walk.Dialog
+)
func onAbout(owner walk.Form) {
showError(runAboutDialog(owner), owner)
@@ -95,7 +98,7 @@ func runAboutDialog(owner walk.Form) error {
return err
}
detailsLbl.SetTextAlignment(walk.AlignHCenterVNear)
- detailsLbl.SetText(l18n.Sprintf("App version: %s\nGo backend version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, device.WireGuardGoVersion, strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), runtime.GOARCH))
+ detailsLbl.SetText(l18n.Sprintf("App version: %s\nDriver version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, driver.Version(), strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), version.Arch()))
copyrightLbl, err := walk.NewTextLabel(showingAboutDialog)
if err != nil {
@@ -104,7 +107,7 @@ func runAboutDialog(owner walk.Form) error {
copyrightFont, _ := walk.NewFont("Segoe UI", 7, 0)
copyrightLbl.SetFont(copyrightFont)
copyrightLbl.SetTextAlignment(walk.AlignHCenterVNear)
- copyrightLbl.SetText("Copyright © 2015-2020 Jason A. Donenfeld. All Rights Reserved.")
+ copyrightLbl.SetText("Copyright © 2015-2022 Jason A. Donenfeld. All Rights Reserved.")
buttonCP, err := walk.NewComposite(showingAboutDialog)
if err != nil {
diff --git a/ui/confview.go b/ui/confview.go
index e34d81b1..78e4df91 100644
--- a/ui/confview.go
+++ b/ui/confview.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -50,6 +50,8 @@ type interfaceView struct {
mtu *labelTextLine
addresses *labelTextLine
dns *labelTextLine
+ scripts *labelTextLine
+ table *labelTextLine
toggleActive *toggleActiveLine
lines []widgetsLine
}
@@ -305,6 +307,8 @@ func newInterfaceView(parent walk.Container) (*interfaceView, error) {
{l18n.Sprintf("MTU:"), &iv.mtu},
{l18n.Sprintf("Addresses:"), &iv.addresses},
{l18n.Sprintf("DNS servers:"), &iv.dns},
+ {l18n.Sprintf("Scripts:"), &iv.scripts},
+ {l18n.Sprintf("Table:"), &iv.table},
}
if iv.lines, err = createLabelTextLines(items, parent, &disposables); err != nil {
return nil, err
@@ -364,7 +368,11 @@ func (iv *interfaceView) widgetsLines() []widgetsLine {
}
func (iv *interfaceView) apply(c *conf.Interface) {
- iv.publicKey.show(c.PrivateKey.Public().String())
+ if IsAdmin {
+ iv.publicKey.show(c.PrivateKey.Public().String())
+ } else {
+ iv.publicKey.hide()
+ }
if c.ListenPort > 0 {
iv.listenPort.show(strconv.Itoa(int(c.ListenPort)))
@@ -398,6 +406,35 @@ func (iv *interfaceView) apply(c *conf.Interface) {
} else {
iv.dns.hide()
}
+
+ var scriptsInUse []string
+ if len(c.PreUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-up"))
+ }
+ if len(c.PostUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-up"))
+ }
+ if len(c.PreDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-down"))
+ }
+ if len(c.PostDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-down"))
+ }
+ if len(scriptsInUse) > 0 {
+ if conf.AdminBool("DangerousScriptExecution") {
+ iv.scripts.show(strings.Join(scriptsInUse, l18n.EnumerationSeparator()))
+ } else {
+ iv.scripts.show(l18n.Sprintf("disabled, per policy"))
+ }
+ } else {
+ iv.scripts.hide()
+ }
+
+ if c.TableOff {
+ iv.table.show(l18n.Sprintf("off"))
+ } else {
+ iv.table.hide()
+ }
}
func (pv *peerView) widgetsLines() []widgetsLine {
@@ -405,9 +442,13 @@ func (pv *peerView) widgetsLines() []widgetsLine {
}
func (pv *peerView) apply(c *conf.Peer) {
- pv.publicKey.show(c.PublicKey.String())
+ if IsAdmin {
+ pv.publicKey.show(c.PublicKey.String())
+ } else {
+ pv.publicKey.hide()
+ }
- if !c.PresharedKey.IsZero() {
+ if !c.PresharedKey.IsZero() && IsAdmin {
pv.presharedKey.show(l18n.Sprintf("enabled"))
} else {
pv.presharedKey.hide()
@@ -564,7 +605,7 @@ func (cv *ConfView) onToggleActiveClicked() {
}()
}
-func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
cv.Synchronize(func() {
cv.interfaze.toggleActive.updateGlobal(globalState)
if cv.tunnel != nil && cv.tunnel.Name == tunnel.Name {
@@ -587,7 +628,7 @@ func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state manager.Tunnel
}
func (cv *ConfView) SetTunnel(tunnel *manager.Tunnel) {
- cv.tunnel = tunnel //XXX: This races with the read in the updateTicker, but it's pointer-sized!
+ cv.tunnel = tunnel // XXX: This races with the read in the updateTicker, but it's pointer-sized!
var config conf.Config
var state manager.TunnelState
diff --git a/ui/editdialog.go b/ui/editdialog.go
index ade6c498..45b25fd0 100644
--- a/ui/editdialog.go
+++ b/ui/editdialog.go
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
+ "net/netip"
"strings"
"github.com/lxn/walk"
@@ -78,7 +79,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
dlg.SetTitle(title)
dlg.SetLayout(layout)
dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
- if icon, err := loadSystemIcon("imageres", 109, 32); err == nil {
+ if icon, err := loadSystemIcon("imageres", -114, 32); err == nil {
dlg.SetIcon(icon)
}
@@ -129,7 +130,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
return nil, err
}
dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)"))
- dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP."))
+ dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, and the interface does not have table off, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP."))
dlg.blockUntunneledTrafficCB.SetVisible(false)
dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged)
@@ -185,15 +186,17 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
return
}
var (
- v40 = [4]byte{}
- v60 = [16]byte{}
- v48 = [4]byte{0x80}
- v68 = [16]byte{0x80}
+ v400 = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
+ v600000 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
+ v401 = netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 1)
+ v600001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 1)
+ v41281 = netip.PrefixFrom(netip.AddrFrom4([4]byte{0x80}), 1)
+ v680001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
)
block := dlg.blockUntunneledTrafficCB.Checked()
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), "temporary")
- var newAllowedIPs []conf.IPCidr
+ var newAllowedIPs []netip.Prefix
if err != nil {
goto err
@@ -202,7 +205,7 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
goto err
}
- newAllowedIPs = make([]conf.IPCidr, 0, len(cfg.Peers[0].AllowedIPs))
+ newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs))
if block {
var (
foundV401 bool
@@ -211,13 +214,13 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
foundV680001 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
- if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
+ if allowedip == v600001 {
foundV600001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v68[:]) {
+ } else if allowedip == v680001 {
foundV680001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
+ } else if allowedip == v401 {
foundV401 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v48[:]) {
+ } else if allowedip == v41281 {
foundV41281 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
@@ -227,44 +230,44 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
goto err
}
if foundV401 && foundV41281 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 0})
+ newAllowedIPs = append(newAllowedIPs, v400)
} else if foundV401 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v401)
} else if foundV41281 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v48[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v41281)
}
if foundV600001 && foundV680001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 0})
+ newAllowedIPs = append(newAllowedIPs, v600000)
} else if foundV600001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v600001)
} else if foundV680001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v68[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
} else {
var (
- foundV400 bool
- foundV600 bool
+ foundV400 bool
+ foundV600000 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
- if allowedip.Cidr == 0 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
- foundV600 = true
- } else if allowedip.Cidr == 0 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
+ if allowedip == v600000 {
+ foundV600000 = true
+ } else if allowedip == v400 {
foundV400 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
}
}
- if !(foundV400 || foundV600) {
+ if !(foundV400 || foundV600000) {
goto err
}
if foundV400 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 1})
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v48[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v401)
+ newAllowedIPs = append(newAllowedIPs, v41281)
}
- if foundV600 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 1})
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v68[:], 1})
+ if foundV600000 {
+ newAllowedIPs = append(newAllowedIPs, v600001)
+ newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
}
@@ -279,7 +282,7 @@ err:
func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) {
dlg.blockUntunneledTraficCheckGuard = true
- switch state {
+ switch syntax.BlockState(state) {
case syntax.InevaluableBlockingUntunneledTraffic:
dlg.blockUntunneledTrafficCB.SetVisible(false)
case syntax.BlockingUntunneledTraffic:
diff --git a/ui/filesave.go b/ui/filesave.go
index 7ca8a2c2..3a54f015 100644
--- a/ui/filesave.go
+++ b/ui/filesave.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -24,7 +24,7 @@ func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func
return true
}
- file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
+ file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o600)
if err != nil {
if os.IsExist(err) {
if walk.DlgCmdNo == walk.MsgBox(owner, l18n.Sprintf("Writing file failed"), l18n.Sprintf(`File ‘%s’ already exists.
diff --git a/ui/icon/dot-gray.svg b/ui/icon/dot.svg
index f2ca5c8d..f2ca5c8d 100644
--- a/ui/icon/dot-gray.svg
+++ b/ui/icon/dot.svg
diff --git a/ui/iconprovider.go b/ui/iconprovider.go
index 8db841c5..154a1628 100644
--- a/ui/iconprovider.go
+++ b/ui/iconprovider.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -73,11 +73,11 @@ func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err err
}
switch state {
case manager.TunnelStarted:
- icon, err = loadSystemIcon("imageres", 101, size)
+ icon, err = loadSystemIcon("imageres", -106, size)
case manager.TunnelStopped:
- icon, err = walk.NewIconFromResourceWithSize("dot-gray.ico", walk.Size{size, size}) // TODO: replace with real icon
+ icon, err = walk.NewIconFromResourceIdWithSize(8, walk.Size{size, size}) // TODO: replace with real icon from imageres/shell32
default:
- icon, err = loadSystemIcon("shell32", 238, size) // TODO: this doesn't look that great overlayed on the app icon
+ icon, err = loadSystemIcon("shell32", -16739, size) // TODO: this doesn't look that great overlayed on the app icon
}
if err == nil {
cachedIconsForWidthAndState[widthAndState{size, state}] = icon
@@ -121,6 +121,14 @@ func loadSystemIcon(dll string, index int32, size int) (icon *walk.Icon, err err
return
}
+func loadShieldIcon(size int) (icon *walk.Icon, err error) {
+ icon, err = loadSystemIcon("imageres", -1028, size)
+ if err != nil {
+ icon, err = loadSystemIcon("imageres", 1, size)
+ }
+ return
+}
+
var cachedLogoIconsForWidth = make(map[int]*walk.Icon)
func loadLogoIcon(size int) (icon *walk.Icon, err error) {
@@ -128,7 +136,7 @@ func loadLogoIcon(size int) (icon *walk.Icon, err error) {
if icon != nil {
return
}
- icon, err = walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{size, size})
+ icon, err = walk.NewIconFromResourceIdWithSize(7, walk.Size{size, size})
if err == nil {
cachedLogoIconsForWidth[size] = icon
}
diff --git a/ui/listview.go b/ui/listview.go
index 60679579..609feb40 100644
--- a/ui/listview.go
+++ b/ui/listview.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -32,7 +32,7 @@ func (t *ListModel) RowCount() int {
return len(t.tunnels)
}
-func (t *ListModel) Value(row, col int) interface{} {
+func (t *ListModel) Value(row, col int) any {
if col != 0 || row < 0 || row >= len(t.tunnels) {
return ""
}
@@ -181,7 +181,7 @@ func (tv *ListView) StyleCell(style *walk.CellStyle) {
}
}
-func (tv *ListView) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (tv *ListView) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
tv.Synchronize(func() {
idx := -1
for i := range tv.model.tunnels {
diff --git a/ui/logpage.go b/ui/logpage.go
index 1de5c920..5b7681b3 100644
--- a/ui/logpage.go
+++ b/ui/logpage.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -164,7 +164,7 @@ func (lp *LogPage) onSave() {
writeFileWithOverwriteHandling(form, fd.FilePath, func(file *os.File) error {
if _, err := ringlogger.Global.WriteTo(file); err != nil {
- return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %v", err)
+ return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %w", err)
}
return nil
@@ -216,6 +216,6 @@ func newLogModel(lp *LogPage) *logModel {
return mdl
}
-func (mdl *logModel) Items() interface{} {
+func (mdl *logModel) Items() any {
return mdl.items
}
diff --git a/ui/managewindow.go b/ui/managewindow.go
index e6855d5d..30a1cedc 100644
--- a/ui/managewindow.go
+++ b/ui/managewindow.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -161,7 +161,7 @@ func (mtw *ManageTunnelsWindow) updateProgressIndicator(globalState manager.Tunn
}
}
-func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
mtw.Synchronize(func() {
mtw.updateProgressIndicator(globalState)
@@ -179,7 +179,9 @@ func (mtw *ManageTunnelsWindow) UpdateFound() {
if mtw.updatePage != nil {
return
}
- mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title()))
+ if IsAdmin {
+ mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title()))
+ }
updatePage, err := NewUpdatePage()
if err == nil {
mtw.updatePage = updatePage
diff --git a/ui/raise.go b/ui/raise.go
index 0eac828a..3cea8a88 100644
--- a/ui/raise.go
+++ b/ui/raise.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -55,7 +55,7 @@ func WaitForRaiseUIThenQuit() {
var handle win.HWINEVENTHOOK
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- handle, err := win.SetWinEventHook(win.EVENT_OBJECT_CREATE, win.EVENT_OBJECT_CREATE, 0, func(hWinEventHook win.HWINEVENTHOOK, event uint32, hwnd win.HWND, idObject int32, idChild int32, idEventThread uint32, dwmsEventTime uint32) uintptr {
+ handle, err := win.SetWinEventHook(win.EVENT_OBJECT_CREATE, win.EVENT_OBJECT_CREATE, 0, func(hWinEventHook win.HWINEVENTHOOK, event uint32, hwnd win.HWND, idObject, idChild int32, idEventThread, dwmsEventTime uint32) uintptr {
class := make([]uint16, len(manageWindowWindowClass)+2) /* Plus 2, one for the null terminator, and one to see if this is only a prefix */
n, err := win.GetClassName(hwnd, &class[0], len(class))
if err != nil || n != len(manageWindowWindowClass) || windows.UTF16ToString(class) != manageWindowWindowClass {
diff --git a/ui/syntax/highlighter.c b/ui/syntax/highlighter.c
deleted file mode 100644
index d89feda1..00000000
--- a/ui/syntax/highlighter.c
+++ /dev/null
@@ -1,641 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include "highlighter.h"
-
-typedef struct {
- const char *s;
- size_t len;
-} string_span_t;
-
-static bool is_decimal(char c)
-{
- return c >= '0' && c <= '9';
-}
-
-static bool is_hexadecimal(char c)
-{
- return is_decimal(c) || ((c | 32) >= 'a' && (c | 32) <= 'f');
-}
-
-static bool is_alphabet(char c)
-{
- return (c | 32) >= 'a' && (c | 32) <= 'z';
-}
-
-static bool is_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- return !memcmp(s.s, c, len);
-}
-
-static bool is_caseless_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- for (size_t i = 0; i < len; ++i) {
- char a = c[i], b = s.s[i];
- if ((unsigned)a - 'a' < 26)
- a &= 95;
- if ((unsigned)b - 'a' < 26)
- b &= 95;
- if (a != b)
- return false;
- }
- return true;
-}
-
-static bool is_valid_key(string_span_t s)
-{
- if (s.len != 44 || s.s[43] != '=')
- return false;
-
- for (size_t i = 0; i < 42; ++i) {
- if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) &&
- s.s[i] != '/' && s.s[i] != '+')
- return false;
- }
- switch (s.s[42]) {
- case 'A':
- case 'E':
- case 'I':
- case 'M':
- case 'Q':
- case 'U':
- case 'Y':
- case 'c':
- case 'g':
- case 'k':
- case 'o':
- case 's':
- case 'w':
- case '4':
- case '8':
- case '0':
- break;
- default:
- return false;
- }
- return true;
-}
-
-static bool is_valid_hostname(string_span_t s)
-{
- size_t num_digit = 0, num_entity = s.len;
-
- if (s.len > 63 || !s.len)
- return false;
- if (s.s[0] == '-' || s.s[s.len - 1] == '-')
- return false;
- if (s.s[0] == '.' || s.s[s.len - 1] == '.')
- return false;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (is_decimal(s.s[i])) {
- ++num_digit;
- continue;
- }
- if (s.s[i] == '.') {
- --num_entity;
- continue;
- }
-
- if (!is_alphabet(s.s[i]) && s.s[i] != '-')
- return false;
-
- if (i && s.s[i] == '.' && s.s[i - 1] == '.')
- return false;
- }
- return num_digit != num_entity;
-}
-
-static bool is_valid_ipv4(string_span_t s)
-{
- for (size_t j, i = 0, pos = 0; i < 4 && pos < s.len; ++i) {
- uint32_t val = 0;
-
- for (j = 0; j < 3 && pos + j < s.len && is_decimal(s.s[pos + j]); ++j)
- val = 10 * val + s.s[pos + j] - '0';
- if (j == 0 || (j > 1 && s.s[pos] == '0') || val > 255)
- return false;
- if (pos + j == s.len && i == 3)
- return true;
- if (s.s[pos + j] != '.')
- return false;
- pos += j + 1;
- }
- return false;
-}
-
-static bool is_valid_ipv6(string_span_t s)
-{
- size_t pos = 0;
- bool seen_colon = false;
-
- if (s.len < 2)
- return false;
- if (s.s[pos] == ':' && s.s[++pos] != ':')
- return false;
- if (s.s[s.len - 1] == ':' && s.s[s.len - 2] != ':')
- return false;
-
- for (size_t j, i = 0; pos < s.len; ++i) {
- if (s.s[pos] == ':' && !seen_colon) {
- seen_colon = true;
- if (++pos == s.len)
- break;
- if (i == 7)
- return false;
- continue;
- }
- for (j = 0; j < 4 && pos + j < s.len && is_hexadecimal(s.s[pos + j]); ++j);
- if (j == 0)
- return false;
- if (pos + j == s.len && (seen_colon || i == 7))
- break;
- if (i == 7)
- return false;
- if (s.s[pos + j] != ':') {
- if (s.s[pos + j] != '.' || (i < 6 && !seen_colon))
- return false;
- return is_valid_ipv4((string_span_t){ s.s + pos, s.len - pos });
- }
- pos += j + 1;
- }
- return true;
-}
-
-static bool is_valid_uint(string_span_t s, bool support_hex, uint64_t min, uint64_t max)
-{
- uint64_t val = 0;
-
- /* Bound this around 32 bits, so that we don't have to write overflow logic. */
- if (s.len > 10 || !s.len)
- return false;
-
- if (support_hex && s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
- for (size_t i = 2; i < s.len; ++i) {
- if ((unsigned)s.s[i] - '0' < 10)
- val = 16 * val + (s.s[i] - '0');
- else if (((unsigned)s.s[i] | 32) - 'a' < 6)
- val = 16 * val + (s.s[i] | 32) - 'a' + 10;
- else
- return false;
- }
- } else {
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_decimal(s.s[i]))
- return false;
- val = 10 * val + s.s[i] - '0';
- }
- }
- return val <= max && val >= min;
-}
-
-static bool is_valid_port(string_span_t s)
-{
- return is_valid_uint(s, false, 0, 65535);
-}
-
-static bool is_valid_mtu(string_span_t s)
-{
- return is_valid_uint(s, false, 576, 65535);
-}
-
-static bool is_valid_persistentkeepalive(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, false, 0, 65535);
-}
-
-#ifndef MOBILE_WGQUICK_SUBSET
-
-static bool is_valid_fwmark(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, true, 0, 4294967295);
-}
-
-static bool is_valid_table(string_span_t s)
-{
- if (is_same(s, "auto"))
- return true;
- if (is_same(s, "off"))
- return true;
- /* This pretty much invalidates the other checks, but rt_names.c's
- * fread_id_name does no validation aside from this. */
- if (s.len < 512)
- return true;
- return is_valid_uint(s, false, 0, 4294967295);
-}
-
-static bool is_valid_saveconfig(string_span_t s)
-{
- return is_same(s, "true") || is_same(s, "false");
-}
-
-static bool is_valid_prepostupdown(string_span_t s)
-{
- /* It's probably not worthwhile to try to validate a bash expression.
- * So instead we just demand non-zero length. */
- return s.len;
-}
-#endif
-
-static bool is_valid_scope(string_span_t s)
-{
- if (s.len > 64 || !s.len)
- return false;
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_alphabet(s.s[i]) && !is_decimal(s.s[i]) &&
- s.s[i] != '_' && s.s[i] != '=' && s.s[i] != '+' &&
- s.s[i] != '.' && s.s[i] != '-')
- return false;
- }
- return true;
-}
-
-static bool is_valid_endpoint(string_span_t s)
-{
-
- if (!s.len)
- return false;
-
- if (s.s[0] == '[') {
- bool seen_scope = false;
- string_span_t hostspan = { s.s + 1, 0 };
-
- for (size_t i = 1; i < s.len; ++i) {
- if (s.s[i] == '%') {
- if (seen_scope)
- return false;
- seen_scope = true;
- if (!is_valid_ipv6(hostspan))
- return false;
- hostspan = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ']') {
- if (seen_scope) {
- if (!is_valid_scope(hostspan))
- return false;
- } else if (!is_valid_ipv6(hostspan)) {
- return false;
- }
- if (i == s.len - 1 || s.s[i + 1] != ':')
- return false;
- return is_valid_port((string_span_t){ s.s + i + 2, s.len - i - 2 });
- } else {
- ++hostspan.len;
- }
- }
- return false;
- }
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ':') {
- string_span_t host = { s.s, i }, port = { s.s + i + 1, s.len - i - 1};
- return is_valid_port(port) && (is_valid_ipv4(host) || is_valid_hostname(host));
- }
- }
- return false;
-}
-
-static bool is_valid_network(string_span_t s)
-{
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == '/') {
- string_span_t ip = { s.s, i }, cidr = { s.s + i + 1, s.len - i - 1};
- uint16_t cidrval = 0;
-
- if (cidr.len > 3 || !cidr.len)
- return false;
-
- for (size_t j = 0; j < cidr.len; ++j) {
- if (!is_decimal(cidr.s[j]))
- return false;
- cidrval = 10 * cidrval + cidr.s[j] - '0';
- }
- if (is_valid_ipv4(ip))
- return cidrval <= 32;
- else if (is_valid_ipv6(ip))
- return cidrval <= 128;
- return false;
- }
- }
- return is_valid_ipv4(s) || is_valid_ipv6(s);
-}
-
-enum field {
- InterfaceSection,
- PrivateKey,
- ListenPort,
- Address,
- DNS,
- MTU,
-#ifndef MOBILE_WGQUICK_SUBSET
- FwMark,
- Table,
- PreUp, PostUp, PreDown, PostDown,
- SaveConfig,
-#endif
-
- PeerSection,
- PublicKey,
- PresharedKey,
- AllowedIPs,
- Endpoint,
- PersistentKeepalive,
-
- Invalid
-};
-
-static enum field section_for_field(enum field t)
-{
- if (t > InterfaceSection && t < PeerSection)
- return InterfaceSection;
- if (t > PeerSection && t < Invalid)
- return PeerSection;
- return Invalid;
-}
-
-static enum field get_field(string_span_t s)
-{
-#define check_enum(t) do { if (is_caseless_same(s, #t)) return t; } while (0)
- check_enum(PrivateKey);
- check_enum(ListenPort);
- check_enum(Address);
- check_enum(DNS);
- check_enum(MTU);
- check_enum(PublicKey);
- check_enum(PresharedKey);
- check_enum(AllowedIPs);
- check_enum(Endpoint);
- check_enum(PersistentKeepalive);
-#ifndef MOBILE_WGQUICK_SUBSET
- check_enum(FwMark);
- check_enum(Table);
- check_enum(PreUp);
- check_enum(PostUp);
- check_enum(PreDown);
- check_enum(PostDown);
- check_enum(SaveConfig);
-#endif
- return Invalid;
-#undef check_enum
-}
-
-static enum field get_sectiontype(string_span_t s)
-{
- if (is_caseless_same(s, "[Peer]"))
- return PeerSection;
- if (is_caseless_same(s, "[Interface]"))
- return InterfaceSection;
- return Invalid;
-}
-
-struct highlight_span_array {
- size_t len, capacity;
- struct highlight_span *spans;
-};
-
-/* A useful OpenBSD-ism. */
-static void *realloc_array(void *optr, size_t nmemb, size_t size)
-{
- if ((nmemb >= (size_t)1 << (sizeof(size_t) * 4) ||
- size >= (size_t)1 << (sizeof(size_t) * 4)) &&
- nmemb > 0 && SIZE_MAX / nmemb < size) {
- errno = ENOMEM;
- return NULL;
- }
- return realloc(optr, size * nmemb);
-}
-
-static bool append_highlight_span(struct highlight_span_array *a, const char *o, string_span_t s, enum highlight_type t)
-{
- if (!s.len)
- return true;
- if (a->len >= a->capacity) {
- struct highlight_span *resized;
-
- a->capacity = a->capacity ? a->capacity * 2 : 64;
- resized = realloc_array(a->spans, a->capacity, sizeof(*resized));
- if (!resized) {
- free(a->spans);
- memset(a, 0, sizeof(*a));
- return false;
- }
- a->spans = resized;
- }
- a->spans[a->len++] = (struct highlight_span){ t, s.s - o, s.len };
- return true;
-}
-
-static void highlight_multivalue_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case DNS:
- if (is_valid_ipv4(s) || is_valid_ipv6(s))
- append_highlight_span(ret, parent.s, s, HighlightIP);
- else if (is_valid_hostname(s))
- append_highlight_span(ret, parent.s, s, HighlightHost);
- else
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- case Address:
- case AllowedIPs: {
- size_t slash;
-
- if (!is_valid_network(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (slash = 0; slash < s.len; ++slash) {
- if (s.s[slash] == '/')
- break;
- }
- if (slash == s.len) {
- append_highlight_span(ret, parent.s, s, HighlightIP);
- } else {
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, slash }, HighlightIP);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash + 1, s.len - slash - 1 }, HighlightCidr);
- }
- break;
- }
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-static void highlight_multivalue(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- string_span_t current_span = { s.s, 0 };
- size_t len_at_last_space = 0;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ',') {
- current_span.len = len_at_last_space;
- highlight_multivalue_value(ret, parent, current_span, section);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + i, 1 }, HighlightDelimiter);
- len_at_last_space = 0;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else {
- len_at_last_space = ++current_span.len;
- }
- }
- current_span.len = len_at_last_space;
- if (current_span.len)
- highlight_multivalue_value(ret, parent, current_span, section);
- else if (ret->spans[ret->len - 1].type == HighlightDelimiter)
- ret->spans[ret->len - 1].type = HighlightError;
-}
-
-static void highlight_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case PrivateKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPrivateKey : HighlightError);
- break;
- case PublicKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPublicKey : HighlightError);
- break;
- case PresharedKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPresharedKey : HighlightError);
- break;
- case MTU:
- append_highlight_span(ret, parent.s, s, is_valid_mtu(s) ? HighlightMTU : HighlightError);
- break;
-#ifndef MOBILE_WGQUICK_SUBSET
- case SaveConfig:
- append_highlight_span(ret, parent.s, s, is_valid_saveconfig(s) ? HighlightSaveConfig : HighlightError);
- break;
- case FwMark:
- append_highlight_span(ret, parent.s, s, is_valid_fwmark(s) ? HighlightFwMark : HighlightError);
- break;
- case Table:
- append_highlight_span(ret, parent.s, s, is_valid_table(s) ? HighlightTable : HighlightError);
- break;
- case PreUp:
- case PostUp:
- case PreDown:
- case PostDown:
- append_highlight_span(ret, parent.s, s, is_valid_prepostupdown(s) ? HighlightCmd : HighlightError);
- break;
-#endif
- case ListenPort:
- append_highlight_span(ret, parent.s, s, is_valid_port(s) ? HighlightPort : HighlightError);
- break;
- case PersistentKeepalive:
- append_highlight_span(ret, parent.s, s, is_valid_persistentkeepalive(s) ? HighlightKeepalive : HighlightError);
- break;
- case Endpoint: {
- size_t colon;
-
- if (!is_valid_endpoint(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (colon = s.len; colon --> 0;) {
- if (s.s[colon] == ':')
- break;
- }
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, colon }, HighlightHost);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon + 1, s.len - colon - 1 }, HighlightPort);
- break;
- }
- case Address:
- case DNS:
- case AllowedIPs:
- highlight_multivalue(ret, parent, s, section);
- break;
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-struct highlight_span *highlight_config(const char *config)
-{
- struct highlight_span_array ret = { 0 };
- const string_span_t s = { config, strlen(config) };
- string_span_t current_span = { s.s, 0 };
- enum field current_section = Invalid, current_field = Invalid;
- enum { OnNone, OnKey, OnValue, OnComment, OnSection } state = OnNone;
- size_t len_at_last_space = 0, equals_location = 0;
-
- for (size_t i = 0; i <= s.len; ++i) {
- if (i == s.len || s.s[i] == '\n' || (state != OnComment && s.s[i] == '#')) {
- if (state == OnKey) {
- current_span.len = len_at_last_space;
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- } else if (state == OnValue) {
- if (current_span.len) {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightDelimiter);
- current_span.len = len_at_last_space;
- highlight_value(&ret, s, current_span, current_field);
- } else {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightError);
- }
- } else if (state == OnSection) {
- current_span.len = len_at_last_space;
- current_section = get_sectiontype(current_span);
- append_highlight_span(&ret, s.s, current_span, current_section == Invalid ? HighlightError : HighlightSection);
- } else if (state == OnComment) {
- append_highlight_span(&ret, s.s, current_span, HighlightComment);
- }
- if (i == s.len)
- break;
- len_at_last_space = 0;
- current_field = Invalid;
- if (s.s[i] == '#') {
- current_span = (string_span_t){ s.s + i, 1 };
- state = OnComment;
- } else {
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnNone;
- }
- } else if (state == OnComment) {
- ++current_span.len;
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else if (s.s[i] == '=' && state == OnKey) {
- current_span.len = len_at_last_space;
- current_field = get_field(current_span);
- enum field section = section_for_field(current_field);
- if (section == Invalid || current_field == Invalid || section != current_section)
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- else
- append_highlight_span(&ret, s.s, current_span, HighlightField);
- equals_location = i;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnValue;
- } else {
- if (state == OnNone)
- state = s.s[i] == '[' ? OnSection : OnKey;
- len_at_last_space = ++current_span.len;
- }
- }
-
- append_highlight_span(&ret, s.s, (string_span_t){ s.s, -1 }, HighlightEnd);
- return ret.spans;
-}
diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go
new file mode 100644
index 00000000..099a23b0
--- /dev/null
+++ b/ui/syntax/highlighter.go
@@ -0,0 +1,631 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ *
+ * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code:
+ * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c
+ */
+
+package syntax
+
+import "unsafe"
+
+type highlight int
+
+const (
+ highlightSection highlight = iota
+ highlightField
+ highlightPrivateKey
+ highlightPublicKey
+ highlightPresharedKey
+ highlightIP
+ highlightCidr
+ highlightHost
+ highlightPort
+ highlightMTU
+ highlightKeepalive
+ highlightComment
+ highlightDelimiter
+ highlightTable
+ highlightCmd
+ highlightError
+)
+
+func validateHighlight(isValid bool, t highlight) highlight {
+ if isValid {
+ return t
+ }
+ return highlightError
+}
+
+type highlightSpan struct {
+ t highlight
+ s int
+ len int
+}
+
+func isDecimal(c byte) bool {
+ return c >= '0' && c <= '9'
+}
+
+func isHexadecimal(c byte) bool {
+ return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f'
+}
+
+func isAlphabet(c byte) bool {
+ return (c|32) >= 'a' && (c|32) <= 'z'
+}
+
+type stringSpan struct {
+ s *byte
+ len int
+}
+
+func (s stringSpan) at(i int) *byte {
+ return (*byte)(unsafe.Add(unsafe.Pointer(s.s), uintptr(i)))
+}
+
+func (s stringSpan) isSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) != cb[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isCaselessSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ a := *s.at(i)
+ b := cb[i]
+ if a-'a' < 26 {
+ a &= 95
+ }
+ if b-'a' < 26 {
+ b &= 95
+ }
+ if a != b {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidKey() bool {
+ if s.len != 44 || *s.at(43) != '=' {
+ return false
+ }
+ for i := 0; i < 42; i++ {
+ if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' {
+ return false
+ }
+ }
+ switch *s.at(42) {
+ case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0':
+ return true
+ }
+ return false
+}
+
+func (s stringSpan) isValidHostname() bool {
+ numDigit := 0
+ numEntity := s.len
+ if s.len > 63 || s.len == 0 {
+ return false
+ }
+ if *s.s == '-' || *s.at(s.len - 1) == '-' {
+ return false
+ }
+ if *s.s == '.' || *s.at(s.len - 1) == '.' {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isDecimal(*s.at(i)) {
+ numDigit++
+ continue
+ }
+ if *s.at(i) == '.' {
+ numEntity--
+ continue
+ }
+ if !isAlphabet(*s.at(i)) && *s.at(i) != '-' {
+ return false
+ }
+ if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' {
+ return false
+ }
+ }
+ return numDigit != numEntity
+}
+
+func (s stringSpan) isValidIPv4() bool {
+ pos := 0
+ for i := 0; i < 4 && pos < s.len; i++ {
+ val := 0
+ j := 0
+ for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ {
+ val = 10*val + int(*s.at(pos + j)-'0')
+ }
+ if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 {
+ return false
+ }
+ if pos+j == s.len && i == 3 {
+ return true
+ }
+ if *s.at(pos + j) != '.' {
+ return false
+ }
+ pos += j + 1
+ }
+ return false
+}
+
+func (s stringSpan) isValidIPv6() bool {
+ if s.len < 2 {
+ return false
+ }
+ pos := 0
+ if *s.at(0) == ':' {
+ if *s.at(1) != ':' {
+ return false
+ }
+ pos = 1
+ }
+ if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' {
+ return false
+ }
+ seenColon := false
+ for i := 0; pos < s.len; i++ {
+ if *s.at(pos) == ':' && !seenColon {
+ seenColon = true
+ pos++
+ if pos == s.len {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ continue
+ }
+ j := 0
+ for ; ; j++ {
+ if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) {
+ continue
+ }
+ break
+ }
+ if j == 0 {
+ return false
+ }
+ if pos+j == s.len && (seenColon || i == 7) {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ if *s.at(pos + j) != ':' {
+ if *s.at(pos + j) != '.' || i < 6 && !seenColon {
+ return false
+ }
+ return stringSpan{s.at(pos), s.len - pos}.isValidIPv4()
+ }
+ pos += j + 1
+ }
+ return true
+}
+
+func (s stringSpan) isValidUint(supportHex bool, min, max uint64) bool {
+ // Bound this around 32 bits, so that we don't have to write overflow logic.
+ if s.len > 10 || s.len == 0 {
+ return false
+ }
+ val := uint64(0)
+ if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' {
+ for i := 2; i < s.len; i++ {
+ if *s.at(i)-'0' < 10 {
+ val = 16*val + uint64(*s.at(i)-'0')
+ } else if (*s.at(i))|32-'a' < 6 {
+ val = 16*val + uint64((*s.at(i)|32)-'a'+10)
+ } else {
+ return false
+ }
+ }
+ } else {
+ for i := 0; i < s.len; i++ {
+ if !isDecimal(*s.at(i)) {
+ return false
+ }
+ val = 10*val + uint64(*s.at(i)-'0')
+ }
+ }
+ return val <= max && val >= min
+}
+
+func (s stringSpan) isValidPort() bool {
+ return s.isValidUint(false, 0, 65535)
+}
+
+func (s stringSpan) isValidMTU() bool {
+ return s.isValidUint(false, 576, 65535)
+}
+
+func (s stringSpan) isValidTable() bool {
+ return s.isSame("off") || s.isSame("auto") || s.isSame("main") || s.isValidUint(false, 0, (1<<32)-1)
+}
+
+func (s stringSpan) isValidPersistentKeepAlive() bool {
+ if s.isSame("off") {
+ return true
+ }
+ return s.isValidUint(false, 0, 65535)
+}
+
+// It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length.
+func (s stringSpan) isValidPrePostUpDown() bool {
+ return s.len != 0
+}
+
+func (s stringSpan) isValidScope() bool {
+ if s.len > 64 || s.len == 0 {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidEndpoint() bool {
+ if s.len == 0 {
+ return false
+ }
+ if *s.s == '[' {
+ seenScope := false
+ hostspan := stringSpan{s.at(1), 0}
+ for i := 1; i < s.len; i++ {
+ if *s.at(i) == '%' {
+ if seenScope {
+ return false
+ }
+ seenScope = true
+ if !hostspan.isValidIPv6() {
+ return false
+ }
+ hostspan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ']' {
+ if seenScope {
+ if !hostspan.isValidScope() {
+ return false
+ }
+ } else if !hostspan.isValidIPv6() {
+ return false
+ }
+ if i == s.len-1 || *s.at((i + 1)) != ':' {
+ return false
+ }
+ return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort()
+ } else {
+ hostspan.len++
+ }
+ }
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ':' {
+ host := stringSpan{s.s, i}
+ port := stringSpan{s.at(i + 1), s.len - i - 1}
+ return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname())
+ }
+ }
+ return false
+}
+
+func (s stringSpan) isValidNetwork() bool {
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == '/' {
+ ip := stringSpan{s.s, i}
+ cidr := stringSpan{s.at(i + 1), s.len - i - 1}
+ cidrval := uint16(0)
+ if cidr.len > 3 || cidr.len == 0 {
+ return false
+ }
+ for j := 0; j < cidr.len; j++ {
+ if !isDecimal(*cidr.at(j)) {
+ return false
+ }
+ cidrval = 10*cidrval + uint16(*cidr.at(j)-'0')
+ }
+ if ip.isValidIPv4() {
+ return cidrval <= 32
+ } else if ip.isValidIPv6() {
+ return cidrval <= 128
+ }
+ return false
+ }
+ }
+ return s.isValidIPv4() || s.isValidIPv6()
+}
+
+type field int32
+
+const (
+ fieldInterfaceSection field = iota
+ fieldPrivateKey
+ fieldListenPort
+ fieldAddress
+ fieldDNS
+ fieldMTU
+ fieldTable
+ fieldPreUp
+ fieldPostUp
+ fieldPreDown
+ fieldPostDown
+ fieldPeerSection
+ fieldPublicKey
+ fieldPresharedKey
+ fieldAllowedIPs
+ fieldEndpoint
+ fieldPersistentKeepalive
+ fieldInvalid
+)
+
+func sectionForField(t field) field {
+ if t > fieldInterfaceSection && t < fieldPeerSection {
+ return fieldInterfaceSection
+ }
+ if t > fieldPeerSection && t < fieldInvalid {
+ return fieldPeerSection
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) field() field {
+ switch {
+ case s.isCaselessSame("PrivateKey"):
+ return fieldPrivateKey
+ case s.isCaselessSame("ListenPort"):
+ return fieldListenPort
+ case s.isCaselessSame("Address"):
+ return fieldAddress
+ case s.isCaselessSame("DNS"):
+ return fieldDNS
+ case s.isCaselessSame("MTU"):
+ return fieldMTU
+ case s.isCaselessSame("Table"):
+ return fieldTable
+ case s.isCaselessSame("PublicKey"):
+ return fieldPublicKey
+ case s.isCaselessSame("PresharedKey"):
+ return fieldPresharedKey
+ case s.isCaselessSame("AllowedIPs"):
+ return fieldAllowedIPs
+ case s.isCaselessSame("Endpoint"):
+ return fieldEndpoint
+ case s.isCaselessSame("PersistentKeepalive"):
+ return fieldPersistentKeepalive
+ case s.isCaselessSame("PreUp"):
+ return fieldPreUp
+ case s.isCaselessSame("PostUp"):
+ return fieldPostUp
+ case s.isCaselessSame("PreDown"):
+ return fieldPreDown
+ case s.isCaselessSame("PostDown"):
+ return fieldPostDown
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) sectionType() field {
+ switch {
+ case s.isCaselessSame("[Peer]"):
+ return fieldPeerSection
+ case s.isCaselessSame("[Interface]"):
+ return fieldInterfaceSection
+ }
+ return fieldInvalid
+}
+
+type highlightSpanArray []highlightSpan
+
+func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) {
+ if s.len == 0 {
+ return
+ }
+ *hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len})
+}
+
+func (hsa *highlightSpanArray) highlightMultivalueValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldDNS:
+ if s.isValidIPv4() || s.isValidIPv6() {
+ hsa.append(parent.s, s, highlightIP)
+ } else if s.isValidHostname() {
+ hsa.append(parent.s, s, highlightHost)
+ } else {
+ hsa.append(parent.s, s, highlightError)
+ }
+ case fieldAddress, fieldAllowedIPs:
+ if !s.isValidNetwork() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ slash := 0
+ for ; slash < s.len; slash++ {
+ if *s.at(slash) == '/' {
+ break
+ }
+ }
+ if slash == s.len {
+ hsa.append(parent.s, s, highlightIP)
+ } else {
+ hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP)
+ hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr)
+ }
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func (hsa *highlightSpanArray) highlightMultivalue(parent, s stringSpan, section field) {
+ currentSpan := stringSpan{s.s, 0}
+ lenAtLastSpace := 0
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ',' {
+ currentSpan.len = lenAtLastSpace
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter)
+ lenAtLastSpace = 0
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else {
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ currentSpan.len = lenAtLastSpace
+ if currentSpan.len != 0 {
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ } else if (*hsa)[len(*hsa)-1].t == highlightDelimiter {
+ (*hsa)[len(*hsa)-1].t = highlightError
+ }
+}
+
+func (hsa *highlightSpanArray) highlightValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldPrivateKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey))
+ case fieldPublicKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey))
+ case fieldPresharedKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey))
+ case fieldMTU:
+ hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU))
+ case fieldTable:
+ hsa.append(parent.s, s, validateHighlight(s.isValidTable(), highlightTable))
+ case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd))
+ case fieldListenPort:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort))
+ case fieldPersistentKeepalive:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive))
+ case fieldEndpoint:
+ if !s.isValidEndpoint() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ colon := s.len
+ for colon > 0 {
+ colon--
+ if *s.at(colon) == ':' {
+ break
+ }
+ }
+ hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost)
+ hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort)
+ case fieldAddress, fieldDNS, fieldAllowedIPs:
+ hsa.highlightMultivalue(parent, s, section)
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func highlightConfig(config string) []highlightSpan {
+ var ret highlightSpanArray
+ b := append([]byte(config), 0)
+ s := stringSpan{&b[0], len(b) - 1}
+ currentSpan := stringSpan{s.s, 0}
+ currentSection := fieldInvalid
+ currentField := fieldInvalid
+ const (
+ onNone = iota
+ onKey
+ onValue
+ onComment
+ onSection
+ )
+ state := onNone
+ lenAtLastSpace := 0
+ equalsLocation := 0
+ for i := 0; i <= s.len; i++ {
+ if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' {
+ if state == onKey {
+ currentSpan.len = lenAtLastSpace
+ ret.append(s.s, currentSpan, highlightError)
+ } else if state == onValue {
+ if currentSpan.len != 0 {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter)
+ currentSpan.len = lenAtLastSpace
+ ret.highlightValue(s, currentSpan, currentField)
+ } else {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError)
+ }
+ } else if state == onSection {
+ currentSpan.len = lenAtLastSpace
+ currentSection = currentSpan.sectionType()
+ ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection))
+ } else if state == onComment {
+ ret.append(s.s, currentSpan, highlightComment)
+ }
+ if i == s.len {
+ break
+ }
+ lenAtLastSpace = 0
+ currentField = fieldInvalid
+ if *s.at(i) == '#' {
+ currentSpan = stringSpan{s.at(i), 1}
+ state = onComment
+ } else {
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onNone
+ }
+ } else if state == onComment {
+ currentSpan.len++
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else if *s.at(i) == '=' && state == onKey {
+ currentSpan.len = lenAtLastSpace
+ currentField = currentSpan.field()
+ section := sectionForField(currentField)
+ if section == fieldInvalid || currentField == fieldInvalid || section != currentSection {
+ ret.append(s.s, currentSpan, highlightError)
+ } else {
+ ret.append(s.s, currentSpan, highlightField)
+ }
+ equalsLocation = i
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onValue
+ } else {
+ if state == onNone {
+ if *s.at(i) == '[' {
+ state = onSection
+ } else {
+ state = onKey
+ }
+ }
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ return ([]highlightSpan)(ret)
+}
diff --git a/ui/syntax/highlighter.h b/ui/syntax/highlighter.h
deleted file mode 100644
index 0a86de78..00000000
--- a/ui/syntax/highlighter.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <sys/types.h>
-
-#define MOBILE_WGQUICK_SUBSET
-
-enum highlight_type {
- HighlightSection,
- HighlightField,
- HighlightPrivateKey,
- HighlightPublicKey,
- HighlightPresharedKey,
- HighlightIP,
- HighlightCidr,
- HighlightHost,
- HighlightPort,
- HighlightMTU,
- HighlightKeepalive,
- HighlightComment,
- HighlightDelimiter,
-#ifndef MOBILE_WGQUICK_SUBSET
- HighlightTable,
- HighlightFwMark,
- HighlightSaveConfig,
- HighlightCmd,
-#endif
- HighlightError,
- HighlightEnd
-};
-
-struct highlight_span {
- enum highlight_type type;
- size_t start, len;
-};
-
-struct highlight_span *highlight_config(const char *config);
diff --git a/ui/syntax/syntaxedit.c b/ui/syntax/syntaxedit.c
deleted file mode 100644
index c18d496b..00000000
--- a/ui/syntax/syntaxedit.c
+++ /dev/null
@@ -1,415 +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 <windowsx.h>
-#include <richedit.h>
-#include <richole.h>
-#include <tom.h>
-
-#include "syntaxedit.h"
-#include "highlighter.h"
-
-const GUID CDECL IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } };
-
-struct syntaxedit_data {
- IRichEditOle *irich;
- ITextDocument *idoc;
- enum block_state last_block_state;
- LONG yheight;
- bool highlight_guard;
-};
-
-static WNDPROC parent_proc;
-
-struct span_style {
- COLORREF color;
- DWORD effects;
-};
-
-static const struct span_style stylemap[] = {
- [HighlightSection] = { .color = RGB(0x32, 0x6D, 0x74), .effects = CFE_BOLD },
- [HighlightField] = { .color = RGB(0x9B, 0x23, 0x93), .effects = CFE_BOLD },
- [HighlightPrivateKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPublicKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPresharedKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightIP] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightCidr] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightHost] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightPort] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightMTU] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightKeepalive] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightComment] = { .color = RGB(0x53, 0x65, 0x79), .effects = CFE_ITALIC },
- [HighlightDelimiter] = { .color = RGB(0x00, 0x00, 0x00) },
-#ifndef MOBILE_WGQUICK_SUBSET
- [HighlightTable] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightFwMark] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightSaveConfig] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightCmd] = { .color = RGB(0x63, 0x75, 0x89) },
-#endif
- [HighlightError] = { .color = RGB(0xC4, 0x1A, 0x16), .effects = CFE_UNDERLINE }
-};
-
-static void evaluate_untunneled_blocking(struct syntaxedit_data *this, HWND hWnd, const char *msg, struct highlight_span *spans)
-{
- enum block_state state = InevaluableBlockingUntunneledTraffic;
- bool on_allowedips = false;
- bool seen_peer = false;
- bool seen_v6_00 = false, seen_v4_00 = false;
- bool seen_v6_01 = false, seen_v6_80001 = false, seen_v4_01 = false, seen_v4_1281 = false;
-
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- switch (span->type) {
- case HighlightError:
- goto done;
- case HighlightSection:
- if (span->len != 6 || strncasecmp(&msg[span->start], "[peer]", 6))
- break;
- if (!seen_peer)
- seen_peer = true;
- else
- goto done;
- break;
- case HighlightField:
- on_allowedips = span->len == 10 && !strncasecmp(&msg[span->start], "allowedips", 10);
- break;
- case HighlightIP:
- if (!on_allowedips || !seen_peer)
- break;
- if ((span + 1)->type != HighlightDelimiter || (span + 2)->type != HighlightCidr)
- break;
- if ((span + 2)->len != 1)
- break;
- if (msg[(span + 2)->start] == '0') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_00 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_00 = true;
- } else if (msg[(span + 2)->start] == '1') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_01 = true;
- else if (span->len == 9 && !strncmp(&msg[span->start], "128.0.0.0", 9))
- seen_v4_1281 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_01 = true;
- else if (span->len == 6 && !strncmp(&msg[span->start], "8000::", 6))
- seen_v6_80001 = true;
- }
- break;
- }
- }
-
- if (seen_v4_00 || seen_v6_00)
- state = BlockingUntunneledTraffic;
- else if ((seen_v4_01 && seen_v4_1281) || (seen_v6_01 && seen_v6_80001))
- state = NotBlockingUntunneledTraffic;
-
-done:
- if (state != this->last_block_state) {
- SendMessage(hWnd, SE_TRAFFIC_BLOCK, 0, state);
- this->last_block_state = state;
- }
-}
-
-static void highlight_text(HWND hWnd)
-{
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_NUMBYTES,
- .codepage = CP_ACP /* Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. */
- };
- GETTEXTEX gettextex = {
- .flags = GT_NOHIDDENTEXT,
- .codepage = gettextlengthex.codepage
- };
- CHARFORMAT2 format = {
- .cbSize = sizeof(CHARFORMAT2),
- .dwMask = CFM_COLOR | CFM_CHARSET | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE,
- .dwEffects = CFE_AUTOCOLOR,
- .yHeight = this->yheight ?: 20 * 10,
- .bCharSet = ANSI_CHARSET
- };
- LRESULT msg_size;
- char *msg = NULL;
- struct highlight_span *spans = NULL;
- CHARRANGE orig_selection;
- POINT original_scroll;
- bool found_private_key = false;
- COLORREF bg_color, bg_inversion;
- size_t num_spans;
-
- if (this->highlight_guard)
- return;
- this->highlight_guard = true;
-
- msg_size = SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0);
- if (msg_size == E_INVALIDARG)
- return;
- gettextex.cb = msg_size + 1;
-
- msg = malloc(msg_size + 1);
- if (!msg)
- goto out;
- if (SendMessage(hWnd, EM_GETTEXTEX, (WPARAM)&gettextex, (LPARAM)msg) <= 0)
- goto out;
-
- /* By default we get CR not CRLF, so just convert to LF. */
- for (size_t i = 0; i < msg_size; ++i) {
- if (msg[i] == '\r')
- msg[i] = '\n';
- }
-
- spans = highlight_config(msg);
- if (!spans)
- goto out;
-
- evaluate_untunneled_blocking(this, hWnd, msg, spans);
-
- this->idoc->lpVtbl->Undo(this->idoc, tomSuspend, NULL);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, 0);
- 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_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
- bg_color = GetSysColor(COLOR_WINDOW);
- bg_inversion = (bg_color & RGB(0xFF, 0xFF, 0xFF)) ^ RGB(0xFF, 0xFF, 0xFF);
- SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, bg_color);
- num_spans = _msize(spans) / sizeof(spans[0]);
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- if (num_spans <= 2048) {
- CHARRANGE selection = { span->start, span->len + span->start };
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&selection);
- format.crTextColor = stylemap[span->type].color ^ bg_inversion;
- format.dwEffects = stylemap[span->type].effects;
- SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
- }
- if (span->type == HighlightPrivateKey && !found_private_key) {
- /* Rather than allocating a new string, we mangle this one, since (for now) we don't use msg again. */
- msg[span->start + span->len] = '\0';
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, (LPARAM)&msg[span->start]);
- found_private_key = true;
- }
- }
- 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);
- RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- this->idoc->lpVtbl->Undo(this->idoc, tomResume, NULL);
- if (!found_private_key)
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, 0);
-
-out:
- free(spans);
- free(msg);
- this->highlight_guard = false;
-}
-
-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, can_undo, can_paste;
- 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));
- can_undo = SendMessage(hWnd, EM_CANUNDO, 0, 0);
- can_paste = SendMessage(hWnd, EM_CANPASTE, CF_TEXT, 0);
-
- popup = GetSubMenu(menu, 0);
- EnableMenuItem(popup, WM_UNDO, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CUT, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_PASTE, MF_BYCOMMAND | (can_paste ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CLEAR, 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_UNDO:
- case WM_CUT:
- case WM_COPY:
- case WM_PASTE:
- case WM_CLEAR:
- 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) / 2;
- y = (rect.top + rect.bottom) / 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: {
- struct syntaxedit_data *this = calloc(1, sizeof(*this));
- SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE);
- assert(this);
- SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
- SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&this->irich);
- assert(this->irich);
- this->irich->lpVtbl->QueryInterface(this->irich, &IID_ITextDocument, (void **)&this->idoc);
- assert(this->idoc);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- SendMessage(hWnd, EM_SETTEXTMODE, TM_SINGLECODEPAGE, 0);
- break;
- }
- case WM_DESTROY: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- this->idoc->lpVtbl->Release(this->idoc);
- this->irich->lpVtbl->Release(this->irich);
- free(this);
- }
- case WM_SETTEXT: {
- LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam);
- highlight_text(hWnd);
- SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
- return ret;
- }
- case SE_SET_PARENT_DPI: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- HDC hdc = GetDC(hWnd);
- if (this->yheight)
- SendMessage(hWnd, EM_SETZOOM, GetDeviceCaps(hdc, LOGPIXELSY), wParam);
- this->yheight = MulDiv(20 * 10, wParam, GetDeviceCaps(hdc, LOGPIXELSY));
- ReleaseDC(hWnd, hdc);
- highlight_text(hWnd);
- return 0;
- }
- case WM_REFLECT + WM_COMMAND:
- case WM_COMMAND:
- case WM_REFLECT + WM_NOTIFY:
- case WM_NOTIFY:
- switch (HIWORD(wParam)) {
- case EN_CHANGE:
- highlight_text(hWnd);
- break;
- }
- break;
- case WM_PASTE:
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- case WM_KEYDOWN: {
- WORD key = LOWORD(wParam);
- if ((key == 'V' && GetKeyState(VK_CONTROL) < 0) ||
- (key == VK_INSERT && GetKeyState(VK_SHIFT) < 0)) {
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- }
- break;
- }
- case WM_CONTEXTMENU:
- context_menu(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
- return 0;
- case WM_THEMECHANGED:
- highlight_text(hWnd);
- break;
- case WM_GETDLGCODE: {
- MSG *m = (MSG *)lParam;
- LRESULT lres = parent_proc(hWnd, Msg, wParam, lParam);
- lres &= ~DLGC_WANTTAB;
- if (m && m->message == WM_KEYDOWN && m->wParam == VK_TAB && GetKeyState(VK_CONTROL) >= 0)
- lres &= ~DLGC_WANTMESSAGE;
- return lres;
- }
- }
- return parent_proc(hWnd, Msg, wParam, lParam);
-}
-
-static long has_loaded = 0;
-
-bool register_syntax_edit(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"WgQuickSyntaxEdit";
- class.lpfnWndProc = child_proc;
- if (!RegisterClassExW(&class))
- goto err;
- parent_proc = pp;
- return true;
-
-err:
- FreeLibrary(lib);
- return false;
-}
diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go
index 257bdbd2..a3c94d86 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-2022 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,59 +84,420 @@ 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: {color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD},
+ highlightField: {color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD},
+ highlightPrivateKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPublicKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPresharedKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightIP: {color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightCidr: {color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightHost: {color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightPort: {color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightMTU: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightTable: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightKeepalive: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightComment: {color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC},
+ highlightDelimiter: {color: win.RGB(0x00, 0x00, 0x00)},
+ highlightCmd: {color: win.RGB(0x63, 0x75, 0x89)},
+ highlightError: {color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE},
+}
+
+func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {
+ state := InevaluableBlockingUntunneledTraffic
+ var onAllowedIPs,
+ onTable,
+ tableOff,
+ 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
+ }
+ case highlightField:
+ onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs")
+ onTable = strings.EqualFold(cfg[span.s:span.s+span.len], "Table")
+ case highlightTable:
+ if onTable {
+ tableOff = cfg[span.s:span.s+span.len] == "off"
+ }
+ 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
+ }
+ }
+ }
+ }
+ if tableOff {
+ return
+ }
+
+ 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 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
}
- case C.SE_TRAFFIC_BLOCK:
- se.blockUntunneledTrafficPublisher.Publish(int(lParam))
+ 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: %w", 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(
- func() interface{} {
+ func() any {
return se.Text()
},
- func(v interface{}) error {
+ func(v any) error {
if s, ok := v.(string); ok {
return se.SetText(s)
}
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()
}
diff --git a/ui/syntax/syntaxedit.h b/ui/syntax/syntaxedit.h
deleted file mode 100644
index 048e7bc4..00000000
--- a/ui/syntax/syntaxedit.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#ifndef SYNTAXEDIT_H
-#define SYNTAXEDIT_H
-
-#include <stdbool.h>
-#include <windows.h>
-#include <richedit.h>
-
-#define SYNTAXEDIT_STYLE (WS_CHILD | ES_MULTILINE | WS_VISIBLE | WS_VSCROLL | WS_BORDER | WS_HSCROLL | WS_TABSTOP | ES_WANTRETURN | ES_NOOLEDRAGDROP)
-#define SYNTAXEDIT_EXTSTYLE (0)
-
-/* The old MFC reflection trick. */
-#define WM_REFLECT (WM_USER + 0x1C00)
-
-#define SE_PRIVATE_KEY (WM_USER + 0x3100)
-#define SE_TRAFFIC_BLOCK (WM_USER + 0x3101)
-#define SE_SET_PARENT_DPI (WM_USER + 0x3102)
-
-enum block_state {
- InevaluableBlockingUntunneledTraffic,
- BlockingUntunneledTraffic,
- NotBlockingUntunneledTraffic
-};
-
-extern bool register_syntax_edit(void);
-
-#endif
diff --git a/ui/tray.go b/ui/tray.go
index f1fb727e..7771c966 100644
--- a/ui/tray.go
+++ b/ui/tray.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -24,7 +24,8 @@ type Tray struct {
*walk.NotifyIcon
// Current known tunnels by name
- tunnels map[string]*walk.Action
+ tunnels map[string]*walk.Action
+ tunnelsAreInBreakoutMenu bool
mtw *ManageTunnelsWindow
@@ -81,10 +82,10 @@ func (tray *Tray) setup() error {
{separator: true},
{separator: true},
{label: l18n.Sprintf("&Manage tunnels…"), handler: tray.onManageTunnels, enabled: true, defawlt: true},
- {label: l18n.Sprintf("&Import tunnel(s) from file…"), handler: tray.onImport, enabled: true},
+ {label: l18n.Sprintf("&Import tunnel(s) from file…"), handler: tray.onImport, enabled: true, hidden: !IsAdmin},
{separator: true},
{label: l18n.Sprintf("&About WireGuard…"), handler: tray.onAbout, enabled: true},
- {label: l18n.Sprintf("E&xit"), handler: onQuit, enabled: true},
+ {label: l18n.Sprintf("E&xit"), handler: onQuit, enabled: true, hidden: !IsAdmin},
} {
var action *walk.Action
if item.separator {
@@ -144,6 +145,17 @@ func (tray *Tray) onTunnelsChange() {
})
}
+func (tray *Tray) sortedTunnels() []string {
+ var names []string
+ for name := range tray.tunnels {
+ names = append(names, name)
+ }
+ sort.SliceStable(names, func(i, j int) bool {
+ return conf.TunnelNameIsLess(names[i], names[j])
+ })
+ return names
+}
+
func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
tunnelAction := walk.NewAction()
tunnelAction.SetText(tunnel.Name)
@@ -172,25 +184,22 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
})
tray.tunnels[tunnel.Name] = tunnelAction
- var names []string
- for name := range tray.tunnels {
- names = append(names, name)
- }
- sort.SliceStable(names, func(i, j int) bool {
- return conf.TunnelNameIsLess(names[i], names[j])
- })
-
var (
idx int
name string
)
- for idx, name = range names {
+ for idx, name = range tray.sortedTunnels() {
if name == tunnel.Name {
break
}
}
- tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tunnelAction)
+ if tray.tunnelsAreInBreakoutMenu {
+ tray.ContextMenu().Actions().At(trayTunnelActionsOffset).Menu().Actions().Insert(idx, tunnelAction)
+ } else {
+ tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tunnelAction)
+ }
+ tray.rebalanceTunnelsMenu()
go func() {
state, err := tclosure.State()
@@ -198,23 +207,76 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
return
}
tray.mtw.Synchronize(func() {
- tray.SetTunnelState(&tclosure, state, false)
+ tray.setTunnelState(&tclosure, state)
})
}()
}
func (tray *Tray) removeTunnelAction(tunnelName string) {
- tray.ContextMenu().Actions().Remove(tray.tunnels[tunnelName])
+ if tray.tunnelsAreInBreakoutMenu {
+ tray.ContextMenu().Actions().At(trayTunnelActionsOffset).Menu().Actions().Remove(tray.tunnels[tunnelName])
+ } else {
+ tray.ContextMenu().Actions().Remove(tray.tunnels[tunnelName])
+ }
delete(tray.tunnels, tunnelName)
+ tray.rebalanceTunnelsMenu()
+}
+
+func (tray *Tray) rebalanceTunnelsMenu() {
+ if tray.tunnelsAreInBreakoutMenu && len(tray.tunnels) <= 10 {
+ menuAction := tray.ContextMenu().Actions().At(trayTunnelActionsOffset)
+ idx := 1
+ for _, name := range tray.sortedTunnels() {
+ tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tray.tunnels[name])
+ idx++
+ }
+ tray.ContextMenu().Actions().Remove(menuAction)
+ menuAction.Menu().Dispose()
+ tray.tunnelsAreInBreakoutMenu = false
+ } else if !tray.tunnelsAreInBreakoutMenu && len(tray.tunnels) > 10 {
+ menu, err := walk.NewMenu()
+ if err != nil {
+ return
+ }
+ for _, name := range tray.sortedTunnels() {
+ action := tray.tunnels[name]
+ menu.Actions().Add(action)
+ tray.ContextMenu().Actions().Remove(action)
+ }
+ menuAction, err := tray.ContextMenu().Actions().InsertMenu(trayTunnelActionsOffset, menu)
+ if err != nil {
+ return
+ }
+ menuAction.SetText(l18n.Sprintf("&Tunnels"))
+ tray.tunnelsAreInBreakoutMenu = true
+ }
}
-func (tray *Tray) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (tray *Tray) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
tray.mtw.Synchronize(func() {
tray.updateGlobalState(globalState)
- tray.SetTunnelState(tunnel, state, err == nil)
- if !tray.mtw.Visible() && err != nil {
+ if err == nil {
+ tunnelAction := tray.tunnels[tunnel.Name]
+ if tunnelAction != nil {
+ wasChecked := tunnelAction.Checked()
+ switch state {
+ case manager.TunnelStarted:
+ if !wasChecked {
+ icon, _ := iconWithOverlayForState(state, 128)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Activated"), l18n.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
+ }
+
+ case manager.TunnelStopped:
+ if wasChecked {
+ icon, _ := loadSystemIcon("imageres", -31, 128) // TODO: this icon isn't very good...
+ tray.ShowCustom(l18n.Sprintf("WireGuard Deactivated"), l18n.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
+ }
+ }
+ }
+ } else if !tray.mtw.Visible() {
tray.ShowError(l18n.Sprintf("WireGuard Tunnel Error"), err.Error())
}
+ tray.setTunnelState(tunnel, state)
})
}
@@ -225,86 +287,63 @@ func (tray *Tray) updateGlobalState(globalState manager.TunnelState) {
actions := tray.ContextMenu().Actions()
statusAction := actions.At(0)
- activeCIDRsAction := actions.At(1)
-
- setTunnelActionsEnabled := func(enabled bool) {
- for i := 0; i < len(tray.tunnels); i++ {
- action := actions.At(trayTunnelActionsOffset + i)
- action.SetEnabled(enabled)
- }
- }
tray.SetToolTip(l18n.Sprintf("WireGuard: %s", textForState(globalState, true)))
stateText := textForState(globalState, false)
+ stateIcon, err := iconForState(globalState, 16)
+ if err == nil {
+ statusAction.SetImage(stateIcon)
+ }
statusAction.SetText(l18n.Sprintf("Status: %s", stateText))
- switch globalState {
- case manager.TunnelStarting:
- setTunnelActionsEnabled(false)
-
- case manager.TunnelStarted:
- activeCIDRsAction.SetVisible(true)
- setTunnelActionsEnabled(true)
-
- case manager.TunnelStopping:
- setTunnelActionsEnabled(false)
+ go func() {
+ var addrs []string
+ tunnels, err := manager.IPCClientTunnels()
+ if err == nil {
+ for i := range tunnels {
+ state, err := tunnels[i].State()
+ if err == nil && state == manager.TunnelStarted {
+ config, err := tunnels[i].RuntimeConfig()
+ if err == nil {
+ for _, addr := range config.Interface.Addresses {
+ addrs = append(addrs, addr.String())
+ }
+ }
+ }
+ }
+ }
+ tray.mtw.Synchronize(func() {
+ activeCIDRsAction := tray.ContextMenu().Actions().At(1)
+ activeCIDRsAction.SetText(l18n.Sprintf("Addresses: %s", strings.Join(addrs, l18n.EnumerationSeparator())))
+ activeCIDRsAction.SetVisible(len(addrs) > 0)
+ })
+ }()
- case manager.TunnelStopped:
- activeCIDRsAction.SetVisible(false)
- setTunnelActionsEnabled(true)
+ for _, action := range tray.tunnels {
+ action.SetEnabled(globalState == manager.TunnelStarted || globalState == manager.TunnelStopped)
}
}
-func (tray *Tray) SetTunnelState(tunnel *manager.Tunnel, state manager.TunnelState, showNotifications bool) {
+func (tray *Tray) setTunnelState(tunnel *manager.Tunnel, state manager.TunnelState) {
tunnelAction := tray.tunnels[tunnel.Name]
if tunnelAction == nil {
return
}
- actions := tray.ContextMenu().Actions()
- activeCIDRsAction := actions.At(1)
-
- wasChecked := tunnelAction.Checked()
-
switch state {
case manager.TunnelStarted:
- activeCIDRsAction.SetText("")
- go func() {
- config, err := tunnel.RuntimeConfig()
- if err == nil {
- var sb strings.Builder
- for i, addr := range config.Interface.Addresses {
- if i > 0 {
- sb.WriteString(l18n.EnumerationSeparator())
- }
-
- sb.WriteString(addr.String())
- }
- tray.mtw.Synchronize(func() {
- activeCIDRsAction.SetText(l18n.Sprintf("Addresses: %s", sb.String()))
- })
- }
- }()
tunnelAction.SetEnabled(true)
tunnelAction.SetChecked(true)
- if !wasChecked && showNotifications {
- icon, _ := iconWithOverlayForState(state, 128)
- tray.ShowCustom(l18n.Sprintf("WireGuard Activated"), l18n.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
- }
case manager.TunnelStopped:
tunnelAction.SetChecked(false)
- if wasChecked && showNotifications {
- icon, _ := loadSystemIcon("imageres", 26, 128) // TODO: this icon isn't very good...
- tray.ShowCustom(l18n.Sprintf("WireGuard Deactivated"), l18n.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
- }
}
}
func (tray *Tray) UpdateFound() {
action := walk.NewAction()
action.SetText(l18n.Sprintf("An Update is Available!"))
- menuIcon, _ := loadSystemIcon("imageres", 1, 16)
+ menuIcon, _ := loadShieldIcon(16)
action.SetImage(menuIcon)
action.SetDefault(true)
showUpdateTab := func() {
@@ -319,7 +358,7 @@ func (tray *Tray) UpdateFound() {
tray.ContextMenu().Actions().Insert(tray.ContextMenu().Actions().Len()-2, action)
showUpdateBalloon := func() {
- icon, _ := loadSystemIcon("imageres", 1, 128)
+ icon, _ := loadShieldIcon(128)
tray.ShowCustom(l18n.Sprintf("WireGuard Update Available"), l18n.Sprintf("An update to WireGuard is now available. You are advised to update as soon as possible."), icon)
}
diff --git a/ui/tunnelspage.go b/ui/tunnelspage.go
index d5933143..d104f598 100644
--- a/ui/tunnelspage.go
+++ b/ui/tunnelspage.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -9,7 +9,7 @@ import (
"archive/zip"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"os"
"path/filepath"
"sort"
@@ -76,6 +76,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
tp.fillerContainer.SetLayout(hlayout)
tp.fillerButton, _ = walk.NewPushButton(tp.fillerContainer)
tp.fillerButton.SetMinMaxSize(walk.Size{200, 0}, walk.Size{200, 0})
+ tp.fillerButton.SetVisible(IsAdmin)
tp.fillerButton.Clicked().Attach(func() {
if tp.fillerHandler != nil {
tp.fillerHandler()
@@ -90,8 +91,9 @@ func NewTunnelsPage() (*TunnelsPage, error) {
if err != nil {
return nil, err
}
- controlsContainer.SetLayout(walk.NewHBoxLayout())
- controlsContainer.Layout().SetMargins(walk.Margins{})
+ hlayout = walk.NewHBoxLayout()
+ hlayout.SetMargins(walk.Margins{})
+ controlsContainer.SetLayout(hlayout)
walk.NewHSpacer(controlsContainer)
@@ -105,6 +107,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
})
editTunnel.SetText(l18n.Sprintf("&Edit"))
editTunnel.Clicked().Attach(tp.onEditTunnel)
+ editTunnel.SetVisible(IsAdmin)
disposables.Spare()
@@ -133,6 +136,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
hlayout := walk.NewHBoxLayout()
hlayout.SetMargins(walk.Margins{})
toolBarContainer.SetLayout(hlayout)
+ toolBarContainer.SetVisible(IsAdmin)
if tp.listToolbar, err = walk.NewToolBarWithOrientationAndButtonStyle(toolBarContainer, walk.Horizontal, walk.ToolBarButtonImageBeforeText); err != nil {
return err
@@ -145,7 +149,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
tp.AddDisposable(addMenu)
importAction := walk.NewAction()
importAction.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
- importActionIcon, _ := loadSystemIcon("imageres", 3, 16)
+ importActionIcon, _ := loadSystemIcon("imageres", -3, 16)
importAction.SetImage(importActionIcon)
importAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
importAction.SetDefault(true)
@@ -153,13 +157,13 @@ func (tp *TunnelsPage) CreateToolbar() error {
addMenu.Actions().Add(importAction)
addAction := walk.NewAction()
addAction.SetText(l18n.Sprintf("Add &empty tunnel…"))
- addActionIcon, _ := loadSystemIcon("imageres", 2, 16)
+ addActionIcon, _ := loadSystemIcon("imageres", -2, 16)
addAction.SetImage(addActionIcon)
addAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
addAction.Triggered().Attach(tp.onAddTunnel)
addMenu.Actions().Add(addAction)
addMenuAction := walk.NewMenuAction(addMenu)
- addMenuActionIcon, _ := loadSystemIcon("shell32", 149, 16)
+ addMenuActionIcon, _ := loadSystemIcon("shell32", -258, 16)
addMenuAction.SetImage(addMenuActionIcon)
addMenuAction.SetText(l18n.Sprintf("Add Tunnel"))
addMenuAction.SetToolTip(importAction.Text())
@@ -169,7 +173,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
deleteAction := walk.NewAction()
- deleteActionIcon, _ := loadSystemIcon("shell32", 131, 16)
+ deleteActionIcon, _ := loadSystemIcon("shell32", -240, 16)
deleteAction.SetImage(deleteActionIcon)
deleteAction.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
deleteAction.SetToolTip(l18n.Sprintf("Remove selected tunnel(s)"))
@@ -178,7 +182,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
exportAction := walk.NewAction()
- exportActionIcon, _ := loadSystemIcon("imageres", 165, 16) // Or "shell32", 45?
+ exportActionIcon, _ := loadSystemIcon("imageres", -174, 16)
exportAction.SetImage(exportActionIcon)
exportAction.SetToolTip(l18n.Sprintf("Export all tunnels to zip"))
exportAction.Triggered().Attach(tp.onExportTunnels)
@@ -206,34 +210,40 @@ func (tp *TunnelsPage) CreateToolbar() error {
importAction2.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
importAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
importAction2.Triggered().Attach(tp.onImport)
+ importAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(importAction2)
tp.ShortcutActions().Add(importAction2)
addAction2 := walk.NewAction()
addAction2.SetText(l18n.Sprintf("Add &empty tunnel…"))
addAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
addAction2.Triggered().Attach(tp.onAddTunnel)
+ addAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(addAction2)
tp.ShortcutActions().Add(addAction2)
exportAction2 := walk.NewAction()
exportAction2.SetText(l18n.Sprintf("Export all tunnels to &zip…"))
exportAction2.Triggered().Attach(tp.onExportTunnels)
+ exportAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(exportAction2)
contextMenu.Actions().Add(walk.NewSeparatorAction())
editAction := walk.NewAction()
editAction.SetText(l18n.Sprintf("Edit &selected tunnel…"))
editAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyE})
+ editAction.SetVisible(IsAdmin)
editAction.Triggered().Attach(tp.onEditTunnel)
contextMenu.Actions().Add(editAction)
tp.ShortcutActions().Add(editAction)
deleteAction2 := walk.NewAction()
deleteAction2.SetText(l18n.Sprintf("&Remove selected tunnel(s)"))
deleteAction2.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
+ deleteAction2.SetVisible(IsAdmin)
deleteAction2.Triggered().Attach(tp.onDelete)
contextMenu.Actions().Add(deleteAction2)
tp.listView.ShortcutActions().Add(deleteAction2)
selectAllAction := walk.NewAction()
selectAllAction.SetText(l18n.Sprintf("Select &all"))
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
+ selectAllAction.SetVisible(IsAdmin)
selectAllAction.Triggered().Attach(tp.onSelectAll)
contextMenu.Actions().Add(selectAllAction)
tp.listView.ShortcutActions().Add(selectAllAction)
@@ -270,7 +280,7 @@ func (tp *TunnelsPage) updateConfView() {
func (tp *TunnelsPage) importFiles(paths []string) {
go func() {
- syncedMsgBox := func(title string, message string, flags walk.MsgBoxStyle) {
+ syncedMsgBox := func(title, message string, flags walk.MsgBoxStyle) {
tp.Synchronize(func() {
walk.MsgBox(tp.Form(), title, message, flags)
})
@@ -288,7 +298,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
for _, path := range paths {
switch strings.ToLower(filepath.Ext(path)) {
case ".conf":
- textConfig, err := ioutil.ReadFile(path)
+ textConfig, err := os.ReadFile(path)
if err != nil {
lastErr = err
continue
@@ -312,7 +322,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
lastErr = err
continue
}
- textConfig, err := ioutil.ReadAll(rc)
+ textConfig, err := io.ReadAll(rc)
rc.Close()
if err != nil {
lastErr = err
@@ -326,6 +336,9 @@ func (tp *TunnelsPage) importFiles(paths []string) {
}
if lastErr != nil || unparsedConfigs == nil {
+ if lastErr == nil {
+ lastErr = errors.New(l18n.Sprintf("no configuration files were found"))
+ }
syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not import selected configuration: %v", lastErr), walk.MsgBoxIconWarning)
return
}
@@ -387,16 +400,16 @@ func (tp *TunnelsPage) exportTunnels(filePath string) {
for _, tunnel := range tp.listView.model.tunnels {
cfg, err := tunnel.StoredConfig()
if err != nil {
- return fmt.Errorf("onExportTunnels: tunnel.StoredConfig failed: %v", err)
+ return fmt.Errorf("onExportTunnels: tunnel.StoredConfig failed: %w", err)
}
w, err := writer.Create(tunnel.Name + ".conf")
if err != nil {
- return fmt.Errorf("onExportTunnels: writer.Create failed: %v", err)
+ return fmt.Errorf("onExportTunnels: writer.Create failed: %w", err)
}
if _, err := w.Write(([]byte)(cfg.ToWgQuick())); err != nil {
- return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %v", err)
+ return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %w", err)
}
}
@@ -409,7 +422,6 @@ func (tp *TunnelsPage) addTunnel(config *conf.Config) {
if err != nil {
showErrorCustom(tp.Form(), l18n.Sprintf("Unable to create tunnel"), err.Error())
}
-
}
// Handlers
@@ -506,7 +518,6 @@ func (tp *TunnelsPage) onDelete() {
tunnelsToDelete := make([]manager.Tunnel, len(indices))
for i, j := range indices {
tunnelsToDelete[i] = tp.listView.model.tunnels[j]
-
}
go func() {
tp.listView.SetSuspendTunnelsUpdate(true)
diff --git a/ui/ui.go b/ui/ui.go
index 0279a8dd..569fc8ae 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -20,9 +20,12 @@ import (
"golang.zx2c4.com/wireguard/windows/version"
)
-var noTrayAvailable = false
-var shouldQuitManagerWhenExiting = false
-var startTime = time.Now()
+var (
+ noTrayAvailable = false
+ shouldQuitManagerWhenExiting = false
+ startTime = time.Now()
+ IsAdmin = false // A global, because this really is global for the process
+)
func RunUI() {
runtime.LockOSThread()
@@ -72,7 +75,7 @@ func RunUI() {
switch updateState {
case manager.UpdateStateFoundUpdate:
mtw.UpdateFound()
- if tray != nil {
+ if tray != nil && IsAdmin {
tray.UpdateFound()
}
case manager.UpdateStateUpdatesDisabledUnofficialBuild:
diff --git a/ui/updatepage.go b/ui/updatepage.go
index 1ed0b74c..ff2bbe5f 100644
--- a/ui/updatepage.go
+++ b/ui/updatepage.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -31,7 +31,7 @@ func NewUpdatePage() (*UpdatePage, error) {
up.SetTitle(l18n.Sprintf("An Update is Available!"))
- tabIcon, _ := loadSystemIcon("imageres", 1, 16)
+ tabIcon, _ := loadShieldIcon(16)
up.SetImage(tabIcon)
up.SetLayout(walk.NewVBoxLayout())
@@ -60,10 +60,16 @@ func NewUpdatePage() (*UpdatePage, error) {
if err != nil {
return nil, err
}
- updateIcon, _ := loadSystemIcon("shell32", 46, 32)
+ updateIcon, _ := loadSystemIcon("shell32", -47, 32)
button.SetImage(updateIcon)
button.SetText(l18n.Sprintf("Update Now"))
+ if !IsAdmin {
+ button.SetText(l18n.Sprintf("Please ask the system administrator to update."))
+ button.SetEnabled(false)
+ status.SetText(l18n.Sprintf("Status: Waiting for administrator"))
+ }
+
walk.NewVSpacer(up)
switchToUpdatingState := func() {