Making suspended mode work again on Windows. Fixes #179

pull/201/head
Oliver 6 years ago
parent 08411f6e81
commit 19fbe6241a

@ -24,13 +24,11 @@ const queueSize = 100
type Application struct {
sync.RWMutex
// The application's screen.
// The application's screen. Apart from Run(), this variable should never be
// set directly. Always use the screenReplacement channel after calling
// Fini(), to set a new screen (or nil to stop the application).
screen tcell.Screen
// Indicates whether the application's screen is currently active. This is
// false during suspended mode.
running bool
// The primitive which currently has the keyboard focus.
focus Primitive
@ -59,16 +57,19 @@ type Application struct {
// Functions queued from goroutines, used to serialize updates to primitives.
updates chan func()
// A channel which signals the end of the suspended mode.
suspendToken chan struct{}
// An object that the screen variable will be set to after Fini() was called.
// Use this channel to set a new screen object for the application
// (screen.Init() and draw() will be called implicitly). A value of nil will
// stop the application.
screenReplacement chan tcell.Screen
}
// NewApplication creates and returns a new application.
func NewApplication() *Application {
return &Application{
events: make(chan tcell.Event, queueSize),
updates: make(chan func(), queueSize),
suspendToken: make(chan struct{}, 1),
events: make(chan tcell.Event, queueSize),
updates: make(chan func(), queueSize),
screenReplacement: make(chan tcell.Screen, 1),
}
}
@ -94,29 +95,29 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
// SetScreen allows you to provide your own tcell.Screen object. For most
// applications, this is not needed and you should be familiar with
// tcell.Screen when using this function. Run() will call Init() and Fini() on
// the provided screen object.
//
// This function is typically called before calling Run(). Calling it while an
// application is running will switch the application to the new screen. Fini()
// will be called on the old screen and Init() on the new screen (errors
// returned by Init() will lead to a panic).
// tcell.Screen when using this function.
//
// Note that calling Suspend() will invoke Fini() on your screen object and it
// will not be restored when suspended mode ends. Instead, a new default screen
// object will be created.
// This function is typically called before the first call to Run(). Init() need
// not be called on the screen.
func (a *Application) SetScreen(screen tcell.Screen) *Application {
a.Lock()
defer a.Unlock()
if a.running {
a.screen.Fini()
if screen == nil {
return a // Invalid input. Do nothing.
}
a.screen = screen
if a.running {
if err := a.screen.Init(); err != nil {
panic(err)
}
a.Lock()
if a.screen == nil {
// Run() has not been called yet.
a.screen = screen
a.Unlock()
return a
}
// Run() is already in progress. Exchange screen.
oldScreen := a.screen
a.Unlock()
oldScreen.Fini()
a.screenReplacement <- screen
return a
}
@ -133,12 +134,11 @@ func (a *Application) Run() error {
a.Unlock()
return err
}
if err = a.screen.Init(); err != nil {
a.Unlock()
return err
}
}
if err = a.screen.Init(); err != nil {
a.Unlock()
return err
}
a.running = true
// We catch panics to clean up because they mess up the terminal.
defer func() {
@ -146,7 +146,6 @@ func (a *Application) Run() error {
if a.screen != nil {
a.screen.Fini()
}
a.running = false
panic(p)
}
}()
@ -158,42 +157,44 @@ func (a *Application) Run() error {
// Separate loop to wait for screen events.
var wg sync.WaitGroup
wg.Add(1)
a.suspendToken <- struct{}{} // We need this to get started.
go func() {
defer wg.Done()
for range a.suspendToken {
for {
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
// We have no screen. Let's stop.
a.QueueEvent(nil)
break
}
for {
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
// We have no screen. Let's stop.
a.QueueEvent(nil)
break
}
// Wait for next event and queue it.
event := screen.PollEvent()
if event != nil {
// Regular event. Queue.
a.QueueEvent(event)
continue
}
// Wait for next event and queue it.
event := screen.PollEvent()
if event != nil {
// Regular event. Queue.
a.QueueEvent(event)
continue
}
// A screen was finalized (event is nil).
a.RLock()
running := a.running
a.RUnlock()
if running {
// The application was stopped. End the event loop.
a.QueueEvent(nil)
return
}
// A screen was finalized (event is nil). Wait for a new scren.
screen = <-a.screenReplacement
if screen == nil {
// No new screen. We're done.
a.QueueEvent(nil)
return
}
// We're in suspended mode (running is false). Pause and wait for new
// token.
break
// We have a new screen. Keep going.
a.Lock()
a.screen = screen
a.Unlock()
// Initialize and draw this screen.
if err := screen.Init(); err != nil {
panic(err)
}
a.draw()
}
}()
@ -252,9 +253,9 @@ EventLoop:
}
}
a.running = false
close(a.suspendToken)
// Wait for the event loop to finish.
wg.Wait()
a.screen = nil
return nil
}
@ -269,7 +270,7 @@ func (a *Application) Stop() {
}
a.screen = nil
screen.Fini()
// a.running is still true, the main loop will clean up.
a.screenReplacement <- nil
}
// Suspend temporarily suspends the application by exiting terminal UI mode and
@ -280,41 +281,26 @@ func (a *Application) Stop() {
// was called. If false is returned, the application was already suspended,
// terminal UI mode was not exited, and "f" was not called.
func (a *Application) Suspend(f func()) bool {
a.Lock()
a.RLock()
screen := a.screen
a.RUnlock()
if screen == nil {
// Screen has not yet been initialized.
a.Unlock()
return false
return false // Screen has not yet been initialized.
}
// Enter suspended mode. Make a new screen here already so our event loop can
// continue.
a.screen = nil
a.running = false
// Enter suspended mode.
screen.Fini()
a.Unlock()
// Wait for "f" to return.
f()
// Initialize our new screen and draw the contents.
a.Lock()
// Make a new screen.
var err error
a.screen, err = tcell.NewScreen()
screen, err = tcell.NewScreen()
if err != nil {
a.Unlock()
panic(err)
}
if err = a.screen.Init(); err != nil {
a.Unlock()
panic(err)
}
a.running = true
a.Unlock()
a.draw()
a.suspendToken <- struct{}{}
a.screenReplacement <- screen
// One key event will get lost, see https://github.com/gdamore/tcell/issues/194
// Continue application loop.

Loading…
Cancel
Save