cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build cgo || !darwin
2025-01-06 16:02:53 -08:00
// Package systray provides a minimal Tailscale systray application.
package systray
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
import (
"context"
2024-09-05 12:11:05 -05:00
"errors"
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
"fmt"
"io"
"log"
2024-12-19 11:31:31 -08:00
"net/http"
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
"os"
2024-12-25 17:30:59 -08:00
"os/signal"
2024-12-20 09:11:04 -08:00
"runtime"
2024-12-20 15:37:00 -08:00
"slices"
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
"strings"
"sync"
2024-12-25 17:30:59 -08:00
"syscall"
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
"time"
"fyne.io/systray"
"github.com/atotto/clipboard"
dbus "github.com/godbus/dbus/v5"
"github.com/toqueteos/webbrowser"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
2024-12-20 15:37:00 -08:00
"tailscale.com/tailcfg"
2025-01-03 10:41:02 -08:00
"tailscale.com/util/slicesx"
2024-12-21 15:58:26 -08:00
"tailscale.com/util/stringsx"
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
)
var (
2024-12-20 09:11:04 -08:00
// newMenuDelay is the amount of time to sleep after creating a new menu,
// but before adding items to it. This works around a bug in some dbus implementations.
newMenuDelay time . Duration
2024-12-20 17:32:10 -08:00
// if true, treat all mullvad exit node countries as single-city.
// Instead of rendering a submenu with cities, just select the highest-priority peer.
hideMullvadCities bool
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
)
2025-01-06 16:02:53 -08:00
// Run starts the systray menu and blocks until the menu exits.
func ( menu * Menu ) Run ( ) {
2024-12-25 17:30:59 -08:00
menu . updateState ( )
// exit cleanly on SIGINT and SIGTERM
go func ( ) {
interrupt := make ( chan os . Signal , 1 )
signal . Notify ( interrupt , syscall . SIGINT , syscall . SIGTERM )
select {
case <- interrupt :
menu . onExit ( )
case <- menu . bgCtx . Done ( ) :
}
} ( )
systray . Run ( menu . onReady , menu . onExit )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
// Menu represents the systray menu, its items, and the current Tailscale state.
type Menu struct {
2024-12-25 17:30:59 -08:00
mu sync . Mutex // protects the entire Menu
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
2024-12-25 17:30:59 -08:00
lc tailscale . LocalClient
status * ipnstate . Status
curProfile ipn . LoginProfile
allProfiles [ ] ipn . LoginProfile
bgCtx context . Context // ctx for background tasks not involving menu item clicks
bgCancel context . CancelFunc
// Top-level menu items
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
connect * systray . MenuItem
disconnect * systray . MenuItem
2024-12-25 17:30:59 -08:00
self * systray . MenuItem
exitNodes * systray . MenuItem
more * systray . MenuItem
quit * systray . MenuItem
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
2024-12-25 17:30:59 -08:00
rebuildCh chan struct { } // triggers a menu rebuild
2024-12-10 13:54:31 -08:00
accountsCh chan ipn . ProfileID
2024-12-20 15:37:00 -08:00
exitNodeCh chan tailcfg . StableNodeID // ID of selected exit node
2024-12-10 13:54:31 -08:00
2024-12-25 17:30:59 -08:00
eventCancel context . CancelFunc // cancel eventLoop
notificationIcon * os . File // icon used for desktop notifications
}
func ( menu * Menu ) init ( ) {
if menu . bgCtx != nil {
// already initialized
return
}
menu . rebuildCh = make ( chan struct { } , 1 )
menu . accountsCh = make ( chan ipn . ProfileID )
menu . exitNodeCh = make ( chan tailcfg . StableNodeID )
// dbus wants a file path for notification icons, so copy to a temp file.
menu . notificationIcon , _ = os . CreateTemp ( "" , "tailscale-systray.png" )
io . Copy ( menu . notificationIcon , connected . renderWithBorder ( 3 ) )
menu . bgCtx , menu . bgCancel = context . WithCancel ( context . Background ( ) )
go menu . watchIPNBus ( )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
2024-12-20 09:11:04 -08:00
func init ( ) {
if runtime . GOOS != "linux" {
// so far, these tweaks are only needed on Linux
return
}
desktop := strings . ToLower ( os . Getenv ( "XDG_CURRENT_DESKTOP" ) )
switch desktop {
2024-12-20 17:32:10 -08:00
case "gnome" :
// GNOME expands submenus downward in the main menu, rather than flyouts to the side.
// Either as a result of that or another limitation, there seems to be a maximum depth of submenus.
// Mullvad countries that have a city submenu are not being rendered, and so can't be selected.
// Handle this by simply treating all mullvad countries as single-city and select the best peer.
hideMullvadCities = true
2024-12-20 09:11:04 -08:00
case "kde" :
// KDE doesn't need a delay, and actually won't render submenus
// if we delay for more than about 400µs.
newMenuDelay = 0
default :
// Add a slight delay to ensure the menu is created before adding items.
//
// Systray implementations that use libdbusmenu sometimes process messages out of order,
// resulting in errors such as:
// (waybar:153009): LIBDBUSMENU-GTK-WARNING **: 18:07:11.551: Children but no menu, someone's been naughty with their 'children-display' property: 'submenu'
//
// See also: https://github.com/fyne-io/systray/issues/12
2024-12-20 15:37:00 -08:00
newMenuDelay = 10 * time . Millisecond
2024-12-20 09:11:04 -08:00
}
}
2024-12-25 17:30:59 -08:00
// onReady is called by the systray package when the menu is ready to be built.
func ( menu * Menu ) onReady ( ) {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
log . Printf ( "starting" )
setAppIcon ( disconnected )
2024-12-25 17:30:59 -08:00
menu . rebuild ( )
2024-12-10 13:54:31 -08:00
}
2024-12-25 17:30:59 -08:00
// updateState updates the Menu state from the Tailscale local client.
func ( menu * Menu ) updateState ( ) {
menu . mu . Lock ( )
defer menu . mu . Unlock ( )
menu . init ( )
2024-12-10 13:54:31 -08:00
2024-12-25 17:30:59 -08:00
var err error
menu . status , err = menu . lc . Status ( menu . bgCtx )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if err != nil {
log . Print ( err )
}
2024-12-25 17:30:59 -08:00
menu . curProfile , menu . allProfiles , err = menu . lc . ProfileStatus ( menu . bgCtx )
2024-12-10 13:54:31 -08:00
if err != nil {
log . Print ( err )
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
// rebuild the systray menu based on the current Tailscale state.
//
// We currently rebuild the entire menu because it is not easy to update the existing menu.
// You cannot iterate over the items in a menu, nor can you remove some items like separators.
// So for now we rebuild the whole thing, and can optimize this later if needed.
2024-12-25 17:30:59 -08:00
func ( menu * Menu ) rebuild ( ) {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
menu . mu . Lock ( )
defer menu . mu . Unlock ( )
2024-12-25 17:30:59 -08:00
menu . init ( )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if menu . eventCancel != nil {
menu . eventCancel ( )
}
2024-12-10 13:54:31 -08:00
ctx := context . Background ( )
ctx , menu . eventCancel = context . WithCancel ( ctx )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
systray . ResetMenu ( )
menu . connect = systray . AddMenuItem ( "Connect" , "" )
menu . disconnect = systray . AddMenuItem ( "Disconnect" , "" )
menu . disconnect . Hide ( )
systray . AddSeparator ( )
2024-12-25 17:30:59 -08:00
// delay to prevent race setting icon on first start
time . Sleep ( newMenuDelay )
2024-12-23 13:38:09 -08:00
// Set systray menu icon and title.
// Also adjust connect/disconnect menu items if needed.
2024-12-25 17:30:59 -08:00
var backendState string
if menu . status != nil {
backendState = menu . status . BackendState
}
switch backendState {
2024-12-23 13:38:09 -08:00
case ipn . Running . String ( ) :
2024-12-25 17:30:59 -08:00
if menu . status . ExitNodeStatus != nil && ! menu . status . ExitNodeStatus . ID . IsZero ( ) {
if menu . status . ExitNodeStatus . Online {
2024-12-27 12:34:16 -08:00
setTooltip ( "Using exit node" )
2024-12-23 13:38:09 -08:00
setAppIcon ( exitNodeOnline )
} else {
2024-12-27 12:34:16 -08:00
setTooltip ( "Exit node offline" )
2024-12-23 13:38:09 -08:00
setAppIcon ( exitNodeOffline )
}
} else {
2024-12-27 12:34:16 -08:00
setTooltip ( fmt . Sprintf ( "Connected to %s" , menu . status . CurrentTailnet . Name ) )
2024-12-23 13:38:09 -08:00
setAppIcon ( connected )
}
menu . connect . SetTitle ( "Connected" )
menu . connect . Disable ( )
menu . disconnect . Show ( )
menu . disconnect . Enable ( )
case ipn . Starting . String ( ) :
2024-12-27 12:34:16 -08:00
setTooltip ( "Connecting" )
2024-12-23 13:38:09 -08:00
setAppIcon ( loading )
default :
2024-12-27 12:34:16 -08:00
setTooltip ( "Disconnected" )
2024-12-23 13:38:09 -08:00
setAppIcon ( disconnected )
}
2024-12-10 13:54:31 -08:00
account := "Account"
2024-12-25 17:30:59 -08:00
if pt := profileTitle ( menu . curProfile ) ; pt != "" {
2024-12-19 11:31:31 -08:00
account = pt
2024-12-10 13:54:31 -08:00
}
accounts := systray . AddMenuItem ( account , "" )
2024-12-25 17:30:59 -08:00
setRemoteIcon ( accounts , menu . curProfile . UserProfile . ProfilePicURL )
2024-12-20 09:11:04 -08:00
time . Sleep ( newMenuDelay )
2024-12-25 17:30:59 -08:00
for _ , profile := range menu . allProfiles {
2024-12-19 11:31:31 -08:00
title := profileTitle ( profile )
var item * systray . MenuItem
2024-12-25 17:30:59 -08:00
if profile . ID == menu . curProfile . ID {
2024-12-19 11:31:31 -08:00
item = accounts . AddSubMenuItemCheckbox ( title , "" , true )
} else {
item = accounts . AddSubMenuItem ( title , "" )
2024-12-10 13:54:31 -08:00
}
2024-12-19 11:31:31 -08:00
setRemoteIcon ( item , profile . UserProfile . ProfilePicURL )
2024-12-21 15:58:26 -08:00
onClick ( ctx , item , func ( ctx context . Context ) {
select {
case <- ctx . Done ( ) :
case menu . accountsCh <- profile . ID :
2024-12-10 13:54:31 -08:00
}
2024-12-21 15:58:26 -08:00
} )
2024-12-10 13:54:31 -08:00
}
2024-12-25 17:30:59 -08:00
if menu . status != nil && menu . status . Self != nil && len ( menu . status . Self . TailscaleIPs ) > 0 {
title := fmt . Sprintf ( "This Device: %s (%s)" , menu . status . Self . HostName , menu . status . Self . TailscaleIPs [ 0 ] )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
menu . self = systray . AddMenuItem ( title , "" )
2024-12-21 15:58:26 -08:00
} else {
menu . self = systray . AddMenuItem ( "This Device: not connected" , "" )
menu . self . Disable ( )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
systray . AddSeparator ( )
2024-12-20 15:37:00 -08:00
menu . rebuildExitNodeMenu ( ctx )
2024-12-25 17:30:59 -08:00
if menu . status != nil {
menu . more = systray . AddMenuItem ( "More settings" , "" )
onClick ( ctx , menu . more , func ( _ context . Context ) {
webbrowser . Open ( "http://100.100.100.100/" )
} )
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
menu . quit = systray . AddMenuItem ( "Quit" , "Quit the app" )
menu . quit . Enable ( )
go menu . eventLoop ( ctx )
}
2024-12-19 11:31:31 -08:00
// profileTitle returns the title string for a profile menu item.
func profileTitle ( profile ipn . LoginProfile ) string {
title := profile . Name
if profile . NetworkProfile . DomainName != "" {
2024-12-20 17:32:10 -08:00
if runtime . GOOS == "windows" || runtime . GOOS == "darwin" {
// windows and mac don't support multi-line menu
title += " (" + profile . NetworkProfile . DomainName + ")"
} else {
title += "\n" + profile . NetworkProfile . DomainName
}
2024-12-19 11:31:31 -08:00
}
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 )
}
}
2024-12-27 12:34:16 -08:00
// setTooltip sets the tooltip text for the systray icon.
func setTooltip ( text string ) {
if runtime . GOOS == "darwin" || runtime . GOOS == "windows" {
systray . SetTooltip ( text )
} else {
// on Linux, SetTitle actually sets the tooltip
systray . SetTitle ( text )
}
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
// 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.
func ( menu * Menu ) eventLoop ( ctx context . Context ) {
for {
select {
case <- ctx . Done ( ) :
return
2024-12-25 17:30:59 -08:00
case <- menu . rebuildCh :
menu . updateState ( )
menu . rebuild ( )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
case <- menu . connect . ClickedCh :
2024-12-25 17:30:59 -08:00
_ , err := menu . lc . EditPrefs ( ctx , & ipn . MaskedPrefs {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
Prefs : ipn . Prefs {
WantRunning : true ,
} ,
WantRunningSet : true ,
} )
if err != nil {
2024-12-25 17:30:59 -08:00
log . Printf ( "error connecting: %v" , err )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
case <- menu . disconnect . ClickedCh :
2024-12-25 17:30:59 -08:00
_ , err := menu . lc . EditPrefs ( ctx , & ipn . MaskedPrefs {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
Prefs : ipn . Prefs {
WantRunning : false ,
} ,
WantRunningSet : true ,
} )
if err != nil {
2024-12-25 17:30:59 -08:00
log . Printf ( "error disconnecting: %v" , err )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
case <- menu . self . ClickedCh :
2024-12-25 17:30:59 -08:00
menu . copyTailscaleIP ( menu . status . Self )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
2024-12-10 13:54:31 -08:00
case id := <- menu . accountsCh :
2024-12-25 17:30:59 -08:00
if err := menu . lc . SwitchProfile ( ctx , id ) ; err != nil {
log . Printf ( "error switching to profile ID %v: %v" , id , err )
2024-12-10 13:54:31 -08:00
}
2024-12-20 15:37:00 -08:00
case exitNode := <- menu . exitNodeCh :
if exitNode . IsZero ( ) {
log . Print ( "disable exit node" )
2024-12-25 17:30:59 -08:00
if err := menu . lc . SetUseExitNode ( ctx , false ) ; err != nil {
log . Printf ( "error disabling exit node: %v" , err )
2024-12-20 15:37:00 -08:00
}
} else {
log . Printf ( "enable exit node: %v" , exitNode )
mp := & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ExitNodeID : exitNode ,
} ,
ExitNodeIDSet : true ,
}
2024-12-25 17:30:59 -08:00
if _ , err := menu . lc . EditPrefs ( ctx , mp ) ; err != nil {
log . Printf ( "error setting exit node: %v" , err )
2024-12-20 15:37:00 -08:00
}
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
case <- menu . quit . ClickedCh :
systray . Quit ( )
}
}
}
2024-12-21 15:58:26 -08:00
// onClick registers a click handler for a menu item.
func onClick ( ctx context . Context , item * systray . MenuItem , fn func ( ctx context . Context ) ) {
go func ( ) {
for {
select {
case <- ctx . Done ( ) :
return
case <- item . ClickedCh :
fn ( ctx )
}
}
} ( )
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
// watchIPNBus subscribes to the tailscale event bus and sends state updates to chState.
// This method does not return.
2024-12-25 17:30:59 -08:00
func ( menu * Menu ) watchIPNBus ( ) {
2024-09-05 12:11:05 -05:00
for {
2024-12-25 17:30:59 -08:00
if err := menu . watchIPNBusInner ( ) ; err != nil {
2024-09-05 12:11:05 -05:00
log . Println ( err )
if errors . Is ( err , context . Canceled ) {
// If the context got canceled, we will never be able to
// reconnect to IPN bus, so exit the process.
log . Fatalf ( "watchIPNBus: %v" , err )
}
}
// If our watch connection breaks, wait a bit before reconnecting. No
// reason to spam the logs if e.g. tailscaled is restarting or goes
// down.
time . Sleep ( 3 * time . Second )
}
}
2024-12-25 17:30:59 -08:00
func ( menu * Menu ) watchIPNBusInner ( ) error {
watcher , err := menu . lc . WatchIPNBus ( menu . bgCtx , ipn . NotifyNoPrivateKeys )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if err != nil {
2024-09-05 12:11:05 -05:00
return fmt . Errorf ( "watching ipn bus: %w" , err )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
defer watcher . Close ( )
for {
select {
2024-12-25 17:30:59 -08:00
case <- menu . bgCtx . Done ( ) :
2024-09-05 12:11:05 -05:00
return nil
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
default :
n , err := watcher . Next ( )
if err != nil {
2024-09-05 12:11:05 -05:00
return fmt . Errorf ( "ipnbus error: %w" , err )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
2024-12-23 13:38:09 -08:00
var rebuild bool
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if n . State != nil {
log . Printf ( "new state: %v" , n . State )
2024-12-23 13:38:09 -08:00
rebuild = true
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
2024-12-21 15:58:26 -08:00
if n . Prefs != nil {
2024-12-23 13:38:09 -08:00
rebuild = true
}
if rebuild {
2024-12-25 17:30:59 -08:00
menu . rebuildCh <- struct { } { }
2024-12-21 15:58:26 -08:00
}
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
}
}
// copyTailscaleIP copies the first Tailscale IP of the given device to the clipboard
// and sends a notification with the copied value.
2024-12-25 17:30:59 -08:00
func ( menu * Menu ) copyTailscaleIP ( device * ipnstate . PeerStatus ) {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if device == nil || len ( device . TailscaleIPs ) == 0 {
return
}
name := strings . Split ( device . DNSName , "." ) [ 0 ]
ip := device . TailscaleIPs [ 0 ] . String ( )
err := clipboard . WriteAll ( ip )
if err != nil {
log . Printf ( "clipboard error: %v" , err )
}
2024-12-25 17:30:59 -08:00
menu . sendNotification ( fmt . Sprintf ( "Copied Address for %v" , name ) , ip )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}
// sendNotification sends a desktop notification with the given title and content.
2024-12-25 17:30:59 -08:00
func ( menu * Menu ) sendNotification ( title , content string ) {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
conn , err := dbus . SessionBus ( )
if err != nil {
log . Printf ( "dbus: %v" , err )
return
}
timeout := 3 * time . Second
obj := conn . Object ( "org.freedesktop.Notifications" , "/org/freedesktop/Notifications" )
call := obj . Call ( "org.freedesktop.Notifications.Notify" , 0 , "Tailscale" , uint32 ( 0 ) ,
2024-12-25 17:30:59 -08:00
menu . notificationIcon . Name ( ) , title , content , [ ] string { } , map [ string ] dbus . Variant { } , int32 ( timeout . Milliseconds ( ) ) )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
if call . Err != nil {
log . Printf ( "dbus: %v" , call . Err )
}
}
2024-12-20 15:37:00 -08:00
func ( menu * Menu ) rebuildExitNodeMenu ( ctx context . Context ) {
2024-12-25 17:30:59 -08:00
if menu . status == nil {
return
}
2024-12-20 15:37:00 -08:00
status := menu . status
menu . exitNodes = systray . AddMenuItem ( "Exit Nodes" , "" )
time . Sleep ( newMenuDelay )
// register a click handler for a menu item to set nodeID as the exit node.
2024-12-21 15:58:26 -08:00
setExitNodeOnClick := func ( item * systray . MenuItem , nodeID tailcfg . StableNodeID ) {
onClick ( ctx , item , func ( ctx context . Context ) {
select {
case <- ctx . Done ( ) :
case menu . exitNodeCh <- nodeID :
2024-12-20 15:37:00 -08:00
}
2024-12-21 15:58:26 -08:00
} )
2024-12-20 15:37:00 -08:00
}
noExitNodeMenu := menu . exitNodes . AddSubMenuItemCheckbox ( "None" , "" , status . ExitNodeStatus == nil )
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( noExitNodeMenu , "" )
2024-12-20 15:37:00 -08:00
// Show recommended exit node if available.
if status . Self . CapMap . Contains ( tailcfg . NodeAttrSuggestExitNodeUI ) {
2024-12-25 17:30:59 -08:00
sugg , err := menu . lc . SuggestExitNode ( ctx )
2024-12-20 15:37:00 -08:00
if err == nil {
title := "Recommended: "
if loc := sugg . Location ; loc . Valid ( ) && loc . Country ( ) != "" {
flag := countryFlag ( loc . CountryCode ( ) )
title += fmt . Sprintf ( "%s %s: %s" , flag , loc . Country ( ) , loc . City ( ) )
} else {
title += strings . Split ( sugg . Name , "." ) [ 0 ]
}
menu . exitNodes . AddSeparator ( )
rm := menu . exitNodes . AddSubMenuItemCheckbox ( title , "" , false )
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( rm , sugg . ID )
2024-12-20 15:37:00 -08:00
if status . ExitNodeStatus != nil && sugg . ID == status . ExitNodeStatus . ID {
rm . Check ( )
}
}
}
// Add tailnet exit nodes if present.
var tailnetExitNodes [ ] * ipnstate . PeerStatus
for _ , ps := range status . Peer {
if ps . ExitNodeOption && ps . Location == nil {
tailnetExitNodes = append ( tailnetExitNodes , ps )
}
}
if len ( tailnetExitNodes ) > 0 {
menu . exitNodes . AddSeparator ( )
menu . exitNodes . AddSubMenuItem ( "Tailnet Exit Nodes" , "" ) . Disable ( )
for _ , ps := range status . Peer {
if ! ps . ExitNodeOption || ps . Location != nil {
continue
}
name := strings . Split ( ps . DNSName , "." ) [ 0 ]
if ! ps . Online {
name += " (offline)"
}
sm := menu . exitNodes . AddSubMenuItemCheckbox ( name , "" , false )
if ! ps . Online {
sm . Disable ( )
}
if status . ExitNodeStatus != nil && ps . ID == status . ExitNodeStatus . ID {
sm . Check ( )
}
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( sm , ps . ID )
2024-12-20 15:37:00 -08:00
}
}
// Add mullvad exit nodes if present.
var mullvadExitNodes mullvadPeers
if status . Self . CapMap . Contains ( "mullvad" ) {
mullvadExitNodes = newMullvadPeers ( status )
}
if len ( mullvadExitNodes . countries ) > 0 {
menu . exitNodes . AddSeparator ( )
menu . exitNodes . AddSubMenuItem ( "Location-based Exit Nodes" , "" ) . Disable ( )
mullvadMenu := menu . exitNodes . AddSubMenuItemCheckbox ( "Mullvad VPN" , "" , false )
for _ , country := range mullvadExitNodes . sortedCountries ( ) {
flag := countryFlag ( country . code )
countryMenu := mullvadMenu . AddSubMenuItemCheckbox ( flag + " " + country . name , "" , false )
// single-city country, no submenu
2024-12-20 17:32:10 -08:00
if len ( country . cities ) == 1 || hideMullvadCities {
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( countryMenu , country . best . ID )
2024-12-20 15:37:00 -08:00
if status . ExitNodeStatus != nil {
for _ , city := range country . cities {
for _ , ps := range city . peers {
if status . ExitNodeStatus . ID == ps . ID {
mullvadMenu . Check ( )
countryMenu . Check ( )
}
}
}
}
continue
}
// multi-city country, build submenu with "best available" option and cities.
time . Sleep ( newMenuDelay )
bm := countryMenu . AddSubMenuItemCheckbox ( "Best Available" , "" , false )
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( bm , country . best . ID )
2024-12-20 15:37:00 -08:00
countryMenu . AddSeparator ( )
for _ , city := range country . sortedCities ( ) {
cityMenu := countryMenu . AddSubMenuItemCheckbox ( city . name , "" , false )
2024-12-21 15:58:26 -08:00
setExitNodeOnClick ( cityMenu , city . best . ID )
2024-12-20 15:37:00 -08:00
if status . ExitNodeStatus != nil {
for _ , ps := range city . peers {
if status . ExitNodeStatus . ID == ps . ID {
mullvadMenu . Check ( )
countryMenu . Check ( )
cityMenu . Check ( )
}
}
}
}
}
}
// TODO: "Allow Local Network Access" and "Run Exit Node" menu items
}
// mullvadPeers contains all mullvad peer nodes, sorted by country and city.
type mullvadPeers struct {
countries map [ string ] * mvCountry // country code (uppercase) => country
}
// sortedCountries returns countries containing mullvad nodes, sorted by name.
func ( mp mullvadPeers ) sortedCountries ( ) [ ] * mvCountry {
2025-01-03 10:41:02 -08:00
countries := slicesx . MapValues ( mp . countries )
2024-12-20 15:37:00 -08:00
slices . SortFunc ( countries , func ( a , b * mvCountry ) int {
2024-12-21 15:58:26 -08:00
return stringsx . CompareFold ( a . name , b . name )
2024-12-20 15:37:00 -08:00
} )
return countries
}
type mvCountry struct {
code string
name string
best * ipnstate . PeerStatus // highest priority peer in the country
cities map [ string ] * mvCity // city code => city
}
// sortedCities returns cities containing mullvad nodes, sorted by name.
func ( mc * mvCountry ) sortedCities ( ) [ ] * mvCity {
2025-01-03 10:41:02 -08:00
cities := slicesx . MapValues ( mc . cities )
2024-12-20 15:37:00 -08:00
slices . SortFunc ( cities , func ( a , b * mvCity ) int {
2024-12-21 15:58:26 -08:00
return stringsx . CompareFold ( a . name , b . name )
2024-12-20 15:37:00 -08:00
} )
return cities
}
// countryFlag takes a 2-character ASCII string and returns the corresponding emoji flag.
// It returns the empty string on error.
func countryFlag ( code string ) string {
if len ( code ) != 2 {
return ""
}
runes := make ( [ ] rune , 0 , 2 )
for i := range 2 {
b := code [ i ] | 32 // lowercase
if b < 'a' || b > 'z' {
return ""
}
// https://en.wikipedia.org/wiki/Regional_indicator_symbol
runes = append ( runes , 0x1F1E6 + rune ( b - 'a' ) )
}
return string ( runes )
}
type mvCity struct {
name string
best * ipnstate . PeerStatus // highest priority peer in the city
peers [ ] * ipnstate . PeerStatus
}
func newMullvadPeers ( status * ipnstate . Status ) mullvadPeers {
countries := make ( map [ string ] * mvCountry )
for _ , ps := range status . Peer {
if ! ps . ExitNodeOption || ps . Location == nil {
continue
}
loc := ps . Location
country , ok := countries [ loc . CountryCode ]
if ! ok {
country = & mvCountry {
code : loc . CountryCode ,
name : loc . Country ,
cities : make ( map [ string ] * mvCity ) ,
}
countries [ loc . CountryCode ] = country
}
city , ok := countries [ loc . CountryCode ] . cities [ loc . CityCode ]
if ! ok {
city = & mvCity {
name : loc . City ,
}
countries [ loc . CountryCode ] . cities [ loc . CityCode ] = city
}
city . peers = append ( city . peers , ps )
if city . best == nil || ps . Location . Priority > city . best . Location . Priority {
city . best = ps
}
if country . best == nil || ps . Location . Priority > country . best . Location . Priority {
country . best = ps
}
}
return mullvadPeers { countries }
}
2024-12-25 17:30:59 -08:00
// onExit is called by the systray package when the menu is exiting.
func ( menu * Menu ) onExit ( ) {
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
log . Printf ( "exiting" )
2024-12-25 17:30:59 -08:00
if menu . bgCancel != nil {
menu . bgCancel ( )
}
if menu . eventCancel != nil {
menu . eventCancel ( )
}
os . Remove ( menu . notificationIcon . Name ( ) )
cmd/systray: add a basic linux systray app
This adds a systray app for linux, similar to the apps for macOS and
windows. There are already a number of community-developed systray apps,
but most of them are either long abandoned, are built for a specific
desktop environment, or simply wrap the tailscale CLI.
This uses fyne.io/systray (a fork of github.com/getlantern/systray)
which uses newer D-Bus specifications to render the tray icon and menu.
This results in a pretty broad support for modern desktop environments.
This initial commit lacks a number of features like profile switching,
device listing, and exit node selection. This is really focused on the
application structure, the interaction with LocalAPI, and some system
integration pieces like the app icon, notifications, and the clipboard.
Updates #1708
Signed-off-by: Will Norris <will@tailscale.com>
2024-07-10 13:45:10 -07:00
}