diff --git a/cmd/tailscale/main.go b/cmd/tailscale/main.go index 6c45dea..6bf9c42 100644 --- a/cmd/tailscale/main.go +++ b/cmd/tailscale/main.go @@ -182,13 +182,14 @@ type FileSendEvent struct { // UIEvent types. type ( - ToggleEvent struct{} - ReauthEvent struct{} - BugEvent struct{} - WebAuthEvent struct{} - GoogleAuthEvent struct{} - LogoutEvent struct{} - BeExitNodeEvent bool + ToggleEvent struct{} + ReauthEvent struct{} + BugEvent struct{} + WebAuthEvent struct{} + GoogleAuthEvent struct{} + LogoutEvent struct{} + BeExitNodeEvent bool + ExitAllowLANEvent bool ) // serverOAuthID is the OAuth ID of the tailscale-android server, used @@ -420,6 +421,9 @@ func (a *App) runBackend() error { case BeExitNodeEvent: state.Prefs.SetAdvertiseExitNode(bool(e)) go b.backend.SetPrefs(state.Prefs) + case ExitAllowLANEvent: + state.Prefs.ExitNodeAllowLANAccess = bool(e) + go b.backend.SetPrefs(state.Prefs) case WebAuthEvent: if !signingIn { go b.backend.StartLoginInteractive() @@ -865,6 +869,7 @@ func (a *App) runUI() error { case p := <-a.prefs: ui.enabled.Value = p.WantRunning ui.runningExit = p.AdvertisesExitNode() + ui.exitLAN.Value = p.ExitNodeAllowLANAccess w.Invalidate() case state.browseURL = <-a.browseURLs: ui.signinType = noSignin @@ -1077,6 +1082,8 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, act jni.Object, s w.WriteClipboard(logMarker) case BeExitNodeEvent: requestBackend(e) + case ExitAllowLANEvent: + requestBackend(e) case WebAuthEvent: a.store.WriteString(loginMethodPrefKey, loginMethodWeb) requestBackend(e) diff --git a/cmd/tailscale/ui.go b/cmd/tailscale/ui.go index 8d59ecb..61608af 100644 --- a/cmd/tailscale/ui.go +++ b/cmd/tailscale/ui.go @@ -49,6 +49,8 @@ type UI struct { enabled widget.Bool search widget.Editor + exitLAN widget.Bool + // webSigin is the button for the web-based sign-in flow. webSignin widget.Clickable @@ -303,6 +305,9 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat } else { d.exits.Value = string(exitID) } + if ui.exitLAN.Changed() { + events = append(events, ExitAllowLANEvent(ui.exitLAN.Value)) + } if ui.googleSignin.Clicked() { ui.signinType = googleSignin @@ -892,12 +897,20 @@ func (ui *UI) layoutExitNodeDialog(gtx layout.Context, sysIns system.Insets, exi }), layout.Flexed(1, func(gtx C) D { gtx.Constraints.Min.Y = 0 - // Add "none" exit node. - n := len(exits) + 1 + // Add "none" exit node, then "Allow LAN" checkbox, then the exit nodes. + n := len(exits) + 2 return d.list.Layout(gtx, n, func(gtx C, idx int) D { + if idx == 0 { + btn := material.CheckBox(ui.theme, &ui.exitLAN, "Allow LAN access") + return layout.Inset{ + Right: unit.Dp(16), + Left: unit.Dp(16), + Bottom: unit.Dp(16), + }.Layout(gtx, btn.Layout) + } node := Peer{Label: "None", Online: true} - if idx >= 1 { - node = exits[idx-1] + if idx >= 2 { + node = exits[idx-2] } lbl := node.Label if !node.Online {