diff --git a/cmd/tailscale/ui.go b/cmd/tailscale/ui.go index 394107bd7d..33e0bd635f 100644 --- a/cmd/tailscale/ui.go +++ b/cmd/tailscale/ui.go @@ -10,6 +10,7 @@ import ( "fmt" "image" "image/color" + "log" "net/netip" "time" @@ -30,6 +31,7 @@ import ( "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" "tailscale.com/tailcfg" + "tailscale.com/version" _ "embed" @@ -78,7 +80,7 @@ type UI struct { list layout.List } - runningExit bool // are we an exit node now? + runningExit bool // are we an exit node now? qr struct { show bool @@ -92,9 +94,11 @@ type UI struct { } menu struct { - open widget.Clickable - dismiss Dismiss - show bool + open widget.Clickable + dismiss Dismiss + show bool + showHistory []showChange + showDebugMenu bool useLoginServer widget.Clickable copy widget.Clickable @@ -247,6 +251,12 @@ func newUI(store *stateStore) (*UI, error) { ui.loginServer.SingleLine = true ui.exitDialog.list.Axis = layout.Vertical ui.shareDialog.list.Axis = layout.Vertical + + // If they've ever set the control plane, give them the debug menu right away. + if v, _ := ui.store.ReadString(customLoginServerPrefKey, ""); v != "" { + ui.menu.showDebugMenu = true + } + return ui, nil } @@ -278,6 +288,36 @@ func (ui *UI) activeDialog() *bool { return nil } +type showChange struct { + at time.Time + shown bool +} + +func (ui *UI) setMenuShown(v bool) { + if v == ui.menu.show { + return + } + ui.menu.show = v + + now := time.Now() + const recent = 5 * time.Second + filt := ui.menu.showHistory[:0] + for _, hi := range ui.menu.showHistory { + if now.Sub(hi.at) < recent { + filt = append(filt, hi) + } + } + ui.menu.showHistory = filt + ui.menu.showHistory = append(ui.menu.showHistory, showChange{ + at: now, + shown: v, + }) + if len(ui.menu.showHistory) >= 6 { + ui.menu.showDebugMenu = true + } + log.Printf("history = %v; show = %v", len(ui.menu.showHistory), ui.menu.showDebugMenu) +} + func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientState) []UIEvent { // "Get started". if ui.intro.show { @@ -302,7 +342,7 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat } } for ui.menu.open.Clicked() { - ui.menu.show = !ui.menu.show + ui.setMenuShown(!ui.menu.show) } netmap := state.backend.NetworkMap @@ -844,7 +884,7 @@ func (ui *UI) layoutIntro(gtx layout.Context, sysIns system.Insets) { func (ui *UI) menuClicked(btn *widget.Clickable) bool { cl := btn.Clicked() if cl { - ui.menu.show = false + ui.setMenuShown(false) } return cl } @@ -1091,7 +1131,7 @@ func layoutDialog(gtx layout.Context, w layout.Widget) layout.Dimensions { func (ui *UI) layoutMenu(gtx layout.Context, sysIns system.Insets, expiry time.Time, showExits bool, needsLogin bool) { ui.menu.dismiss.Add(gtx, color.NRGBA{}) if ui.menu.dismiss.Dismissed(gtx) { - ui.menu.show = false + ui.setMenuShown(false) } layout.Inset{ Top: unit.Add(gtx.Metric, sysIns.Top, unit.Dp(2)), @@ -1103,11 +1143,15 @@ func (ui *UI) layoutMenu(gtx layout.Context, sysIns system.Insets, expiry time.T return D{} } if needsLogin { - items := []menuItem{ - {title: "Use login server", btn: &menu.useLoginServer}, + var items []menuItem + title := "Tailscale " + version.Short + if ui.menu.showDebugMenu { + items = []menuItem{ + {title: "Change server", btn: &menu.useLoginServer}, + } } return layoutMenu(ui.theme, gtx, items, func(gtx C) D { - l := material.Caption(ui.theme, "Advanced settings") + l := material.Caption(ui.theme, title) return l.Layout(gtx) }) }