diff --git a/CHANGELOG.md b/CHANGELOG.md index 963d340..f637449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 20200905-4 + +- Update fan daemon to be better about loading config + - There appears to be a 'delay' with config loading that causes 0's to come in for temp values, we work around this now +- Update slideshow to keep it running UNTIL a user presses a key to enter the config ui +- Added support for slideshow restart interval (this is for re-randomizing the slideshow) +- Adjust randomization flag for fim to be a little more reliable (put back the seeded version) + ## 20200905-3 - Update inotify to use configured path for albums instead of being hard coded diff --git a/cmd/gui/gui.go b/cmd/gui/gui.go index aa51f26..d03608a 100644 --- a/cmd/gui/gui.go +++ b/cmd/gui/gui.go @@ -33,6 +33,14 @@ func main() { // Load the config file pfConfig, configFileProvider := config.LoadConfig() + // For some reason the restart interval comes through the config as 0s + // Similar to the fan daemon, keep reloading config until we get a valid value + restartDuration := pfConfig.Duration(config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL) + for restartDuration.Milliseconds() < 1 { + pfConfig, configFileProvider = config.LoadConfig() + restartDuration = pfConfig.Duration(config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL) + } + // Watch for config changes and re-load config if needed configFileProvider.Watch(func(event interface{}, err error) { if err != nil { diff --git a/ui/slideshow.go b/ui/slideshow.go index 00d8aca..76fd5e6 100644 --- a/ui/slideshow.go +++ b/ui/slideshow.go @@ -20,6 +20,34 @@ const ( PATH_TEMP_FOR_SLIDESHOW = "/run/piframe/fim" ) +// fim placeholders so we can reset them as needed +var fim *exec.Cmd = nil +var stdin io.WriteCloser = nil + +func setupFim(PATH_TEMP_FOR_SLIDESHOW string) { + // Prep slideshow command and arguments + // NOTE: The random flag is seeded with time() ; this is bad as we will be restarting the slideshow at about the same time per the configurd schedule + // We use the non-seeded form to ensure that it's a little more random (or at least hope it's a little more random) + CMD_FIM := "/usr/bin/fim" + ARGS_FIM := []string{"--no-commandline", "--no-history", "--etc-fimrc", "/usr/local/etc/fimrc", + "--device", "/dev/fb0", "--vt", "1", + "--execute-commands-early", "\"clear\"", "--final-commands", "\"clear\"", + "--autozoom", "--random-no-seed", "--recursive", "--cd-and-readdir", + PATH_TEMP_FOR_SLIDESHOW} + + // fim command that'll be executed + fim = exec.Command(CMD_FIM, ARGS_FIM...) + // Put fim into a process group so ALL processes that may be executed are exited when main process exits + fim.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + // Setup stdin for fim to control slideshow + stdinLocal, err := fim.StdinPipe() + if err != nil { + log.Fatalf("Error getting fim stdin : %s", err) + } + stdin = stdinLocal +} + func Slideshow(pfconfig *koanf.Koanf) { // Prep folder setup needed for fim // fim does NOT allow multiple folders passed on the CLI (as far as KemoNine can tell) @@ -45,21 +73,21 @@ func Slideshow(pfconfig *koanf.Koanf) { if err != nil { log.Fatalf("Error setting up slideshow : %s", err) } - // Loop over the directory's files. - for index := range(dirFiles) { - fileHere := dirFiles[index] + // Loop over the directory's files. + for index := range dirFiles { + fileHere := dirFiles[index] - // Get name of file and its full path. - nameHere := fileHere.Name() - fullPath := PATH_TEMP_FOR_SLIDESHOW + "/" + nameHere + // Get name of file and its full path. + nameHere := fileHere.Name() + fullPath := PATH_TEMP_FOR_SLIDESHOW + "/" + nameHere - // Remove the file. + // Remove the file. err = os.Remove(fullPath) if err != nil { log.Fatalf("Error setting up slideshow : %s", err) } } - + // Setup symlinks to selected albums to be used with slideshow // Add albums full paths to command line args for fim albumRootPath := pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT) @@ -80,7 +108,7 @@ func Slideshow(pfconfig *koanf.Koanf) { } mode := fileStat.Mode() if mode.IsRegular() { - err = os.Symlink(filePath, PATH_TEMP_FOR_SLIDESHOW + "/" + file.Name()) + err = os.Symlink(filePath, PATH_TEMP_FOR_SLIDESHOW+"/"+file.Name()) if err != nil { log.Fatalf("Error setting up slideshow : %s", err) } @@ -94,29 +122,9 @@ func Slideshow(pfconfig *koanf.Koanf) { } } - // fim placeholder so we can operate on it when a exit slideshow is received - var fim *exec.Cmd = nil - - // Prep slideshow command and arguments - // NOTE: The random flag is seeded with time() ; this is bad as we will be restarting the slideshow at about the same time per the configurd schedule - // We use the non-seeded form to ensure that it's a little more random (or at least hope it's a little more random) - CMD_FIM := "/usr/bin/fim" - ARGS_FIM := []string{"--no-commandline", "--no-history", "--etc-fimrc", "/usr/local/etc/fimrc", - "--device", "/dev/fb0", "--vt", "1", - "--execute-commands-early", "\"clear\"", "--final-commands", "\"clear\"", - "--autozoom", "--random-no-seed", "--recursive", "--cd-and-readdir", - PATH_TEMP_FOR_SLIDESHOW} - - // fim command that'll be executed - fim = exec.Command(CMD_FIM, ARGS_FIM...) - // Put fim into a process group so ALL processes that may be executed are exited when main process exits - fim.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - - // Setup stdin for fim to control slideshow - stdin, err := fim.StdinPipe() - if err != nil { - log.Fatalf("Error getting fim stdin : %s", err) - } + // Setup fim for run + // Commands can't be re-used so we move this to a function to support the restart interval cleanly + setupFim(PATH_TEMP_FOR_SLIDESHOW) // Advance slideshow every interval as defined in const() ticker := time.NewTicker(pfconfig.Duration(config.CONFIG_KEY_SLIDESHOW_INTERVAL)) @@ -144,6 +152,15 @@ func Slideshow(pfconfig *koanf.Koanf) { } // NOT deferring keyboard.close as we do that by hand further down when fim exits ahead of showing the config UI + // Keep running fim until we're signaled by the user to do otherwise + // Common reasons that we MUST do this kind of goofy (bugs / behavior) + // - fim doesn't quite see the /run changes where the albums are linked and bails out + // - OOM kicks in and kills fim (it's kinda RAM heavy sometimes) + // - Corrupt or invalid image causes fim to crash + // - The timeout for re-randomizing kicked free and fim needs to restart + // - fim thinks the slideshow has 'ended' for whatever reason and we really want to always show no matter what + STOP_SLIDESHOW := false + // Goroutine for tracking which keys are pressed and controlling fim if appropriate keyboardCtx, keyboardCancel := context.WithCancel(context.Background()) go func(keyboardCtx context.Context) { @@ -167,6 +184,8 @@ func Slideshow(pfconfig *koanf.Koanf) { // Exit fim and move to the config UI if event.Key == keyboard.KeyEsc || event.Key == keyboard.KeyEnter || event.Key == keyboard.KeySpace { + // We are being told to stop the slideshow, oblidge the user + STOP_SLIDESHOW = true if fim != nil { // Just in case someone lays on exit key or similar during startup pgid, err := syscall.Getpgid(fim.Process.Pid) if err == nil { @@ -174,7 +193,6 @@ func Slideshow(pfconfig *koanf.Koanf) { log.Fatalf("failed to kill fim : %s", err) } } - } break } @@ -192,17 +210,45 @@ func Slideshow(pfconfig *koanf.Koanf) { } }(keyboardCtx) - // 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) + // Restart fim after configured timeout ; This is setup as a ticker due to KemoNine not getting CommandWithContext stuff to work properly (lots of pointer related crashes and the like) + fimTicker := time.NewTicker(pfconfig.Duration(config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL)) + stop_fim_ticker := make(chan struct{}) + go func() { + for { + select { + case <-fimTicker.C: + if fim != nil { // Just in case someone lays on exit key or similar during startup + pgid, err := syscall.Getpgid(fim.Process.Pid) + if err == nil { + if err := syscall.Kill(-pgid, 9); err != nil { + log.Fatalf("failed to kill fim : %s", err) + } + } + } + case <-stop_fim_ticker: + fimTicker.Stop() + return + } } + }() + + // Run fim + for !STOP_SLIDESHOW { + 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 { + // We are NOT going to fatal here as there are many 'valid' reasons fim would exit for no good reason ;) + log.Printf("Error running fim : %s", err) + } + } + setupFim(PATH_TEMP_FOR_SLIDESHOW) } // Stop fim slideshow advancing go routine close(stop_ticker) + // Stop restart go routine + close(stop_fim_ticker) // Stop listening to keyboard events keyboard.Close()