diff --git a/README.md b/README.md index eeec660..bf09a16 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Open source solution for advanced remote control and coordination of one or more - Bash - Chromium - LibreOffice (optional, presentation view) +- Xreader (optional, pdf view) - ImageMagick (optional, image preview) - ffmpeg (optional, video preview) - Ghostscript (optional, pdf preview) diff --git a/display/pkg/main.go b/display/pkg/main.go index d34ac20..f781e2a 100644 --- a/display/pkg/main.go +++ b/display/pkg/main.go @@ -47,20 +47,33 @@ func GetDeviceMac() (string, error) { return "", fmt.Errorf("no suitable MAC address found") } +var runningHelpProgram *exec.Cmd = nil + func OpenPresentation(path string) error { tempDirPath, err := os.MkdirTemp("", "plg-mudics-libreoffice-profile-") if err != nil { return fmt.Errorf("failed to create temporary profile directory: %w", err) } - cmd := exec.Command("soffice", "--show", path, "--nologo", "--norestore", fmt.Sprintf("-env:UserInstallation=file:///%s", tempDirPath)) - result := shared.RunShellCommand(cmd) - if result.ExitCode != 0 { + runningHelpProgram = exec.Command("soffice", "--show", path, "--nologo", "--norestore", fmt.Sprintf("-env:UserInstallation=file:///%s", tempDirPath)) + result := shared.RunShellCommand(runningHelpProgram) + killedByParent := -1 + if result.ExitCode != 0 && result.ExitCode != killedByParent { return errors.New(result.Stderr) } return nil } +func OpenPDF(path string) error { + runningHelpProgram = exec.Command("xreader", path, "--presentation") + result := shared.RunShellCommandNonBlocking(runningHelpProgram) + killedByParent := -1 + if result.ExitCode != 0 && result.ExitCode != killedByParent { + return fmt.Errorf("could not open pdf: %s (%d)", result.Stderr, result.ExitCode) + } + return nil +} + type KeyAction int const ( @@ -154,3 +167,11 @@ func ResolveStorageFilePath(pathParam string) (string, bool, error) { return fullPath, true, nil } + +func CloseRunningProgram() error { + if runningHelpProgram == nil { + return nil + } + err := runningHelpProgram.Process.Kill() + return err +} diff --git a/display/web/main.go b/display/web/main.go index b5fb68a..badfabc 100644 --- a/display/web/main.go +++ b/display/web/main.go @@ -15,11 +15,9 @@ import ( "path/filepath" shared "plg-mudics/shared" "strings" - "time" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" - "github.com/micmonay/keybd_event" "github.com/skip2/go-qrcode" "plg-mudics/display/pkg" @@ -266,6 +264,8 @@ func downloadFileRoute(ctx echo.Context) error { } func openFileRoute(ctx echo.Context) error { + var err error + pathParam := ctx.Param("path") fullPath := ctx.Get("fullPath").(string) @@ -277,7 +277,7 @@ func openFileRoute(ctx echo.Context) error { return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Cant connect to display browser client"}) } - err := resetView() + err = resetView() if err != nil { slog.Error("Failed to reset view", "error", err) return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Failed to reset view"}) @@ -295,15 +295,18 @@ func openFileRoute(ctx echo.Context) error { imageTemplate(pathParam).Render(context.Background(), &templateBuffer) sseConnection <- templateBuffer.String() case ".pptx", ".odp": - err := pkg.OpenPresentation(fullPath) - if err != nil { - slog.Error("Failed to open presentation", "file", pathParam, "error", err) - return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Failed to open presentation"}) - } + err = pkg.OpenPresentation(fullPath) + case ".pdf": + err = pkg.OpenPDF(fullPath) default: return ctx.JSON(http.StatusUnsupportedMediaType, shared.ErrorResponse{Description: "Unsupported file type"}) } + if err != nil { + slog.Error("Failed to open file", "file", pathParam, "error", err) + return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Failed to open file"}) + } + slog.Info("Successfully run file", "file", pathParam) return ctx.JSON(http.StatusOK, struct{}{}) } @@ -377,16 +380,9 @@ func previewRoute(ctx echo.Context) error { // Reset previous file views so they dont collide with the new one func resetView() error { - var err error - - err = pkg.KeyboardInput(keybd_event.VK_ESC, pkg.KeyPress) + err := pkg.CloseRunningProgram() if err != nil { - return fmt.Errorf("failed to send ESC key: %w", err) - } - time.Sleep(400 * time.Millisecond) - err = pkg.KeyboardInput(keybd_event.VK_ESC, pkg.KeyRelease) - if err != nil { - return fmt.Errorf("failed to send ESC key: %w", err) + return fmt.Errorf("failed to close running program: %w", err) } sseConnection <- "" diff --git a/nixos/configuration.nix b/nixos/configuration.nix index 63a7b54..4265c83 100644 --- a/nixos/configuration.nix +++ b/nixos/configuration.nix @@ -91,6 +91,7 @@ nushell unzip iputils + xreader # Libraries imagemagick diff --git a/shared/main.go b/shared/main.go index 91d16f8..a596961 100644 --- a/shared/main.go +++ b/shared/main.go @@ -2,8 +2,10 @@ package shared import ( "bytes" + "context" "errors" "os/exec" + "time" ) type CommandResponse struct { @@ -24,6 +26,34 @@ func RunShellCommand(cmd *exec.Cmd) CommandResponse { cmd.Stderr = &stderr err := cmd.Run() + return parseCmdResult(cmd, stdout, stderr, err) + +} + +func RunShellCommandNonBlocking(cmd *exec.Cmd) CommandResponse { + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Start() + + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + + done := make(chan error, 1) + + go func() { + done <- cmd.Wait() + }() + + select { + case <-ctx.Done(): + return CommandResponse{} + case err := <-done: + return parseCmdResult(cmd, stdout, stderr, err) + } +} + +func parseCmdResult(cmd *exec.Cmd, stdout, stderr bytes.Buffer, err error) CommandResponse { commandOutput := CommandResponse{ Stdout: stdout.String(), Stderr: stderr.String(),