mirror of
https://github.com/amnezia-vpn/amneziawg-windows-client.git
synced 2026-05-17 08:15:44 +03:00
* replace wg.exe with awg.exe * update docs, update admin reg key * rename awg modules * build awg from source * remove unused params * improve work with tools * safe rebranding: change upgrade code, windows class and name * safe rebranding: wg -> awg * update dependencies, fixed showing transfered KBs * Revert "remove application update feature" This reverts commit9670f4298e. * Revert "remove application update feature #2" This reverts commitb0c96a9fd6. * updatepage and tray change WireGuard to AmneziaWG * move crypto from indirect to direct --------- Signed-off-by: Roman Zakharchuk <romikb@mail.ru>
393 lines
10 KiB
Go
393 lines
10 KiB
Go
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
package ui
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/amnezia-vpn/amneziawg-windows-client/l18n"
|
|
"github.com/amnezia-vpn/amneziawg-windows-client/manager"
|
|
"github.com/amnezia-vpn/amneziawg-windows/conf"
|
|
|
|
"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("AmneziaWG: 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 AmneziaWG…"), 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("AmneziaWG 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("AmneziaWG Deactivated"), l18n.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
|
|
}
|
|
}
|
|
}
|
|
} else if !tray.mtw.Visible() {
|
|
tray.ShowError(l18n.Sprintf("AmneziaWG 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("AmneziaWG: %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("AmneziaWG Update Available"), l18n.Sprintf("An update to AmneziaWG 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()
|
|
}
|