mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-05 16:37:09 +00:00
feat(display): pdf support
This commit is contained in:
@@ -10,6 +10,7 @@ Open source solution for advanced remote control and coordination of one or more
|
|||||||
- Bash
|
- Bash
|
||||||
- Chromium
|
- Chromium
|
||||||
- LibreOffice (optional, presentation view)
|
- LibreOffice (optional, presentation view)
|
||||||
|
- Xreader (optional, pdf view)
|
||||||
- ImageMagick (optional, image preview)
|
- ImageMagick (optional, image preview)
|
||||||
- ffmpeg (optional, video preview)
|
- ffmpeg (optional, video preview)
|
||||||
- Ghostscript (optional, pdf preview)
|
- Ghostscript (optional, pdf preview)
|
||||||
|
|||||||
+24
-3
@@ -47,20 +47,33 @@ func GetDeviceMac() (string, error) {
|
|||||||
return "", fmt.Errorf("no suitable MAC address found")
|
return "", fmt.Errorf("no suitable MAC address found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var runningHelpProgram *exec.Cmd = nil
|
||||||
|
|
||||||
func OpenPresentation(path string) error {
|
func OpenPresentation(path string) error {
|
||||||
tempDirPath, err := os.MkdirTemp("", "plg-mudics-libreoffice-profile-")
|
tempDirPath, err := os.MkdirTemp("", "plg-mudics-libreoffice-profile-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temporary profile directory: %w", err)
|
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))
|
runningHelpProgram = exec.Command("soffice", "--show", path, "--nologo", "--norestore", fmt.Sprintf("-env:UserInstallation=file:///%s", tempDirPath))
|
||||||
result := shared.RunShellCommand(cmd)
|
result := shared.RunShellCommand(runningHelpProgram)
|
||||||
if result.ExitCode != 0 {
|
killedByParent := -1
|
||||||
|
if result.ExitCode != 0 && result.ExitCode != killedByParent {
|
||||||
return errors.New(result.Stderr)
|
return errors.New(result.Stderr)
|
||||||
}
|
}
|
||||||
return nil
|
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
|
type KeyAction int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -154,3 +167,11 @@ func ResolveStorageFilePath(pathParam string) (string, bool, error) {
|
|||||||
|
|
||||||
return fullPath, true, nil
|
return fullPath, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseRunningProgram() error {
|
||||||
|
if runningHelpProgram == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := runningHelpProgram.Process.Kill()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
+13
-17
@@ -15,11 +15,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
shared "plg-mudics/shared"
|
shared "plg-mudics/shared"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
"github.com/micmonay/keybd_event"
|
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
|
|
||||||
"plg-mudics/display/pkg"
|
"plg-mudics/display/pkg"
|
||||||
@@ -266,6 +264,8 @@ func downloadFileRoute(ctx echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openFileRoute(ctx echo.Context) error {
|
func openFileRoute(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
pathParam := ctx.Param("path")
|
pathParam := ctx.Param("path")
|
||||||
fullPath := ctx.Get("fullPath").(string)
|
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"})
|
return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Cant connect to display browser client"})
|
||||||
}
|
}
|
||||||
|
|
||||||
err := resetView()
|
err = resetView()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to reset view", "error", err)
|
slog.Error("Failed to reset view", "error", err)
|
||||||
return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Failed to reset view"})
|
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)
|
imageTemplate(pathParam).Render(context.Background(), &templateBuffer)
|
||||||
sseConnection <- templateBuffer.String()
|
sseConnection <- templateBuffer.String()
|
||||||
case ".pptx", ".odp":
|
case ".pptx", ".odp":
|
||||||
err := pkg.OpenPresentation(fullPath)
|
err = pkg.OpenPresentation(fullPath)
|
||||||
if err != nil {
|
case ".pdf":
|
||||||
slog.Error("Failed to open presentation", "file", pathParam, "error", err)
|
err = pkg.OpenPDF(fullPath)
|
||||||
return ctx.JSON(http.StatusInternalServerError, shared.ErrorResponse{Description: "Failed to open presentation"})
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return ctx.JSON(http.StatusUnsupportedMediaType, shared.ErrorResponse{Description: "Unsupported file type"})
|
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)
|
slog.Info("Successfully run file", "file", pathParam)
|
||||||
return ctx.JSON(http.StatusOK, struct{}{})
|
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
|
// Reset previous file views so they dont collide with the new one
|
||||||
func resetView() error {
|
func resetView() error {
|
||||||
var err error
|
err := pkg.CloseRunningProgram()
|
||||||
|
|
||||||
err = pkg.KeyboardInput(keybd_event.VK_ESC, pkg.KeyPress)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send ESC key: %w", err)
|
return fmt.Errorf("failed to close running program: %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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sseConnection <- ""
|
sseConnection <- ""
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
nushell
|
nushell
|
||||||
unzip
|
unzip
|
||||||
iputils
|
iputils
|
||||||
|
xreader
|
||||||
|
|
||||||
# Libraries
|
# Libraries
|
||||||
imagemagick
|
imagemagick
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package shared
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandResponse struct {
|
type CommandResponse struct {
|
||||||
@@ -24,6 +26,34 @@ func RunShellCommand(cmd *exec.Cmd) CommandResponse {
|
|||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
err := cmd.Run()
|
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{
|
commandOutput := CommandResponse{
|
||||||
Stdout: stdout.String(),
|
Stdout: stdout.String(),
|
||||||
Stderr: stderr.String(),
|
Stderr: stderr.String(),
|
||||||
|
|||||||
Reference in New Issue
Block a user