package main import ( "fmt" "io" "log" "math" "net" "os/exec" "strings" "time" "github.com/eiannone/keyboard" "github.com/gdamore/tcell" "github.com/guillermo/go.procmeminfo" "github.com/rivo/tview" ) const ( CMD_FINDMNT = "/usr/bin/findmnt" CMD_FIM = "/usr/local/bin/pf-fim.sh" SLIDESHOW_INTERVAL = 300 * time.Second ) func main() { // fim placeholder so we can operate on it when a exit slideshow is received var fim *exec.Cmd = nil // Run slideshow fim = exec.Command(CMD_FIM) // Setup stdin for fim to control slideshow stdin, err := fim.StdinPipe() if err != nil { log.Fatalf("Error getting fim stdin : %s", err) } // Advance slideshow every interval as defined in const() ticker := time.NewTicker(SLIDESHOW_INTERVAL) stop_ticker := make(chan struct{}) go func() { for { select { case <-ticker.C: _, err = io.WriteString(stdin, "n") if err != nil { log.Fatalf("Error advancing slides : %s", err) } case <-stop_ticker: ticker.Stop() stdin.Close() return } } }() // Start watching for key strokes and echo them back to stdout keysEvents, err := keyboard.GetKeys(10) if err != nil { panic(err) } defer func() { _ = keyboard.Close() }() // Goroutine for tracking which keys are pressed and controlling fim if appropriate go func() { for { event := <-keysEvents if event.Err != nil { panic(event.Err) } log.Printf("You pressed: key %X\n", event.Key) // Keys for fim event management (previous/next in particular) fimKey := "" if event.Key == keyboard.KeyArrowLeft || event.Key == keyboard.KeyArrowDown { fimKey = "p" } if event.Key == keyboard.KeyArrowRight || event.Key == keyboard.KeyArrowUp { fimKey = "n" } // Exit fim and move to the config UI if event.Key == keyboard.KeyEsc || event.Key == keyboard.KeyEnter || event.Key == keyboard.KeySpace { if fim != nil { // Just in case someone lays on exit key or similar during startup if err := fim.Process.Kill(); err != nil { log.Fatalf("failed to kill fim : %s", err) } } break } // Control fim if we received a valid key for next/previous slide if fimKey != "" { log.Printf("Sending key to fim : %s", fimKey) _, err = io.WriteString(stdin, fimKey) if err != nil { log.Fatalf("Error controlling fim : %s", err) } ticker.Reset(SLIDESHOW_INTERVAL) } } }() // Run fim // if err := fim.Run(); err != nil { // // Unwrap the error a bit so we can find out if a signal killed fim or something else // // An exit code of -1 means the program didn't exit in time or was terminated by a signal (per the docs) // if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() != -1 { // log.Fatalf("Error running fim : %s", err) // } // } // Stop fim slideshow advancing go routine close(stop_ticker) // Memory info for status panel meminfo := &procmeminfo.MemInfo{} err = meminfo.Update() if err != nil { log.Printf("Error getting memory info : %s", err) } // Network interfaces for status panel ifaces, err := net.Interfaces() if err != nil { log.Printf("Error getting netork interfaces : %s", err) return } // Disk use findmntOut, _ := exec.Command(CMD_FINDMNT, "-n", "-l", "-o", "TARGET,USE%", "-t", "ext4,exfat,vfat,btrfs,zfs,xfs").Output() filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n") // Run config UI when slideshow stops app := tview.NewApplication() // Header headerTitle := tview.NewTextView(). SetText("PiFrame"). SetTextAlign(tview.AlignCenter). SetTextColor(tcell.ColorAqua) headerSubTitle := tview.NewTextView(). SetText("Management Utility"). SetTextAlign(tview.AlignCenter). SetTextColor(tcell.ColorSilver) header := tview.NewFlex(). SetDirection(tview.FlexRow) header.AddItem(headerTitle, 0, 1, false). AddItem(headerSubTitle, 0, 1, false) // Footer fields (Left Column) exitButton := tview.NewButton("Exit"). SetSelectedFunc(func() { app.Stop() }) exitButton.SetLabelColor(tcell.ColorBlack). SetBorder(true). SetBorderColor(tcell.ColorBlack). SetBackgroundColor(tcell.ColorGreen). SetRect(0, 0, 22, 3) rebootButton := tview.NewButton("Reboot"). SetSelectedFunc(func() { app.Stop() }) rebootButton.SetLabelColor(tcell.ColorBlack). SetBorder(true). SetBorderColor(tcell.ColorBlack). SetBackgroundColor(tcell.ColorYellow). SetRect(0, 0, 22, 3) powerOffButton := tview.NewButton("Power Off"). SetSelectedFunc(func() { app.Stop() }) powerOffButton.SetLabelColor(tcell.ColorBlack). SetBorder(true). SetBorderColor(tcell.ColorBlack). SetBackgroundColor(tcell.ColorRed). SetRect(0, 0, 22, 3) // Footer footer := tview.NewFlex() footer.AddItem(exitButton, 0, 1, false). AddItem(rebootButton, 0, 1, false). AddItem(powerOffButton, 0, 1, false) // Setup menu menu := tview.NewFlex(). SetDirection(tview.FlexRow) menu.SetTitle("Menu"). SetBorder(true). SetTitleColor(tcell.ColorAqua) // Setup main panel (Center column) main := tview.NewFlex(). SetDirection(tview.FlexRow) main.SetTitle(""). SetBorder(true) // Side bar fields sideBarMemoryTitle := tview.NewTextView(). SetText("Memory Use (Mb)"). SetTextColor(tcell.ColorYellow) divisor := math.Pow(1024.0, 2.0) sideBarMemoryStats := tview.NewTextView(). SetText(fmt.Sprintf(" %.1f / %.1f", float64(meminfo.Used())/divisor, float64(meminfo.Total())/divisor)) sideBarSwapTitle := tview.NewTextView(). SetText("Swap Use"). SetTextColor(tcell.ColorYellow) sideBarSwapStats := tview.NewTextView(). SetText(fmt.Sprintf(" %d%%", meminfo.Swap())) sideBarFilesystemTitle := tview.NewTextView(). SetText("Disk Use"). SetTextColor(tcell.ColorYellow) var sideBarFilesystems []*tview.TextView for _, i := range filesystems { filesystemAsTextView := tview.NewTextView(). SetText(fmt.Sprintf(" %s", i)) sideBarFilesystems = append(sideBarFilesystems, filesystemAsTextView) } sideBarIPAddressesTitle := tview.NewTextView(). SetText("IP Addresses"). SetTextColor(tcell.ColorYellow) var sideBarIPAddresses []*tview.TextView for _, i := range ifaces { addrs, err := i.Addrs() if err != nil { log.Printf("Error getting interface addresses : %s", err) continue } for _, a := range addrs { ipAsTextView := tview.NewTextView(). SetText(fmt.Sprintf(" %v : %s", i.Name, a.String())) sideBarIPAddresses = append(sideBarIPAddresses, ipAsTextView) } } // Setup side bar (Right column) sideBar := tview.NewFlex(). SetDirection(tview.FlexRow) sideBar.SetTitle("System Info"). SetBorder(true). SetTitleColor(tcell.ColorAqua) sideBar.AddItem(sideBarMemoryTitle, 1, 1, false) sideBar.AddItem(sideBarMemoryStats, 1, 1, false) sideBar.AddItem(sideBarSwapTitle, 1, 1, false) sideBar.AddItem(sideBarSwapStats, 1, 1, false) sideBar.AddItem(sideBarFilesystemTitle, 1, 1, false) for _, filesystemAsTextView := range sideBarFilesystems { sideBar.AddItem(filesystemAsTextView, 1, 1, false) } sideBar.AddItem(sideBarIPAddressesTitle, 1, 1, false) for _, ipAsTextView := range sideBarIPAddresses { sideBar.AddItem(ipAsTextView, 1, 1, false) } // Main UI grid := tview.NewGrid(). SetRows(2, 0, 4). SetColumns(25, 50, 50). SetBorders(true). AddItem(header, 0, 0, 1, 3, 0, 0, false). AddItem(footer, 2, 0, 1, 3, 0, 0, false) grid.AddItem(menu, 1, 0, 1, 1, 0, 100, true). AddItem(main, 1, 1, 1, 1, 0, 100, false). AddItem(sideBar, 1, 2, 1, 1, 0, 100, false) // Show UI and panic if there are any errors if err := app.SetRoot(grid, true).EnableMouse(false).Run(); err != nil { panic(err) } }