mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-09 01:27:42 +00:00
cmd/systray: improve profile menu
Bring UI closer to macOS and windows: - split login and tailnet name over separate lines - render profile picture (with very simple caching) - use checkbox to indicate active profile. I've not found any desktops that can't render checkboxes, so I'd like to explore other options if needed. Updates #1708 Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
parent
e8f1721147
commit
89adcd853d
@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -118,10 +119,11 @@ func (menu *Menu) rebuild(state state) {
|
||||
systray.AddSeparator()
|
||||
|
||||
account := "Account"
|
||||
if state.curProfile.Name != "" {
|
||||
account += fmt.Sprintf(" (%s)", state.curProfile.Name)
|
||||
if pt := profileTitle(state.curProfile); pt != "" {
|
||||
account = pt
|
||||
}
|
||||
accounts := systray.AddMenuItem(account, "")
|
||||
setRemoteIcon(accounts, state.curProfile.UserProfile.ProfilePicURL)
|
||||
// The dbus message about this menu item must propagate to the receiving
|
||||
// end before we attach any submenu items. Otherwise the receiver may not
|
||||
// yet record the parent menu item and error out.
|
||||
@ -132,13 +134,14 @@ func (menu *Menu) rebuild(state state) {
|
||||
// Aggregate all clicks into a shared channel.
|
||||
menu.accountsCh = make(chan ipn.ProfileID)
|
||||
for _, profile := range state.allProfiles {
|
||||
title := fmt.Sprintf("%s (%s)", profile.Name, profile.NetworkProfile.DomainName)
|
||||
// Note: we could use AddSubMenuItemCheckbox instead of this formatting
|
||||
// hack, but checkboxes don't work across all desktops unfortunately.
|
||||
title := profileTitle(profile)
|
||||
var item *systray.MenuItem
|
||||
if profile.ID == state.curProfile.ID {
|
||||
title = "* " + title
|
||||
item = accounts.AddSubMenuItemCheckbox(title, "", true)
|
||||
} else {
|
||||
item = accounts.AddSubMenuItem(title, "")
|
||||
}
|
||||
item := accounts.AddSubMenuItem(title, "")
|
||||
setRemoteIcon(item, profile.UserProfile.ProfilePicURL)
|
||||
go func(profile ipn.LoginProfile) {
|
||||
for {
|
||||
select {
|
||||
@ -170,6 +173,44 @@ func (menu *Menu) rebuild(state state) {
|
||||
go menu.eventLoop(ctx)
|
||||
}
|
||||
|
||||
// profileTitle returns the title string for a profile menu item.
|
||||
func profileTitle(profile ipn.LoginProfile) string {
|
||||
title := profile.Name
|
||||
if profile.NetworkProfile.DomainName != "" {
|
||||
title += "\n" + profile.NetworkProfile.DomainName
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
var (
|
||||
cacheMu sync.Mutex
|
||||
httpCache = map[string][]byte{} // URL => response body
|
||||
)
|
||||
|
||||
// setRemoteIcon sets the icon for menu to the specified remote image.
|
||||
// Remote images are fetched as needed and cached.
|
||||
func setRemoteIcon(menu *systray.MenuItem, urlStr string) {
|
||||
if menu == nil || urlStr == "" {
|
||||
return
|
||||
}
|
||||
|
||||
cacheMu.Lock()
|
||||
b, ok := httpCache[urlStr]
|
||||
if !ok {
|
||||
resp, err := http.Get(urlStr)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
b, _ = io.ReadAll(resp.Body)
|
||||
httpCache[urlStr] = b
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
cacheMu.Unlock()
|
||||
|
||||
if len(b) > 0 {
|
||||
menu.SetIcon(b)
|
||||
}
|
||||
}
|
||||
|
||||
// eventLoop is the main event loop for handling click events on menu items
|
||||
// and responding to Tailscale state changes.
|
||||
// This method does not return until ctx.Done is closed.
|
||||
|
Loading…
x
Reference in New Issue
Block a user