Split up GUI code some and modularize it

This commit is contained in:
KemoNine 2020-09-02 20:40:28 -04:00
parent a506546f1e
commit 1841b19211
5 changed files with 443 additions and 430 deletions

View File

@ -1,3 +1,3 @@
# ui # gui
This is the main source code for the custom UI used by PiFrame. This is responsible for things like restart, WiFi config, ensuring the slide show is running and more. This is the main source code for the custom UI used by PiFrame. This is responsible for things like restart, WiFi config, ensuring the slide show is running and more.

10
cmd/gui/gui.go Normal file
View File

@ -0,0 +1,10 @@
package main
import (
"git.kemonine.info/PiFrame/ui"
)
func main() {
ui.Slideshow()
ui.ConfigGui()
}

3
ui/README.md Normal file
View File

@ -0,0 +1,3 @@
# ui
This is the main implementation for various aspects of the main PiFrame [G]UI

View File

@ -1,422 +1,422 @@
package main package ui
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"math" "math"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/guillermo/go.procmeminfo" "github.com/guillermo/go.procmeminfo"
"github.com/rivo/tview" "github.com/rivo/tview"
"git.kemonine.info/PiFrame/wifi" "git.kemonine.info/PiFrame/wifi"
) )
const ( const (
CMD_SYSTEMCTL = "/usr/bin/systemctl" CMD_SYSTEMCTL = "/usr/bin/systemctl"
CMD_FINDMNT = "/usr/bin/findmnt" CMD_FINDMNT = "/usr/bin/findmnt"
CMD_VCGENCMD = "/opt/vc/bin/vcgencmd" CMD_VCGENCMD = "/opt/vc/bin/vcgencmd"
FILE_CPU_TEMP = "/sys/class/thermal/thermal_zone0/temp" FILE_CPU_TEMP = "/sys/class/thermal/thermal_zone0/temp"
ALBUM_ROOT_DIR = "/tank/pictures/" ALBUM_ROOT_DIR = "/tank/pictures/"
SYNCTHING_FOLDER_SKIP = ".stfolder" SYNCTHING_FOLDER_SKIP = ".stfolder"
) )
const ( const (
PAGE_MAIN_UI = "PAGE_MAIN_UI" PAGE_MAIN_UI = "PAGE_MAIN_UI"
PAGE_EXIT = "PAGE_EXIT" PAGE_EXIT = "PAGE_EXIT"
PAGE_REBOOT = "PAGE_REBOOT" PAGE_REBOOT = "PAGE_REBOOT"
PAGE_POWEROFF = "PAGE_POWEROFF" PAGE_POWEROFF = "PAGE_POWEROFF"
) )
func main() { func ConfigGui() {
// Memory info for status panel // Memory info for status panel
meminfo := &procmeminfo.MemInfo{} meminfo := &procmeminfo.MemInfo{}
err := meminfo.Update() err := meminfo.Update()
if err != nil { if err != nil {
log.Printf("Error getting memory info : %s", err) log.Printf("Error getting memory info : %s", err)
} }
// Network interfaces for status panel // Network interfaces for status panel
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
log.Fatalf("Error getting netork interfaces : %s", err) log.Fatalf("Error getting netork interfaces : %s", err)
return return
} }
// Disk use // Disk use
findmntOut, err := exec.Command(CMD_FINDMNT, "-n", "-l", findmntOut, err := exec.Command(CMD_FINDMNT, "-n", "-l",
"-o", "TARGET,USE%", "-o", "TARGET,USE%",
"-t", "ext4,exfat,vfat,btrfs,zfs,xfs").Output() "-t", "ext4,exfat,vfat,btrfs,zfs,xfs").Output()
if err != nil { if err != nil {
log.Fatalf("Error getting disk use : %s", err) log.Fatalf("Error getting disk use : %s", err)
} }
filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n") filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n")
// GPU Temp // GPU Temp
vcgencmdOut, err := exec.Command(CMD_VCGENCMD, "measure_temp").Output() vcgencmdOut, err := exec.Command(CMD_VCGENCMD, "measure_temp").Output()
if err != nil { if err != nil {
log.Fatalf("Error getting GPU temp : %s", err) log.Fatalf("Error getting GPU temp : %s", err)
} }
gpuTemp := strings.Split(strings.Trim(string(vcgencmdOut), "\n"), "=")[1] gpuTemp := strings.Split(strings.Trim(string(vcgencmdOut), "\n"), "=")[1]
// CPU Temp // CPU Temp
cpuTempFileContents, err := ioutil.ReadFile(FILE_CPU_TEMP) cpuTempFileContents, err := ioutil.ReadFile(FILE_CPU_TEMP)
if err != nil { if err != nil {
log.Fatalf("Error getting CPU temp : %s", err) log.Fatalf("Error getting CPU temp : %s", err)
} }
cpuTempStr := strings.Trim(string(cpuTempFileContents), "\n") cpuTempStr := strings.Trim(string(cpuTempFileContents), "\n")
cpuTempInt, err := strconv.Atoi(cpuTempStr) cpuTempInt, err := strconv.Atoi(cpuTempStr)
if err != nil { if err != nil {
log.Fatalf("Error processing CPU temp : %S", err) log.Fatalf("Error processing CPU temp : %S", err)
} }
cpuTemp := fmt.Sprintf("%.2f'C", float64(cpuTempInt)/1000.0) cpuTemp := fmt.Sprintf("%.2f'C", float64(cpuTempInt)/1000.0)
// Get list of all folders that can be used as albums // Get list of all folders that can be used as albums
var albums []string var albums []string
err = filepath.Walk(ALBUM_ROOT_DIR, func(path string, fi os.FileInfo, err error) error { err = filepath.Walk(ALBUM_ROOT_DIR, func(path string, fi os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
if fi.IsDir() { if fi.IsDir() {
if strings.Contains(path, SYNCTHING_FOLDER_SKIP) { if strings.Contains(path, SYNCTHING_FOLDER_SKIP) {
return nil return nil
} }
albumName := strings.TrimPrefix(path, ALBUM_ROOT_DIR) albumName := strings.TrimPrefix(path, ALBUM_ROOT_DIR)
if albumName == "" { if albumName == "" {
albumName = "Main Folder" albumName = "Main Folder"
} }
albums = append(albums, albumName) albums = append(albums, albumName)
} }
return nil return nil
}) })
if err != nil { if err != nil {
log.Fatalf("Error getting list of albums : %s", err) log.Fatalf("Error getting list of albums : %s", err)
} }
// Run config UI when slideshow stops // Run config UI when slideshow stops
app := tview.NewApplication() app := tview.NewApplication()
// Header // Header
headerTitle := tview.NewTextView(). headerTitle := tview.NewTextView().
SetText("PiFrame"). SetText("PiFrame").
SetTextAlign(tview.AlignCenter). SetTextAlign(tview.AlignCenter).
SetTextColor(tcell.ColorAqua) SetTextColor(tcell.ColorAqua)
headerSubTitle := tview.NewTextView(). headerSubTitle := tview.NewTextView().
SetText("Management Utility"). SetText("Management Utility").
SetTextAlign(tview.AlignCenter). SetTextAlign(tview.AlignCenter).
SetTextColor(tcell.ColorSilver) SetTextColor(tcell.ColorSilver)
header := tview.NewFlex(). header := tview.NewFlex().
SetDirection(tview.FlexRow) SetDirection(tview.FlexRow)
header.AddItem(headerTitle, 0, 1, false). header.AddItem(headerTitle, 0, 1, false).
AddItem(headerSubTitle, 0, 1, false) AddItem(headerSubTitle, 0, 1, false)
// Footer fields (Left Column) // Footer fields (Left Column)
exitButton := tview.NewButton("Exit"). exitButton := tview.NewButton("Exit").
SetBackgroundColorActivated(tcell.ColorGray) SetBackgroundColorActivated(tcell.ColorGray)
exitButton.SetLabelColor(tcell.ColorBlack). exitButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true). SetBorder(true).
SetBorderColor(tcell.ColorBlack). SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorGreen). SetBackgroundColor(tcell.ColorGreen).
SetRect(0, 0, 22, 3) SetRect(0, 0, 22, 3)
rebootButton := tview.NewButton("Reboot"). rebootButton := tview.NewButton("Reboot").
SetBackgroundColorActivated(tcell.ColorGray) SetBackgroundColorActivated(tcell.ColorGray)
rebootButton.SetLabelColor(tcell.ColorBlack). rebootButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true). SetBorder(true).
SetBorderColor(tcell.ColorBlack). SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorYellow). SetBackgroundColor(tcell.ColorYellow).
SetRect(0, 0, 22, 3) SetRect(0, 0, 22, 3)
powerOffButton := tview.NewButton("Power Off"). powerOffButton := tview.NewButton("Power Off").
SetBackgroundColorActivated(tcell.ColorGray) SetBackgroundColorActivated(tcell.ColorGray)
powerOffButton.SetLabelColor(tcell.ColorBlack). powerOffButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true). SetBorder(true).
SetBorderColor(tcell.ColorBlack). SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorRed). SetBackgroundColor(tcell.ColorRed).
SetRect(0, 0, 22, 3) SetRect(0, 0, 22, 3)
// Footer // Footer
footer := tview.NewFlex() footer := tview.NewFlex()
footer.AddItem(exitButton, 0, 1, false). footer.AddItem(exitButton, 0, 1, false).
AddItem(rebootButton, 0, 1, false). AddItem(rebootButton, 0, 1, false).
AddItem(powerOffButton, 0, 1, false) AddItem(powerOffButton, 0, 1, false)
// Setup menu // Setup menu
menu := tview.NewList() menu := tview.NewList()
menu.SetBorder(true). menu.SetBorder(true).
SetTitle("Menu"). SetTitle("Menu").
SetTitleColor(tcell.ColorAqua) SetTitleColor(tcell.ColorAqua)
menu.AddItem("Select Albums", "", '1', nil) menu.AddItem("Select Albums", "", '1', nil)
menu.AddItem("Configure WiFi", "", '2', nil) menu.AddItem("Configure WiFi", "", '2', nil)
// Setup base var for main column so the menu setup is easier to manage // Setup base var for main column so the menu setup is easier to manage
main := tview.NewFlex(). main := tview.NewFlex().
SetDirection(tview.FlexRow) SetDirection(tview.FlexRow)
// Setup main panel (Center column) // Setup main panel (Center column)
main.SetTitle(""). main.SetTitle("").
SetBorder(true). SetBorder(true).
SetTitleColor(tcell.ColorAqua) SetTitleColor(tcell.ColorAqua)
// WiFi Config Form // WiFi Config Form
wifiConfigForm := tview.NewForm() wifiConfigForm := tview.NewForm()
wifiConfigAccessPoint := "" wifiConfigAccessPoint := ""
wifiConfigPassword := "" wifiConfigPassword := ""
wifiConfigForm.AddInputField("Access Point", "", 0, nil, func(value string) { wifiConfigForm.AddInputField("Access Point", "", 0, nil, func(value string) {
wifiConfigAccessPoint = value wifiConfigAccessPoint = value
}) })
wifiConfigForm.AddPasswordField("Password", "", 0, '*', func(value string) { wifiConfigForm.AddPasswordField("Password", "", 0, '*', func(value string) {
wifiConfigPassword = value wifiConfigPassword = value
}) })
wifiConfigForm.AddButton("Apply", func() { wifiConfigForm.AddButton("Apply", func() {
// Cleanup old wifi configs and apply new one // Cleanup old wifi configs and apply new one
nmWifi := wifi.New(wifiConfigAccessPoint, wifiConfigPassword) nmWifi := wifi.New(wifiConfigAccessPoint, wifiConfigPassword)
nmWifi.ApplyConfig() nmWifi.ApplyConfig()
}) })
wifiConfigForm.AddButton("Cancel", func() { wifiConfigForm.AddButton("Cancel", func() {
main.Clear() main.Clear()
app.SetFocus(menu) app.SetFocus(menu)
}) })
// Select Albums Form // Select Albums Form
selectAlbumsForm := tview.NewForm() selectAlbumsForm := tview.NewForm()
for _, album := range albums { for _, album := range albums {
selectAlbumsForm.AddCheckbox(album, true, nil) selectAlbumsForm.AddCheckbox(album, true, nil)
} }
selectAlbumsForm.AddButton("Apply", nil) selectAlbumsForm.AddButton("Apply", nil)
selectAlbumsForm.AddButton("Cancel", func() { selectAlbumsForm.AddButton("Cancel", func() {
main.Clear() main.Clear()
app.SetFocus(menu) app.SetFocus(menu)
}) })
// Setup menu selection handler // Setup menu selection handler
menu.SetSelectedFunc(func(index int, title string, desc string, shortcut rune) { menu.SetSelectedFunc(func(index int, title string, desc string, shortcut rune) {
if title == "Select Albums" { if title == "Select Albums" {
main.SetTitle("Select Albums") main.SetTitle("Select Albums")
main.Clear() main.Clear()
main.AddItem(selectAlbumsForm, 0, 1, true) main.AddItem(selectAlbumsForm, 0, 1, true)
app.SetFocus(selectAlbumsForm) app.SetFocus(selectAlbumsForm)
} }
if title == "Configure WiFi" { if title == "Configure WiFi" {
main.SetTitle("Configure WiFi") main.SetTitle("Configure WiFi")
main.Clear() main.Clear()
main.AddItem(wifiConfigForm, 0, 1, true) main.AddItem(wifiConfigForm, 0, 1, true)
app.SetFocus(wifiConfigForm) app.SetFocus(wifiConfigForm)
} }
}) })
// Side bar fields // Side bar fields
sideBarCPUTempTitle := tview.NewTextView(). sideBarCPUTempTitle := tview.NewTextView().
SetText("CPU Temperature"). SetText("CPU Temperature").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
sideBarCPUTemp := tview.NewTextView(). sideBarCPUTemp := tview.NewTextView().
SetText(fmt.Sprintf(" %s", cpuTemp)) SetText(fmt.Sprintf(" %s", cpuTemp))
sideBarGPUTempTitle := tview.NewTextView(). sideBarGPUTempTitle := tview.NewTextView().
SetText("GPU Temperature"). SetText("GPU Temperature").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
sideBarGPUTemp := tview.NewTextView(). sideBarGPUTemp := tview.NewTextView().
SetText(fmt.Sprintf(" %s", gpuTemp)) SetText(fmt.Sprintf(" %s", gpuTemp))
sideBarMemoryTitle := tview.NewTextView(). sideBarMemoryTitle := tview.NewTextView().
SetText("Memory Use (Mb)"). SetText("Memory Use (Mb)").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
divisor := math.Pow(1024.0, 2.0) divisor := math.Pow(1024.0, 2.0)
sideBarMemoryStats := tview.NewTextView(). sideBarMemoryStats := tview.NewTextView().
SetText(fmt.Sprintf(" %.1f / %.1f", SetText(fmt.Sprintf(" %.1f / %.1f",
float64(meminfo.Used())/divisor, float64(meminfo.Used())/divisor,
float64(meminfo.Total())/divisor)) float64(meminfo.Total())/divisor))
sideBarSwapTitle := tview.NewTextView(). sideBarSwapTitle := tview.NewTextView().
SetText("Swap Use"). SetText("Swap Use").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
sideBarSwapStats := tview.NewTextView(). sideBarSwapStats := tview.NewTextView().
SetText(fmt.Sprintf(" %d%%", meminfo.Swap())) SetText(fmt.Sprintf(" %d%%", meminfo.Swap()))
sideBarFilesystemTitle := tview.NewTextView(). sideBarFilesystemTitle := tview.NewTextView().
SetText("Disk Use"). SetText("Disk Use").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
var sideBarFilesystems []*tview.TextView var sideBarFilesystems []*tview.TextView
for _, i := range filesystems { for _, i := range filesystems {
filesystemAsTextView := tview.NewTextView(). filesystemAsTextView := tview.NewTextView().
SetText(fmt.Sprintf(" %s", i)) SetText(fmt.Sprintf(" %s", i))
sideBarFilesystems = append(sideBarFilesystems, filesystemAsTextView) sideBarFilesystems = append(sideBarFilesystems, filesystemAsTextView)
} }
sideBarIPAddressesTitle := tview.NewTextView(). sideBarIPAddressesTitle := tview.NewTextView().
SetText("IP Addresses"). SetText("IP Addresses").
SetTextColor(tcell.ColorYellow) SetTextColor(tcell.ColorYellow)
var sideBarIPAddresses []*tview.TextView var sideBarIPAddresses []*tview.TextView
for _, i := range ifaces { for _, i := range ifaces {
addrs, err := i.Addrs() addrs, err := i.Addrs()
if err != nil { if err != nil {
log.Printf("Error getting interface addresses : %s", err) log.Printf("Error getting interface addresses : %s", err)
continue continue
} }
for _, a := range addrs { for _, a := range addrs {
ipAsTextView := tview.NewTextView(). ipAsTextView := tview.NewTextView().
SetText(fmt.Sprintf(" %v : %s", i.Name, a.String())) SetText(fmt.Sprintf(" %v : %s", i.Name, a.String()))
sideBarIPAddresses = append(sideBarIPAddresses, ipAsTextView) sideBarIPAddresses = append(sideBarIPAddresses, ipAsTextView)
} }
} }
// Setup side bar (Right column) // Setup side bar (Right column)
sideBar := tview.NewFlex(). sideBar := tview.NewFlex().
SetDirection(tview.FlexRow) SetDirection(tview.FlexRow)
sideBar.SetTitle("System Info"). sideBar.SetTitle("System Info").
SetBorder(true). SetBorder(true).
SetTitleColor(tcell.ColorAqua) SetTitleColor(tcell.ColorAqua)
sideBar.AddItem(sideBarCPUTempTitle, 1, 1, false) sideBar.AddItem(sideBarCPUTempTitle, 1, 1, false)
sideBar.AddItem(sideBarCPUTemp, 1, 1, false) sideBar.AddItem(sideBarCPUTemp, 1, 1, false)
sideBar.AddItem(sideBarGPUTempTitle, 1, 1, false) sideBar.AddItem(sideBarGPUTempTitle, 1, 1, false)
sideBar.AddItem(sideBarGPUTemp, 1, 1, false) sideBar.AddItem(sideBarGPUTemp, 1, 1, false)
sideBar.AddItem(sideBarMemoryTitle, 1, 1, false) sideBar.AddItem(sideBarMemoryTitle, 1, 1, false)
sideBar.AddItem(sideBarMemoryStats, 1, 1, false) sideBar.AddItem(sideBarMemoryStats, 1, 1, false)
sideBar.AddItem(sideBarSwapTitle, 1, 1, false) sideBar.AddItem(sideBarSwapTitle, 1, 1, false)
sideBar.AddItem(sideBarSwapStats, 1, 1, false) sideBar.AddItem(sideBarSwapStats, 1, 1, false)
sideBar.AddItem(sideBarFilesystemTitle, 1, 1, false) sideBar.AddItem(sideBarFilesystemTitle, 1, 1, false)
for _, filesystemAsTextView := range sideBarFilesystems { for _, filesystemAsTextView := range sideBarFilesystems {
sideBar.AddItem(filesystemAsTextView, 1, 1, false) sideBar.AddItem(filesystemAsTextView, 1, 1, false)
} }
sideBar.AddItem(sideBarIPAddressesTitle, 1, 1, false) sideBar.AddItem(sideBarIPAddressesTitle, 1, 1, false)
for _, ipAsTextView := range sideBarIPAddresses { for _, ipAsTextView := range sideBarIPAddresses {
sideBar.AddItem(ipAsTextView, 1, 1, false) sideBar.AddItem(ipAsTextView, 1, 1, false)
} }
// Pages // Pages
pages := tview.NewPages() pages := tview.NewPages()
// Main UI // Main UI
mainUI := tview.NewGrid(). mainUI := tview.NewGrid().
SetRows(2, 0, 4). SetRows(2, 0, 4).
SetColumns(25, 50, 50). SetColumns(25, 50, 50).
SetBorders(true). SetBorders(true).
AddItem(header, 0, 0, 1, 3, 0, 0, false). AddItem(header, 0, 0, 1, 3, 0, 0, false).
AddItem(footer, 2, 0, 1, 3, 0, 0, false) AddItem(footer, 2, 0, 1, 3, 0, 0, false)
mainUI.AddItem(menu, 1, 0, 1, 1, 0, 100, true). mainUI.AddItem(menu, 1, 0, 1, 1, 0, 100, true).
AddItem(main, 1, 1, 1, 1, 0, 100, false). AddItem(main, 1, 1, 1, 1, 0, 100, false).
AddItem(sideBar, 1, 2, 1, 1, 0, 100, false) AddItem(sideBar, 1, 2, 1, 1, 0, 100, false)
pages.AddPage(PAGE_MAIN_UI, mainUI, true, true) pages.AddPage(PAGE_MAIN_UI, mainUI, true, true)
// Button modals // Button modals
exitModal := tview.NewModal(). exitModal := tview.NewModal().
SetText("Are you sure you want to [red]EXIT?"). SetText("Are you sure you want to [red]EXIT?").
AddButtons([]string{"Yes", "Cancel"}). AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) { SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" { if buttonLabel == "Yes" {
app.Stop() app.Stop()
} }
pages.SwitchToPage(PAGE_MAIN_UI) pages.SwitchToPage(PAGE_MAIN_UI)
}) })
pages.AddPage(PAGE_EXIT, exitModal, true, false) pages.AddPage(PAGE_EXIT, exitModal, true, false)
exitButton.SetSelectedFunc(func() { exitButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_EXIT) pages.ShowPage(PAGE_EXIT)
}) })
rebootModal := tview.NewModal(). rebootModal := tview.NewModal().
SetText("Are you sure you want to [red]REBOOT?"). SetText("Are you sure you want to [red]REBOOT?").
AddButtons([]string{"Yes", "Cancel"}). AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) { SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" { if buttonLabel == "Yes" {
err := exec.Command(CMD_SYSTEMCTL, "reboot").Run() err := exec.Command(CMD_SYSTEMCTL, "reboot").Run()
if err != nil { if err != nil {
log.Fatalf("Could not reboot : %s ", err) log.Fatalf("Could not reboot : %s ", err)
} }
} }
pages.SwitchToPage(PAGE_MAIN_UI) pages.SwitchToPage(PAGE_MAIN_UI)
}) })
pages.AddPage(PAGE_REBOOT, rebootModal, true, false) pages.AddPage(PAGE_REBOOT, rebootModal, true, false)
rebootButton.SetSelectedFunc(func() { rebootButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_REBOOT) pages.ShowPage(PAGE_REBOOT)
}) })
powerOffModal := tview.NewModal(). powerOffModal := tview.NewModal().
SetText("Are you sure you want to [red]POWER [red]OFF?"). SetText("Are you sure you want to [red]POWER [red]OFF?").
AddButtons([]string{"Yes", "Cancel"}). AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) { SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" { if buttonLabel == "Yes" {
err := exec.Command(CMD_SYSTEMCTL, "poweroff").Run() err := exec.Command(CMD_SYSTEMCTL, "poweroff").Run()
if err != nil { if err != nil {
log.Fatalf("Could not power off : %s ", err) log.Fatalf("Could not power off : %s ", err)
} }
} }
pages.SwitchToPage(PAGE_MAIN_UI) pages.SwitchToPage(PAGE_MAIN_UI)
}) })
pages.AddPage(PAGE_POWEROFF, powerOffModal, true, false) pages.AddPage(PAGE_POWEROFF, powerOffModal, true, false)
powerOffButton.SetSelectedFunc(func() { powerOffButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_POWEROFF) pages.ShowPage(PAGE_POWEROFF)
}) })
// Setup tracking of which are of the UI can/has focus // Setup tracking of which are of the UI can/has focus
primitivesThatCanFocus := []tview.Primitive{menu, exitButton, rebootButton, powerOffButton} primitivesThatCanFocus := []tview.Primitive{menu, exitButton, rebootButton, powerOffButton}
currentFocus := 0 currentFocus := 0
// Setup basic switching between main menu and buttons for the UI // Setup basic switching between main menu and buttons for the UI
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
// Don't process input if we aren't on an existing element that can focus // Don't process input if we aren't on an existing element that can focus
canContinue := false canContinue := false
focusedPrimitive := app.GetFocus() focusedPrimitive := app.GetFocus()
// Override some of the default behavior so up/dn move between fields in forms // Override some of the default behavior so up/dn move between fields in forms
// Per API GetFocusedItemIndex on a form will be -1 if a form item isn't currently focused // Per API GetFocusedItemIndex on a form will be -1 if a form item isn't currently focused
// We use this as a bit of a cheat to figure out if we're inside of a form that needs better nav options for users (ie. tab doesn't exist on a remote) // We use this as a bit of a cheat to figure out if we're inside of a form that needs better nav options for users (ie. tab doesn't exist on a remote)
wifiField, wifiButton := wifiConfigForm.GetFocusedItemIndex() wifiField, wifiButton := wifiConfigForm.GetFocusedItemIndex()
albumField, albumButton := selectAlbumsForm.GetFocusedItemIndex() albumField, albumButton := selectAlbumsForm.GetFocusedItemIndex()
if wifiField != -1 || wifiButton != -1 || albumField != -1 || albumButton != -1 { if wifiField != -1 || wifiButton != -1 || albumField != -1 || albumButton != -1 {
switch event.Key() { switch event.Key() {
case tcell.KeyUp: case tcell.KeyUp:
return tcell.NewEventKey(tcell.KeyBacktab, 0, event.Modifiers()) return tcell.NewEventKey(tcell.KeyBacktab, 0, event.Modifiers())
case tcell.KeyDown: case tcell.KeyDown:
return tcell.NewEventKey(tcell.KeyTab, 0, event.Modifiers()) return tcell.NewEventKey(tcell.KeyTab, 0, event.Modifiers())
} }
} }
// Standard override for the screen primitives we are using (ie. don't take over control in modals) // Standard override for the screen primitives we are using (ie. don't take over control in modals)
for _, primitive := range primitivesThatCanFocus { for _, primitive := range primitivesThatCanFocus {
if primitive == focusedPrimitive { if primitive == focusedPrimitive {
canContinue = true canContinue = true
break break
} }
} }
// Bail if we shouldn't be affecting user input // Bail if we shouldn't be affecting user input
if !canContinue { if !canContinue {
return event return event
} }
// Add various forms of nav that make sense for a TUI (common stuff folks are used to) // Add various forms of nav that make sense for a TUI (common stuff folks are used to)
currentFocusChanged := false currentFocusChanged := false
switch event.Key() { switch event.Key() {
case tcell.KeyRight, tcell.KeyTab: case tcell.KeyRight, tcell.KeyTab:
currentFocus += 1 currentFocus += 1
if currentFocus >= len(primitivesThatCanFocus) { if currentFocus >= len(primitivesThatCanFocus) {
currentFocus = 0 currentFocus = 0
} }
currentFocusChanged = true currentFocusChanged = true
case tcell.KeyLeft, tcell.KeyBacktab: case tcell.KeyLeft, tcell.KeyBacktab:
currentFocus -= 1 currentFocus -= 1
if currentFocus < 0 { if currentFocus < 0 {
currentFocus = len(primitivesThatCanFocus) - 1 currentFocus = len(primitivesThatCanFocus) - 1
} }
currentFocusChanged = true currentFocusChanged = true
} }
// Update the focus based on navigation // Update the focus based on navigation
if currentFocusChanged { if currentFocusChanged {
app.SetFocus(primitivesThatCanFocus[currentFocus]) app.SetFocus(primitivesThatCanFocus[currentFocus])
return nil return nil
} }
// Pass through event so the main UI can process stuff properly // Pass through event so the main UI can process stuff properly
return event return event
}) })
// Show UI and panic if there are any errors // Show UI and panic if there are any errors
if err := app.SetRoot(pages, true).SetFocus(primitivesThatCanFocus[currentFocus]).EnableMouse(false).Run(); err != nil { if err := app.SetRoot(pages, true).SetFocus(primitivesThatCanFocus[currentFocus]).EnableMouse(false).Run(); err != nil {
log.Fatalf("Failed to run UI : ", err) log.Fatalf("Failed to run UI : ", err)
} }
} }

View File

@ -100,13 +100,13 @@ func Slideshow() {
}(keyboardCtx) }(keyboardCtx)
// Run fim // Run fim
// if err := fim.Run(); err != nil { if err := fim.Run(); err != nil {
// // Unwrap the error a bit so we can find out if a signal killed fim or something else // 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) // 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 { if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() != -1 {
// log.Fatalf("Error running fim : %s", err) log.Fatalf("Error running fim : %s", err)
// } }
// } }
// Stop fim slideshow advancing go routine // Stop fim slideshow advancing go routine
close(stop_ticker) close(stop_ticker)