golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/managewindow.go (about) 1 /* SPDX-License-Identifier: MIT 2 * 3 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. 4 */ 5 6 package ui 7 8 import ( 9 "sync" 10 "unsafe" 11 12 "github.com/lxn/walk" 13 "github.com/lxn/win" 14 "golang.org/x/sys/windows" 15 16 "golang.zx2c4.com/wireguard/windows/l18n" 17 "golang.zx2c4.com/wireguard/windows/manager" 18 ) 19 20 type ManageTunnelsWindow struct { 21 walk.FormBase 22 23 tabs *walk.TabWidget 24 tunnelsPage *TunnelsPage 25 logPage *LogPage 26 updatePage *UpdatePage 27 28 tunnelChangedCB *manager.TunnelChangeCallback 29 } 30 31 const ( 32 manageWindowWindowClass = "WireGuard UI - Manage Tunnels" 33 raiseMsg = win.WM_USER + 0x3510 34 aboutWireGuardCmd = 0x37 35 ) 36 37 var taskbarButtonCreatedMsg uint32 38 39 var initedManageTunnels sync.Once 40 41 func NewManageTunnelsWindow() (*ManageTunnelsWindow, error) { 42 initedManageTunnels.Do(func() { 43 walk.AppendToWalkInit(func() { 44 walk.MustRegisterWindowClass(manageWindowWindowClass) 45 taskbarButtonCreatedMsg = win.RegisterWindowMessage(windows.StringToUTF16Ptr("TaskbarButtonCreated")) 46 }) 47 }) 48 49 var err error 50 var disposables walk.Disposables 51 defer disposables.Treat() 52 53 font, err := walk.NewFont("Segoe UI", 9, 0) 54 if err != nil { 55 return nil, err 56 } 57 58 mtw := new(ManageTunnelsWindow) 59 mtw.SetName("WireGuard") 60 61 err = walk.InitWindow(mtw, nil, manageWindowWindowClass, win.WS_OVERLAPPEDWINDOW, win.WS_EX_CONTROLPARENT) 62 if err != nil { 63 return nil, err 64 } 65 disposables.Add(mtw) 66 win.ChangeWindowMessageFilterEx(mtw.Handle(), raiseMsg, win.MSGFLT_ALLOW, nil) 67 mtw.SetPersistent(true) 68 69 if icon, err := loadLogoIcon(32); err == nil { 70 mtw.SetIcon(icon) 71 } 72 mtw.SetTitle("WireGuard") 73 mtw.SetFont(font) 74 mtw.SetSize(walk.Size{675, 525}) 75 mtw.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0}) 76 vlayout := walk.NewVBoxLayout() 77 vlayout.SetMargins(walk.Margins{5, 5, 5, 5}) 78 mtw.SetLayout(vlayout) 79 mtw.Closing().Attach(func(canceled *bool, reason walk.CloseReason) { 80 // "Close to tray" instead of exiting application 81 *canceled = true 82 if !noTrayAvailable { 83 mtw.Hide() 84 } else { 85 win.ShowWindow(mtw.Handle(), win.SW_MINIMIZE) 86 } 87 }) 88 mtw.VisibleChanged().Attach(func() { 89 if mtw.Visible() { 90 mtw.tunnelsPage.updateConfView() 91 win.SetForegroundWindow(mtw.Handle()) 92 win.BringWindowToTop(mtw.Handle()) 93 mtw.logPage.scrollToBottom() 94 } 95 }) 96 97 if mtw.tabs, err = walk.NewTabWidget(mtw); err != nil { 98 return nil, err 99 } 100 101 if mtw.tunnelsPage, err = NewTunnelsPage(); err != nil { 102 return nil, err 103 } 104 mtw.tabs.Pages().Add(mtw.tunnelsPage.TabPage) 105 mtw.tunnelsPage.CreateToolbar() 106 107 if mtw.logPage, err = NewLogPage(); err != nil { 108 return nil, err 109 } 110 mtw.tabs.Pages().Add(mtw.logPage.TabPage) 111 112 mtw.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(mtw.onTunnelChange) 113 globalState, _ := manager.IPCClientGlobalState() 114 mtw.onTunnelChange(nil, manager.TunnelUnknown, globalState, nil) 115 116 systemMenu := win.GetSystemMenu(mtw.Handle(), false) 117 if systemMenu != 0 { 118 win.InsertMenuItem(systemMenu, 0, true, &win.MENUITEMINFO{ 119 CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})), 120 FMask: win.MIIM_ID | win.MIIM_STRING | win.MIIM_FTYPE, 121 FType: win.MIIM_STRING, 122 DwTypeData: windows.StringToUTF16Ptr(l18n.Sprintf("&About WireGuard…")), 123 WID: uint32(aboutWireGuardCmd), 124 }) 125 win.InsertMenuItem(systemMenu, 1, true, &win.MENUITEMINFO{ 126 CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})), 127 FMask: win.MIIM_TYPE, 128 FType: win.MFT_SEPARATOR, 129 }) 130 } 131 132 disposables.Spare() 133 134 return mtw, nil 135 } 136 137 func (mtw *ManageTunnelsWindow) Dispose() { 138 if mtw.tunnelChangedCB != nil { 139 mtw.tunnelChangedCB.Unregister() 140 mtw.tunnelChangedCB = nil 141 } 142 mtw.FormBase.Dispose() 143 } 144 145 func (mtw *ManageTunnelsWindow) updateProgressIndicator(globalState manager.TunnelState) { 146 pi := mtw.ProgressIndicator() 147 if pi == nil { 148 return 149 } 150 switch globalState { 151 case manager.TunnelStopping, manager.TunnelStarting: 152 pi.SetState(walk.PIIndeterminate) 153 default: 154 pi.SetState(walk.PINoProgress) 155 } 156 if icon, err := iconForState(globalState, 16); err == nil { 157 if globalState == manager.TunnelStopped { 158 icon = nil 159 } 160 pi.SetOverlayIcon(icon, textForState(globalState, false)) 161 } 162 } 163 164 func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) { 165 mtw.Synchronize(func() { 166 mtw.updateProgressIndicator(globalState) 167 168 if err != nil && mtw.Visible() { 169 errMsg := err.Error() 170 if len(errMsg) > 0 && errMsg[len(errMsg)-1] != '.' { 171 errMsg += "." 172 } 173 showWarningCustom(mtw, l18n.Sprintf("Tunnel Error"), l18n.Sprintf("%s\n\nPlease consult the log for more information.", errMsg)) 174 } 175 }) 176 } 177 178 func (mtw *ManageTunnelsWindow) UpdateFound() { 179 if mtw.updatePage != nil { 180 return 181 } 182 if IsAdmin { 183 mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title())) 184 } 185 updatePage, err := NewUpdatePage() 186 if err == nil { 187 mtw.updatePage = updatePage 188 mtw.tabs.Pages().Add(updatePage.TabPage) 189 } 190 } 191 192 func (mtw *ManageTunnelsWindow) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { 193 switch msg { 194 case win.WM_QUERYENDSESSION: 195 if lParam == win.ENDSESSION_CLOSEAPP { 196 return win.TRUE 197 } 198 case win.WM_ENDSESSION: 199 if lParam == win.ENDSESSION_CLOSEAPP && wParam == 1 { 200 walk.App().Exit(198) 201 } 202 case win.WM_SYSCOMMAND: 203 if wParam == aboutWireGuardCmd { 204 onAbout(mtw) 205 return 0 206 } 207 case raiseMsg: 208 if mtw.tunnelsPage == nil || mtw.tabs == nil { 209 mtw.Synchronize(func() { 210 mtw.SendMessage(msg, wParam, lParam) 211 }) 212 return 0 213 } 214 if !mtw.Visible() { 215 mtw.tunnelsPage.listView.SelectFirstActiveTunnel() 216 if mtw.tabs.Pages().Len() != 3 { 217 mtw.tabs.SetCurrentIndex(0) 218 } 219 } 220 if mtw.tabs.Pages().Len() == 3 { 221 mtw.tabs.SetCurrentIndex(2) 222 } 223 raise(mtw.Handle()) 224 return 0 225 case taskbarButtonCreatedMsg: 226 ret := mtw.FormBase.WndProc(hwnd, msg, wParam, lParam) 227 go func() { 228 globalState, err := manager.IPCClientGlobalState() 229 if err == nil { 230 mtw.Synchronize(func() { 231 mtw.updateProgressIndicator(globalState) 232 }) 233 } 234 }() 235 return ret 236 } 237 238 return mtw.FormBase.WndProc(hwnd, msg, wParam, lParam) 239 }