//autorefresh view that autorefreshes its content every second func autorefresh(dry *app.Dry, screen *ui.Screen, keyboardQueue chan termbox.Event, done chan<- struct{}, doneStats chan<- bool, errC <-chan error) { screen.Clear() v := ui.NewMarkupView("", 0, 0, screen.Width, screen.Height, false) //used to coordinate rendering betwen the ticker //and the exit event var mutex = &sync.Mutex{} app.Write(dry, v) err := v.Render() if err != nil { ui.ShowErrorMessage(screen, keyboardQueue, err) } screen.Flush() //the ticker is created after the first render timestampQueue := time.NewTicker(1000 * time.Millisecond) loop: for { select { case <-errC: { mutex.Lock() timestampQueue.Stop() break loop } case event := <-keyboardQueue: switch event.Type { case termbox.EventKey: if event.Key == termbox.KeyEsc { //the lock is acquired and the time-based refresh queue is stopped //before breaking the loop mutex.Lock() timestampQueue.Stop() break loop } } case <-timestampQueue.C: { mutex.Lock() v.Clear() app.Write(dry, v) v.Render() screen.Flush() mutex.Unlock() } } } //cleanup before exiting, the screen is cleared and the lock released termbox.HideCursor() screen.Clear() screen.Sync() mutex.Unlock() doneStats <- true done <- struct{}{} }
//less shows dry output in a "less" emulator func less(dry *app.Dry, screen *ui.Screen, keyboardQueue chan termbox.Event, done chan struct{}) { screen.Clear() v := ui.NewLess() v.MarkupSupport() go app.Write(dry, v) if err := v.Focus(keyboardQueue); err != nil { ui.ShowErrorMessage(screen, keyboardQueue, err) } termbox.HideCursor() screen.Clear() screen.Sync() done <- struct{}{} }
//less shows dry output in a "less" emulator func less(dry *Dry, screen *ui.Screen, keyboardQueue chan termbox.Event, done chan struct{}) { screen.Clear() v := ui.NewLess() v.MarkupSupport() go Write(dry, v) //Focus blocks until v decides that it does not want focus any more if err := v.Focus(keyboardQueue); err != nil { ui.ShowErrorMessage(screen, keyboardQueue, err) } termbox.HideCursor() screen.Clear() screen.Sync() done <- struct{}{} }
func stream(screen *ui.Screen, stream io.ReadCloser, keyboardQueue chan termbox.Event, done chan<- struct{}) { screen.Clear() screen.Sync() v := ui.NewLess() go func() { io.Copy(v, stream) }() if err := v.Focus(keyboardQueue); err != nil { ui.ShowErrorMessage(screen, keyboardQueue, err) } stream.Close() termbox.HideCursor() screen.Clear() screen.Sync() done <- struct{}{} }
//----------------------------------------------------------------------------- func mainScreen(dry *app.Dry, screen *ui.Screen) { if ok, _ := dry.Ok(); !ok { return } keyboardQueue, done := ui.EventChannel() timestampQueue := time.NewTicker(1 * time.Second) viewClosed := make(chan struct{}, 1) keyboardQueueForView := make(chan termbox.Event) dryOutputChan := dry.OuputChannel() statusBar := ui.NewStatusBar(0) defer timestampQueue.Stop() defer close(done) defer close(keyboardQueueForView) defer close(viewClosed) app.Render(dry, screen, statusBar) //belongs outside the loop var viewMode = false go func(viewMode *bool) { for { dryMessage := <-dryOutputChan if !*viewMode { statusBar.StatusMessage(dryMessage, 10*time.Second) if dry.Changed() { screen.Clear() app.Render(dry, screen, statusBar) } else { statusBar.Render() } screen.Flush() } } }(&viewMode) loop: for { //Used for refresh-forcing events happening outside dry var refresh = false select { case <-timestampQueue.C: if !viewMode { timestamp := time.Now().Format(`15:04:05`) screen.RenderLine(0, 0, `<right><white>`+timestamp+`</></right>`) screen.Flush() } case <-viewClosed: viewMode = false dry.ShowContainers() case event := <-keyboardQueue: switch event.Type { case termbox.EventKey: if !viewMode { if event.Key == termbox.KeyEsc || event.Ch == 'q' || event.Ch == 'Q' { break loop } else if event.Key == termbox.KeyArrowUp { //cursor up screen.ScrollCursorUp() refresh = true } else if event.Key == termbox.KeyArrowDown { // cursor down screen.ScrollCursorDown() refresh = true } else if event.Key == termbox.KeyF1 { //sort dry.Sort() } else if event.Key == termbox.KeyF2 { //show all containers dry.ToggleShowAllContainers() } else if event.Key == termbox.KeyF5 { // refresh dry.Refresh() } else if event.Key == termbox.KeyF10 { // docker info dry.ShowInfo() viewMode = true go less(dry, screen, keyboardQueueForView, viewClosed) } else if event.Ch == '?' || event.Ch == 'h' || event.Ch == 'H' { //help viewMode = true dry.ShowHelp() go less(dry, screen, keyboardQueueForView, viewClosed) } else if event.Ch == 'e' || event.Ch == 'E' { //remove dry.Rm(screen.CursorPosition()) } else if event.Key == termbox.KeyCtrlE { //remove all stopped dry.RemoveAllStoppedContainers() } else if event.Ch == 'k' || event.Ch == 'K' { //kill dry.Kill(screen.CursorPosition()) } else if event.Ch == 'l' || event.Ch == 'L' { //logs if logs, err := dry.Logs(screen.CursorPosition()); err == nil { viewMode = true go stream(screen, logs, keyboardQueueForView, viewClosed) } } else if event.Ch == 'r' || event.Ch == 'R' { //start dry.StartContainer(screen.CursorPosition()) } else if event.Ch == 's' || event.Ch == 'S' { //stats done, errC, err := dry.Stats(screen.CursorPosition()) if err == nil { viewMode = true go autorefresh(dry, screen, keyboardQueueForView, viewClosed, done, errC) } } else if event.Ch == 't' || event.Ch == 'T' { //stop dry.StopContainer(screen.CursorPosition()) } else if event.Key == termbox.KeyEnter { //inspect dry.Inspect(screen.CursorPosition()) viewMode = true go less(dry, screen, keyboardQueueForView, viewClosed) } } else if viewMode { //The view handles the event keyboardQueueForView <- event } case termbox.EventResize: screen.Resize() refresh = true } } if !viewMode && (refresh || dry.Changed()) { screen.Clear() app.Render(dry, screen, statusBar) } } log.Debug("something broke the loop") }
//RenderLoop renders dry until it quits func RenderLoop(dry *Dry, screen *ui.Screen) { if ok, _ := dry.Ok(); !ok { return } keyboardQueue, done := ui.EventChannel() timestampQueue := time.NewTicker(1 * time.Second) viewClosed := make(chan struct{}, 1) keyboardQueueForView := make(chan termbox.Event) dryOutputChan := dry.OuputChannel() statusBar := ui.NewStatusBar(0) defer timestampQueue.Stop() defer close(done) defer close(keyboardQueueForView) defer close(viewClosed) Render(dry, screen, statusBar) //focus creation belongs outside the loop focus := &focusTracker{&sync.Mutex{}, true} go func(focus *focusTracker) { for { dryMessage, ok := <-dryOutputChan if ok { if focus.hasFocus() { statusBar.StatusMessage(dryMessage, 10*time.Second) if dry.Changed() { screen.Clear() Render(dry, screen, statusBar) } else { statusBar.Render() } screen.Flush() } } else { return } } }(focus) loop: for { //Used for refresh-forcing events happening outside dry var refresh = false select { case <-timestampQueue.C: if focus.hasFocus() { timestamp := time.Now().Format(`15:04:05`) screen.RenderLine(0, 0, `<right><white>`+timestamp+`</></right>`) screen.Flush() } case <-viewClosed: focus.set(true) dry.ShowMainView() refresh = true case event := <-keyboardQueue: switch event.Type { case termbox.EventKey: if focus.hasFocus() { if event.Key == termbox.KeyEsc || event.Ch == 'q' || event.Ch == 'Q' { break loop } else { handler := eventHandlerFactory(dry, screen, keyboardQueueForView, viewClosed) if handler != nil { r, f := handler.handle(event) refresh = r focus.set(f) } else { log.Panic("There is no event handler") } } } else { //Whoever has the focus, handles the event keyboardQueueForView <- event } case termbox.EventResize: screen.Resize() refresh = true } } if focus.hasFocus() && refresh { screen.Clear() Render(dry, screen, statusBar) } } log.Debug("something broke the loop. Time to die") }