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  }