cmd/tailscale: synchronize UI switch with ipn.Prefs.WantRunning

Updates tailscale/tailscale#420

Signed-off-by: Elias Naur <mail@eliasnaur.com>
pull/2/head
Elias Naur 5 years ago
parent 3db59c3a9b
commit 8ae20a98eb

@ -39,6 +39,10 @@ type App struct {
// logged in. // logged in.
vpnClosed chan struct{} vpnClosed chan struct{}
// backend is the channel for events from the frontend to the
// backend.
backend chan UIEvent
// mu protects the following fields. // mu protects the following fields.
mu sync.Mutex mu sync.Mutex
// netState is the most recent network state. // netState is the most recent network state.
@ -46,6 +50,8 @@ type App struct {
// browseURL is set whenever the backend wants to // browseURL is set whenever the backend wants to
// browse. // browse.
browseURL *string browseURL *string
// prefs is set when new preferences arrive.
prefs *ipn.Prefs
} }
type clientState struct { type clientState struct {
@ -55,9 +61,6 @@ type clientState struct {
query string query string
Peers []UIPeer Peers []UIPeer
// WantsEnabled is the desired state of the VPN: enabled or
// disabled.
WantsEnabled bool
} }
type NetworkState struct { type NetworkState struct {
@ -93,6 +96,7 @@ func main() {
appCtx: jni.Object(app.AppContext()), appCtx: jni.Object(app.AppContext()),
updates: make(chan struct{}, 1), updates: make(chan struct{}, 1),
vpnClosed: make(chan struct{}, 1), vpnClosed: make(chan struct{}, 1),
backend: make(chan UIEvent),
} }
appDir, err := app.DataDir() appDir, err := app.DataDir()
if err != nil { if err != nil {
@ -100,21 +104,20 @@ func main() {
} }
a.appDir = appDir a.appDir = appDir
a.store = newStateStore(a.appDir, a.jvm, a.appCtx) a.store = newStateStore(a.appDir, a.jvm, a.appCtx)
events := make(chan UIEvent)
go func() { go func() {
if err := a.runBackend(events); err != nil { if err := a.runBackend(); err != nil {
fatalErr(err) fatalErr(err)
} }
}() }()
go func() { go func() {
if err := a.runUI(events); err != nil { if err := a.runUI(); err != nil {
fatalErr(err) fatalErr(err)
} }
}() }()
app.Main() app.Main()
} }
func (a *App) runBackend(events <-chan UIEvent) error { func (a *App) runBackend() error {
var cfg *router.Config var cfg *router.Config
var state NetworkState var state NetworkState
var service jni.Object var service jni.Object
@ -141,10 +144,16 @@ func (a *App) runBackend(events <-chan UIEvent) error {
alarmChan = timer.C alarmChan = timer.C
} }
} }
var prefs *ipn.Prefs var prefs struct {
mu sync.Mutex
prefs *ipn.Prefs
}
err = b.Start(func(n ipn.Notify) { err = b.Start(func(n ipn.Notify) {
if p := n.Prefs; p != nil { if p := n.Prefs; p != nil {
prefs = p prefs.mu.Lock()
prefs.prefs = p.Clone()
prefs.mu.Unlock()
a.setPrefs(prefs.prefs)
} }
if s := n.State; s != nil { if s := n.State; s != nil {
oldState := state.State oldState := state.State
@ -183,20 +192,31 @@ func (a *App) runBackend(events <-chan UIEvent) error {
if err != nil { if err != nil {
return err return err
} }
prefs.Hostname = a.hostname() {
b.backend.SetPrefs(prefs) prefs.mu.Lock()
prefs.prefs.Hostname = a.hostname()
p := prefs.prefs
prefs.mu.Unlock()
b.backend.SetPrefs(p)
}
for { for {
select { select {
case <-alarmChan: case <-alarmChan:
if m := state.NetworkMap; m != nil && service != 0 { if m := state.NetworkMap; m != nil && service != 0 {
alarm(a.notifyExpiry(service, m.Expiry)) alarm(a.notifyExpiry(service, m.Expiry))
} }
case e := <-events: case e := <-a.backend:
switch e.(type) { switch e := e.(type) {
case ReauthEvent: case ReauthEvent:
b.backend.StartLoginInteractive() b.backend.StartLoginInteractive()
case LogoutEvent: case LogoutEvent:
b.backend.Logout() b.backend.Logout()
case ConnectEvent:
prefs.mu.Lock()
p := prefs.prefs
prefs.mu.Unlock()
p.WantRunning = e.Enable
b.backend.SetPrefs(p)
} }
case s := <-onConnect: case s := <-onConnect:
jni.Do(a.jvm, func(env jni.Env) error { jni.Do(a.jvm, func(env jni.Env) error {
@ -334,6 +354,16 @@ func (a *App) notify(state NetworkState) {
} }
} }
func (a *App) setPrefs(prefs *ipn.Prefs) {
a.mu.Lock()
a.prefs = prefs
a.mu.Unlock()
select {
case a.updates <- struct{}{}:
default:
}
}
func (a *App) setURL(url string) { func (a *App) setURL(url string) {
a.mu.Lock() a.mu.Lock()
a.browseURL = &url a.browseURL = &url
@ -344,7 +374,7 @@ func (a *App) setURL(url string) {
} }
} }
func (a *App) runUI(backend chan<- UIEvent) error { func (a *App) runUI() error {
w := app.NewWindow() w := app.NewWindow()
gofont.Register() gofont.Register()
ui, err := newUI(a.store) ui, err := newUI(a.store)
@ -357,13 +387,10 @@ func (a *App) runUI(backend chan<- UIEvent) error {
var ops op.Ops var ops op.Ops
state := new(clientState) state := new(clientState)
var peer jni.Object var peer jni.Object
state.WantsEnabled, _ = a.store.ReadBool(enabledKey, true)
ui.enabled.Value = state.WantsEnabled
for { for {
select { select {
case <-a.vpnClosed: case <-a.vpnClosed:
state.WantsEnabled = false a.request(ConnectEvent{Enable: false})
w.Invalidate()
case <-a.updates: case <-a.updates:
a.mu.Lock() a.mu.Lock()
oldState := state.net.State oldState := state.net.State
@ -372,13 +399,17 @@ func (a *App) runUI(backend chan<- UIEvent) error {
state.browseURL = *a.browseURL state.browseURL = *a.browseURL
a.browseURL = nil a.browseURL = nil
} }
if a.prefs != nil {
ui.enabled.Value = a.prefs.WantRunning
a.prefs = nil
}
a.mu.Unlock() a.mu.Unlock()
a.updateState(peer, state) a.updateState(peer, state)
w.Invalidate() w.Invalidate()
if peer != 0 { if peer != 0 {
newState := state.net.State newState := state.net.State
// Start VPN if we just logged in. // Start VPN if we just logged in.
if state.WantsEnabled && oldState <= ipn.Stopped && newState > ipn.Stopped { if oldState <= ipn.Stopped && newState > ipn.Stopped {
if err := a.callVoidMethod(peer, "prepareVPN", "()V"); err != nil { if err := a.callVoidMethod(peer, "prepareVPN", "()V"); err != nil {
fatalErr(err) fatalErr(err)
} }
@ -386,7 +417,11 @@ func (a *App) runUI(backend chan<- UIEvent) error {
} }
case peer = <-onPeerCreated: case peer = <-onPeerCreated:
w.Invalidate() w.Invalidate()
a.setVPNState(peer, state) if state.net.State > ipn.Stopped {
if err := a.callVoidMethod(peer, "prepareVPN", "()V"); err != nil {
return err
}
}
case p := <-onPeerDestroyed: case p := <-onPeerDestroyed:
jni.Do(a.jvm, func(env jni.Env) error { jni.Do(a.jvm, func(env jni.Env) error {
defer jni.DeleteGlobalRef(env, p) defer jni.DeleteGlobalRef(env, p)
@ -397,8 +432,10 @@ func (a *App) runUI(backend chan<- UIEvent) error {
return nil return nil
}) })
case <-vpnPrepared: case <-vpnPrepared:
if err := a.callVoidMethod(a.appCtx, "startVPN", "()V"); err != nil { if state.net.State > ipn.Stopped {
return err if err := a.callVoidMethod(a.appCtx, "startVPN", "()V"); err != nil {
return err
}
} }
case e := <-w.Events(): case e := <-w.Events():
switch e := e.(type) { switch e := e.(type) {
@ -408,7 +445,7 @@ func (a *App) runUI(backend chan<- UIEvent) error {
gtx := layout.NewContext(&ops, e.Queue, e.Config, e.Size) gtx := layout.NewContext(&ops, e.Queue, e.Config, e.Size)
events := ui.layout(gtx, e.Insets, state) events := ui.layout(gtx, e.Insets, state)
e.Frame(gtx.Ops) e.Frame(gtx.Ops)
a.processUIEvents(backend, w, events, peer, state) a.processUIEvents(w, events, peer, state)
} }
} }
} }
@ -480,49 +517,30 @@ func (a *App) updateState(javaPeer jni.Object, state *clientState) {
state.Peers = peers state.Peers = peers
} }
func (a *App) processUIEvents(backend chan<- UIEvent, w *app.Window, events []UIEvent, peer jni.Object, state *clientState) { func (a *App) request(e UIEvent) {
go func() {
a.backend <- e
}()
}
func (a *App) processUIEvents(w *app.Window, events []UIEvent, peer jni.Object, state *clientState) {
for _, e := range events { for _, e := range events {
switch e := e.(type) { switch e := e.(type) {
case ReauthEvent: case ReauthEvent:
go func() { a.request(e)
backend <- e
}()
case LogoutEvent: case LogoutEvent:
go func() { a.request(e)
backend <- e case ConnectEvent:
}() a.request(e)
case CopyEvent: case CopyEvent:
w.WriteClipboard(e.Text) w.WriteClipboard(e.Text)
case SearchEvent: case SearchEvent:
state.query = strings.ToLower(e.Query) state.query = strings.ToLower(e.Query)
a.updateState(peer, state) a.updateState(peer, state)
case ConnectEvent:
if e.Enable == state.WantsEnabled {
return
}
if e.Enable && peer == 0 {
return
}
state.WantsEnabled = e.Enable
a.store.WriteBool(enabledKey, e.Enable)
a.updateState(peer, state)
a.setVPNState(peer, state)
} }
} }
} }
func (a *App) setVPNState(peer jni.Object, state *clientState) {
var err error
if state.WantsEnabled && state.net.State > ipn.Stopped {
err = a.callVoidMethod(peer, "prepareVPN", "()V")
} else {
err = a.callVoidMethod(a.appCtx, "stopVPN", "()V")
}
if err != nil {
fatalErr(err)
}
}
func (a *App) browseToURL(peer jni.Object, url string) { func (a *App) browseToURL(peer jni.Object, url string) {
err := jni.Do(a.jvm, func(env jni.Env) error { err := jni.Do(a.jvm, func(env jni.Env) error {
jurl := jni.JavaString(env, url) jurl := jni.JavaString(env, url)

@ -139,7 +139,6 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
if ui.enabled.Changed() { if ui.enabled.Changed() {
ui.events = append(ui.events, ConnectEvent{Enable: ui.enabled.Value}) ui.events = append(ui.events, ConnectEvent{Enable: ui.enabled.Value})
} }
ui.enabled.Value = state.WantsEnabled
for _, e := range ui.search.Events() { for _, e := range ui.search.Events() {
if _, ok := e.(widget.ChangeEvent); ok { if _, ok := e.(widget.ChangeEvent); ok {

Loading…
Cancel
Save