[client] Add "Deselect All" Menu Item to Exit Node Menu (#3877)

* [client] Enhance exit node menu functionality with deselect all option

* Hide exit nodes before removal in recreateExitNodeMenu

* recreateExitNodeMenu adding mutex locks

* Refetch exit nodes after deselecting all in exit node menu
This commit is contained in:
hakansa
2025-06-03 10:49:13 +03:00
committed by GitHub
parent af27aaf9af
commit 616b19c064
2 changed files with 100 additions and 8 deletions

View File

@@ -235,9 +235,11 @@ type serviceClient struct {
eventManager *event.Manager
exitNodeMu sync.Mutex
mExitNodeItems []menuHandler
logFile string
exitNodeMu sync.Mutex
mExitNodeItems []menuHandler
exitNodeStates []exitNodeState
mExitNodeDeselectAll *systray.MenuItem
logFile string
}
type menuHandler struct {
@@ -1035,11 +1037,11 @@ func (s *serviceClient) updateConfig() error {
lazyConnectionEnabled := s.mLazyConnEnabled.Checked()
loginRequest := proto.LoginRequest{
IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd",
ServerSSHAllowed: &sshAllowed,
RosenpassEnabled: &rosenpassEnabled,
DisableAutoConnect: &disableAutoStart,
DisableNotifications: &notificationsDisabled,
IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd",
ServerSSHAllowed: &sshAllowed,
RosenpassEnabled: &rosenpassEnabled,
DisableAutoConnect: &disableAutoStart,
DisableNotifications: &notificationsDisabled,
LazyConnectionEnabled: &lazyConnectionEnabled,
}

View File

@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"runtime"
"slices"
"sort"
"strings"
"time"
@@ -33,6 +34,11 @@ const (
type filter string
type exitNodeState struct {
id string
selected bool
}
func (s *serviceClient) showNetworksUI() {
s.wNetworks = s.app.NewWindow("Networks")
s.wNetworks.SetOnClosed(s.cancel)
@@ -357,18 +363,45 @@ func (s *serviceClient) updateExitNodes() {
}
func (s *serviceClient) recreateExitNodeMenu(exitNodes []*proto.Network) {
var exitNodeIDs []exitNodeState
for _, node := range exitNodes {
exitNodeIDs = append(exitNodeIDs, exitNodeState{
id: node.ID,
selected: node.Selected,
})
}
sort.Slice(exitNodeIDs, func(i, j int) bool {
return exitNodeIDs[i].id < exitNodeIDs[j].id
})
if slices.Equal(s.exitNodeStates, exitNodeIDs) {
log.Debug("Exit node menu already up to date")
return
}
for _, node := range s.mExitNodeItems {
node.cancel()
node.Hide()
node.Remove()
}
s.mExitNodeItems = nil
if s.mExitNodeDeselectAll != nil {
s.mExitNodeDeselectAll.Remove()
s.mExitNodeDeselectAll = nil
}
if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" {
s.mExitNode.Remove()
s.mExitNode = systray.AddMenuItem("Exit Node", exitNodeMenuDescr)
}
var showDeselectAll bool
for _, node := range exitNodes {
if node.Selected {
showDeselectAll = true
}
menuItem := s.mExitNode.AddSubMenuItemCheckbox(
node.ID,
fmt.Sprintf("Use exit node %s", node.ID),
@@ -383,6 +416,32 @@ func (s *serviceClient) recreateExitNodeMenu(exitNodes []*proto.Network) {
go s.handleChecked(ctx, node.ID, menuItem)
}
s.exitNodeStates = exitNodeIDs
if showDeselectAll {
s.mExitNode.AddSeparator()
deselectAllItem := s.mExitNode.AddSubMenuItem("Deselect All", "Deselect All")
s.mExitNodeDeselectAll = deselectAllItem
go func() {
for {
_, ok := <-deselectAllItem.ClickedCh
if !ok {
// channel closed: exit the goroutine
return
}
exitNodes, err := s.handleExitNodeMenuDeselectAll()
if err != nil {
log.Warnf("failed to handle deselect all exit nodes: %v", err)
} else {
s.exitNodeMu.Lock()
s.recreateExitNodeMenu(exitNodes)
s.exitNodeMu.Unlock()
}
}
}()
}
}
func (s *serviceClient) getExitNodes(conn proto.DaemonServiceClient) ([]*proto.Network, error) {
@@ -420,6 +479,37 @@ func (s *serviceClient) handleChecked(ctx context.Context, id string, item *syst
}
}
func (s *serviceClient) handleExitNodeMenuDeselectAll() ([]*proto.Network, error) {
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
return nil, fmt.Errorf("get client: %v", err)
}
exitNodes, err := s.getExitNodes(conn)
if err != nil {
return nil, fmt.Errorf("get exit nodes: %v", err)
}
var ids []string
for _, e := range exitNodes {
if e.Selected {
ids = append(ids, e.ID)
}
}
// deselect selected exit nodes
if err := s.deselectOtherExitNodes(conn, ids); err != nil {
return nil, err
}
updatedExitNodes, err := s.getExitNodes(conn)
if err != nil {
return nil, fmt.Errorf("re-fetch exit nodes: %v", err)
}
return updatedExitNodes, nil
}
// Add function to toggle exit node selection
func (s *serviceClient) toggleExitNode(nodeID string, item *systray.MenuItem) error {
conn, err := s.getSrvClient(defaultFailTimeout)