diff options
author | Alexander Neumann <alexander.neumann@picos-software.com> | 2019-04-24 15:29:38 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2019-04-24 15:42:51 +0200 |
commit | 4a0ee91473619db8a2c8a100758f1c24c39e7e39 (patch) | |
tree | 5df163a3e81b948fdf05f26429739d12b41ffc2e /ui/manage_tunnels.go | |
parent | ui: programmatically compute colors (diff) | |
download | wireguard-windows-4a0ee91473619db8a2c8a100758f1c24c39e7e39.tar.xz wireguard-windows-4a0ee91473619db8a2c8a100758f1c24c39e7e39.zip |
ui: use tabs in main window and refactor tunnels ui and log dialog into tab pages
requires https://github.com/lxn/walk/commit/edb74ee350e9585ddd212acad445ec383950f2cc for status image background
Signed-off-by: Alexander Neumann <alexander.neumann@picos-software.com>
Diffstat (limited to '')
-rw-r--r-- | ui/manage_tunnels.go | 398 |
1 files changed, 21 insertions, 377 deletions
diff --git a/ui/manage_tunnels.go b/ui/manage_tunnels.go index 35c067a6..69d90a0f 100644 --- a/ui/manage_tunnels.go +++ b/ui/manage_tunnels.go @@ -6,17 +6,8 @@ package ui import ( - "archive/zip" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" - "github.com/lxn/walk" "github.com/lxn/win" - "golang.zx2c4.com/wireguard/windows/conf" "golang.zx2c4.com/wireguard/windows/ringlogger" "golang.zx2c4.com/wireguard/windows/service" ) @@ -24,13 +15,10 @@ import ( type ManageTunnelsWindow struct { *walk.MainWindow - icon *walk.Icon - logger *ringlogger.Ringlogger - tunnelTracker *TunnelTracker - tunnelsView *TunnelsView - confView *ConfView - tunnelAddedPublisher walk.StringEventPublisher - tunnelDeletedPublisher walk.StringEventPublisher + icon *walk.Icon + logger *ringlogger.Ringlogger + tunnelsPage *TunnelsPage + logPage *LogPage } func NewManageTunnelsWindow(icon *walk.Icon, logger *ringlogger.Ringlogger) (*ManageTunnelsWindow, error) { @@ -63,103 +51,23 @@ func NewManageTunnelsWindow(icon *walk.Icon, logger *ringlogger.Ringlogger) (*Ma // "Close to tray" instead of exiting application onQuit() }) - mtw.Starting().Attach(func() { - mtw.updateConfView() - win.SetForegroundWindow(mtw.Handle()) - win.BringWindowToTop(mtw.Handle()) - }) - - splitter, _ := walk.NewHSplitter(mtw) - splitter.SetSuspended(true) - defer func() { - splitter.SetSuspended(false) - }() - - tunnelsContainer, _ := walk.NewComposite(splitter) - tunnelsContainer.SetLayout(walk.NewVBoxLayout()) - - splitter.SetFixed(tunnelsContainer, true) - - mtw.tunnelsView, _ = NewTunnelsView(tunnelsContainer) - mtw.tunnelsView.ItemActivated().Attach(mtw.onTunnelsViewItemActivated) - mtw.tunnelsView.CurrentIndexChanged().Attach(mtw.updateConfView) - - // ToolBar actions - { - // HACK: Because of https://github.com/lxn/walk/issues/481 - // we need to put the ToolBar into its own Composite. - toolBarContainer, _ := walk.NewComposite(tunnelsContainer) - toolBarContainer.SetLayout(walk.NewHBoxLayout()) - - tunnelsToolBar, _ := walk.NewToolBarWithOrientationAndButtonStyle(toolBarContainer, walk.Horizontal, walk.ToolBarButtonTextOnly) - - importAction := walk.NewAction() - importAction.SetText("Import tunnels from file...") - importAction.Triggered().Attach(mtw.onImport) - - addAction := walk.NewAction() - addAction.SetText("Add empty tunnel") - addAction.Triggered().Attach(mtw.onAddTunnel) - - viewLogAction := walk.NewAction() - viewLogAction.SetText("View Log") - viewLogAction.Triggered().Attach(mtw.onViewLog) - - exportTunnelsAction := walk.NewAction() - exportTunnelsAction.SetText("Export tunnels to zip...") - exportTunnelsAction.Triggered().Attach(mtw.onExportTunnels) - - addMenu, _ := walk.NewMenu() - mtw.AddDisposable(addMenu) - addMenu.Actions().Add(addAction) - addMenu.Actions().Add(importAction) - addMenuAction, _ := tunnelsToolBar.Actions().AddMenu(addMenu) - addMenuAction.SetText("➕") - - deleteAction := walk.NewAction() - tunnelsToolBar.Actions().Add(deleteAction) - deleteAction.SetText("➖") - deleteAction.Triggered().Attach(mtw.onDelete) - - settingsMenu, _ := walk.NewMenu() - mtw.AddDisposable(settingsMenu) - settingsMenu.Actions().Add(viewLogAction) - settingsMenu.Actions().Add(exportTunnelsAction) - settingsMenuAction, _ := tunnelsToolBar.Actions().AddMenu(settingsMenu) - settingsMenuAction.SetText("⚙") - } + mtw.VisibleChanged().Attach(func() { + if mtw.Visible() { + mtw.tunnelsPage.updateConfView() + win.SetForegroundWindow(mtw.Handle()) + win.BringWindowToTop(mtw.Handle()) - currentTunnelContainer, _ := walk.NewComposite(splitter) - currentTunnelContainer.SetLayout(walk.NewVBoxLayout()) - splitter.Layout().(interface{ SetStretchFactor(walk.Widget, int) error }).SetStretchFactor(currentTunnelContainer, 3) - - mtw.confView, _ = NewConfView(currentTunnelContainer) - - updateConfViewTicker := time.NewTicker(time.Second) - mtw.Disposing().Attach(updateConfViewTicker.Stop) - go func() { - for range updateConfViewTicker.C { - mtw.Synchronize(func() { - mtw.updateConfView() - }) + mtw.logPage.scrollToBottom() } - }() - - controlsContainer, _ := walk.NewComposite(currentTunnelContainer) - controlsContainer.SetLayout(walk.NewHBoxLayout()) - controlsContainer.Layout().SetMargins(walk.Margins{}) + }) - walk.NewHSpacer(controlsContainer) + tabWidget, _ := walk.NewTabWidget(mtw) - editTunnel, _ := walk.NewPushButton(controlsContainer) - editTunnel.SetEnabled(false) - mtw.tunnelsView.CurrentIndexChanged().Attach(func() { - editTunnel.SetEnabled(mtw.tunnelsView.CurrentIndex() > -1) - }) - editTunnel.SetText("Edit") - editTunnel.Clicked().Attach(mtw.onEditTunnel) + mtw.tunnelsPage, _ = NewTunnelsPage() + tabWidget.Pages().Add(mtw.tunnelsPage.TabPage) - mtw.tunnelsView.SetCurrentIndex(0) + mtw.logPage, _ = NewLogPage(logger) + tabWidget.Pages().Add(mtw.logPage.TabPage) disposables.Spare() @@ -167,286 +75,22 @@ func NewManageTunnelsWindow(icon *walk.Icon, logger *ringlogger.Ringlogger) (*Ma } func (mtw *ManageTunnelsWindow) TunnelTracker() *TunnelTracker { - return mtw.tunnelTracker + return mtw.tunnelsPage.tunnelTracker } func (mtw *ManageTunnelsWindow) SetTunnelTracker(tunnelTracker *TunnelTracker) { - mtw.tunnelTracker = tunnelTracker + mtw.tunnelsPage.tunnelTracker = tunnelTracker - mtw.confView.SetTunnelTracker(tunnelTracker) + mtw.tunnelsPage.confView.SetTunnelTracker(tunnelTracker) } func (mtw *ManageTunnelsWindow) SetTunnelState(tunnel *service.Tunnel, state service.TunnelState) { - mtw.tunnelsView.SetTunnelState(tunnel, state) + mtw.tunnelsPage.SetTunnelState(tunnel, state) - icon, err := mtw.tunnelsView.imageProvider.IconWithOverlayForState(mtw.icon, state) + icon, err := mtw.tunnelsPage.tunnelsView.imageProvider.IconWithOverlayForState(mtw.icon, state) if err != nil { return } mtw.SetIcon(icon) } - -func (mtw *ManageTunnelsWindow) updateConfView() { - if !mtw.Visible() { - return - } - - mtw.confView.SetTunnel(mtw.tunnelsView.CurrentTunnel()) -} - -// importFiles tries to import a list of configurations. -func (mtw *ManageTunnelsWindow) importFiles(paths []string) { - type unparsedConfig struct { - Name string - Config string - } - - var ( - unparsedConfigs []unparsedConfig - lastErr error - ) - - // Note: other versions of WireGuard start with all .zip files, then all .conf files. - // To reproduce that if needed, inverse-sort the array. - for _, path := range paths { - switch filepath.Ext(path) { - case ".conf": - textConfig, err := ioutil.ReadFile(path) - if err != nil { - lastErr = err - continue - } - unparsedConfigs = append(unparsedConfigs, unparsedConfig{Name: strings.TrimSuffix(filepath.Base(path), ".conf"), 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 filepath.Ext(f.Name) != ".conf" { - continue - } - - rc, err := f.Open() - if err != nil { - lastErr = err - continue - } - textConfig, err := ioutil.ReadAll(rc) - rc.Close() - if err != nil { - lastErr = err - continue - } - unparsedConfigs = append(unparsedConfigs, unparsedConfig{Name: strings.TrimSuffix(filepath.Base(f.Name), ".conf"), Config: string(textConfig)}) - } - - r.Close() - } - } - - if lastErr != nil || unparsedConfigs == nil { - walk.MsgBox(mtw, "Error", fmt.Sprintf("Could not parse some files: %v", lastErr), walk.MsgBoxIconWarning) - return - } - - var configs []*conf.Config - - for _, unparsedConfig := range unparsedConfigs { - config, err := conf.FromWgQuick(unparsedConfig.Config, unparsedConfig.Name) - if err != nil { - lastErr = err - continue - } - service.IPCClientNewTunnel(config) - configs = append(configs, config) - } - - m, n := len(configs), len(unparsedConfigs) - switch { - case n == 1 && m != n: - walk.MsgBox(mtw, "Error", fmt.Sprintf("Could not parse some files: %v", lastErr), walk.MsgBoxIconWarning) - case n == 1 && m == n: - // TODO: Select tunnel in the list - case m == n: - walk.MsgBox(mtw, "Imported tunnels", fmt.Sprintf("Imported %d tunnels", m), walk.MsgBoxOK) - case m != n: - walk.MsgBox(mtw, "Imported tunnels", fmt.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning) - default: - panic("unreachable case") - } -} - -func (mtw *ManageTunnelsWindow) exportTunnels(filePath string) { - writeFileWithOverwriteHandling(mtw, filePath, func(file *os.File) error { - writer := zip.NewWriter(file) - - for _, tunnel := range mtw.tunnelsView.model.tunnels { - cfg, err := tunnel.StoredConfig() - if err != nil { - return fmt.Errorf("onExportTunnels: tunnel.StoredConfig failed: %v", err) - } - - w, err := writer.Create(tunnel.Name + ".conf") - if err != nil { - return fmt.Errorf("onExportTunnels: writer.Create failed: %v", err) - } - - if _, err := w.Write(([]byte)(cfg.ToWgQuick())); err != nil { - return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %v", err) - } - } - - return writer.Close() - }) -} - -func (mtw *ManageTunnelsWindow) addTunnel(config *conf.Config) { - tunnel, err := service.IPCClientNewTunnel(config) - if err != nil { - walk.MsgBox(mtw, "Unable to create tunnel", err.Error(), walk.MsgBoxIconError) - return - } - - model := mtw.tunnelsView.model - model.tunnels = append(model.tunnels, tunnel) - model.PublishRowsReset() - model.Sort(model.SortedColumn(), model.SortOrder()) - - for i, t := range model.tunnels { - if t.Name == tunnel.Name { - mtw.tunnelsView.SetCurrentIndex(i) - break - } - } - - mtw.confView.SetTunnel(&tunnel) - - mtw.tunnelAddedPublisher.Publish(tunnel.Name) -} - -func (mtw *ManageTunnelsWindow) deleteTunnel(tunnel *service.Tunnel) { - tunnel.Delete() - - model := mtw.tunnelsView.model - - for i, t := range model.tunnels { - if t.Name == tunnel.Name { - model.tunnels = append(model.tunnels[:i], model.tunnels[i+1:]...) - model.PublishRowsRemoved(i, i) - break - } - } - - mtw.tunnelDeletedPublisher.Publish(tunnel.Name) -} - -func (mtw *ManageTunnelsWindow) TunnelAdded() *walk.StringEvent { - return mtw.tunnelAddedPublisher.Event() -} - -func (mtw *ManageTunnelsWindow) TunnelDeleted() *walk.StringEvent { - return mtw.tunnelDeletedPublisher.Event() -} - -// Handlers - -func (mtw *ManageTunnelsWindow) onTunnelsViewItemActivated() { - if mtw.tunnelTracker.InTransition() { - return - } - - var err error - var title string - tunnel := mtw.tunnelsView.CurrentTunnel() - activeTunnel := mtw.tunnelTracker.ActiveTunnel() - if tunnel != nil && activeTunnel != nil && tunnel.Name == activeTunnel.Name { - err, title = mtw.tunnelTracker.DeactivateTunnel(), "Deactivating tunnel failed" - } else { - err, title = mtw.tunnelTracker.ActivateTunnel(tunnel), "Activating tunnel failed" - } - if err != nil { - walk.MsgBox(mtw, title, fmt.Sprintf("Error: %s", err.Error()), walk.MsgBoxIconError) - } -} - -func (mtw *ManageTunnelsWindow) onEditTunnel() { - tunnel := mtw.tunnelsView.CurrentTunnel() - if tunnel == nil { - // Misfired event? - return - } - - if config := runTunnelConfigDialog(mtw, tunnel); config != nil { - // Delete old one - mtw.deleteTunnel(tunnel) - - // Save new one - mtw.addTunnel(config) - } -} - -func (mtw *ManageTunnelsWindow) onAddTunnel() { - if config := runTunnelConfigDialog(mtw, nil); config != nil { - // Save new - mtw.addTunnel(config) - } -} - -func (mtw *ManageTunnelsWindow) onDelete() { - currentTunnel := mtw.tunnelsView.CurrentTunnel() - if currentTunnel == nil { - // Misfired event? - return - } - - if walk.DlgCmdNo == walk.MsgBox( - mtw, - fmt.Sprintf(`Delete "%s"`, currentTunnel.Name), - fmt.Sprintf(`Are you sure you want to delete "%s"?`, currentTunnel.Name), - walk.MsgBoxYesNo|walk.MsgBoxIconWarning) { - return - } - - mtw.deleteTunnel(currentTunnel) - - mtw.tunnelDeletedPublisher.Publish(currentTunnel.Name) -} - -func (mtw *ManageTunnelsWindow) onImport() { - dlg := walk.FileDialog{ - Filter: "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*", - Title: "Import tunnel(s) from file...", - } - - if ok, _ := dlg.ShowOpenMultiple(mtw); !ok { - return - } - - mtw.importFiles(dlg.FilePaths) -} - -func (mtw *ManageTunnelsWindow) onExportTunnels() { - dlg := walk.FileDialog{ - Filter: "Configuration ZIP Files (*.zip)|*.zip", - Title: "Export tunnels to zip...", - } - - if ok, _ := dlg.ShowSave(mtw); !ok { - return - } - - if !strings.HasSuffix(dlg.FilePath, ".zip") { - dlg.FilePath += ".zip" - } - - mtw.exportTunnels(dlg.FilePath) -} - -func (mtw *ManageTunnelsWindow) onViewLog() { - runLogDialog(mtw, mtw.logger) -} |