From ab1b46d71cb592b3b05d5698aeb35c22c9bcc66f Mon Sep 17 00:00:00 2001 From: DaanSelen Date: Tue, 13 Jan 2026 10:49:20 +0100 Subject: [PATCH] chore: reorder and expand script --- example.pptx => examples/example.pptx | Bin scholing.pptx => examples/scholing.pptx | Bin install.sh | 41 +++++- .../{pres.service => presentation.service} | 0 src/action.go | 23 +++- src/draw.go | 124 +++++++++++------- src/main.go | 15 ++- 7 files changed, 139 insertions(+), 64 deletions(-) rename example.pptx => examples/example.pptx (100%) rename scholing.pptx => examples/scholing.pptx (100%) rename service-files/{pres.service => presentation.service} (100%) diff --git a/example.pptx b/examples/example.pptx similarity index 100% rename from example.pptx rename to examples/example.pptx diff --git a/scholing.pptx b/examples/scholing.pptx similarity index 100% rename from scholing.pptx rename to examples/scholing.pptx diff --git a/install.sh b/install.sh index 8baa2b3..e5d4640 100644 --- a/install.sh +++ b/install.sh @@ -22,4 +22,43 @@ fi if [[ ! -d /opt/${program_name} ]]; then mkdir -p /opt/${program_name} -m 755 chown -R ${userland_name}:${userland_name} /opt/${program_name} -fi \ No newline at end of file +fi + +cat > /home/${userland_name}/.config/systemd/user/presentation.service << EOF +[Unit] +Description="Systemec Akarton Raspberry Pi Uploader Program (SARPUS)" +After=graphical.target + +[Service] +Type=simple +Environment=DISPLAY=:0 +WorkingDirectory=/opt/akartontv +ExecStartPre=/usr/bin/sleep 5 +ExecStart=/usr/bin/libreoffice --impress --show /opt/akartontv/presentation.pptx --norestore +ExecStop=/usr/bin/killall libreoffice +Restart=always +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=default.target +EOF + +cat > /home/${userland_name}/.config/systemd/user/video.service << EOF +[Unit] +Description="Systemec Akarton Raspberry Pi Uploader Program (SARPUS)" +After=graphical.target + +[Service] +Type=simple +Environment=DISPLAY=:0 +WorkingDirectory=/opt/akartontv +ExecStart=/usr/bin/vlc --loop /opt/akartontv/video.mp4 -I dummy --no-video-title +ExecStop=/usr/bin/killall vlc +Restart=always +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=default.target +EOF \ No newline at end of file diff --git a/service-files/pres.service b/service-files/presentation.service similarity index 100% rename from service-files/pres.service rename to service-files/presentation.service diff --git a/src/action.go b/src/action.go index d1dc8d6..ef18167 100644 --- a/src/action.go +++ b/src/action.go @@ -94,7 +94,11 @@ func sftpUploadFile(targetName, localPath, remotePath string, cfg RaspiConfig) b return true } -func restartShow(targetName string, cfg RaspiConfig) bool { +func restartShow(targetName string, targetMode int, cfg RaspiConfig) bool { + const restartPresentation string = "systemctl --user restart presentation" + const restartVideo string = "systemctl --user restart video" + var err error + sshClient, err := createSSHClient(targetName, cfg) if err != nil { @@ -106,14 +110,23 @@ func restartShow(targetName string, cfg RaspiConfig) bool { session, err := sshClient.NewSession() if err != nil { log.Printf("Failed to create session over the connection: %v", err) + return false } defer session.Close() - output, err := session.CombinedOutput("systemctl --user restart pres") - if err != nil { - log.Printf("Failed to restart the show over SSH: %v", err) + var output []byte + switch targetMode { + case 1: + output, err = session.CombinedOutput(restartPresentation) + case 2: + output, err = session.CombinedOutput(restartVideo) } - log.Println(output) + if err != nil { + log.Printf("Failed to restart the show over SSH: %v", err) + return false + } + + log.Println(string(output)) return true } diff --git a/src/draw.go b/src/draw.go index 32c72d6..033d2a7 100644 --- a/src/draw.go +++ b/src/draw.go @@ -3,6 +3,7 @@ package main import ( "image/color" "log" + "path/filepath" "time" "fyne.io/fyne/v2" @@ -14,36 +15,49 @@ import ( "fyne.io/fyne/v2/widget" ) +type FlashKind int + +const ( + FlashError FlashKind = iota + FlashSuccess +) + var ( presFileFilter []string = []string{".pptx", ".odp"} videoFileFilter []string = []string{".mp4", ".mkv", ".mov", ".webm"} + + flashTimers = map[*widget.Button]*time.Timer{} ) -func flashColor(btnPtr *widget.Button, color string) { - var sleepAmount int = 1 - // "red" or "green" - // Set red on UI thread +func flashColor(btn *widget.Button, kind FlashKind) { + if t, ok := flashTimers[btn]; ok { + t.Stop() // cancel previous flash + } + duration := 1 * time.Second // default flash duration + // Determine button style based on kind + var importance widget.Importance + switch kind { + case FlashError: + importance = widget.DangerImportance + case FlashSuccess: + importance = widget.SuccessImportance + duration = 3 * time.Second + } + + // Apply the flash immediately on UI thread fyne.CurrentApp().Driver().DoFromGoroutine(func() { - switch color { - case "red": - btnPtr.Importance = widget.DangerImportance - case "green": - btnPtr.Importance = widget.SuccessImportance - sleepAmount = 3 - } - refreshButtons(btnPtr) + btn.Importance = importance + btn.Refresh() }, false) - log.Println("Waiting to change button") - time.Sleep(time.Duration(sleepAmount) * time.Second) - log.Println("Done waiting") - - // Reset on UI thread - fyne.CurrentApp().Driver().DoFromGoroutine(func() { - btnPtr.Importance = widget.LowImportance - refreshButtons(btnPtr) - }, false) + // Schedule reset after duration (non-blocking) + flashTimers[btn] = time.AfterFunc(duration, func() { + fyne.CurrentApp().Driver().DoFromGoroutine(func() { + btn.Importance = widget.LowImportance + btn.Refresh() + }, false) + }) } func drawSeparator(trailNewLine bool) *fyne.Container { @@ -116,20 +130,17 @@ func drawModeRow(targetMode *int) *fyne.Container { func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, reloadBtn *widget.Button, cfg RaspiConfig) *fyne.Container { actionText := widget.NewLabel("Select Target") + var previousTarget string var verifyBtn *widget.Button + // Left side for selection of target piSelection := widget.NewRadioGroup(raspiNames, func(selected string) { - if len(selected) != 0 { - *raspiTarget = selected - log.Println("Selected target:", selected) - } else { - log.Println("Deselected Target") - *raspiTarget = "" + if selected == previousTarget { + return // nothing changed } - // Reload all buttons! - verifyBtn.Importance = widget.LowImportance - uploadBtn.Importance = widget.LowImportance - reloadBtn.Importance = widget.LowImportance + previousTarget = selected + *raspiTarget = selected + refreshButtons(verifyBtn, uploadBtn, reloadBtn) }) @@ -154,13 +165,13 @@ func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, relo if credOK { // Must update UI on main thread fyne.CurrentApp().Driver().DoFromGoroutine(func() { - go flashColor(verifyBtn, "green") // flashcolor should handle RunOnMain internally + flashColor(verifyBtn, FlashSuccess) // flashcolor should handle RunOnMain internally uploadBtn.Enable() reloadBtn.Enable() }, false) } else { - go flashColor(verifyBtn, "red") // flashcolor should handle RunOnMain internally + flashColor(verifyBtn, FlashError) // flashcolor should handle RunOnMain internally } }() }) @@ -178,11 +189,12 @@ func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, relo return wholeCol } -func drawFileSelection(localPath *string, targetMode *int, parentWindow *fyne.Window) *fyne.Container { +func drawFileSelection(localPath *string, targetMode *int, parentWindow fyne.Window) *fyne.Container { actionText := widget.NewLabel("Select File") pathLabel := widget.NewLabel("No File Selected Yet...") - uploadBtn := widget.NewButton("Click to Select File", func() { + var uploadBtn *widget.Button + uploadBtn = widget.NewButton("Click to Select File", func() { // Stupid dialog error on 'headless' servers... log.Println("Ignore the next error. ~Refer: https://github.com/fyne-io/fyne/issues/4110") @@ -194,9 +206,11 @@ func drawFileSelection(localPath *string, targetMode *int, parentWindow *fyne.Wi pathLabel.SetText(r.URI().Name()) *localPath = r.URI().Path() + uploadBtn.Importance = widget.HighImportance + refreshButtons(uploadBtn) log.Println("Local path of selected file:", *localPath) } - }, *parentWindow) + }, parentWindow) var fileFilter storage.FileFilter switch *targetMode { @@ -231,9 +245,9 @@ func drawFileSelection(localPath *string, targetMode *int, parentWindow *fyne.Wi } // targetMode *int -func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, cfg RaspiConfig) (*fyne.Container, *widget.Button, *widget.Button) { - remotePresPath := "/opt/akartontv/presentation.pptx" - //remoteVideoPath := "/opt/akartontv/video.mp4" +func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, targetMode *int, cfg RaspiConfig) (*fyne.Container, *widget.Button, *widget.Button) { + fExt := filepath.Ext(*localUploadPath) + remotePresPath := "/opt/akartontv/media" + fExt // Configuration of the bottom of the application var uploadBtn *widget.Button @@ -241,14 +255,18 @@ func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, cfg RaspiCon uploadBtn.Importance = widget.HighImportance refreshButtons(uploadBtn) - if sftpUploadFile(*raspiTarget, *localUploadPath, remotePresPath, cfg) { - go flashColor(uploadBtn, "green") - } else { - go flashColor(uploadBtn, "red") - } + go func() { + ok := sftpUploadFile(*raspiTarget, *localUploadPath, remotePresPath, cfg) + + if ok { + flashColor(uploadBtn, FlashSuccess) + } else { + flashColor(uploadBtn, FlashError) + } + }() }) uploadWide := container.NewGridWrap( - buttonSize, + dButtonSize, uploadBtn, ) @@ -257,14 +275,18 @@ func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, cfg RaspiCon reloadBtn.Importance = widget.HighImportance refreshButtons(uploadBtn) - if restartShow(*raspiTarget, cfg) { - go flashColor(reloadBtn, "green") // flashcolor should handle RunOnMain internally - } else { - go flashColor(reloadBtn, "red") - } + go func() { + ok := restartShow(*raspiTarget, *targetMode, cfg) + + if ok { + flashColor(reloadBtn, FlashSuccess) + } else { + flashColor(reloadBtn, FlashError) + } + }() }) reloadWide := container.NewGridWrap( - buttonSize, + dButtonSize, reloadBtn, ) diff --git a/src/main.go b/src/main.go index b7a2bd1..5f1f2a7 100644 --- a/src/main.go +++ b/src/main.go @@ -22,14 +22,14 @@ const ( ) var ( - windowSize fyne.Size = fyne.NewSize(750, 700) // Default Window size - buttonSize fyne.Size = fyne.NewSize(200, 50) // Default button size - entrySize fyne.Size = fyne.NewSize(200, 40) + windowSize fyne.Size = fyne.NewSize(750, 700) // Default Window size + buttonSize fyne.Size = fyne.NewSize(200, 50) // Default button size + dButtonSize fyne.Size = fyne.NewSize(150, 50) // Button size for upload & reload + entrySize fyne.Size = fyne.NewSize(200, 40) ) func main() { cfg := readConfig() - log.Println(cfg) app := app.NewWithID("nl.systemec.rpi-charon") app.Settings().SetTheme(theme.DefaultTheme()) @@ -54,10 +54,10 @@ func main() { } // Call the draw functions -> ./src/draw.go - footerRow, uploadBtn, reloadBtn := drawFooter(app, &raspiTarget, &localUploadPath, cfg) + footerRow, uploadBtn, reloadBtn := drawFooter(app, &raspiTarget, &localUploadPath, &targetMode, cfg) modeBtnRow := drawModeRow(&targetMode) selectionRow := drawTargetSection(raspiNames, &raspiTarget, uploadBtn, reloadBtn, cfg) - fileSelectRow := drawFileSelection(&localUploadPath, &targetMode, &w) + fileSelectRow := drawFileSelection(&localUploadPath, &targetMode, w) center := container.NewVBox( modeBtnRow, @@ -80,7 +80,8 @@ func main() { } func refreshButtons(givenButtons ...*widget.Button) { - for _, btn := range givenButtons { + for _, b := range givenButtons { + btn := b fyne.CurrentApp().Driver().DoFromGoroutine(func() { btn.Refresh() }, false)