cmd/systray: set app icon and title consistently

Refactor code to set app icon and title as part of rebuild, rather than
separately in eventLoop. This fixes several cases where they weren't
getting updated properly. This change also makes use of the new exit
node icons.

Updates #1708

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
Will Norris 2024-12-23 13:38:09 -08:00 committed by Will Norris
parent 2bdbe5b2ab
commit 86f273d930
2 changed files with 50 additions and 37 deletions

View File

@ -136,6 +136,7 @@ var (
1, 1, 1,
0, 1, 0,
},
// draw an arrow mask in the bottom right corner with a reasonably thick line width.
dotMask: func(dc *gg.Context, borderUnits int, radius int) *image.Alpha {
bu, r := float64(borderUnits), float64(radius)
@ -144,13 +145,14 @@ var (
x2 := x1 + (r * 5)
mc := gg.NewContext(dc.Width(), dc.Height())
mc.DrawLine(x1, y, x2, y)
mc.DrawLine(x2-(1.5*r), y-(1.5*r), x2, y)
mc.DrawLine(x2-(1.5*r), y+(1.5*r), x2, y)
mc.DrawLine(x1, y, x2, y) // arrow center line
mc.DrawLine(x2-(1.5*r), y-(1.5*r), x2, y) // top of arrow tip
mc.DrawLine(x2-(1.5*r), y+(1.5*r), x2, y) // bottom of arrow tip
mc.SetLineWidth(r * 3)
mc.Stroke()
return mc.AsMask()
},
// draw an arrow in the bottom right corner over the masked area.
overlay: func(dc *gg.Context, borderUnits int, radius int) {
bu, r := float64(borderUnits), float64(radius)
@ -158,9 +160,9 @@ var (
y := r * (bu + 7)
x2 := x1 + (r * 5)
dc.DrawLine(x1, y, x2, y)
dc.DrawLine(x2-(1.5*r), y-(1.5*r), x2, y)
dc.DrawLine(x2-(1.5*r), y+(1.5*r), x2, y)
dc.DrawLine(x1, y, x2, y) // arrow center line
dc.DrawLine(x2-(1.5*r), y-(1.5*r), x2, y) // top of arrow tip
dc.DrawLine(x2-(1.5*r), y+(1.5*r), x2, y) // bottom of arrow tip
dc.SetColor(fg)
dc.SetLineWidth(r)
dc.Stroke()
@ -174,6 +176,7 @@ var (
1, 1, 1,
0, 1, 0,
},
// Draw a square that hides the four dots in the bottom right corner,
dotMask: func(dc *gg.Context, borderUnits int, radius int) *image.Alpha {
bu, r := float64(borderUnits), float64(radius)
x := r * (bu + 3)
@ -183,13 +186,14 @@ var (
mc.Fill()
return mc.AsMask()
},
// draw a red "x" over the bottom right corner.
overlay: func(dc *gg.Context, borderUnits int, radius int) {
bu, r := float64(borderUnits), float64(radius)
x1 := r * (bu + 4)
x2 := x1 + (r * 3.5)
dc.DrawLine(x1, x1, x2, x2)
dc.DrawLine(x1, x2, x2, x1)
dc.DrawLine(x1, x1, x2, x2) // top-left to bottom-right stroke
dc.DrawLine(x1, x2, x2, x1) // bottom-left to top-right stroke
dc.SetColor(red)
dc.SetLineWidth(r)
dc.Stroke()

View File

@ -34,10 +34,7 @@ import (
var (
localClient tailscale.LocalClient
chState chan ipn.State // tailscale state changes
chRebuild chan struct{} // triggers a menu rebuild
rebuildCh chan struct{} // triggers a menu rebuild
appIcon *os.File
// newMenuDelay is the amount of time to sleep after creating a new menu,
@ -112,8 +109,7 @@ func onReady() {
appIcon, _ = os.CreateTemp("", "tailscale-systray.png")
io.Copy(appIcon, connected.renderWithBorder(3))
chState = make(chan ipn.State, 1)
chRebuild = make(chan struct{}, 1)
rebuildCh = make(chan struct{}, 1)
menu := new(Menu)
menu.rebuild(fetchState(ctx))
@ -170,6 +166,34 @@ func (menu *Menu) rebuild(state state) {
menu.disconnect.Hide()
systray.AddSeparator()
// Set systray menu icon and title.
// Also adjust connect/disconnect menu items if needed.
switch menu.status.BackendState {
case ipn.Running.String():
if state.status.ExitNodeStatus != nil && !state.status.ExitNodeStatus.ID.IsZero() {
if state.status.ExitNodeStatus.Online {
systray.SetTitle("Using exit node")
setAppIcon(exitNodeOnline)
} else {
systray.SetTitle("Exit node offline")
setAppIcon(exitNodeOffline)
}
} else {
systray.SetTitle(fmt.Sprintf("Connected to %s", state.status.CurrentTailnet.Name))
setAppIcon(connected)
}
menu.connect.SetTitle("Connected")
menu.connect.Disable()
menu.disconnect.Show()
menu.disconnect.Enable()
case ipn.Starting.String():
systray.SetTitle("Connecting")
setAppIcon(loading)
default:
systray.SetTitle("Disconnected")
setAppIcon(disconnected)
}
account := "Account"
if pt := profileTitle(state.curProfile); pt != "" {
account = pt
@ -268,27 +292,8 @@ func (menu *Menu) eventLoop(ctx context.Context) {
select {
case <-ctx.Done():
return
case <-chRebuild:
case <-rebuildCh:
menu.rebuild(fetchState(ctx))
case state := <-chState:
switch state {
case ipn.Running:
setAppIcon(loading)
menu.rebuild(fetchState(ctx))
setAppIcon(connected)
menu.connect.SetTitle("Connected")
menu.connect.Disable()
menu.disconnect.Show()
menu.disconnect.Enable()
case ipn.NoState, ipn.Stopped:
setAppIcon(disconnected)
menu.rebuild(fetchState(ctx))
menu.connect.SetTitle("Connect")
menu.connect.Enable()
menu.disconnect.Hide()
case ipn.Starting:
setAppIcon(loading)
}
case <-menu.connect.ClickedCh:
_, err := localClient.EditPrefs(ctx, &ipn.MaskedPrefs{
Prefs: ipn.Prefs{
@ -397,12 +402,16 @@ func watchIPNBusInner(ctx context.Context) error {
if err != nil {
return fmt.Errorf("ipnbus error: %w", err)
}
var rebuild bool
if n.State != nil {
chState <- *n.State
log.Printf("new state: %v", n.State)
rebuild = true
}
if n.Prefs != nil {
chRebuild <- struct{}{}
rebuild = true
}
if rebuild {
rebuildCh <- struct{}{}
}
}
}