mirror of
https://github.com/amnezia-vpn/euphoria-windows.git
synced 2026-05-17 08:15:59 +03:00
adapt new implementation
This commit is contained in:
102
addressconfig.go
102
addressconfig.go
@@ -9,28 +9,30 @@ import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
|
||||
"github.com/amnezia-vpn/amnezia-wg/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/tunnel/firewall"
|
||||
"github.com/amnezia-vpn/awg-windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
|
||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
||||
if len(addresses) == 0 {
|
||||
return
|
||||
}
|
||||
includedInAddresses := func(a net.IPNet) bool {
|
||||
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
|
||||
for _, addr := range addresses {
|
||||
ip := addr.IP
|
||||
ipNetAddr := prefixToIPNet(addr)
|
||||
ip := ipNetAddr.IP
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
}
|
||||
mA, _ := addr.Mask.Size()
|
||||
mA, _ := ipNetAddr.Mask.Size()
|
||||
mB, _ := a.Mask.Size()
|
||||
if bytes.Equal(ip, a.IP) && mA == mB {
|
||||
return true
|
||||
@@ -51,11 +53,55 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
||||
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
|
||||
if includedInAddresses(ipnet) {
|
||||
log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName())
|
||||
iface.LUID.DeleteIPAddress(ipnet)
|
||||
addr, _ := netip.AddrFromSlice(ip)
|
||||
b := int(bits(ip))
|
||||
iface.LUID.DeleteIPAddress(netip.PrefixFrom(addr, b) )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func bits(ip net.IP) uint8 {
|
||||
if ip.To4() != nil {
|
||||
return 32
|
||||
}
|
||||
return 128
|
||||
}
|
||||
|
||||
func prefixBits(prefix netip.Prefix) uint8 {
|
||||
bits := uint8(32)
|
||||
if prefix.Addr().Is6() {
|
||||
bits = 128
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func prefixToIP(prefix netip.Prefix) net.IP {
|
||||
s := prefix.Addr().AsSlice()
|
||||
return net.IP(s)
|
||||
}
|
||||
|
||||
func prefixToMask(prefix netip.Prefix) net.IPMask {
|
||||
return prefixToIPNet(prefix).Mask
|
||||
}
|
||||
|
||||
func prefixToIPNet(prefix netip.Prefix) net.IPNet{
|
||||
ip := prefixToIP(prefix)
|
||||
return net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(prefix.Bits(), int(bits(ip))),
|
||||
}
|
||||
}
|
||||
|
||||
func maskPrefix(prefix netip.Prefix) netip.Prefix{
|
||||
ip := prefixToIP(prefix)
|
||||
b := int(bits(ip))
|
||||
mask := net.CIDRMask(int(prefix.Bits()), b)
|
||||
for i := 0; i < b/8; i++ {
|
||||
ip[i] &= mask[i]
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(ip)
|
||||
return netip.PrefixFrom(addr, b)
|
||||
}
|
||||
|
||||
func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *tun.NativeTun) error {
|
||||
luid := winipcfg.LUID(tun.LUID())
|
||||
@@ -65,13 +111,12 @@ func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *t
|
||||
estimatedRouteCount += len(peer.AllowedIPs)
|
||||
}
|
||||
routes := make([]winipcfg.RouteData, 0, estimatedRouteCount)
|
||||
addresses := make([]net.IPNet, len(conf.Interface.Addresses))
|
||||
addresses := make([]netip.Prefix, len(conf.Interface.Addresses))
|
||||
var haveV4Address, haveV6Address bool
|
||||
for i, addr := range conf.Interface.Addresses {
|
||||
addresses[i] = addr.IPNet()
|
||||
if addr.Bits() == 32 {
|
||||
for _, addr := range conf.Interface.Addresses {
|
||||
if prefixBits(addr) == 32 {
|
||||
haveV4Address = true
|
||||
} else if addr.Bits() == 128 {
|
||||
} else if prefixBits(addr) == 128 {
|
||||
haveV6Address = true
|
||||
}
|
||||
}
|
||||
@@ -80,24 +125,27 @@ func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *t
|
||||
foundDefault6 := false
|
||||
for _, peer := range conf.Peers {
|
||||
for _, allowedip := range peer.AllowedIPs {
|
||||
allowedip.MaskSelf()
|
||||
if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) {
|
||||
allowedip = maskPrefix(allowedip)
|
||||
pBits := prefixBits(allowedip)
|
||||
if (pBits == 32 && !haveV4Address) || (pBits == 128 && !haveV6Address) {
|
||||
continue
|
||||
}
|
||||
route := winipcfg.RouteData{
|
||||
Destination: allowedip.IPNet(),
|
||||
Destination: allowedip,
|
||||
Metric: 0,
|
||||
}
|
||||
if allowedip.Bits() == 32 {
|
||||
if allowedip.Cidr == 0 {
|
||||
if pBits == 32 {
|
||||
if allowedip.Bits() == 0 {
|
||||
foundDefault4 = true
|
||||
}
|
||||
route.NextHop = net.IPv4zero
|
||||
} else if allowedip.Bits() == 128 {
|
||||
if allowedip.Cidr == 0 {
|
||||
ipv4Zero, _ := netip.ParseAddr("0.0.0.0")
|
||||
route.NextHop = ipv4Zero
|
||||
} else if pBits == 128 {
|
||||
if allowedip.Bits() == 0 {
|
||||
foundDefault6 = true
|
||||
}
|
||||
route.NextHop = net.IPv6zero
|
||||
ipv6Zero, _ := netip.ParseAddr("0000:0000:0000:0000:0000:0000:0000:0000")
|
||||
route.NextHop = ipv6Zero
|
||||
}
|
||||
routes = append(routes, route)
|
||||
}
|
||||
@@ -117,22 +165,22 @@ func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *t
|
||||
if routes[i].Metric != routes[j].Metric {
|
||||
return routes[i].Metric < routes[j].Metric
|
||||
}
|
||||
if c := bytes.Compare(routes[i].NextHop, routes[j].NextHop); c != 0 {
|
||||
if c := routes[i].NextHop.Compare(routes[j].NextHop); c != 0 {
|
||||
return c < 0
|
||||
}
|
||||
if c := bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP); c != 0 {
|
||||
if c := routes[i].Destination.Addr().Compare(routes[j].Destination.Addr()); c != 0 {
|
||||
return c < 0
|
||||
}
|
||||
if c := bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask); c != 0 {
|
||||
if c := bytes.Compare(prefixToMask(routes[i].Destination), prefixToMask(routes[j].Destination)); c != 0 {
|
||||
return c < 0
|
||||
}
|
||||
return false
|
||||
})
|
||||
for i := 0; i < len(routes); i++ {
|
||||
if i > 0 && routes[i].Metric == routes[i-1].Metric &&
|
||||
bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) &&
|
||||
bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) &&
|
||||
bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) {
|
||||
(routes[i].NextHop.Compare(routes[i-1].NextHop) == 0) &&
|
||||
(routes[i].Destination.Addr().Compare(routes[i-1].Destination.Addr()) == 0) &&
|
||||
bytes.Equal(prefixToMask(routes[i].Destination), prefixToMask(routes[i-1].Destination)) {
|
||||
continue
|
||||
}
|
||||
deduplicatedRoutes = append(deduplicatedRoutes, &routes[i])
|
||||
@@ -174,8 +222,8 @@ func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error {
|
||||
if len(conf.Peers) == 1 && !conf.Interface.TableOff {
|
||||
nextallowedip:
|
||||
for _, allowedip := range conf.Peers[0].AllowedIPs {
|
||||
if allowedip.Cidr == 0 {
|
||||
for _, b := range allowedip.IP {
|
||||
if allowedip.Bits() == 0 {
|
||||
for _, b := range prefixToIP(allowedip) {
|
||||
if b != 0 {
|
||||
continue nextallowedip
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ if exist .deps\prepared goto :build
|
||||
rmdir /s /q .deps 2> NUL
|
||||
mkdir .deps || goto :error
|
||||
cd .deps || goto :error
|
||||
call :download go.zip https://go.dev/dl/go1.18.8.windows-amd64.zip 980788761e75ed33ffc4f2a7a3ff07cd90949bd023eb1a8d855ef0b5de9cbcba || goto :error
|
||||
call :download go.zip https://go.dev/dl/go1.20.8.windows-amd64.zip 6308336f7060023f2c9c58cdeefaa1389b5c4a96b4bd87b6ad742c57f8ac00da || goto :error
|
||||
rem Mirror of https://github.com/mstorsjo/llvm-mingw/releases/download/20201020/llvm-mingw-20201020-msvcrt-x86_64.zip
|
||||
call :download llvm-mingw-msvcrt.zip https://download.wireguard.com/windows-toolchain/distfiles/llvm-mingw-20201020-msvcrt-x86_64.zip 2e46593245090df96d15e360e092f0b62b97e93866e0162dca7f93b16722b844 || goto :error
|
||||
call :download wintun.zip https://www.wintun.net/builds/wintun-0.12.zip eba90e26686ed86595ae0a6d4d3f4f022924b1758f5148a32a91c60cc6e604df || goto :error
|
||||
|
||||
@@ -83,10 +83,10 @@ func deterministicGUID(c *conf.Config) *windows.GUID {
|
||||
if bi, bj := sortedAllowedIPs[i].Bits(), sortedAllowedIPs[j].Bits(); bi != bj {
|
||||
return bi < bj
|
||||
}
|
||||
if sortedAllowedIPs[i].Cidr != sortedAllowedIPs[j].Cidr {
|
||||
return sortedAllowedIPs[i].Cidr < sortedAllowedIPs[j].Cidr
|
||||
if sortedAllowedIPs[i].Bits() != sortedAllowedIPs[j].Bits() {
|
||||
return sortedAllowedIPs[i].Bits() < sortedAllowedIPs[j].Bits()
|
||||
}
|
||||
return bytes.Compare(sortedAllowedIPs[i].IP[:], sortedAllowedIPs[j].IP[:]) < 0
|
||||
return sortedAllowedIPs[i].Addr().Compare(sortedAllowedIPs[j].Addr()) < 0
|
||||
})
|
||||
for _, allowedip := range sortedAllowedIPs {
|
||||
b2String(allowedip.String())
|
||||
|
||||
10
go.mod
10
go.mod
@@ -1,18 +1,16 @@
|
||||
module github.com/amnezia-vpn/awg-windows
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e
|
||||
github.com/amnezia-vpn/amnezia-wg v0.1.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/tevino/abool/v2 v2.1.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
)
|
||||
|
||||
33
go.sum
33
go.sum
@@ -1,32 +1,17 @@
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
github.com/amnezia-vpn/amnezia-wg v0.1.0 h1:a3plpoJSWbD+N8cK78vu/LEy4TQWYh7/YClpWZC4S4Y=
|
||||
github.com/amnezia-vpn/amnezia-wg v0.1.0/go.mod h1:y3O3XsXzUXQYDwRNulQbnnAAhsjPYCnBjGmb2Daes64=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
|
||||
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"github.com/amnezia-vpn/amnezia-wg/conn"
|
||||
"github.com/amnezia-vpn/amnezia-wg/tun"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/services"
|
||||
|
||||
@@ -8,7 +8,7 @@ package main
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"github.com/amnezia-vpn/amnezia-wg/ipc"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
)
|
||||
|
||||
10
service.go
10
service.go
@@ -14,13 +14,13 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amnezia-wg/conn"
|
||||
"github.com/amnezia-vpn/amnezia-wg/device"
|
||||
"github.com/amnezia-vpn/amnezia-wg/ipc"
|
||||
"github.com/amnezia-vpn/amnezia-wg/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/elevate"
|
||||
@@ -104,7 +104,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
|
||||
log.Println("Shutting down")
|
||||
}()
|
||||
|
||||
err = ringlogger.InitGlobalLogger("TUN")
|
||||
err = ringlogger.InitGlobalLogger("TUN", "awg")
|
||||
if err != nil {
|
||||
serviceError = services.ErrorRingloggerOpen
|
||||
return
|
||||
|
||||
@@ -31,6 +31,8 @@ const (
|
||||
ErrorDropPrivileges
|
||||
ErrorRunScript
|
||||
ErrorWin32
|
||||
ErrorCreateWintun
|
||||
ErrorUAPIListen
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
@@ -69,6 +71,10 @@ func (e Error) Error() string {
|
||||
return "An error occurred while running a configuration script command"
|
||||
case ErrorWin32:
|
||||
return "An internal Windows error has occurred"
|
||||
case ErrorUAPIListen:
|
||||
return "Unable to listen on named pipe"
|
||||
case ErrorCreateWintun:
|
||||
return "Unable to create Wintun interface"
|
||||
default:
|
||||
return "An unknown error has occurred"
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
)
|
||||
|
||||
func ServiceNameOfTunnel(tunnelName string) (string, error) {
|
||||
if !conf.TunnelNameIsValid(tunnelName) {
|
||||
return "", errors.New("Tunnel name is not valid")
|
||||
}
|
||||
return "WireGuardTunnel$" + tunnelName, nil
|
||||
}
|
||||
|
||||
func PipePathOfTunnel(tunnelName string) (string, error) {
|
||||
if !conf.TunnelNameIsValid(tunnelName) {
|
||||
return "", errors.New("Tunnel name is not valid")
|
||||
}
|
||||
return `\\.\pipe\ProtectedPrefix\Administrators\WireGuard\` + tunnelName, nil
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amnezia-wg/conn"
|
||||
"github.com/amnezia-vpn/amnezia-wg/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ package tunnel
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"github.com/amnezia-vpn/amnezia-wg/ipc"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
)
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/driver"
|
||||
"github.com/lxn/walk"
|
||||
"github.com/lxn/win"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"github.com/amnezia-vpn/awg-windows/version"
|
||||
)
|
||||
|
||||
var (
|
||||
easterEggIndex = -1
|
||||
showingAboutDialog *walk.Dialog
|
||||
)
|
||||
|
||||
func onAbout(owner walk.Form) {
|
||||
showError(runAboutDialog(owner), owner)
|
||||
}
|
||||
|
||||
func runAboutDialog(owner walk.Form) error {
|
||||
if showingAboutDialog != nil {
|
||||
showingAboutDialog.Show()
|
||||
raise(showingAboutDialog.Handle())
|
||||
return nil
|
||||
}
|
||||
|
||||
vbl := walk.NewVBoxLayout()
|
||||
vbl.SetMargins(walk.Margins{80, 20, 80, 20})
|
||||
vbl.SetSpacing(10)
|
||||
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
var err error
|
||||
showingAboutDialog, err = walk.NewDialogWithFixedSize(owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
showingAboutDialog = nil
|
||||
}()
|
||||
disposables.Add(showingAboutDialog)
|
||||
showingAboutDialog.SetTitle(l18n.Sprintf("About WireGuard"))
|
||||
showingAboutDialog.SetLayout(vbl)
|
||||
if icon, err := loadLogoIcon(32); err == nil {
|
||||
showingAboutDialog.SetIcon(icon)
|
||||
}
|
||||
|
||||
font, _ := walk.NewFont("Segoe UI", 9, 0)
|
||||
showingAboutDialog.SetFont(font)
|
||||
|
||||
iv, err := walk.NewImageView(showingAboutDialog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iv.SetCursor(walk.CursorHand())
|
||||
iv.MouseUp().Attach(func(x, y int, button walk.MouseButton) {
|
||||
if button == walk.LeftButton {
|
||||
win.ShellExecute(showingAboutDialog.Handle(), nil, windows.StringToUTF16Ptr("https://www.wireguard.com/"), nil, nil, win.SW_SHOWNORMAL)
|
||||
} else if easterEggIndex >= 0 && button == walk.RightButton {
|
||||
if icon, err := loadSystemIcon("moricons", int32(easterEggIndex), 128); err == nil {
|
||||
iv.SetImage(icon)
|
||||
easterEggIndex++
|
||||
} else {
|
||||
easterEggIndex = -1
|
||||
if logo, err := loadLogoIcon(128); err == nil {
|
||||
iv.SetImage(logo)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if logo, err := loadLogoIcon(128); err == nil {
|
||||
iv.SetImage(logo)
|
||||
}
|
||||
iv.Accessibility().SetName(l18n.Sprintf("WireGuard logo image"))
|
||||
|
||||
wgLbl, err := walk.NewTextLabel(showingAboutDialog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wgFont, _ := walk.NewFont("Segoe UI", 16, walk.FontBold)
|
||||
wgLbl.SetFont(wgFont)
|
||||
wgLbl.SetTextAlignment(walk.AlignHCenterVNear)
|
||||
wgLbl.SetText("WireGuard")
|
||||
|
||||
detailsLbl, err := walk.NewTextLabel(showingAboutDialog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detailsLbl.SetTextAlignment(walk.AlignHCenterVNear)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
copyrightFont, _ := walk.NewFont("Segoe UI", 7, 0)
|
||||
copyrightLbl.SetFont(copyrightFont)
|
||||
copyrightLbl.SetTextAlignment(walk.AlignHCenterVNear)
|
||||
copyrightLbl.SetText("Copyright © 2015-2021 Jason A. Donenfeld. All Rights Reserved.")
|
||||
|
||||
buttonCP, err := walk.NewComposite(showingAboutDialog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hbl := walk.NewHBoxLayout()
|
||||
hbl.SetMargins(walk.Margins{VNear: 10})
|
||||
buttonCP.SetLayout(hbl)
|
||||
walk.NewHSpacer(buttonCP)
|
||||
closePB, err := walk.NewPushButton(buttonCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
closePB.SetAlignment(walk.AlignHCenterVNear)
|
||||
closePB.SetText(l18n.Sprintf("Close"))
|
||||
closePB.Clicked().Attach(showingAboutDialog.Accept)
|
||||
donatePB, err := walk.NewPushButton(buttonCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
donatePB.SetAlignment(walk.AlignHCenterVNear)
|
||||
donatePB.SetText(l18n.Sprintf("♥ &Donate!"))
|
||||
donatePB.Clicked().Attach(func() {
|
||||
if easterEggIndex == -1 {
|
||||
easterEggIndex = 0
|
||||
}
|
||||
win.ShellExecute(showingAboutDialog.Handle(), nil, windows.StringToUTF16Ptr("https://www.wireguard.com/donations/"), nil, nil, win.SW_SHOWNORMAL)
|
||||
showingAboutDialog.Accept()
|
||||
})
|
||||
walk.NewHSpacer(buttonCP)
|
||||
|
||||
showingAboutDialog.SetDefaultButton(donatePB)
|
||||
showingAboutDialog.SetCancelButton(closePB)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
showingAboutDialog.Run()
|
||||
|
||||
return nil
|
||||
}
|
||||
799
ui/confview.go
799
ui/confview.go
@@ -1,799 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
"github.com/lxn/win"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
type widgetsLine interface {
|
||||
widgets() (walk.Widget, walk.Widget)
|
||||
}
|
||||
|
||||
type widgetsLinesView interface {
|
||||
widgetsLines() []widgetsLine
|
||||
}
|
||||
|
||||
type labelStatusLine struct {
|
||||
label *walk.TextLabel
|
||||
statusComposite *walk.Composite
|
||||
statusImage *walk.ImageView
|
||||
statusLabel *walk.LineEdit
|
||||
}
|
||||
|
||||
type labelTextLine struct {
|
||||
label *walk.TextLabel
|
||||
text *walk.TextEdit
|
||||
}
|
||||
|
||||
type toggleActiveLine struct {
|
||||
composite *walk.Composite
|
||||
button *walk.PushButton
|
||||
}
|
||||
|
||||
type interfaceView struct {
|
||||
status *labelStatusLine
|
||||
publicKey *labelTextLine
|
||||
listenPort *labelTextLine
|
||||
|
||||
junkPacketCount *labelTextLine
|
||||
junkPacketMinSize *labelTextLine
|
||||
junkPacketMaxSize *labelTextLine
|
||||
initPacketJunkSize *labelTextLine
|
||||
responsePacketJunkSize *labelTextLine
|
||||
initPacketMagicHeader *labelTextLine
|
||||
responsePacketMagicHeader *labelTextLine
|
||||
underloadPacketMagicHeader *labelTextLine
|
||||
transportPacketMagicHeader *labelTextLine
|
||||
|
||||
mtu *labelTextLine
|
||||
addresses *labelTextLine
|
||||
dns *labelTextLine
|
||||
scripts *labelTextLine
|
||||
table *labelTextLine
|
||||
toggleActive *toggleActiveLine
|
||||
lines []widgetsLine
|
||||
}
|
||||
|
||||
type peerView struct {
|
||||
publicKey *labelTextLine
|
||||
presharedKey *labelTextLine
|
||||
allowedIPs *labelTextLine
|
||||
endpoint *labelTextLine
|
||||
persistentKeepalive *labelTextLine
|
||||
latestHandshake *labelTextLine
|
||||
transfer *labelTextLine
|
||||
lines []widgetsLine
|
||||
}
|
||||
|
||||
type ConfView struct {
|
||||
*walk.ScrollView
|
||||
name *walk.GroupBox
|
||||
interfaze *interfaceView
|
||||
peers map[conf.Key]*peerView
|
||||
tunnelChangedCB *manager.TunnelChangeCallback
|
||||
tunnel *manager.Tunnel
|
||||
updateTicker *time.Ticker
|
||||
}
|
||||
|
||||
func (lsl *labelStatusLine) widgets() (walk.Widget, walk.Widget) {
|
||||
return lsl.label, lsl.statusComposite
|
||||
}
|
||||
|
||||
func (lsl *labelStatusLine) update(state manager.TunnelState) {
|
||||
icon, err := iconForState(state, 14)
|
||||
if err == nil {
|
||||
lsl.statusImage.SetImage(icon)
|
||||
} else {
|
||||
lsl.statusImage.SetImage(nil)
|
||||
}
|
||||
|
||||
s, e := lsl.statusLabel.TextSelection()
|
||||
lsl.statusLabel.SetText(textForState(state, false))
|
||||
lsl.statusLabel.SetTextSelection(s, e)
|
||||
}
|
||||
|
||||
func (lsl *labelStatusLine) Dispose() {
|
||||
lsl.label.Dispose()
|
||||
lsl.statusComposite.Dispose()
|
||||
}
|
||||
|
||||
func newLabelStatusLine(parent walk.Container) (*labelStatusLine, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
lsl := new(labelStatusLine)
|
||||
|
||||
if lsl.label, err = walk.NewTextLabel(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lsl.label)
|
||||
lsl.label.SetText(l18n.Sprintf("Status:"))
|
||||
lsl.label.SetTextAlignment(walk.AlignHFarVNear)
|
||||
|
||||
if lsl.statusComposite, err = walk.NewComposite(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lsl.statusComposite)
|
||||
layout := walk.NewHBoxLayout()
|
||||
layout.SetMargins(walk.Margins{})
|
||||
layout.SetAlignment(walk.AlignHNearVNear)
|
||||
layout.SetSpacing(0)
|
||||
lsl.statusComposite.SetLayout(layout)
|
||||
|
||||
if lsl.statusImage, err = walk.NewImageView(lsl.statusComposite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lsl.statusImage)
|
||||
lsl.statusImage.SetMargin(2)
|
||||
lsl.statusImage.SetMode(walk.ImageViewModeIdeal)
|
||||
|
||||
if lsl.statusLabel, err = walk.NewLineEdit(lsl.statusComposite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lsl.statusLabel)
|
||||
win.SetWindowLong(lsl.statusLabel.Handle(), win.GWL_EXSTYLE, win.GetWindowLong(lsl.statusLabel.Handle(), win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
|
||||
lsl.statusLabel.SetReadOnly(true)
|
||||
lsl.statusLabel.SetBackground(walk.NullBrush())
|
||||
lsl.statusLabel.FocusedChanged().Attach(func() {
|
||||
lsl.statusLabel.SetTextSelection(0, 0)
|
||||
})
|
||||
lsl.update(manager.TunnelUnknown)
|
||||
lsl.statusLabel.Accessibility().SetRole(walk.AccRoleStatictext)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return lsl, nil
|
||||
}
|
||||
|
||||
func (lt *labelTextLine) widgets() (walk.Widget, walk.Widget) {
|
||||
return lt.label, lt.text
|
||||
}
|
||||
|
||||
func (lt *labelTextLine) show(text string) {
|
||||
s, e := lt.text.TextSelection()
|
||||
lt.text.SetText(text)
|
||||
lt.label.SetVisible(true)
|
||||
lt.text.SetVisible(true)
|
||||
lt.text.SetTextSelection(s, e)
|
||||
}
|
||||
|
||||
func (lt *labelTextLine) hide() {
|
||||
lt.text.SetText("")
|
||||
lt.label.SetVisible(false)
|
||||
lt.text.SetVisible(false)
|
||||
}
|
||||
|
||||
func (lt *labelTextLine) Dispose() {
|
||||
lt.label.Dispose()
|
||||
lt.text.Dispose()
|
||||
}
|
||||
|
||||
func newLabelTextLine(fieldName string, parent walk.Container) (*labelTextLine, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
lt := new(labelTextLine)
|
||||
|
||||
if lt.label, err = walk.NewTextLabel(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lt.label)
|
||||
lt.label.SetText(fieldName)
|
||||
lt.label.SetTextAlignment(walk.AlignHFarVNear)
|
||||
lt.label.SetVisible(false)
|
||||
|
||||
if lt.text, err = walk.NewTextEdit(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lt.text)
|
||||
win.SetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE, win.GetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
|
||||
lt.text.SetCompactHeight(true)
|
||||
lt.text.SetReadOnly(true)
|
||||
lt.text.SetBackground(walk.NullBrush())
|
||||
lt.text.SetVisible(false)
|
||||
lt.text.FocusedChanged().Attach(func() {
|
||||
lt.text.SetTextSelection(0, 0)
|
||||
})
|
||||
lt.text.Accessibility().SetRole(walk.AccRoleStatictext)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return lt, nil
|
||||
}
|
||||
|
||||
func (tal *toggleActiveLine) widgets() (walk.Widget, walk.Widget) {
|
||||
return nil, tal.composite
|
||||
}
|
||||
|
||||
func (tal *toggleActiveLine) updateGlobal(globalState manager.TunnelState) {
|
||||
tal.button.SetEnabled(globalState == manager.TunnelStarted || globalState == manager.TunnelStopped)
|
||||
}
|
||||
|
||||
func (tal *toggleActiveLine) update(state manager.TunnelState) {
|
||||
var text string
|
||||
|
||||
switch state {
|
||||
case manager.TunnelStarted:
|
||||
text = l18n.Sprintf("&Deactivate")
|
||||
case manager.TunnelStopped:
|
||||
text = l18n.Sprintf("&Activate")
|
||||
case manager.TunnelStarting, manager.TunnelStopping:
|
||||
text = textForState(state, true)
|
||||
default:
|
||||
text = ""
|
||||
}
|
||||
|
||||
tal.button.SetText(text)
|
||||
tal.button.SetVisible(state != manager.TunnelUnknown)
|
||||
}
|
||||
|
||||
func (tal *toggleActiveLine) Dispose() {
|
||||
tal.composite.Dispose()
|
||||
}
|
||||
|
||||
func newToggleActiveLine(parent walk.Container) (*toggleActiveLine, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
tal := new(toggleActiveLine)
|
||||
|
||||
if tal.composite, err = walk.NewComposite(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(tal.composite)
|
||||
layout := walk.NewHBoxLayout()
|
||||
layout.SetMargins(walk.Margins{0, 0, 0, 6})
|
||||
tal.composite.SetLayout(layout)
|
||||
|
||||
if tal.button, err = walk.NewPushButton(tal.composite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(tal.button)
|
||||
walk.NewHSpacer(tal.composite)
|
||||
tal.update(manager.TunnelStopped)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return tal, nil
|
||||
}
|
||||
|
||||
type labelTextLineItem struct {
|
||||
label string
|
||||
ptr **labelTextLine
|
||||
}
|
||||
|
||||
func createLabelTextLines(items []labelTextLineItem, parent walk.Container, disposables *walk.Disposables) ([]widgetsLine, error) {
|
||||
var err error
|
||||
var disps walk.Disposables
|
||||
defer disps.Treat()
|
||||
|
||||
wls := make([]widgetsLine, len(items))
|
||||
for i, item := range items {
|
||||
if *item.ptr, err = newLabelTextLine(item.label, parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disps.Add(*item.ptr)
|
||||
if disposables != nil {
|
||||
disposables.Add(*item.ptr)
|
||||
}
|
||||
wls[i] = *item.ptr
|
||||
}
|
||||
|
||||
disps.Spare()
|
||||
|
||||
return wls, nil
|
||||
}
|
||||
|
||||
func newInterfaceView(parent walk.Container) (*interfaceView, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
iv := new(interfaceView)
|
||||
|
||||
if iv.status, err = newLabelStatusLine(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(iv.status)
|
||||
|
||||
items := []labelTextLineItem{
|
||||
{l18n.Sprintf("Public key:"), &iv.publicKey},
|
||||
{l18n.Sprintf("Listen port:"), &iv.listenPort},
|
||||
{l18n.Sprintf("Jc:"), &iv.junkPacketCount},
|
||||
{l18n.Sprintf("Jmin:"), &iv.junkPacketMinSize},
|
||||
{l18n.Sprintf("Jmax:"), &iv.junkPacketMaxSize},
|
||||
{l18n.Sprintf("S1:"), &iv.initPacketJunkSize},
|
||||
{l18n.Sprintf("S2:"), &iv.responsePacketJunkSize},
|
||||
{l18n.Sprintf("H1:"), &iv.initPacketMagicHeader},
|
||||
{l18n.Sprintf("H2:"), &iv.responsePacketMagicHeader},
|
||||
{l18n.Sprintf("H3:"), &iv.underloadPacketMagicHeader},
|
||||
{l18n.Sprintf("H4:"), &iv.transportPacketMagicHeader},
|
||||
{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
|
||||
}
|
||||
|
||||
if iv.toggleActive, err = newToggleActiveLine(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(iv.toggleActive)
|
||||
|
||||
iv.lines = append([]widgetsLine{iv.status}, append(iv.lines, iv.toggleActive)...)
|
||||
|
||||
layoutInGrid(iv, parent.Layout().(*walk.GridLayout))
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return iv, nil
|
||||
}
|
||||
|
||||
func newPeerView(parent walk.Container) (*peerView, error) {
|
||||
pv := new(peerView)
|
||||
|
||||
items := []labelTextLineItem{
|
||||
{l18n.Sprintf("Public key:"), &pv.publicKey},
|
||||
{l18n.Sprintf("Preshared key:"), &pv.presharedKey},
|
||||
{l18n.Sprintf("Allowed IPs:"), &pv.allowedIPs},
|
||||
{l18n.Sprintf("Endpoint:"), &pv.endpoint},
|
||||
{l18n.Sprintf("Persistent keepalive:"), &pv.persistentKeepalive},
|
||||
{l18n.Sprintf("Latest handshake:"), &pv.latestHandshake},
|
||||
{l18n.Sprintf("Transfer:"), &pv.transfer},
|
||||
}
|
||||
var err error
|
||||
if pv.lines, err = createLabelTextLines(items, parent, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layoutInGrid(pv, parent.Layout().(*walk.GridLayout))
|
||||
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
func layoutInGrid(view widgetsLinesView, layout *walk.GridLayout) {
|
||||
for i, l := range view.widgetsLines() {
|
||||
w1, w2 := l.widgets()
|
||||
|
||||
if w1 != nil {
|
||||
layout.SetRange(w1, walk.Rectangle{0, i, 1, 1})
|
||||
}
|
||||
if w2 != nil {
|
||||
layout.SetRange(w2, walk.Rectangle{2, i, 1, 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (iv *interfaceView) widgetsLines() []widgetsLine {
|
||||
return iv.lines
|
||||
}
|
||||
|
||||
func (iv *interfaceView) apply(c *conf.Interface) {
|
||||
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)))
|
||||
} else {
|
||||
iv.listenPort.hide()
|
||||
}
|
||||
|
||||
if c.JunkPacketCount > 0 {
|
||||
iv.junkPacketCount.show(strconv.Itoa(int(c.JunkPacketCount)))
|
||||
} else {
|
||||
iv.junkPacketCount.hide()
|
||||
}
|
||||
|
||||
if c.JunkPacketMinSize > 0 {
|
||||
iv.junkPacketMinSize.show(strconv.Itoa(int(c.JunkPacketMinSize)))
|
||||
} else {
|
||||
iv.junkPacketMinSize.hide()
|
||||
}
|
||||
|
||||
if c.JunkPacketMaxSize > 0 {
|
||||
iv.junkPacketMaxSize.show(strconv.Itoa(int(c.JunkPacketMaxSize)))
|
||||
} else {
|
||||
iv.junkPacketMaxSize.hide()
|
||||
}
|
||||
|
||||
if c.InitPacketJunkSize > 0 {
|
||||
iv.initPacketJunkSize.show(strconv.Itoa(int(c.InitPacketJunkSize)))
|
||||
} else {
|
||||
iv.initPacketJunkSize.hide()
|
||||
}
|
||||
|
||||
if c.ResponsePacketJunkSize > 0 {
|
||||
iv.responsePacketJunkSize.show(strconv.Itoa(int(c.ResponsePacketJunkSize)))
|
||||
} else {
|
||||
iv.responsePacketJunkSize.hide()
|
||||
}
|
||||
|
||||
if c.InitPacketMagicHeader > 0 {
|
||||
iv.initPacketMagicHeader.show(strconv.FormatUint(uint64(c.InitPacketMagicHeader), 10))
|
||||
} else {
|
||||
iv.initPacketMagicHeader.hide()
|
||||
}
|
||||
|
||||
if c.ResponsePacketMagicHeader > 0 {
|
||||
iv.responsePacketMagicHeader.show(strconv.FormatUint(uint64(c.ResponsePacketMagicHeader), 10))
|
||||
} else {
|
||||
iv.responsePacketMagicHeader.hide()
|
||||
}
|
||||
|
||||
if c.UnderloadPacketMagicHeader > 0 {
|
||||
iv.underloadPacketMagicHeader.show(strconv.FormatUint(uint64(c.UnderloadPacketMagicHeader), 10))
|
||||
} else {
|
||||
iv.underloadPacketMagicHeader.hide()
|
||||
}
|
||||
|
||||
if c.TransportPacketMagicHeader > 0 {
|
||||
iv.transportPacketMagicHeader.show(strconv.FormatUint(uint64(c.TransportPacketMagicHeader), 10))
|
||||
} else {
|
||||
iv.transportPacketMagicHeader.hide()
|
||||
}
|
||||
|
||||
if c.MTU > 0 {
|
||||
iv.mtu.show(strconv.Itoa(int(c.MTU)))
|
||||
} else {
|
||||
iv.mtu.hide()
|
||||
}
|
||||
|
||||
if len(c.Addresses) > 0 {
|
||||
addrStrings := make([]string, len(c.Addresses))
|
||||
for i, address := range c.Addresses {
|
||||
addrStrings[i] = address.String()
|
||||
}
|
||||
iv.addresses.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
|
||||
} else {
|
||||
iv.addresses.hide()
|
||||
}
|
||||
|
||||
if len(c.DNS)+len(c.DNSSearch) > 0 {
|
||||
addrStrings := make([]string, 0, len(c.DNS)+len(c.DNSSearch))
|
||||
for _, address := range c.DNS {
|
||||
addrStrings = append(addrStrings, address.String())
|
||||
}
|
||||
addrStrings = append(addrStrings, c.DNSSearch...)
|
||||
iv.dns.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
|
||||
} 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 {
|
||||
return pv.lines
|
||||
}
|
||||
|
||||
func (pv *peerView) apply(c *conf.Peer) {
|
||||
if IsAdmin {
|
||||
pv.publicKey.show(c.PublicKey.String())
|
||||
} else {
|
||||
pv.publicKey.hide()
|
||||
}
|
||||
|
||||
if !c.PresharedKey.IsZero() && IsAdmin {
|
||||
pv.presharedKey.show(l18n.Sprintf("enabled"))
|
||||
} else {
|
||||
pv.presharedKey.hide()
|
||||
}
|
||||
|
||||
if len(c.AllowedIPs) > 0 {
|
||||
addrStrings := make([]string, len(c.AllowedIPs))
|
||||
for i, address := range c.AllowedIPs {
|
||||
addrStrings[i] = address.String()
|
||||
}
|
||||
pv.allowedIPs.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
|
||||
} else {
|
||||
pv.allowedIPs.hide()
|
||||
}
|
||||
|
||||
if !c.Endpoint.IsEmpty() {
|
||||
pv.endpoint.show(c.Endpoint.String())
|
||||
} else {
|
||||
pv.endpoint.hide()
|
||||
}
|
||||
|
||||
if c.PersistentKeepalive > 0 {
|
||||
pv.persistentKeepalive.show(strconv.Itoa(int(c.PersistentKeepalive)))
|
||||
} else {
|
||||
pv.persistentKeepalive.hide()
|
||||
}
|
||||
|
||||
if !c.LastHandshakeTime.IsEmpty() {
|
||||
pv.latestHandshake.show(c.LastHandshakeTime.String())
|
||||
} else {
|
||||
pv.latestHandshake.hide()
|
||||
}
|
||||
|
||||
if c.RxBytes > 0 || c.TxBytes > 0 {
|
||||
pv.transfer.show(l18n.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
|
||||
} else {
|
||||
pv.transfer.hide()
|
||||
}
|
||||
}
|
||||
|
||||
func newPaddedGroupGrid(parent walk.Container) (group *walk.GroupBox, err error) {
|
||||
group, err = walk.NewGroupBox(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
group.Dispose()
|
||||
}
|
||||
}()
|
||||
layout := walk.NewGridLayout()
|
||||
layout.SetMargins(walk.Margins{10, 5, 10, 5})
|
||||
layout.SetSpacing(0)
|
||||
err = group.SetLayout(layout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spacer, err := walk.NewSpacerWithCfg(group, &walk.SpacerCfg{walk.GrowableHorz | walk.GreedyHorz, walk.Size{10, 0}, false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(spacer, walk.Rectangle{1, 0, 1, 1})
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func NewConfView(parent walk.Container) (*ConfView, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
cv := new(ConfView)
|
||||
if cv.ScrollView, err = walk.NewScrollView(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(cv)
|
||||
vlayout := walk.NewVBoxLayout()
|
||||
vlayout.SetMargins(walk.Margins{5, 0, 5, 0})
|
||||
cv.SetLayout(vlayout)
|
||||
if cv.name, err = newPaddedGroupGrid(cv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.interfaze, err = newInterfaceView(cv.name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.interfaze.toggleActive.button.Clicked().Attach(cv.onToggleActiveClicked)
|
||||
cv.peers = make(map[conf.Key]*peerView)
|
||||
cv.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(cv.onTunnelChanged)
|
||||
cv.SetTunnel(nil)
|
||||
globalState, err := manager.IPCClientGlobalState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.interfaze.toggleActive.updateGlobal(globalState)
|
||||
|
||||
if err := walk.InitWrapperWindow(cv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.SetDoubleBuffering(true)
|
||||
cv.updateTicker = time.NewTicker(time.Second)
|
||||
go func() {
|
||||
for range cv.updateTicker.C {
|
||||
if !cv.Visible() || !cv.Form().Visible() || win.IsIconic(cv.Form().Handle()) {
|
||||
continue
|
||||
}
|
||||
if cv.tunnel != nil {
|
||||
tunnel := cv.tunnel
|
||||
var state manager.TunnelState
|
||||
var config conf.Config
|
||||
if state, _ = tunnel.State(); state == manager.TunnelStarted {
|
||||
config, _ = tunnel.RuntimeConfig()
|
||||
}
|
||||
if config.Name == "" {
|
||||
config, _ = tunnel.StoredConfig()
|
||||
}
|
||||
cv.Synchronize(func() {
|
||||
cv.setTunnel(tunnel, &config, state)
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return cv, nil
|
||||
}
|
||||
|
||||
func (cv *ConfView) Dispose() {
|
||||
if cv.tunnelChangedCB != nil {
|
||||
cv.tunnelChangedCB.Unregister()
|
||||
cv.tunnelChangedCB = nil
|
||||
}
|
||||
if cv.updateTicker != nil {
|
||||
cv.updateTicker.Stop()
|
||||
cv.updateTicker = nil
|
||||
}
|
||||
cv.ScrollView.Dispose()
|
||||
}
|
||||
|
||||
func (cv *ConfView) onToggleActiveClicked() {
|
||||
cv.interfaze.toggleActive.button.SetEnabled(false)
|
||||
go func() {
|
||||
oldState, err := cv.tunnel.Toggle()
|
||||
if err != nil {
|
||||
cv.Synchronize(func() {
|
||||
if oldState == manager.TunnelUnknown {
|
||||
showErrorCustom(cv.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
|
||||
} else if oldState == manager.TunnelStopped {
|
||||
showErrorCustom(cv.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
|
||||
} else if oldState == manager.TunnelStarted {
|
||||
showErrorCustom(cv.Form(), l18n.Sprintf("Failed to deactivate tunnel"), 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 {
|
||||
cv.interfaze.status.update(state)
|
||||
cv.interfaze.toggleActive.update(state)
|
||||
}
|
||||
})
|
||||
if cv.tunnel != nil && cv.tunnel.Name == tunnel.Name {
|
||||
var config conf.Config
|
||||
if state == manager.TunnelStarted {
|
||||
config, _ = tunnel.RuntimeConfig()
|
||||
}
|
||||
if config.Name == "" {
|
||||
config, _ = tunnel.StoredConfig()
|
||||
}
|
||||
cv.Synchronize(func() {
|
||||
cv.setTunnel(tunnel, &config, state)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *ConfView) SetTunnel(tunnel *manager.Tunnel) {
|
||||
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
|
||||
if tunnel != nil {
|
||||
go func() {
|
||||
if state, _ = tunnel.State(); state == manager.TunnelStarted {
|
||||
config, _ = tunnel.RuntimeConfig()
|
||||
}
|
||||
if config.Name == "" {
|
||||
config, _ = tunnel.StoredConfig()
|
||||
}
|
||||
cv.Synchronize(func() {
|
||||
cv.setTunnel(tunnel, &config, state)
|
||||
})
|
||||
}()
|
||||
} else {
|
||||
cv.setTunnel(tunnel, &config, state)
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state manager.TunnelState) {
|
||||
if !(cv.tunnel == nil || tunnel == nil || tunnel.Name == cv.tunnel.Name) {
|
||||
return
|
||||
}
|
||||
|
||||
title := l18n.Sprintf("Interface: %s", config.Name)
|
||||
if cv.name.Title() != title {
|
||||
cv.SetSuspended(true)
|
||||
defer cv.SetSuspended(false)
|
||||
cv.name.SetTitle(title)
|
||||
}
|
||||
cv.name.SetVisible(tunnel != nil)
|
||||
|
||||
cv.interfaze.apply(&config.Interface)
|
||||
cv.interfaze.status.update(state)
|
||||
cv.interfaze.toggleActive.update(state)
|
||||
inverse := make(map[*peerView]bool, len(cv.peers))
|
||||
all := make([]*peerView, 0, len(cv.peers))
|
||||
for _, pv := range cv.peers {
|
||||
inverse[pv] = true
|
||||
all = append(all, pv)
|
||||
}
|
||||
someMatch := false
|
||||
for _, peer := range config.Peers {
|
||||
_, ok := cv.peers[peer.PublicKey]
|
||||
if ok {
|
||||
someMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, peer := range config.Peers {
|
||||
if pv := cv.peers[peer.PublicKey]; (!someMatch && len(all) > 0) || pv != nil {
|
||||
if pv == nil {
|
||||
pv = all[0]
|
||||
all = all[1:]
|
||||
k, e := conf.NewPrivateKeyFromString(pv.publicKey.text.Text())
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
delete(cv.peers, *k)
|
||||
cv.peers[peer.PublicKey] = pv
|
||||
}
|
||||
pv.apply(&peer)
|
||||
inverse[pv] = false
|
||||
} else {
|
||||
group, err := newPaddedGroupGrid(cv)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
group.SetTitle(l18n.Sprintf("Peer"))
|
||||
pv, err := newPeerView(group)
|
||||
if err != nil {
|
||||
group.Dispose()
|
||||
continue
|
||||
}
|
||||
pv.apply(&peer)
|
||||
cv.peers[peer.PublicKey] = pv
|
||||
}
|
||||
}
|
||||
for pv, remove := range inverse {
|
||||
if !remove {
|
||||
continue
|
||||
}
|
||||
k, e := conf.NewPrivateKeyFromString(pv.publicKey.text.Text())
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
delete(cv.peers, *k)
|
||||
groupBox := pv.publicKey.label.Parent().AsContainerBase().Parent().(*walk.GroupBox)
|
||||
groupBox.SetVisible(false)
|
||||
groupBox.Parent().Children().Remove(groupBox)
|
||||
groupBox.Dispose()
|
||||
}
|
||||
}
|
||||
345
ui/editdialog.go
345
ui/editdialog.go
@@ -1,345 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
"github.com/lxn/win"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"github.com/amnezia-vpn/awg-windows/ui/syntax"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
type EditDialog struct {
|
||||
*walk.Dialog
|
||||
nameEdit *walk.LineEdit
|
||||
pubkeyEdit *walk.LineEdit
|
||||
syntaxEdit *syntax.SyntaxEdit
|
||||
blockUntunneledTrafficCB *walk.CheckBox
|
||||
saveButton *walk.PushButton
|
||||
config conf.Config
|
||||
lastPrivateKey string
|
||||
blockUntunneledTraficCheckGuard bool
|
||||
}
|
||||
|
||||
func runEditDialog(owner walk.Form, tunnel *manager.Tunnel) *conf.Config {
|
||||
dlg, err := newEditDialog(owner, tunnel)
|
||||
if showError(err, owner) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dlg.Run() == walk.DlgCmdOK {
|
||||
return &dlg.config
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
dlg := new(EditDialog)
|
||||
|
||||
var title string
|
||||
if tunnel == nil {
|
||||
title = l18n.Sprintf("Create new tunnel")
|
||||
} else {
|
||||
title = l18n.Sprintf("Edit tunnel")
|
||||
}
|
||||
|
||||
if tunnel == nil {
|
||||
// Creating a new tunnel, create a new private key and use the default template
|
||||
pk, _ := conf.NewPrivateKey()
|
||||
dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
|
||||
} else {
|
||||
dlg.config, _ = tunnel.StoredConfig()
|
||||
}
|
||||
|
||||
layout := walk.NewGridLayout()
|
||||
layout.SetSpacing(6)
|
||||
layout.SetMargins(walk.Margins{10, 10, 10, 10})
|
||||
layout.SetColumnStretchFactor(1, 3)
|
||||
|
||||
if dlg.Dialog, err = walk.NewDialog(owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(dlg)
|
||||
dlg.SetIcon(owner.Icon())
|
||||
dlg.SetTitle(title)
|
||||
dlg.SetLayout(layout)
|
||||
dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
|
||||
if icon, err := loadSystemIcon("imageres", -114, 32); err == nil {
|
||||
dlg.SetIcon(icon)
|
||||
}
|
||||
|
||||
nameLabel, err := walk.NewTextLabel(dlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
|
||||
nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
|
||||
nameLabel.SetText(l18n.Sprintf("&Name:"))
|
||||
|
||||
if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1})
|
||||
dlg.nameEdit.SetText(dlg.config.Name)
|
||||
|
||||
pubkeyLabel, err := walk.NewTextLabel(dlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
|
||||
pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
|
||||
pubkeyLabel.SetText(l18n.Sprintf("&Public key:"))
|
||||
|
||||
if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
|
||||
dlg.pubkeyEdit.SetReadOnly(true)
|
||||
dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
|
||||
dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext)
|
||||
|
||||
if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1})
|
||||
|
||||
buttonsContainer, err := walk.NewComposite(dlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
|
||||
buttonsContainer.SetLayout(walk.NewHBoxLayout())
|
||||
buttonsContainer.Layout().SetMargins(walk.Margins{})
|
||||
|
||||
if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil {
|
||||
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, 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)
|
||||
|
||||
walk.NewHSpacer(buttonsContainer)
|
||||
|
||||
if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dlg.saveButton.SetText(l18n.Sprintf("&Save"))
|
||||
dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
|
||||
|
||||
cancelButton, err := walk.NewPushButton(buttonsContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cancelButton.SetText(l18n.Sprintf("Cancel"))
|
||||
cancelButton.Clicked().Attach(dlg.Cancel)
|
||||
|
||||
dlg.SetCancelButton(cancelButton)
|
||||
dlg.SetDefaultButton(dlg.saveButton)
|
||||
|
||||
dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged)
|
||||
dlg.syntaxEdit.BlockUntunneledTrafficStateChanged().Attach(dlg.onBlockUntunneledTrafficStateChanged)
|
||||
dlg.syntaxEdit.SetText(dlg.config.ToWgQuick())
|
||||
|
||||
// Insert a dummy label immediately preceding syntaxEdit to have screen readers read it.
|
||||
// Otherwise they fallback to "RichEdit Control".
|
||||
syntaxEditWnd := dlg.syntaxEdit.Handle()
|
||||
parentWnd := win.GetParent(syntaxEditWnd)
|
||||
labelWnd := win.CreateWindowEx(0,
|
||||
windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")),
|
||||
win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0,
|
||||
parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil)
|
||||
prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV)
|
||||
nextWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDNEXT)
|
||||
win.SetWindowPos(labelWnd, prevWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
|
||||
win.SetWindowPos(syntaxEditWnd, labelWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
|
||||
win.SetWindowPos(nextWnd, syntaxEditWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
|
||||
|
||||
if tunnel != nil {
|
||||
dlg.Starting().Attach(func() {
|
||||
dlg.syntaxEdit.SetFocus()
|
||||
})
|
||||
}
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return dlg, nil
|
||||
}
|
||||
|
||||
func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
|
||||
if dlg.blockUntunneledTraficCheckGuard {
|
||||
return
|
||||
}
|
||||
var (
|
||||
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 []netip.Prefix
|
||||
|
||||
if err != nil {
|
||||
goto err
|
||||
}
|
||||
if len(cfg.Peers) != 1 {
|
||||
goto err
|
||||
}
|
||||
|
||||
newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs))
|
||||
if block {
|
||||
var (
|
||||
foundV401 bool
|
||||
foundV41281 bool
|
||||
foundV600001 bool
|
||||
foundV680001 bool
|
||||
)
|
||||
for _, allowedip := range cfg.Peers[0].AllowedIPs {
|
||||
if allowedip == v600001 {
|
||||
foundV600001 = true
|
||||
} else if allowedip == v680001 {
|
||||
foundV680001 = true
|
||||
} else if allowedip == v401 {
|
||||
foundV401 = true
|
||||
} else if allowedip == v41281 {
|
||||
foundV41281 = true
|
||||
} else {
|
||||
newAllowedIPs = append(newAllowedIPs, allowedip)
|
||||
}
|
||||
}
|
||||
if !((foundV401 && foundV41281) || (foundV600001 && foundV680001)) {
|
||||
goto err
|
||||
}
|
||||
if foundV401 && foundV41281 {
|
||||
newAllowedIPs = append(newAllowedIPs, v400)
|
||||
} else if foundV401 {
|
||||
newAllowedIPs = append(newAllowedIPs, v401)
|
||||
} else if foundV41281 {
|
||||
newAllowedIPs = append(newAllowedIPs, v41281)
|
||||
}
|
||||
if foundV600001 && foundV680001 {
|
||||
newAllowedIPs = append(newAllowedIPs, v600000)
|
||||
} else if foundV600001 {
|
||||
newAllowedIPs = append(newAllowedIPs, v600001)
|
||||
} else if foundV680001 {
|
||||
newAllowedIPs = append(newAllowedIPs, v680001)
|
||||
}
|
||||
cfg.Peers[0].AllowedIPs = newAllowedIPs
|
||||
} else {
|
||||
var (
|
||||
foundV400 bool
|
||||
foundV600000 bool
|
||||
)
|
||||
for _, allowedip := range cfg.Peers[0].AllowedIPs {
|
||||
if allowedip == v600000 {
|
||||
foundV600000 = true
|
||||
} else if allowedip == v400 {
|
||||
foundV400 = true
|
||||
} else {
|
||||
newAllowedIPs = append(newAllowedIPs, allowedip)
|
||||
}
|
||||
}
|
||||
if !(foundV400 || foundV600000) {
|
||||
goto err
|
||||
}
|
||||
if foundV400 {
|
||||
newAllowedIPs = append(newAllowedIPs, v401)
|
||||
newAllowedIPs = append(newAllowedIPs, v41281)
|
||||
}
|
||||
if foundV600000 {
|
||||
newAllowedIPs = append(newAllowedIPs, v600001)
|
||||
newAllowedIPs = append(newAllowedIPs, v680001)
|
||||
}
|
||||
cfg.Peers[0].AllowedIPs = newAllowedIPs
|
||||
}
|
||||
dlg.syntaxEdit.SetText(cfg.ToWgQuick())
|
||||
return
|
||||
|
||||
err:
|
||||
text := dlg.syntaxEdit.Text()
|
||||
dlg.syntaxEdit.SetText("")
|
||||
dlg.syntaxEdit.SetText(text)
|
||||
}
|
||||
|
||||
func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) {
|
||||
dlg.blockUntunneledTraficCheckGuard = true
|
||||
switch syntax.BlockState(state) {
|
||||
case syntax.InevaluableBlockingUntunneledTraffic:
|
||||
dlg.blockUntunneledTrafficCB.SetVisible(false)
|
||||
case syntax.BlockingUntunneledTraffic:
|
||||
dlg.blockUntunneledTrafficCB.SetVisible(true)
|
||||
dlg.blockUntunneledTrafficCB.SetChecked(true)
|
||||
case syntax.NotBlockingUntunneledTraffic:
|
||||
dlg.blockUntunneledTrafficCB.SetVisible(true)
|
||||
dlg.blockUntunneledTrafficCB.SetChecked(false)
|
||||
}
|
||||
dlg.blockUntunneledTraficCheckGuard = false
|
||||
}
|
||||
|
||||
func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
|
||||
if privateKey == dlg.lastPrivateKey {
|
||||
return
|
||||
}
|
||||
dlg.lastPrivateKey = privateKey
|
||||
key, _ := conf.NewPrivateKeyFromString(privateKey)
|
||||
if key != nil {
|
||||
dlg.pubkeyEdit.SetText(key.Public().String())
|
||||
} else {
|
||||
dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
|
||||
}
|
||||
}
|
||||
|
||||
func (dlg *EditDialog) onSaveButtonClicked() {
|
||||
newName := dlg.nameEdit.Text()
|
||||
if newName == "" {
|
||||
showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required."))
|
||||
return
|
||||
}
|
||||
if !conf.TunnelNameIsValid(newName) {
|
||||
showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name ‘%s’ is invalid.", newName))
|
||||
return
|
||||
}
|
||||
newNameLower := strings.ToLower(newName)
|
||||
|
||||
if newNameLower != strings.ToLower(dlg.config.Name) {
|
||||
existingTunnelList, err := manager.IPCClientTunnels()
|
||||
if err != nil {
|
||||
showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error())
|
||||
return
|
||||
}
|
||||
for _, tunnel := range existingTunnelList {
|
||||
if strings.ToLower(tunnel.Name) == newNameLower {
|
||||
showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name ‘%s’.", newName))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
|
||||
if err != nil {
|
||||
showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dlg.config = *cfg
|
||||
dlg.Accept()
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
)
|
||||
|
||||
func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func(file *os.File) error) bool {
|
||||
showError := func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
showErrorCustom(owner, l18n.Sprintf("Writing file failed"), err.Error())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Do you want to overwrite it?`, filePath), walk.MsgBoxYesNo|walk.MsgBoxDefButton2|walk.MsgBoxIconWarning) {
|
||||
return false
|
||||
}
|
||||
|
||||
if file, err = os.Create(filePath); err != nil {
|
||||
return !showError(err)
|
||||
}
|
||||
} else {
|
||||
return !showError(err)
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return !showError(write(file))
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256" height="256" version="1.1" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><g><circle cx="128" cy="128" r="120" fill="#e1e1e1" stroke="#cacaca" stroke-linecap="square" stroke-width="8"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 262 B |
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="400" height="400" version="1.1" viewBox="0 0 400 400" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="a"><path d="m0 300h300v-300h-300z"/></clipPath></defs><g transform="matrix(1.3333 0 0 -1.3333 0 400)"><g clip-path="url(#a)"><g transform="translate(177.57 268.56)"><path d="m0 0c0.969-0.066 2.097 0.81 3.987 1.635-1.862 0.802-2.973 1.655-3.939 1.593-0.996-0.063-2.592-1.031-2.567-1.578 0.027-0.603 1.532-1.583 2.519-1.65" fill="#871719"/></g><g transform="translate(179.32 268.11)"><path d="m0 0c0.969-0.066 2.097 0.81 3.987 1.635-1.862 0.802-2.973 1.655-3.939 1.593-0.996-0.063-2.592-1.031-2.567-1.578 0.027-0.603 1.532-1.583 2.519-1.65" fill="#871719"/></g><g transform="translate(299.74 154.44)"><path d="m0 0s6.94 145.56-153.04 145.56c-141.48 0-145.9-139.63-145.9-139.63s-20.811-160.37 149.16-160.37c163.02 0 149.78 154.44 149.78 154.44" fill="#871719"/></g><g transform="translate(133.86 128.17)"><path d="m0 0c-2.627-1.39-4.65-2.414-6.63-3.517-8.1-4.512-15.026-10.419-20.544-17.868-1.784-2.409-3.01-2.603-5.727-0.941-35.338 21.61-37.609 75.843 0.983 99.453 30.017 18.364 68.365 7.14 82.735-20.477 2.723-5.234 3.069-13.291 1.345-18.782-5.955-18.955-20.015-29.586-39.313-34.102 5.689 4.87 10.218 10.393 11.659 18.025 1.452 7.687-0.084 14.638-4.542 20.956-6.773 9.596-19.868 13.544-30.811 9.389-11.881-4.511-18.39-15.354-17.216-28.683 1.09-12.381 10.484-20.405 28.061-23.453" fill="#fff"/></g><g transform="translate(58.513 66.293)"><path d="M 0,0 C 2.838,19.152 25.265,36.788 44.23,34.776 38.356,26.832 35.643,17.846 34.988,8.883 28.686,7.722 22.747,6.941 16.981,5.478 11.304,4.037 5.803,1.903 0,0" fill="#fff"/></g><g transform="translate(183.79 273.09)"><path d="m0 0c1.061 0.812 2.155 1.494 3.472 0.408 0.75-0.617 1.478-1.257 2.386-2.032-1.127-0.595-2.042-1.096-2.975-1.567-1.306-0.658-2.282-0.218-3.072 0.822-0.642 0.844-0.757 1.645 0.189 2.369m15.447-157.8c-1.598 1.382-2.611 1.381-4.485 0.182-6.359-4.068-12.867-7.922-19.481-11.562-3.792-2.086-7.898-3.599-12.653-5.724 1.633-0.421 2.418-0.619 3.201-0.827 17.776-4.73 27.272-20.335 23.065-37.813-3.741-15.544-19.52-25.482-34.812-22.86-12.748 2.186-23.877 12.772-25.735 25.456-2.026 13.824 4.859 27.119 17.108 32.689 6.794 3.089 13.771 5.778 20.549 8.9 7.706 3.551 16.038 6.355 22.766 11.296 16.7 12.262 27.012 29.145 31.033 49.523 2.408 12.207 2.245 24.36-3.339 35.95-4.286 8.895-11.319 15.357-18.875 21.253-7.775 6.068-16.007 11.554-23.747 17.664-2.095 1.653-3.509 4.505-4.478 7.09-0.411 1.095 0.925 4.066 1.819 4.227 4.746 0.852 9.596 1.29 14.425 1.473 5.574 0.21 11.164 0.032 16.746-0.042 1.21-0.015 2.853 0.141 3.549-0.542 2.891-2.843 5.159-1.014 7.166 0.856 1.689 1.573 2.893 3.668 4.236 5.433-0.815 0.12-2.487 0.541-4.168 0.581-5.613 0.133-11.233 0.047-16.843 0.253-1 0.037-1.963 1.066-2.942 1.637 1.031 0.409 2.058 1.165 3.093 1.175 9.682 0.091 19.366 0.054 29.057 0.054 0.011 5.038-6.722 11.936-12.704 13.806-0.045-0.682-0.087-1.317-0.131-1.994-5.944-0.141-11.778-0.03-17.078 2.788-1.396 0.743-2.309 2.394-3.446 3.627-1.431 1.551-2.605 3.547-4.349 4.559-3.576 2.076-7.48 3.58-11.211 5.397-13.259 6.458-27.262 6.231-42.302 4.854 8.991-2.092 17.11-3.982 25.23-5.872-0.093-0.494-0.185-0.987-0.278-1.481-10.86-1.455-21.134 2.528-31.756 4.003 3.849-2.254 7.749-4.35 11.778-6.158 4.095-1.837 8.316-3.39 12.538-5.091-5.364-4.583-10.746-5.588-17.488-4.048-3.686 0.842-7.585 1.29-11.348 1.106-3.887-0.19-7.802-1.147-11.332-3.506 3.78-1.916 7.263-3.506 10.549-5.432 1.355-0.795 2.909-2.144 3.287-3.536 0.904-3.333 1.166-6.841 1.687-10.281-6.188-0.701-17.071-6.994-19.27-11.09 9.512-1.831 19.868 0.383 28.942-5.746-2.989-2.262-9.949-5.075-12.502-7.007 3.156-0.827 10.469-0.423 13.33-0.229 2.409 0.164 3.521 0.223 4.508-0.59l28.001-21.921c2.944-2.374 14.835-13.629 17.939-20.704 2.643-6.023 2.966-11.148 2.965-12.398-2e-3 -3.355-0.413-8.609-2.721-14.469-0.969-2.461-3.812-7.912-9.677-14.267-9.09-9.847-20.783-15.17-33.57-17.807-29.732-6.13-54.436-37.881-47.462-72.884 8.142-40.866 53.247-62.991 90.107-43.552 23.824 12.564 36.456 37.078 33.072 63.762-2.045 16.12-9.338 29.269-21.563 39.839" fill="#fff"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,144 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/lxn/walk"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
type widthAndState struct {
|
||||
width int
|
||||
state manager.TunnelState
|
||||
}
|
||||
|
||||
type widthAndDllIdx struct {
|
||||
width int
|
||||
idx int32
|
||||
dll string
|
||||
}
|
||||
|
||||
var cachedOverlayIconsForWidthAndState = make(map[widthAndState]walk.Image)
|
||||
|
||||
func iconWithOverlayForState(state manager.TunnelState, size int) (icon walk.Image, err error) {
|
||||
icon = cachedOverlayIconsForWidthAndState[widthAndState{size, state}]
|
||||
if icon != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wireguardIcon, err := loadLogoIcon(size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if state == manager.TunnelStopped {
|
||||
return wireguardIcon, err // TODO: if we find something prettier than the gray dot, then remove this clause
|
||||
}
|
||||
|
||||
iconSize := wireguardIcon.Size()
|
||||
w := int(float64(iconSize.Width) * 0.65)
|
||||
h := int(float64(iconSize.Height) * 0.65)
|
||||
overlayBounds := walk.Rectangle{iconSize.Width - w, iconSize.Height - h, w, h}
|
||||
overlayIcon, err := iconForState(state, overlayBounds.Width)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
icon = walk.NewPaintFuncImage(walk.Size{size, size}, func(canvas *walk.Canvas, bounds walk.Rectangle) error {
|
||||
if err := canvas.DrawImageStretched(wireguardIcon, bounds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := canvas.DrawImageStretched(overlayIcon, overlayBounds); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
cachedOverlayIconsForWidthAndState[widthAndState{size, state}] = icon
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var cachedIconsForWidthAndState = make(map[widthAndState]*walk.Icon)
|
||||
|
||||
func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err error) {
|
||||
icon = cachedIconsForWidthAndState[widthAndState{size, state}]
|
||||
if icon != nil {
|
||||
return
|
||||
}
|
||||
switch state {
|
||||
case manager.TunnelStarted:
|
||||
icon, err = loadSystemIcon("imageres", -106, size)
|
||||
case manager.TunnelStopped:
|
||||
icon, err = walk.NewIconFromResourceIdWithSize(8, walk.Size{size, size}) // TODO: replace with real icon from imageres/shell32
|
||||
default:
|
||||
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
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func textForState(state manager.TunnelState, withEllipsis bool) (text string) {
|
||||
switch state {
|
||||
case manager.TunnelStarted:
|
||||
text = l18n.Sprintf("Active")
|
||||
case manager.TunnelStarting:
|
||||
text = l18n.Sprintf("Activating")
|
||||
case manager.TunnelStopped:
|
||||
text = l18n.Sprintf("Inactive")
|
||||
case manager.TunnelStopping:
|
||||
text = l18n.Sprintf("Deactivating")
|
||||
case manager.TunnelUnknown:
|
||||
text = l18n.Sprintf("Unknown state")
|
||||
}
|
||||
if withEllipsis {
|
||||
switch state {
|
||||
case manager.TunnelStarting, manager.TunnelStopping:
|
||||
text += "…"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var cachedSystemIconsForWidthAndDllIdx = make(map[widthAndDllIdx]*walk.Icon)
|
||||
|
||||
func loadSystemIcon(dll string, index int32, size int) (icon *walk.Icon, err error) {
|
||||
icon = cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}]
|
||||
if icon != nil {
|
||||
return
|
||||
}
|
||||
icon, err = walk.NewIconFromSysDLLWithSize(dll, int(index), size)
|
||||
if err == nil {
|
||||
cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}] = icon
|
||||
}
|
||||
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) {
|
||||
icon = cachedLogoIconsForWidth[size]
|
||||
if icon != nil {
|
||||
return
|
||||
}
|
||||
icon, err = walk.NewIconFromResourceIdWithSize(7, walk.Size{size, size})
|
||||
if err == nil {
|
||||
cachedLogoIconsForWidth[size] = icon
|
||||
}
|
||||
return
|
||||
}
|
||||
290
ui/listview.go
290
ui/listview.go
@@ -1,290 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/lxn/win"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
)
|
||||
|
||||
// ListModel is a struct to store the currently known tunnels to the GUI, suitable as a model for a walk.TableView.
|
||||
type ListModel struct {
|
||||
walk.TableModelBase
|
||||
walk.SorterBase
|
||||
|
||||
tunnels []manager.Tunnel
|
||||
lastObservedState map[manager.Tunnel]manager.TunnelState
|
||||
}
|
||||
|
||||
var cachedListViewIconsForWidthAndState = make(map[widthAndState]*walk.Bitmap)
|
||||
|
||||
func (t *ListModel) RowCount() int {
|
||||
return len(t.tunnels)
|
||||
}
|
||||
|
||||
func (t *ListModel) Value(row, col int) any {
|
||||
if col != 0 || row < 0 || row >= len(t.tunnels) {
|
||||
return ""
|
||||
}
|
||||
return t.tunnels[row].Name
|
||||
}
|
||||
|
||||
func (t *ListModel) Sort(col int, order walk.SortOrder) error {
|
||||
sort.SliceStable(t.tunnels, func(i, j int) bool {
|
||||
return conf.TunnelNameIsLess(t.tunnels[i].Name, t.tunnels[j].Name)
|
||||
})
|
||||
|
||||
return t.SorterBase.Sort(col, order)
|
||||
}
|
||||
|
||||
type ListView struct {
|
||||
*walk.TableView
|
||||
|
||||
model *ListModel
|
||||
|
||||
tunnelChangedCB *manager.TunnelChangeCallback
|
||||
tunnelsChangedCB *manager.TunnelsChangeCallback
|
||||
tunnelsUpdateSuspended int32
|
||||
}
|
||||
|
||||
func NewListView(parent walk.Container) (*ListView, error) {
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
tv, err := walk.NewTableView(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(tv)
|
||||
|
||||
tv.SetDoubleBuffering(true)
|
||||
|
||||
model := new(ListModel)
|
||||
model.lastObservedState = make(map[manager.Tunnel]manager.TunnelState)
|
||||
tv.SetModel(model)
|
||||
tv.SetLastColumnStretched(true)
|
||||
tv.SetHeaderHidden(true)
|
||||
tv.SetIgnoreNowhere(true)
|
||||
tv.SetScrollbarOrientation(walk.Vertical)
|
||||
tv.Columns().Add(walk.NewTableViewColumn())
|
||||
|
||||
tunnelsView := &ListView{
|
||||
TableView: tv,
|
||||
model: model,
|
||||
}
|
||||
tv.SetCellStyler(tunnelsView)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
tunnelsView.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(tunnelsView.onTunnelChange)
|
||||
tunnelsView.tunnelsChangedCB = manager.IPCClientRegisterTunnelsChange(tunnelsView.onTunnelsChange)
|
||||
|
||||
return tunnelsView, nil
|
||||
}
|
||||
|
||||
func (tv *ListView) Dispose() {
|
||||
if tv.tunnelChangedCB != nil {
|
||||
tv.tunnelChangedCB.Unregister()
|
||||
tv.tunnelChangedCB = nil
|
||||
}
|
||||
if tv.tunnelsChangedCB != nil {
|
||||
tv.tunnelsChangedCB.Unregister()
|
||||
tv.tunnelsChangedCB = nil
|
||||
}
|
||||
tv.TableView.Dispose()
|
||||
}
|
||||
|
||||
func (tv *ListView) CurrentTunnel() *manager.Tunnel {
|
||||
idx := tv.CurrentIndex()
|
||||
if idx == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tv.model.tunnels[idx]
|
||||
}
|
||||
|
||||
var dummyBitmap *walk.Bitmap
|
||||
|
||||
func (tv *ListView) StyleCell(style *walk.CellStyle) {
|
||||
row := style.Row()
|
||||
if row < 0 || row >= len(tv.model.tunnels) {
|
||||
return
|
||||
}
|
||||
tunnel := &tv.model.tunnels[row]
|
||||
|
||||
var state manager.TunnelState
|
||||
var ok bool
|
||||
state, ok = tv.model.lastObservedState[tv.model.tunnels[row]]
|
||||
if !ok {
|
||||
var err error
|
||||
state, err = tunnel.State()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tv.model.lastObservedState[tv.model.tunnels[row]] = state
|
||||
}
|
||||
|
||||
icon, err := iconForState(state, 14)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
margin := tv.IntFrom96DPI(1)
|
||||
bitmapWidth := tv.IntFrom96DPI(16)
|
||||
|
||||
if win.IsAppThemed() {
|
||||
cacheKey := widthAndState{bitmapWidth, state}
|
||||
if cacheValue, ok := cachedListViewIconsForWidthAndState[cacheKey]; ok {
|
||||
style.Image = cacheValue
|
||||
return
|
||||
}
|
||||
bitmap, err := walk.NewBitmapWithTransparentPixelsForDPI(walk.Size{bitmapWidth, bitmapWidth}, tv.DPI())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
canvas, err := walk.NewCanvasFromImage(bitmap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bounds := walk.Rectangle{X: margin, Y: margin, Height: bitmapWidth - 2*margin, Width: bitmapWidth - 2*margin}
|
||||
err = canvas.DrawImageStretchedPixels(icon, bounds)
|
||||
canvas.Dispose()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cachedListViewIconsForWidthAndState[cacheKey] = bitmap
|
||||
style.Image = bitmap
|
||||
} else {
|
||||
if dummyBitmap == nil {
|
||||
dummyBitmap, _ = walk.NewBitmapForDPI(tv.SizeFrom96DPI(walk.Size{}), 96)
|
||||
}
|
||||
style.Image = dummyBitmap
|
||||
canvas := style.Canvas()
|
||||
if canvas == nil {
|
||||
return
|
||||
}
|
||||
bounds := style.BoundsPixels()
|
||||
bounds.Width = bitmapWidth - 2*margin
|
||||
bounds.X = (bounds.Height - bounds.Width) / 2
|
||||
bounds.Height = bounds.Width
|
||||
bounds.Y += bounds.X
|
||||
canvas.DrawImageStretchedPixels(icon, bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *ListView) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
|
||||
tv.Synchronize(func() {
|
||||
idx := -1
|
||||
for i := range tv.model.tunnels {
|
||||
if tv.model.tunnels[i].Name == tunnel.Name {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if idx != -1 {
|
||||
tv.model.lastObservedState[tv.model.tunnels[idx]] = state
|
||||
tv.model.PublishRowChanged(idx)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (tv *ListView) onTunnelsChange() {
|
||||
if atomic.LoadInt32(&tv.tunnelsUpdateSuspended) == 0 {
|
||||
tv.Load(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *ListView) SetSuspendTunnelsUpdate(suspend bool) {
|
||||
if suspend {
|
||||
atomic.AddInt32(&tv.tunnelsUpdateSuspended, 1)
|
||||
} else {
|
||||
atomic.AddInt32(&tv.tunnelsUpdateSuspended, -1)
|
||||
}
|
||||
tv.Load(true)
|
||||
}
|
||||
|
||||
func (tv *ListView) Load(asyncUI bool) {
|
||||
tunnels, err := manager.IPCClientTunnels()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
doUI := func() {
|
||||
newTunnels := make(map[manager.Tunnel]bool, len(tunnels))
|
||||
oldTunnels := make(map[manager.Tunnel]bool, len(tv.model.tunnels))
|
||||
for _, tunnel := range tunnels {
|
||||
newTunnels[tunnel] = true
|
||||
}
|
||||
for i := len(tv.model.tunnels); i > 0; {
|
||||
i--
|
||||
tunnel := tv.model.tunnels[i]
|
||||
oldTunnels[tunnel] = true
|
||||
if !newTunnels[tunnel] {
|
||||
tv.model.tunnels = append(tv.model.tunnels[:i], tv.model.tunnels[i+1:]...)
|
||||
tv.model.PublishRowsRemoved(i, i) // TODO: Do we have to call that everytime or can we pass a range?
|
||||
delete(tv.model.lastObservedState, tunnel)
|
||||
}
|
||||
}
|
||||
didAdd := false
|
||||
firstTunnelName := ""
|
||||
for tunnel := range newTunnels {
|
||||
if !oldTunnels[tunnel] {
|
||||
if len(firstTunnelName) == 0 || !conf.TunnelNameIsLess(firstTunnelName, tunnel.Name) {
|
||||
firstTunnelName = tunnel.Name
|
||||
}
|
||||
tv.model.tunnels = append(tv.model.tunnels, tunnel)
|
||||
didAdd = true
|
||||
}
|
||||
}
|
||||
if didAdd {
|
||||
tv.model.PublishRowsReset()
|
||||
tv.model.Sort(tv.model.SortedColumn(), tv.model.SortOrder())
|
||||
if len(tv.SelectedIndexes()) == 0 {
|
||||
tv.selectTunnel(firstTunnelName)
|
||||
}
|
||||
}
|
||||
}
|
||||
if asyncUI {
|
||||
tv.Synchronize(doUI)
|
||||
} else {
|
||||
doUI()
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *ListView) selectTunnel(tunnelName string) {
|
||||
for i, tunnel := range tv.model.tunnels {
|
||||
if tunnel.Name == tunnelName {
|
||||
tv.SetCurrentIndex(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tv *ListView) SelectFirstActiveTunnel() {
|
||||
tunnels := make([]manager.Tunnel, len(tv.model.tunnels))
|
||||
copy(tunnels, tv.model.tunnels)
|
||||
go func() {
|
||||
for _, tunnel := range tunnels {
|
||||
state, err := tunnel.State()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if state == manager.TunnelStarting || state == manager.TunnelStarted {
|
||||
tv.Synchronize(func() {
|
||||
tv.selectTunnel(tunnel.Name)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
221
ui/logpage.go
221
ui/logpage.go
@@ -1,221 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"github.com/amnezia-vpn/awg-windows/ringlogger"
|
||||
"github.com/lxn/walk"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLogLinesDisplayed = 10000
|
||||
)
|
||||
|
||||
type LogPage struct {
|
||||
*walk.TabPage
|
||||
logView *walk.TableView
|
||||
model *logModel
|
||||
}
|
||||
|
||||
func NewLogPage() (*LogPage, error) {
|
||||
lp := &LogPage{}
|
||||
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
if lp.TabPage, err = walk.NewTabPage(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(lp)
|
||||
|
||||
lp.Disposing().Attach(func() {
|
||||
lp.model.quit <- true
|
||||
})
|
||||
|
||||
lp.SetTitle(l18n.Sprintf("Log"))
|
||||
lp.SetLayout(walk.NewVBoxLayout())
|
||||
|
||||
if lp.logView, err = walk.NewTableView(lp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lp.logView.SetAlternatingRowBG(true)
|
||||
lp.logView.SetLastColumnStretched(true)
|
||||
lp.logView.SetGridlines(true)
|
||||
|
||||
contextMenu, err := walk.NewMenu()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lp.logView.AddDisposable(contextMenu)
|
||||
copyAction := walk.NewAction()
|
||||
copyAction.SetText(l18n.Sprintf("&Copy"))
|
||||
copyAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyC})
|
||||
copyAction.Triggered().Attach(lp.onCopy)
|
||||
contextMenu.Actions().Add(copyAction)
|
||||
lp.ShortcutActions().Add(copyAction)
|
||||
selectAllAction := walk.NewAction()
|
||||
selectAllAction.SetText(l18n.Sprintf("Select &all"))
|
||||
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
|
||||
selectAllAction.Triggered().Attach(lp.onSelectAll)
|
||||
contextMenu.Actions().Add(selectAllAction)
|
||||
lp.ShortcutActions().Add(selectAllAction)
|
||||
saveAction := walk.NewAction()
|
||||
saveAction.SetText(l18n.Sprintf("&Save to file…"))
|
||||
saveAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyS})
|
||||
saveAction.Triggered().Attach(lp.onSave)
|
||||
contextMenu.Actions().Add(saveAction)
|
||||
lp.ShortcutActions().Add(saveAction)
|
||||
lp.logView.SetContextMenu(contextMenu)
|
||||
setSelectionStatus := func() {
|
||||
copyAction.SetEnabled(len(lp.logView.SelectedIndexes()) > 0)
|
||||
selectAllAction.SetEnabled(len(lp.logView.SelectedIndexes()) < len(lp.model.items))
|
||||
}
|
||||
lp.logView.SelectedIndexesChanged().Attach(setSelectionStatus)
|
||||
|
||||
stampCol := walk.NewTableViewColumn()
|
||||
stampCol.SetName("Stamp")
|
||||
stampCol.SetTitle(l18n.Sprintf("Time"))
|
||||
stampCol.SetFormat("2006-01-02 15:04:05.000")
|
||||
stampCol.SetWidth(140)
|
||||
lp.logView.Columns().Add(stampCol)
|
||||
|
||||
msgCol := walk.NewTableViewColumn()
|
||||
msgCol.SetName("Line")
|
||||
msgCol.SetTitle(l18n.Sprintf("Log message"))
|
||||
lp.logView.Columns().Add(msgCol)
|
||||
|
||||
lp.model = newLogModel(lp)
|
||||
lp.model.RowsReset().Attach(setSelectionStatus)
|
||||
lp.logView.SetModel(lp.model)
|
||||
setSelectionStatus()
|
||||
|
||||
buttonsContainer, err := walk.NewComposite(lp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buttonsContainer.SetLayout(walk.NewHBoxLayout())
|
||||
buttonsContainer.Layout().SetMargins(walk.Margins{})
|
||||
|
||||
walk.NewHSpacer(buttonsContainer)
|
||||
|
||||
saveButton, err := walk.NewPushButton(buttonsContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
saveButton.SetText(l18n.Sprintf("&Save"))
|
||||
saveButton.Clicked().Attach(lp.onSave)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return lp, nil
|
||||
}
|
||||
|
||||
func (lp *LogPage) isAtBottom() bool {
|
||||
return len(lp.model.items) == 0 || lp.logView.ItemVisible(len(lp.model.items)-1)
|
||||
}
|
||||
|
||||
func (lp *LogPage) scrollToBottom() {
|
||||
lp.logView.EnsureItemVisible(len(lp.model.items) - 1)
|
||||
}
|
||||
|
||||
func (lp *LogPage) onCopy() {
|
||||
var logLines strings.Builder
|
||||
selectedItemIndexes := lp.logView.SelectedIndexes()
|
||||
if len(selectedItemIndexes) == 0 {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(selectedItemIndexes); i++ {
|
||||
logItem := lp.model.items[selectedItemIndexes[i]]
|
||||
logLines.WriteString(fmt.Sprintf("%s: %s\r\n", logItem.Stamp.Format("2006-01-02 15:04:05.000"), logItem.Line))
|
||||
}
|
||||
walk.Clipboard().SetText(logLines.String())
|
||||
}
|
||||
|
||||
func (lp *LogPage) onSelectAll() {
|
||||
lp.logView.SetSelectedIndexes([]int{-1})
|
||||
}
|
||||
|
||||
func (lp *LogPage) onSave() {
|
||||
fd := walk.FileDialog{
|
||||
Filter: l18n.Sprintf("Text Files (*.txt)|*.txt|All Files (*.*)|*.*"),
|
||||
FilePath: fmt.Sprintf("wireguard-log-%s.txt", time.Now().Format("2006-01-02T150405")),
|
||||
Title: l18n.Sprintf("Export log to file"),
|
||||
}
|
||||
|
||||
form := lp.Form()
|
||||
|
||||
if ok, _ := fd.ShowSave(form); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if fd.FilterIndex == 1 && !strings.HasSuffix(fd.FilePath, ".txt") {
|
||||
fd.FilePath = fd.FilePath + ".txt"
|
||||
}
|
||||
|
||||
writeFileWithOverwriteHandling(form, fd.FilePath, func(file *os.File) error {
|
||||
if _, err := ringlogger.Global.WriteTo(file); err != nil {
|
||||
return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type logModel struct {
|
||||
walk.ReflectTableModelBase
|
||||
lp *LogPage
|
||||
quit chan bool
|
||||
items []ringlogger.FollowLine
|
||||
}
|
||||
|
||||
func newLogModel(lp *LogPage) *logModel {
|
||||
mdl := &logModel{lp: lp, quit: make(chan bool)}
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
cursor := ringlogger.CursorAll
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
var items []ringlogger.FollowLine
|
||||
items, cursor = ringlogger.Global.FollowFromCursor(cursor)
|
||||
if len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
mdl.lp.Synchronize(func() {
|
||||
isAtBottom := mdl.lp.isAtBottom() && len(lp.logView.SelectedIndexes()) <= 1
|
||||
|
||||
mdl.items = append(mdl.items, items...)
|
||||
if len(mdl.items) > maxLogLinesDisplayed {
|
||||
mdl.items = mdl.items[len(mdl.items)-maxLogLinesDisplayed:]
|
||||
}
|
||||
mdl.PublishRowsReset()
|
||||
|
||||
if isAtBottom {
|
||||
mdl.lp.scrollToBottom()
|
||||
}
|
||||
})
|
||||
|
||||
case <-mdl.quit:
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return mdl
|
||||
}
|
||||
|
||||
func (mdl *logModel) Items() any {
|
||||
return mdl.items
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
"github.com/lxn/win"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
type ManageTunnelsWindow struct {
|
||||
walk.FormBase
|
||||
|
||||
tabs *walk.TabWidget
|
||||
tunnelsPage *TunnelsPage
|
||||
logPage *LogPage
|
||||
updatePage *UpdatePage
|
||||
|
||||
tunnelChangedCB *manager.TunnelChangeCallback
|
||||
}
|
||||
|
||||
const (
|
||||
manageWindowWindowClass = "WireGuard UI - Manage Tunnels"
|
||||
raiseMsg = win.WM_USER + 0x3510
|
||||
aboutWireGuardCmd = 0x37
|
||||
)
|
||||
|
||||
var taskbarButtonCreatedMsg uint32
|
||||
|
||||
var initedManageTunnels sync.Once
|
||||
|
||||
func NewManageTunnelsWindow() (*ManageTunnelsWindow, error) {
|
||||
initedManageTunnels.Do(func() {
|
||||
walk.AppendToWalkInit(func() {
|
||||
walk.MustRegisterWindowClass(manageWindowWindowClass)
|
||||
taskbarButtonCreatedMsg = win.RegisterWindowMessage(windows.StringToUTF16Ptr("TaskbarButtonCreated"))
|
||||
})
|
||||
})
|
||||
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
font, err := walk.NewFont("Segoe UI", 9, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mtw := new(ManageTunnelsWindow)
|
||||
mtw.SetName("WireGuard")
|
||||
|
||||
err = walk.InitWindow(mtw, nil, manageWindowWindowClass, win.WS_OVERLAPPEDWINDOW, win.WS_EX_CONTROLPARENT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(mtw)
|
||||
win.ChangeWindowMessageFilterEx(mtw.Handle(), raiseMsg, win.MSGFLT_ALLOW, nil)
|
||||
mtw.SetPersistent(true)
|
||||
|
||||
if icon, err := loadLogoIcon(32); err == nil {
|
||||
mtw.SetIcon(icon)
|
||||
}
|
||||
mtw.SetTitle("WireGuard")
|
||||
mtw.SetFont(font)
|
||||
mtw.SetSize(walk.Size{675, 525})
|
||||
mtw.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
|
||||
vlayout := walk.NewVBoxLayout()
|
||||
vlayout.SetMargins(walk.Margins{5, 5, 5, 5})
|
||||
mtw.SetLayout(vlayout)
|
||||
mtw.Closing().Attach(func(canceled *bool, reason walk.CloseReason) {
|
||||
// "Close to tray" instead of exiting application
|
||||
*canceled = true
|
||||
if !noTrayAvailable {
|
||||
mtw.Hide()
|
||||
} else {
|
||||
win.ShowWindow(mtw.Handle(), win.SW_MINIMIZE)
|
||||
}
|
||||
})
|
||||
mtw.VisibleChanged().Attach(func() {
|
||||
if mtw.Visible() {
|
||||
mtw.tunnelsPage.updateConfView()
|
||||
win.SetForegroundWindow(mtw.Handle())
|
||||
win.BringWindowToTop(mtw.Handle())
|
||||
mtw.logPage.scrollToBottom()
|
||||
}
|
||||
})
|
||||
|
||||
if mtw.tabs, err = walk.NewTabWidget(mtw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mtw.tunnelsPage, err = NewTunnelsPage(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mtw.tabs.Pages().Add(mtw.tunnelsPage.TabPage)
|
||||
mtw.tunnelsPage.CreateToolbar()
|
||||
|
||||
if mtw.logPage, err = NewLogPage(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mtw.tabs.Pages().Add(mtw.logPage.TabPage)
|
||||
|
||||
mtw.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(mtw.onTunnelChange)
|
||||
globalState, _ := manager.IPCClientGlobalState()
|
||||
mtw.onTunnelChange(nil, manager.TunnelUnknown, globalState, nil)
|
||||
|
||||
systemMenu := win.GetSystemMenu(mtw.Handle(), false)
|
||||
if systemMenu != 0 {
|
||||
win.InsertMenuItem(systemMenu, 0, true, &win.MENUITEMINFO{
|
||||
CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
|
||||
FMask: win.MIIM_ID | win.MIIM_STRING | win.MIIM_FTYPE,
|
||||
FType: win.MIIM_STRING,
|
||||
DwTypeData: windows.StringToUTF16Ptr(l18n.Sprintf("&About WireGuard…")),
|
||||
WID: uint32(aboutWireGuardCmd),
|
||||
})
|
||||
win.InsertMenuItem(systemMenu, 1, true, &win.MENUITEMINFO{
|
||||
CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
|
||||
FMask: win.MIIM_TYPE,
|
||||
FType: win.MFT_SEPARATOR,
|
||||
})
|
||||
}
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return mtw, nil
|
||||
}
|
||||
|
||||
func (mtw *ManageTunnelsWindow) Dispose() {
|
||||
if mtw.tunnelChangedCB != nil {
|
||||
mtw.tunnelChangedCB.Unregister()
|
||||
mtw.tunnelChangedCB = nil
|
||||
}
|
||||
mtw.FormBase.Dispose()
|
||||
}
|
||||
|
||||
func (mtw *ManageTunnelsWindow) updateProgressIndicator(globalState manager.TunnelState) {
|
||||
pi := mtw.ProgressIndicator()
|
||||
if pi == nil {
|
||||
return
|
||||
}
|
||||
switch globalState {
|
||||
case manager.TunnelStopping, manager.TunnelStarting:
|
||||
pi.SetState(walk.PIIndeterminate)
|
||||
default:
|
||||
pi.SetState(walk.PINoProgress)
|
||||
}
|
||||
if icon, err := iconForState(globalState, 16); err == nil {
|
||||
if globalState == manager.TunnelStopped {
|
||||
icon = nil
|
||||
}
|
||||
pi.SetOverlayIcon(icon, textForState(globalState, false))
|
||||
}
|
||||
}
|
||||
|
||||
func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
|
||||
mtw.Synchronize(func() {
|
||||
mtw.updateProgressIndicator(globalState)
|
||||
|
||||
if err != nil && mtw.Visible() {
|
||||
errMsg := err.Error()
|
||||
if len(errMsg) > 0 && errMsg[len(errMsg)-1] != '.' {
|
||||
errMsg += "."
|
||||
}
|
||||
showWarningCustom(mtw, l18n.Sprintf("Tunnel Error"), l18n.Sprintf("%s\n\nPlease consult the log for more information.", errMsg))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (mtw *ManageTunnelsWindow) UpdateFound() {
|
||||
if mtw.updatePage != nil {
|
||||
return
|
||||
}
|
||||
if IsAdmin {
|
||||
mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title()))
|
||||
}
|
||||
updatePage, err := NewUpdatePage()
|
||||
if err == nil {
|
||||
mtw.updatePage = updatePage
|
||||
mtw.tabs.Pages().Add(updatePage.TabPage)
|
||||
}
|
||||
}
|
||||
|
||||
func (mtw *ManageTunnelsWindow) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
switch msg {
|
||||
case win.WM_QUERYENDSESSION:
|
||||
if lParam == win.ENDSESSION_CLOSEAPP {
|
||||
return win.TRUE
|
||||
}
|
||||
case win.WM_ENDSESSION:
|
||||
if lParam == win.ENDSESSION_CLOSEAPP && wParam == 1 {
|
||||
walk.App().Exit(198)
|
||||
}
|
||||
case win.WM_SYSCOMMAND:
|
||||
if wParam == aboutWireGuardCmd {
|
||||
onAbout(mtw)
|
||||
return 0
|
||||
}
|
||||
case raiseMsg:
|
||||
if mtw.tunnelsPage == nil || mtw.tabs == nil {
|
||||
mtw.Synchronize(func() {
|
||||
mtw.SendMessage(msg, wParam, lParam)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
if !mtw.Visible() {
|
||||
mtw.tunnelsPage.listView.SelectFirstActiveTunnel()
|
||||
if mtw.tabs.Pages().Len() != 3 {
|
||||
mtw.tabs.SetCurrentIndex(0)
|
||||
}
|
||||
}
|
||||
if mtw.tabs.Pages().Len() == 3 {
|
||||
mtw.tabs.SetCurrentIndex(2)
|
||||
}
|
||||
raise(mtw.Handle())
|
||||
return 0
|
||||
case taskbarButtonCreatedMsg:
|
||||
ret := mtw.FormBase.WndProc(hwnd, msg, wParam, lParam)
|
||||
go func() {
|
||||
globalState, err := manager.IPCClientGlobalState()
|
||||
if err == nil {
|
||||
mtw.Synchronize(func() {
|
||||
mtw.updateProgressIndicator(globalState)
|
||||
})
|
||||
}
|
||||
}()
|
||||
return ret
|
||||
}
|
||||
|
||||
return mtw.FormBase.WndProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
79
ui/raise.go
79
ui/raise.go
@@ -1,79 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/lxn/win"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
)
|
||||
|
||||
func raise(hwnd win.HWND) {
|
||||
if win.IsIconic(hwnd) {
|
||||
win.ShowWindow(hwnd, win.SW_RESTORE)
|
||||
}
|
||||
|
||||
win.SetActiveWindow(hwnd)
|
||||
win.SetWindowPos(hwnd, win.HWND_TOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW)
|
||||
win.SetForegroundWindow(hwnd)
|
||||
win.SetWindowPos(hwnd, win.HWND_NOTOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW)
|
||||
}
|
||||
|
||||
func raiseRemote(hwnd win.HWND) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
win.SendMessage(hwnd, raiseMsg, 0, 0)
|
||||
currentForegroundHwnd := win.GetForegroundWindow()
|
||||
currentForegroundThreadId := win.GetWindowThreadProcessId(currentForegroundHwnd, nil)
|
||||
currentThreadId := win.GetCurrentThreadId()
|
||||
win.AttachThreadInput(int32(currentForegroundThreadId), int32(currentThreadId), true)
|
||||
win.SetWindowPos(hwnd, win.HWND_TOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW)
|
||||
win.SetWindowPos(hwnd, win.HWND_NOTOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE|win.SWP_SHOWWINDOW)
|
||||
win.SetForegroundWindow(hwnd)
|
||||
win.AttachThreadInput(int32(currentForegroundThreadId), int32(currentThreadId), false)
|
||||
win.SetFocus(hwnd)
|
||||
win.SetActiveWindow(hwnd)
|
||||
}
|
||||
|
||||
func RaiseUI() bool {
|
||||
hwnd := win.FindWindow(windows.StringToUTF16Ptr(manageWindowWindowClass), nil)
|
||||
if hwnd == 0 {
|
||||
return false
|
||||
}
|
||||
raiseRemote(hwnd)
|
||||
return true
|
||||
}
|
||||
|
||||
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, 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 {
|
||||
return 0
|
||||
}
|
||||
win.UnhookWinEvent(handle)
|
||||
raiseRemote(hwnd)
|
||||
os.Exit(0)
|
||||
return 0
|
||||
}, 0, 0, win.WINEVENT_SKIPOWNPROCESS|win.WINEVENT_OUTOFCONTEXT)
|
||||
if err != nil {
|
||||
showErrorCustom(nil, l18n.Sprintf("WireGuard Detection Error"), l18n.Sprintf("Unable to wait for WireGuard window to appear: %v", err))
|
||||
}
|
||||
for {
|
||||
var msg win.MSG
|
||||
if m := win.GetMessage(&msg, 0, 0, 0); m != 0 {
|
||||
win.TranslateMessage(&msg)
|
||||
win.DispatchMessage(&msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,696 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 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 (
|
||||
"math"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type highlight int
|
||||
|
||||
const (
|
||||
highlightSection highlight = iota
|
||||
highlightField
|
||||
highlightPrivateKey
|
||||
highlightPublicKey
|
||||
highlightPresharedKey
|
||||
highlightIP
|
||||
highlightCidr
|
||||
highlightHost
|
||||
highlightPort
|
||||
highlightMTU
|
||||
highlightKeepalive
|
||||
highlightComment
|
||||
highlightDelimiter
|
||||
highlightTable
|
||||
highlightCmd
|
||||
highlightJc
|
||||
highlightJmin
|
||||
highlightJmax
|
||||
highlightS1
|
||||
highlightS2
|
||||
highlightH1
|
||||
highlightH2
|
||||
highlightH3
|
||||
highlightH4
|
||||
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) isValidUint16() bool {
|
||||
return s.isValidUint(false, 0, math.MaxUint16)
|
||||
}
|
||||
|
||||
func (s stringSpan) isValidUint32() bool {
|
||||
return s.isValidUint(false, 0, math.MaxUint32)
|
||||
}
|
||||
|
||||
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
|
||||
fieldJc
|
||||
fieldJmin
|
||||
fieldJmax
|
||||
fieldS1
|
||||
fieldS2
|
||||
fieldH1
|
||||
fieldH2
|
||||
fieldH3
|
||||
fieldH4
|
||||
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("Jc"):
|
||||
return fieldJc
|
||||
case s.isCaselessSame("Jmin"):
|
||||
return fieldJmin
|
||||
case s.isCaselessSame("Jmax"):
|
||||
return fieldJmax
|
||||
case s.isCaselessSame("S1"):
|
||||
return fieldS1
|
||||
case s.isCaselessSame("S2"):
|
||||
return fieldS2
|
||||
case s.isCaselessSame("H1"):
|
||||
return fieldH1
|
||||
case s.isCaselessSame("H2"):
|
||||
return fieldH2
|
||||
case s.isCaselessSame("H3"):
|
||||
return fieldH3
|
||||
case s.isCaselessSame("H4"):
|
||||
return fieldH4
|
||||
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 fieldJc:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint16(), highlightJc))
|
||||
case fieldJmin:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint16(), highlightJmin))
|
||||
case fieldJmax:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint16(), highlightJmax))
|
||||
case fieldS1:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint16(), highlightS1))
|
||||
case fieldS2:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint16(), highlightS2))
|
||||
case fieldH1:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint32(), highlightH1))
|
||||
case fieldH2:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint32(), highlightH2))
|
||||
case fieldH3:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint32(), highlightH3))
|
||||
case fieldH4:
|
||||
hsa.append(parent.s, s, validateHighlight(s.isValidUint32(), highlightH4))
|
||||
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)
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 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"
|
||||
)
|
||||
|
||||
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 BlockState = iota
|
||||
BlockingUntunneledTraffic
|
||||
NotBlockingUntunneledTraffic
|
||||
)
|
||||
|
||||
func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags {
|
||||
return walk.GrowableHorz | walk.GrowableVert | walk.GreedyHorz | walk.GreedyVert
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) MinSizeHint() walk.Size {
|
||||
return walk.Size{20, 12}
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) SizeHint() walk.Size {
|
||||
return walk.Size{200, 100}
|
||||
}
|
||||
|
||||
func (*SyntaxEdit) CreateLayoutItem(ctx *walk.LayoutContext) walk.LayoutItem {
|
||||
return walk.NewGreedyLayoutItem()
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) Text() string {
|
||||
textLength := se.SendMessage(win.WM_GETTEXTLENGTH, 0, 0)
|
||||
buf := make([]uint16, textLength+1)
|
||||
se.SendMessage(win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0])))
|
||||
return strings.Replace(syscall.UTF16ToString(buf), "\r\n", "\n", -1)
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) SetText(text string) (err error) {
|
||||
if text == se.Text() {
|
||||
return nil
|
||||
}
|
||||
text = strings.Replace(text, "\n", "\r\n", -1)
|
||||
if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
|
||||
err = errors.New("WM_SETTEXT failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) TextChanged() *walk.Event {
|
||||
return se.textChangedPublisher.Event()
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) PrivateKeyChanged() *walk.StringEvent {
|
||||
return se.privateKeyPublisher.Event()
|
||||
}
|
||||
|
||||
func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent {
|
||||
return se.blockUntunneledTrafficPublisher.Event()
|
||||
}
|
||||
|
||||
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)},
|
||||
highlightJc: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightJmin: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightJmax: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightS1: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightS2: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightH1: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightH2: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightH3: {color:win.RGB(0x81, 0x5F, 0x03)},
|
||||
highlightH4: {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_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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
|
||||
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{}
|
||||
if err := walk.InitWidget(
|
||||
se,
|
||||
parent,
|
||||
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
|
||||
}
|
||||
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() any {
|
||||
return se.Text()
|
||||
},
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
392
ui/tray.go
392
ui/tray.go
@@ -1,392 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
)
|
||||
|
||||
// Status + active CIDRs + separator
|
||||
const trayTunnelActionsOffset = 3
|
||||
|
||||
type Tray struct {
|
||||
*walk.NotifyIcon
|
||||
|
||||
// Current known tunnels by name
|
||||
tunnels map[string]*walk.Action
|
||||
tunnelsAreInBreakoutMenu bool
|
||||
|
||||
mtw *ManageTunnelsWindow
|
||||
|
||||
tunnelChangedCB *manager.TunnelChangeCallback
|
||||
tunnelsChangedCB *manager.TunnelsChangeCallback
|
||||
|
||||
clicked func()
|
||||
}
|
||||
|
||||
func NewTray(mtw *ManageTunnelsWindow) (*Tray, error) {
|
||||
var err error
|
||||
|
||||
tray := &Tray{
|
||||
mtw: mtw,
|
||||
tunnels: make(map[string]*walk.Action),
|
||||
}
|
||||
|
||||
tray.NotifyIcon, err = walk.NewNotifyIcon(mtw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tray, tray.setup()
|
||||
}
|
||||
|
||||
func (tray *Tray) setup() error {
|
||||
tray.clicked = tray.onManageTunnels
|
||||
|
||||
tray.SetToolTip(l18n.Sprintf("WireGuard: Deactivated"))
|
||||
tray.SetVisible(true)
|
||||
if icon, err := loadLogoIcon(16); err == nil {
|
||||
tray.SetIcon(icon)
|
||||
}
|
||||
|
||||
tray.MouseDown().Attach(func(x, y int, button walk.MouseButton) {
|
||||
if button == walk.LeftButton {
|
||||
tray.clicked()
|
||||
}
|
||||
})
|
||||
tray.MessageClicked().Attach(func() {
|
||||
tray.clicked()
|
||||
})
|
||||
|
||||
for _, item := range [...]struct {
|
||||
label string
|
||||
handler walk.EventHandler
|
||||
enabled bool
|
||||
hidden bool
|
||||
separator bool
|
||||
defawlt bool
|
||||
}{
|
||||
{label: l18n.Sprintf("Status: Unknown")},
|
||||
{label: l18n.Sprintf("Addresses: None"), hidden: true},
|
||||
{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, hidden: !IsAdmin},
|
||||
{separator: true},
|
||||
{label: l18n.Sprintf("&About WireGuard…"), handler: tray.onAbout, enabled: true},
|
||||
{label: l18n.Sprintf("E&xit"), handler: onQuit, enabled: true, hidden: !IsAdmin},
|
||||
} {
|
||||
var action *walk.Action
|
||||
if item.separator {
|
||||
action = walk.NewSeparatorAction()
|
||||
} else {
|
||||
action = walk.NewAction()
|
||||
action.SetText(item.label)
|
||||
action.SetEnabled(item.enabled)
|
||||
action.SetVisible(!item.hidden)
|
||||
action.SetDefault(item.defawlt)
|
||||
if item.handler != nil {
|
||||
action.Triggered().Attach(item.handler)
|
||||
}
|
||||
}
|
||||
|
||||
tray.ContextMenu().Actions().Add(action)
|
||||
}
|
||||
tray.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(tray.onTunnelChange)
|
||||
tray.tunnelsChangedCB = manager.IPCClientRegisterTunnelsChange(tray.onTunnelsChange)
|
||||
tray.onTunnelsChange()
|
||||
globalState, _ := manager.IPCClientGlobalState()
|
||||
tray.updateGlobalState(globalState)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tray *Tray) Dispose() error {
|
||||
if tray.tunnelChangedCB != nil {
|
||||
tray.tunnelChangedCB.Unregister()
|
||||
tray.tunnelChangedCB = nil
|
||||
}
|
||||
if tray.tunnelsChangedCB != nil {
|
||||
tray.tunnelsChangedCB.Unregister()
|
||||
tray.tunnelsChangedCB = nil
|
||||
}
|
||||
return tray.NotifyIcon.Dispose()
|
||||
}
|
||||
|
||||
func (tray *Tray) onTunnelsChange() {
|
||||
tunnels, err := manager.IPCClientTunnels()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tray.mtw.Synchronize(func() {
|
||||
tunnelSet := make(map[string]bool, len(tunnels))
|
||||
for _, tunnel := range tunnels {
|
||||
tunnelSet[tunnel.Name] = true
|
||||
if tray.tunnels[tunnel.Name] == nil {
|
||||
tray.addTunnelAction(&tunnel)
|
||||
}
|
||||
}
|
||||
for trayTunnel := range tray.tunnels {
|
||||
if !tunnelSet[trayTunnel] {
|
||||
tray.removeTunnelAction(trayTunnel)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
tunnelAction.SetEnabled(true)
|
||||
tunnelAction.SetCheckable(true)
|
||||
tclosure := *tunnel
|
||||
tunnelAction.Triggered().Attach(func() {
|
||||
tunnelAction.SetChecked(!tunnelAction.Checked())
|
||||
go func() {
|
||||
oldState, err := tclosure.Toggle()
|
||||
if err != nil {
|
||||
tray.mtw.Synchronize(func() {
|
||||
raise(tray.mtw.Handle())
|
||||
tray.mtw.tunnelsPage.listView.selectTunnel(tclosure.Name)
|
||||
tray.mtw.tabs.SetCurrentIndex(0)
|
||||
if oldState == manager.TunnelUnknown {
|
||||
showErrorCustom(tray.mtw, l18n.Sprintf("Failed to determine tunnel state"), err.Error())
|
||||
} else if oldState == manager.TunnelStopped {
|
||||
showErrorCustom(tray.mtw, l18n.Sprintf("Failed to activate tunnel"), err.Error())
|
||||
} else if oldState == manager.TunnelStarted {
|
||||
showErrorCustom(tray.mtw, l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
})
|
||||
tray.tunnels[tunnel.Name] = tunnelAction
|
||||
|
||||
var (
|
||||
idx int
|
||||
name string
|
||||
)
|
||||
for idx, name = range tray.sortedTunnels() {
|
||||
if name == tunnel.Name {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tray.mtw.Synchronize(func() {
|
||||
tray.setTunnelState(&tclosure, state)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func (tray *Tray) removeTunnelAction(tunnelName string) {
|
||||
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, globalState manager.TunnelState, err error) {
|
||||
tray.mtw.Synchronize(func() {
|
||||
tray.updateGlobalState(globalState)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
func (tray *Tray) updateGlobalState(globalState manager.TunnelState) {
|
||||
if icon, err := iconWithOverlayForState(globalState, 16); err == nil {
|
||||
tray.SetIcon(icon)
|
||||
}
|
||||
|
||||
actions := tray.ContextMenu().Actions()
|
||||
statusAction := actions.At(0)
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
})
|
||||
}()
|
||||
|
||||
for _, action := range tray.tunnels {
|
||||
action.SetEnabled(globalState == manager.TunnelStarted || globalState == manager.TunnelStopped)
|
||||
}
|
||||
}
|
||||
|
||||
func (tray *Tray) setTunnelState(tunnel *manager.Tunnel, state manager.TunnelState) {
|
||||
tunnelAction := tray.tunnels[tunnel.Name]
|
||||
if tunnelAction == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch state {
|
||||
case manager.TunnelStarted:
|
||||
tunnelAction.SetEnabled(true)
|
||||
tunnelAction.SetChecked(true)
|
||||
|
||||
case manager.TunnelStopped:
|
||||
tunnelAction.SetChecked(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (tray *Tray) UpdateFound() {
|
||||
action := walk.NewAction()
|
||||
action.SetText(l18n.Sprintf("An Update is Available!"))
|
||||
menuIcon, _ := loadShieldIcon(16)
|
||||
action.SetImage(menuIcon)
|
||||
action.SetDefault(true)
|
||||
showUpdateTab := func() {
|
||||
if !tray.mtw.Visible() {
|
||||
tray.mtw.tunnelsPage.listView.SelectFirstActiveTunnel()
|
||||
}
|
||||
tray.mtw.tabs.SetCurrentIndex(2)
|
||||
raise(tray.mtw.Handle())
|
||||
}
|
||||
action.Triggered().Attach(showUpdateTab)
|
||||
tray.clicked = showUpdateTab
|
||||
tray.ContextMenu().Actions().Insert(tray.ContextMenu().Actions().Len()-2, action)
|
||||
|
||||
showUpdateBalloon := func() {
|
||||
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)
|
||||
}
|
||||
|
||||
timeSinceStart := time.Now().Sub(startTime)
|
||||
if timeSinceStart < time.Second*3 {
|
||||
time.AfterFunc(time.Second*3-timeSinceStart, func() {
|
||||
tray.mtw.Synchronize(showUpdateBalloon)
|
||||
})
|
||||
} else {
|
||||
showUpdateBalloon()
|
||||
}
|
||||
}
|
||||
|
||||
func (tray *Tray) onManageTunnels() {
|
||||
tray.mtw.tunnelsPage.listView.SelectFirstActiveTunnel()
|
||||
tray.mtw.tabs.SetCurrentIndex(0)
|
||||
raise(tray.mtw.Handle())
|
||||
}
|
||||
|
||||
func (tray *Tray) onAbout() {
|
||||
if tray.mtw.Visible() {
|
||||
onAbout(tray.mtw)
|
||||
} else {
|
||||
onAbout(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (tray *Tray) onImport() {
|
||||
raise(tray.mtw.Handle())
|
||||
tray.mtw.tunnelsPage.onImport()
|
||||
}
|
||||
@@ -1,606 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/conf"
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
type TunnelsPage struct {
|
||||
*walk.TabPage
|
||||
|
||||
listView *ListView
|
||||
listContainer walk.Container
|
||||
listToolbar *walk.ToolBar
|
||||
confView *ConfView
|
||||
fillerButton *walk.PushButton
|
||||
fillerHandler func()
|
||||
|
||||
fillerContainer *walk.Composite
|
||||
currentTunnelContainer *walk.Composite
|
||||
}
|
||||
|
||||
func NewTunnelsPage() (*TunnelsPage, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
tp := new(TunnelsPage)
|
||||
if tp.TabPage, err = walk.NewTabPage(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(tp)
|
||||
|
||||
tp.SetTitle(l18n.Sprintf("Tunnels"))
|
||||
tp.SetLayout(walk.NewHBoxLayout())
|
||||
|
||||
tp.listContainer, _ = walk.NewComposite(tp)
|
||||
vlayout := walk.NewVBoxLayout()
|
||||
vlayout.SetMargins(walk.Margins{})
|
||||
vlayout.SetSpacing(0)
|
||||
tp.listContainer.SetLayout(vlayout)
|
||||
|
||||
if tp.listView, err = NewListView(tp.listContainer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tp.currentTunnelContainer, err = walk.NewComposite(tp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vlayout = walk.NewVBoxLayout()
|
||||
vlayout.SetMargins(walk.Margins{})
|
||||
tp.currentTunnelContainer.SetLayout(vlayout)
|
||||
|
||||
if tp.fillerContainer, err = walk.NewComposite(tp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp.fillerContainer.SetVisible(false)
|
||||
hlayout := walk.NewHBoxLayout()
|
||||
hlayout.SetMargins(walk.Margins{})
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
if tp.confView, err = NewConfView(tp.currentTunnelContainer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
controlsContainer, err := walk.NewComposite(tp.currentTunnelContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hlayout = walk.NewHBoxLayout()
|
||||
hlayout.SetMargins(walk.Margins{})
|
||||
controlsContainer.SetLayout(hlayout)
|
||||
|
||||
walk.NewHSpacer(controlsContainer)
|
||||
|
||||
editTunnel, err := walk.NewPushButton(controlsContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
editTunnel.SetEnabled(false)
|
||||
tp.listView.CurrentIndexChanged().Attach(func() {
|
||||
editTunnel.SetEnabled(tp.listView.CurrentIndex() > -1)
|
||||
})
|
||||
editTunnel.SetText(l18n.Sprintf("&Edit"))
|
||||
editTunnel.Clicked().Attach(tp.onEditTunnel)
|
||||
editTunnel.SetVisible(IsAdmin)
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
tp.listView.ItemCountChanged().Attach(tp.onTunnelsChanged)
|
||||
tp.listView.SelectedIndexesChanged().Attach(tp.onSelectedTunnelsChanged)
|
||||
tp.listView.ItemActivated().Attach(tp.onTunnelsViewItemActivated)
|
||||
tp.listView.CurrentIndexChanged().Attach(tp.updateConfView)
|
||||
tp.listView.Load(false)
|
||||
tp.onTunnelsChanged()
|
||||
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) CreateToolbar() error {
|
||||
if tp.listToolbar != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HACK: Because of https://github.com/lxn/walk/issues/481
|
||||
// we need to put the ToolBar into its own Composite.
|
||||
toolBarContainer, err := walk.NewComposite(tp.listContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toolBarContainer.SetDoubleBuffering(true)
|
||||
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
|
||||
}
|
||||
|
||||
addMenu, err := walk.NewMenu()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tp.AddDisposable(addMenu)
|
||||
importAction := walk.NewAction()
|
||||
importAction.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
|
||||
importActionIcon, _ := loadSystemIcon("imageres", -3, 16)
|
||||
importAction.SetImage(importActionIcon)
|
||||
importAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
|
||||
importAction.SetDefault(true)
|
||||
importAction.Triggered().Attach(tp.onImport)
|
||||
addMenu.Actions().Add(importAction)
|
||||
addAction := walk.NewAction()
|
||||
addAction.SetText(l18n.Sprintf("Add &empty tunnel…"))
|
||||
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", -258, 16)
|
||||
addMenuAction.SetImage(addMenuActionIcon)
|
||||
addMenuAction.SetText(l18n.Sprintf("Add Tunnel"))
|
||||
addMenuAction.SetToolTip(importAction.Text())
|
||||
addMenuAction.Triggered().Attach(tp.onImport)
|
||||
tp.listToolbar.Actions().Add(addMenuAction)
|
||||
|
||||
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
|
||||
|
||||
deleteAction := walk.NewAction()
|
||||
deleteActionIcon, _ := loadSystemIcon("shell32", -240, 16)
|
||||
deleteAction.SetImage(deleteActionIcon)
|
||||
deleteAction.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
|
||||
deleteAction.SetToolTip(l18n.Sprintf("Remove selected tunnel(s)"))
|
||||
deleteAction.Triggered().Attach(tp.onDelete)
|
||||
tp.listToolbar.Actions().Add(deleteAction)
|
||||
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
|
||||
|
||||
exportAction := walk.NewAction()
|
||||
exportActionIcon, _ := loadSystemIcon("imageres", -174, 16)
|
||||
exportAction.SetImage(exportActionIcon)
|
||||
exportAction.SetToolTip(l18n.Sprintf("Export all tunnels to zip"))
|
||||
exportAction.Triggered().Attach(tp.onExportTunnels)
|
||||
tp.listToolbar.Actions().Add(exportAction)
|
||||
|
||||
fixContainerWidthToToolbarWidth := func() {
|
||||
toolbarWidth := tp.listToolbar.SizeHint().Width
|
||||
tp.listContainer.SetMinMaxSizePixels(walk.Size{toolbarWidth, 0}, walk.Size{toolbarWidth, 0})
|
||||
}
|
||||
fixContainerWidthToToolbarWidth()
|
||||
tp.listToolbar.SizeChanged().Attach(fixContainerWidthToToolbarWidth)
|
||||
|
||||
contextMenu, err := walk.NewMenu()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tp.listView.AddDisposable(contextMenu)
|
||||
toggleAction := walk.NewAction()
|
||||
toggleAction.SetText(l18n.Sprintf("&Toggle"))
|
||||
toggleAction.SetDefault(true)
|
||||
toggleAction.Triggered().Attach(tp.onTunnelsViewItemActivated)
|
||||
contextMenu.Actions().Add(toggleAction)
|
||||
contextMenu.Actions().Add(walk.NewSeparatorAction())
|
||||
importAction2 := walk.NewAction()
|
||||
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)
|
||||
tp.listView.SetContextMenu(contextMenu)
|
||||
|
||||
setSelectionOrientedOptions := func() {
|
||||
selected := len(tp.listView.SelectedIndexes())
|
||||
all := len(tp.listView.model.tunnels)
|
||||
deleteAction.SetEnabled(selected > 0)
|
||||
deleteAction2.SetEnabled(selected > 0)
|
||||
toggleAction.SetEnabled(selected == 1)
|
||||
selectAllAction.SetEnabled(selected < all)
|
||||
editAction.SetEnabled(selected == 1)
|
||||
}
|
||||
tp.listView.SelectedIndexesChanged().Attach(setSelectionOrientedOptions)
|
||||
setSelectionOrientedOptions()
|
||||
setExport := func() {
|
||||
all := len(tp.listView.model.tunnels)
|
||||
exportAction.SetEnabled(all > 0)
|
||||
exportAction2.SetEnabled(all > 0)
|
||||
}
|
||||
setExportRange := func(from, to int) { setExport() }
|
||||
tp.listView.model.RowsInserted().Attach(setExportRange)
|
||||
tp.listView.model.RowsRemoved().Attach(setExportRange)
|
||||
tp.listView.model.RowsReset().Attach(setExport)
|
||||
setExport()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) updateConfView() {
|
||||
tp.confView.SetTunnel(tp.listView.CurrentTunnel())
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) importFiles(paths []string) {
|
||||
go func() {
|
||||
syncedMsgBox := func(title, message string, flags walk.MsgBoxStyle) {
|
||||
tp.Synchronize(func() {
|
||||
walk.MsgBox(tp.Form(), title, message, flags)
|
||||
})
|
||||
}
|
||||
type unparsedConfig struct {
|
||||
Name string
|
||||
Config string
|
||||
}
|
||||
|
||||
var (
|
||||
unparsedConfigs []unparsedConfig
|
||||
lastErr error
|
||||
)
|
||||
|
||||
for _, path := range paths {
|
||||
switch strings.ToLower(filepath.Ext(path)) {
|
||||
case ".conf":
|
||||
textConfig, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
unparsedConfigs = append(unparsedConfigs, unparsedConfig{Name: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)), Config: string(textConfig)})
|
||||
case ".zip":
|
||||
// 1 .conf + 1 error .zip edge case?
|
||||
r, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
if strings.ToLower(filepath.Ext(f.Name)) != ".conf" {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
textConfig, err := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
unparsedConfigs = append(unparsedConfigs, unparsedConfig{Name: strings.TrimSuffix(filepath.Base(f.Name), filepath.Ext(f.Name)), Config: string(textConfig)})
|
||||
}
|
||||
|
||||
r.Close()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Add in reverse order so that the first one is selected.
|
||||
sort.Slice(unparsedConfigs, func(i, j int) bool {
|
||||
return conf.TunnelNameIsLess(unparsedConfigs[j].Name, unparsedConfigs[i].Name)
|
||||
})
|
||||
|
||||
existingTunnelList, err := manager.IPCClientTunnels()
|
||||
if err != nil {
|
||||
syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not enumerate existing tunnels: %v", lastErr), walk.MsgBoxIconWarning)
|
||||
return
|
||||
}
|
||||
existingLowerTunnels := make(map[string]bool, len(existingTunnelList))
|
||||
for _, tunnel := range existingTunnelList {
|
||||
existingLowerTunnels[strings.ToLower(tunnel.Name)] = true
|
||||
}
|
||||
|
||||
configCount := 0
|
||||
tp.listView.SetSuspendTunnelsUpdate(true)
|
||||
for _, unparsedConfig := range unparsedConfigs {
|
||||
if existingLowerTunnels[strings.ToLower(unparsedConfig.Name)] {
|
||||
lastErr = errors.New(l18n.Sprintf("Another tunnel already exists with the name ‘%s’", unparsedConfig.Name))
|
||||
continue
|
||||
}
|
||||
config, err := conf.FromWgQuickWithUnknownEncoding(unparsedConfig.Config, unparsedConfig.Name)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
_, err = manager.IPCClientNewTunnel(config)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
configCount++
|
||||
}
|
||||
tp.listView.SetSuspendTunnelsUpdate(false)
|
||||
|
||||
m, n := configCount, len(unparsedConfigs)
|
||||
switch {
|
||||
case n == 1 && m != n:
|
||||
syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Unable to import configuration: %v", lastErr), walk.MsgBoxIconWarning)
|
||||
case n == 1 && m == n:
|
||||
// nothing
|
||||
case m == n:
|
||||
syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d tunnels", m), walk.MsgBoxIconInformation)
|
||||
case m != n:
|
||||
syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) exportTunnels(filePath string) {
|
||||
writeFileWithOverwriteHandling(tp.Form(), filePath, func(file *os.File) error {
|
||||
writer := zip.NewWriter(file)
|
||||
|
||||
for _, tunnel := range tp.listView.model.tunnels {
|
||||
cfg, err := tunnel.StoredConfig()
|
||||
if err != nil {
|
||||
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: %w", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write(([]byte)(cfg.ToWgQuick())); err != nil {
|
||||
return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return writer.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) addTunnel(config *conf.Config) {
|
||||
_, err := manager.IPCClientNewTunnel(config)
|
||||
if err != nil {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Unable to create tunnel"), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
func (tp *TunnelsPage) onTunnelsViewItemActivated() {
|
||||
go func() {
|
||||
globalState, err := manager.IPCClientGlobalState()
|
||||
if err != nil || (globalState != manager.TunnelStarted && globalState != manager.TunnelStopped) {
|
||||
return
|
||||
}
|
||||
oldState, err := tp.listView.CurrentTunnel().Toggle()
|
||||
if err != nil {
|
||||
tp.Synchronize(func() {
|
||||
if oldState == manager.TunnelUnknown {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
|
||||
} else if oldState == manager.TunnelStopped {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
|
||||
} else if oldState == manager.TunnelStarted {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onEditTunnel() {
|
||||
tunnel := tp.listView.CurrentTunnel()
|
||||
if tunnel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if config := runEditDialog(tp.Form(), tunnel); config != nil {
|
||||
go func() {
|
||||
priorState, err := tunnel.State()
|
||||
tunnel.Delete()
|
||||
tunnel.WaitForStop()
|
||||
tunnel, err2 := manager.IPCClientNewTunnel(config)
|
||||
if err == nil && err2 == nil && (priorState == manager.TunnelStarting || priorState == manager.TunnelStarted) {
|
||||
tunnel.Start()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onAddTunnel() {
|
||||
if config := runEditDialog(tp.Form(), nil); config != nil {
|
||||
// Save new
|
||||
tp.addTunnel(config)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onDelete() {
|
||||
indices := tp.listView.SelectedIndexes()
|
||||
if len(indices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var title, question string
|
||||
if len(indices) > 1 {
|
||||
tunnelCount := len(indices)
|
||||
title = l18n.Sprintf("Delete %d tunnels", tunnelCount)
|
||||
question = l18n.Sprintf("Are you sure you would like to delete %d tunnels?", tunnelCount)
|
||||
} else {
|
||||
tunnelName := tp.listView.model.tunnels[indices[0]].Name
|
||||
title = l18n.Sprintf("Delete tunnel ‘%s’", tunnelName)
|
||||
question = l18n.Sprintf("Are you sure you would like to delete tunnel ‘%s’?", tunnelName)
|
||||
}
|
||||
if walk.DlgCmdNo == walk.MsgBox(
|
||||
tp.Form(),
|
||||
title,
|
||||
l18n.Sprintf("%s You cannot undo this action.", question),
|
||||
walk.MsgBoxYesNo|walk.MsgBoxIconWarning) {
|
||||
return
|
||||
}
|
||||
|
||||
selectTunnelAfter := ""
|
||||
if len(indices) < len(tp.listView.model.tunnels) {
|
||||
sort.Ints(indices)
|
||||
max := 0
|
||||
for i, idx := range indices {
|
||||
if idx+1 < len(tp.listView.model.tunnels) && (i+1 == len(indices) || idx+1 != indices[i+1]) {
|
||||
max = idx + 1
|
||||
} else if idx-1 >= 0 && (i == 0 || idx-1 != indices[i-1]) {
|
||||
max = idx - 1
|
||||
}
|
||||
}
|
||||
selectTunnelAfter = tp.listView.model.tunnels[max].Name
|
||||
}
|
||||
if len(selectTunnelAfter) > 0 {
|
||||
tp.listView.selectTunnel(selectTunnelAfter)
|
||||
}
|
||||
|
||||
tunnelsToDelete := make([]manager.Tunnel, len(indices))
|
||||
for i, j := range indices {
|
||||
tunnelsToDelete[i] = tp.listView.model.tunnels[j]
|
||||
}
|
||||
go func() {
|
||||
tp.listView.SetSuspendTunnelsUpdate(true)
|
||||
var errors []error
|
||||
for _, tunnel := range tunnelsToDelete {
|
||||
err := tunnel.Delete()
|
||||
if err != nil && (len(errors) == 0 || errors[len(errors)-1].Error() != err.Error()) {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
tp.listView.SetSuspendTunnelsUpdate(false)
|
||||
if len(errors) > 0 {
|
||||
tp.listView.Synchronize(func() {
|
||||
if len(errors) == 1 {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnel"), l18n.Sprintf("A tunnel was unable to be removed: %s", errors[0].Error()))
|
||||
} else {
|
||||
showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnels"), l18n.Sprintf("%d tunnels were unable to be removed.", len(errors)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onSelectAll() {
|
||||
tp.listView.SetSelectedIndexes([]int{-1})
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onImport() {
|
||||
dlg := walk.FileDialog{
|
||||
Filter: l18n.Sprintf("Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*"),
|
||||
Title: l18n.Sprintf("Import tunnel(s) from file"),
|
||||
}
|
||||
|
||||
if ok, _ := dlg.ShowOpenMultiple(tp.Form()); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
tp.importFiles(dlg.FilePaths)
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onExportTunnels() {
|
||||
dlg := walk.FileDialog{
|
||||
Filter: l18n.Sprintf("Configuration ZIP Files (*.zip)|*.zip"),
|
||||
Title: l18n.Sprintf("Export tunnels to zip"),
|
||||
}
|
||||
|
||||
if ok, _ := dlg.ShowSave(tp.Form()); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(dlg.FilePath, ".zip") {
|
||||
dlg.FilePath += ".zip"
|
||||
}
|
||||
|
||||
tp.exportTunnels(dlg.FilePath)
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) swapFiller(enabled bool) bool {
|
||||
if tp.fillerContainer.Visible() == enabled {
|
||||
return enabled
|
||||
}
|
||||
tp.SetSuspended(true)
|
||||
tp.fillerContainer.SetVisible(enabled)
|
||||
tp.currentTunnelContainer.SetVisible(!enabled)
|
||||
tp.SetSuspended(false)
|
||||
return enabled
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onTunnelsChanged() {
|
||||
if tp.swapFiller(tp.listView.model.RowCount() == 0) {
|
||||
tp.fillerButton.SetText(l18n.Sprintf("Import tunnel(s) from file"))
|
||||
tp.fillerHandler = tp.onImport
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TunnelsPage) onSelectedTunnelsChanged() {
|
||||
if tp.listView.model.RowCount() == 0 {
|
||||
return
|
||||
}
|
||||
indices := tp.listView.SelectedIndexes()
|
||||
tunnelCount := len(indices)
|
||||
if tp.swapFiller(tunnelCount > 1) {
|
||||
tp.fillerButton.SetText(l18n.Sprintf("Delete %d tunnels", tunnelCount))
|
||||
tp.fillerHandler = tp.onDelete
|
||||
}
|
||||
}
|
||||
133
ui/ui.go
133
ui/ui.go
@@ -1,133 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/lxn/walk"
|
||||
"github.com/lxn/win"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"github.com/amnezia-vpn/awg-windows/version"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
)
|
||||
|
||||
var (
|
||||
noTrayAvailable = false
|
||||
shouldQuitManagerWhenExiting = false
|
||||
startTime = time.Now()
|
||||
IsAdmin = false // A global, because this really is global for the process
|
||||
)
|
||||
|
||||
func RunUI() {
|
||||
runtime.LockOSThread()
|
||||
windows.SetProcessPriorityBoost(windows.CurrentProcess(), false)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
showErrorCustom(nil, "Panic", fmt.Sprint(err, "\n\n", string(debug.Stack())))
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
err error
|
||||
mtw *ManageTunnelsWindow
|
||||
tray *Tray
|
||||
)
|
||||
|
||||
for mtw == nil {
|
||||
mtw, err = NewManageTunnelsWindow()
|
||||
if err != nil {
|
||||
time.Sleep(time.Millisecond * 400)
|
||||
}
|
||||
}
|
||||
|
||||
for tray == nil {
|
||||
tray, err = NewTray(mtw)
|
||||
if err != nil {
|
||||
if version.OsIsCore() {
|
||||
noTrayAvailable = true
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 400)
|
||||
}
|
||||
}
|
||||
|
||||
manager.IPCClientRegisterManagerStopping(func() {
|
||||
mtw.Synchronize(func() {
|
||||
walk.App().Exit(0)
|
||||
})
|
||||
})
|
||||
|
||||
onUpdateNotification := func(updateState manager.UpdateState) {
|
||||
if updateState == manager.UpdateStateUnknown {
|
||||
return
|
||||
}
|
||||
mtw.Synchronize(func() {
|
||||
switch updateState {
|
||||
case manager.UpdateStateFoundUpdate:
|
||||
mtw.UpdateFound()
|
||||
if tray != nil && IsAdmin {
|
||||
tray.UpdateFound()
|
||||
}
|
||||
case manager.UpdateStateUpdatesDisabledUnofficialBuild:
|
||||
mtw.SetTitle(l18n.Sprintf("%s (unsigned build, no updates)", mtw.Title()))
|
||||
}
|
||||
})
|
||||
}
|
||||
manager.IPCClientRegisterUpdateFound(onUpdateNotification)
|
||||
go func() {
|
||||
updateState, err := manager.IPCClientUpdateState()
|
||||
if err == nil {
|
||||
onUpdateNotification(updateState)
|
||||
}
|
||||
}()
|
||||
|
||||
if tray == nil {
|
||||
win.ShowWindow(mtw.Handle(), win.SW_MINIMIZE)
|
||||
}
|
||||
|
||||
mtw.Run()
|
||||
if tray != nil {
|
||||
tray.Dispose()
|
||||
}
|
||||
mtw.Dispose()
|
||||
|
||||
if shouldQuitManagerWhenExiting {
|
||||
_, err := manager.IPCClientQuit(true)
|
||||
if err != nil {
|
||||
showErrorCustom(nil, l18n.Sprintf("Error Exiting WireGuard"), l18n.Sprintf("Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onQuit() {
|
||||
shouldQuitManagerWhenExiting = true
|
||||
walk.App().Exit(0)
|
||||
}
|
||||
|
||||
func showError(err error, owner walk.Form) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
showErrorCustom(owner, l18n.Sprintf("Error"), err.Error())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func showErrorCustom(owner walk.Form, title, message string) {
|
||||
walk.MsgBox(owner, title, message, walk.MsgBoxIconError)
|
||||
}
|
||||
|
||||
func showWarningCustom(owner walk.Form, title, message string) {
|
||||
walk.MsgBox(owner, title, message, walk.MsgBoxIconWarning)
|
||||
}
|
||||
142
ui/updatepage.go
142
ui/updatepage.go
@@ -1,142 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/lxn/walk"
|
||||
|
||||
"github.com/amnezia-vpn/awg-windows/l18n"
|
||||
"golang.zx2c4.com/wireguard/windows/manager"
|
||||
"golang.zx2c4.com/wireguard/windows/updater"
|
||||
)
|
||||
|
||||
type UpdatePage struct {
|
||||
*walk.TabPage
|
||||
}
|
||||
|
||||
func NewUpdatePage() (*UpdatePage, error) {
|
||||
var err error
|
||||
var disposables walk.Disposables
|
||||
defer disposables.Treat()
|
||||
|
||||
up := &UpdatePage{}
|
||||
|
||||
if up.TabPage, err = walk.NewTabPage(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disposables.Add(up)
|
||||
|
||||
up.SetTitle(l18n.Sprintf("An Update is Available!"))
|
||||
|
||||
tabIcon, _ := loadShieldIcon(16)
|
||||
up.SetImage(tabIcon)
|
||||
|
||||
up.SetLayout(walk.NewVBoxLayout())
|
||||
|
||||
instructions, err := walk.NewTextLabel(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instructions.SetText(l18n.Sprintf("An update to WireGuard is available. It is highly advisable to update without delay."))
|
||||
instructions.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
|
||||
|
||||
status, err := walk.NewTextLabel(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status.SetText(l18n.Sprintf("Status: Waiting for user"))
|
||||
status.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
|
||||
|
||||
bar, err := walk.NewProgressBar(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bar.SetVisible(false)
|
||||
|
||||
button, err := walk.NewPushButton(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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() {
|
||||
if !bar.Visible() {
|
||||
up.SetSuspended(true)
|
||||
button.SetEnabled(false)
|
||||
button.SetVisible(false)
|
||||
bar.SetVisible(true)
|
||||
bar.SetMarqueeMode(true)
|
||||
up.SetSuspended(false)
|
||||
status.SetText(l18n.Sprintf("Status: Waiting for updater service"))
|
||||
}
|
||||
}
|
||||
|
||||
switchToReadyState := func() {
|
||||
if bar.Visible() {
|
||||
up.SetSuspended(true)
|
||||
bar.SetVisible(false)
|
||||
bar.SetValue(0)
|
||||
bar.SetRange(0, 1)
|
||||
bar.SetMarqueeMode(false)
|
||||
button.SetVisible(true)
|
||||
button.SetEnabled(true)
|
||||
up.SetSuspended(false)
|
||||
}
|
||||
}
|
||||
|
||||
button.Clicked().Attach(func() {
|
||||
switchToUpdatingState()
|
||||
err := manager.IPCClientUpdate()
|
||||
if err != nil {
|
||||
switchToReadyState()
|
||||
status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
|
||||
}
|
||||
})
|
||||
|
||||
manager.IPCClientRegisterUpdateProgress(func(dp updater.DownloadProgress) {
|
||||
up.Synchronize(func() {
|
||||
switchToUpdatingState()
|
||||
if dp.Error != nil {
|
||||
switchToReadyState()
|
||||
err := dp.Error
|
||||
status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
|
||||
return
|
||||
}
|
||||
if len(dp.Activity) > 0 {
|
||||
stateText := dp.Activity
|
||||
status.SetText(l18n.Sprintf("Status: %s", stateText))
|
||||
}
|
||||
if dp.BytesTotal > 0 {
|
||||
bar.SetMarqueeMode(false)
|
||||
bar.SetRange(0, int(dp.BytesTotal))
|
||||
bar.SetValue(int(dp.BytesDownloaded))
|
||||
} else {
|
||||
bar.SetMarqueeMode(true)
|
||||
bar.SetValue(0)
|
||||
bar.SetRange(0, 1)
|
||||
}
|
||||
if dp.Complete {
|
||||
switchToReadyState()
|
||||
status.SetText(l18n.Sprintf("Status: Complete!"))
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
disposables.Spare()
|
||||
|
||||
return up, nil
|
||||
}
|
||||
Reference in New Issue
Block a user