diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07c477a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Binaries +remote-touchpad + +# Systemd service state +.remote-touchpad.service.state + +# Logs and run files +.rts-log + +# Environment files +.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e06388..e59454c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 1.5.4 (2026-08-02) + +* Improve X11 keyboard input +* Update dependencies + +## 1.5.3 (2025-12-24) + +* Update dependencies + +## 1.5.2 (2025-10-12) + +* Update dependencies + ## 1.5.0 (2025-03-18) * Add restore token support for the remote desktop portal diff --git a/README.md b/README.md index fd05b7a..e039950 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,35 @@ Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11. go install github.com/unrud/remote-touchpad@latest ``` +## Linux User Service Installation + +To install remote-touchpad as a user-specific systemd service (no root access required): + +1. Clone the repository and navigate to the project directory: + + ```sh + git clone https://github.com/Unrud/remote-touchpad.git + cd remote-touchpad + ``` + +2. Install the service using the provided wrapper script (no sudo required): + + ```sh + ./rts install + ``` + +3. Access the service: + + * View service status: `./rts status` + * Get URL: `./rts url` + * View logs: `./rts log` + * Stop service: `./rts stop` + * Start service: `./rts start` + * Restart service: `./rts restart` + * Uninstall: `./rts uninstall` + +The service will run in the background and maintain a persistent connection with URL and QR code accessible via the `url` command. + ## Screenshots ![screenshot 1](https://raw.githubusercontent.com/Unrud/remote-touchpad/master/screenshots/1.png) diff --git a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml index 9e2c36b..113ef4d 100644 --- a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml +++ b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml @@ -23,7 +23,22 @@ Unrud - + + +
    +
  • Improve X11 keyboard input
  • +
  • Update dependencies
  • +
+
+
+ + +
    +
  • Update dependencies
  • +
+
+
+
  • Update dependencies
  • @@ -358,16 +373,16 @@ - https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.1/screenshots/1.png + https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/1.png - https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.1/screenshots/2.png + https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/2.png - https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.1/screenshots/3.png + https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/3.png - https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.1/screenshots/4.png + https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/4.png diff --git a/flatpak/com.github.unrud.RemoteTouchpad.yaml b/flatpak/com.github.unrud.RemoteTouchpad.yaml index 54e8e97..239160e 100644 --- a/flatpak/com.github.unrud.RemoteTouchpad.yaml +++ b/flatpak/com.github.unrud.RemoteTouchpad.yaml @@ -17,8 +17,8 @@ modules: path: .. - type: git url: https://github.com/godbus/dbus - tag: v5.1.0 - commit: e523abc905595cf17fb0001a7d77eaaddfaa216d + tag: v5.2.2 + commit: a8ac15ba63645f02ffd57f4b443203279ab40b30 dest: vendor/github.com/godbus/dbus/v5 - type: git url: https://github.com/skip2/go-qrcode @@ -26,14 +26,14 @@ modules: dest: vendor/github.com/skip2/go-qrcode - type: git url: https://github.com/golang/net - tag: v0.46.0 - commit: 2002a064a49010d8b881673c73f04e0b741e7c32 + tag: v0.57.0 + commit: b8f09f6f062ceb4531b7af4bd17a5c8fe9c4b2b5 dest: vendor/golang.org/x/net - type: git - url: https://github.com/golang/crypto - tag: v0.43.0 - commit: 627cb894b6b2021e34c4ad4af4c0a963127491e4 - dest: vendor/golang.org/x/crypto + url: https://github.com/golang/sys + tag: v0.47.0 + commit: 9e7e939dcafac07e8ab4cffa6e5fc74908413f00 + dest: vendor/golang.org/x/sys - type: file path: modules.txt dest: vendor diff --git a/flatpak/modules.txt b/flatpak/modules.txt index 6662b65..6092904 100644 --- a/flatpak/modules.txt +++ b/flatpak/modules.txt @@ -1,17 +1,17 @@ # github.com/bendahl/uinput v1.7.0 ## explicit; go 1.13 github.com/bendahl/uinput -# github.com/godbus/dbus/v5 v5.1.0 -## explicit; go 1.12 +# github.com/godbus/dbus/v5 v5.2.2 +## explicit; go 1.20 github.com/godbus/dbus/v5 # github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e ## explicit; go 1.13 github.com/skip2/go-qrcode github.com/skip2/go-qrcode/bitset github.com/skip2/go-qrcode/reedsolomon -# golang.org/x/crypto v0.36.0 -## explicit; go 1.23.0 -golang.org/x/crypto/hkdf -# golang.org/x/net v0.37.0 -## explicit; go 1.23.0 +# golang.org/x/net v0.57.0 +## explicit; go 1.25.0 golang.org/x/net/websocket +# golang.org/x/sys v0.47.0 +## explicit; go 1.25.0 +golang.org/x/sys/unix diff --git a/go.mod b/go.mod index 75e1e34..5afd19d 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,12 @@ module github.com/unrud/remote-touchpad -go 1.24.0 - -toolchain go1.24.8 +go 1.25.5 require ( github.com/bendahl/uinput v1.7.0 - github.com/godbus/dbus/v5 v5.1.0 + github.com/godbus/dbus/v5 v5.2.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - golang.org/x/crypto v0.43.0 - golang.org/x/net v0.46.0 + golang.org/x/net v0.57.0 ) + +require golang.org/x/sys v0.47.0 // indirect diff --git a/go.sum b/go.sum index 736cffa..783b4df 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg= github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/host.go b/host.go index 54874e5..622b3bf 100644 --- a/host.go +++ b/host.go @@ -74,7 +74,8 @@ func findDefaultHost() string { continue } for _, linkLocalPrefix := range []string{ - "169.254.", "fe8", "fe9", "fea", "feb"} { + "169.254.", "fe8", "fe9", "fea", "feb", + } { if strings.HasPrefix(ip.String(), linkLocalPrefix) { addIP(20, ip) continue addrs diff --git a/inputcontrol/controller.go b/inputcontrol/controller.go index 06ed8e4..111e937 100644 --- a/inputcontrol/controller.go +++ b/inputcontrol/controller.go @@ -21,8 +21,10 @@ package inputcontrol import "sort" -type PointerButton int -type Key int +type ( + PointerButton int + Key int +) const ( PointerButtonLeft PointerButton = iota diff --git a/inputcontrol/controller_portal.go b/inputcontrol/controller_portal.go index 7ee2f81..99ecf8b 100644 --- a/inputcontrol/controller_portal.go +++ b/inputcontrol/controller_portal.go @@ -24,6 +24,7 @@ package inputcontrol import ( "crypto/aes" "crypto/cipher" + "crypto/hkdf" "crypto/rand" "crypto/sha256" "errors" @@ -35,7 +36,6 @@ import ( "slices" "github.com/godbus/dbus/v5" - "golang.org/x/crypto/hkdf" ) const ( @@ -88,12 +88,14 @@ func InitPortalController() (Controller, error) { "org.freedesktop.portal.RemoteDesktop.version") if err != nil { return nil, &UnsupportedPlatformError{ - fmt.Errorf("getting 'version' failed: %w", err)} + fmt.Errorf("getting 'version' failed: %w", err), + } } remoteDesktopVersion, ok := remoteDesktopVersionV.Value().(uint32) if !ok { return nil, &UnsupportedPlatformError{ - errors.New("unexpected 'version' type")} + errors.New("unexpected 'version' type"), + } } restoreTokenStore, err := func() (*secretStore, error) { if remoteDesktopVersion < 2 { @@ -103,7 +105,7 @@ func InitPortalController() (Controller, error) { if err != nil { return nil, err } - if err := os.MkdirAll(cacheDirectory, 0700); err != nil { + if err := os.MkdirAll(cacheDirectory, 0o700); err != nil { return nil, err } secret, err := retrieveSecret(bus) @@ -120,17 +122,20 @@ func InitPortalController() (Controller, error) { "org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes") if err != nil { return nil, &UnsupportedPlatformError{ - fmt.Errorf("getting 'AvailableDeviceTypes' failed: %w", err)} + fmt.Errorf("getting 'AvailableDeviceTypes' failed: %w", err), + } } availableDeviceTypes, ok := availableDeviceTypesV.Value().(uint32) if !ok { return nil, &UnsupportedPlatformError{ - errors.New("unexpected 'AvailableDeviceTypes' return type")} + errors.New("unexpected 'AvailableDeviceTypes' return type"), + } } if availableDeviceTypes&deviceKeyboard == 0 || availableDeviceTypes&devicePointer == 0 { return nil, &UnsupportedPlatformError{ - errors.New("keyboard or pointer source type not supported")} + errors.New("keyboard or pointer source type not supported"), + } } createSessionResults, err := checkResponse(getResponse(bus, portalDesktop, "org.freedesktop.portal.RemoteDesktop.CreateSession", 0, @@ -138,12 +143,14 @@ func InitPortalController() (Controller, error) { )) if err != nil { return nil, &UnsupportedPlatformError{ - fmt.Errorf("calling 'CreateSession' failed: %w", err)} + fmt.Errorf("calling 'CreateSession' failed: %w", err), + } } sessionHandleString, ok := createSessionResults["session_handle"].Value().(string) if !ok { return nil, &UnsupportedPlatformError{ - errors.New("unexpected 'session_handle' type in 'CreateSession' return value")} + errors.New("unexpected 'session_handle' type in 'CreateSession' return value"), + } } sessionHandle := dbus.ObjectPath(sessionHandleString) selectDevicesOptions := map[string]dbus.Variant{ @@ -165,7 +172,8 @@ func InitPortalController() (Controller, error) { )) if err != nil { return nil, &UnsupportedPlatformError{ - fmt.Errorf("calling 'SelectDevices' failed: %w", err)} + fmt.Errorf("calling 'SelectDevices' failed: %w", err), + } } startResponseStatus, startResults, err := getResponse(bus, portalDesktop, "org.freedesktop.portal.RemoteDesktop.Start", 0, @@ -173,7 +181,8 @@ func InitPortalController() (Controller, error) { ) if err != nil { return nil, &UnsupportedPlatformError{ - fmt.Errorf("calling 'Start' failed: %w", err)} + fmt.Errorf("calling 'Start' failed: %w", err), + } } if startResponseStatus != 0 { return nil, errors.New("keyboard or pointer access denied") @@ -186,14 +195,17 @@ func InitPortalController() (Controller, error) { devices, ok := startResults["devices"].Value().(uint32) if !ok { return nil, &UnsupportedPlatformError{ - errors.New("unexpected 'devices' type in 'Start' return value")} + errors.New("unexpected 'devices' type in 'Start' return value"), + } } if devices&deviceKeyboard == 0 || devices&devicePointer == 0 { return nil, errors.New("keyboard or pointer access denied") } cleanupBus = false - return &portalController{bus: bus, portalDesktop: portalDesktop, - sessionHandle: sessionHandle}, nil + return &portalController{ + bus: bus, portalDesktop: portalDesktop, + sessionHandle: sessionHandle, + }, nil } func retrieveSecret(bus *dbus.Conn) ([]byte, error) { @@ -230,9 +242,8 @@ type secretStore struct { } func newSecretStore(key []byte, filename string) (*secretStore, error) { - hkdf := hkdf.New(sha256.New, key, nil, nil) - derivedKey := make([]byte, 32) - if _, err := io.ReadFull(hkdf, derivedKey); err != nil { + derivedKey, err := hkdf.Key(sha256.New, key, nil, "", 32) + if err != nil { return nil, err } block, err := aes.NewCipher(derivedKey) @@ -268,11 +279,12 @@ func (s *secretStore) Store(data []byte) error { return err } ciphertext := s.aesgcm.Seal(nil, nonce, data, nil) - return os.WriteFile(s.filename, slices.Concat(nonce, ciphertext), 0600) + return os.WriteFile(s.filename, slices.Concat(nonce, ciphertext), 0o600) } func getResponse(bus *dbus.Conn, object dbus.BusObject, method string, - flags dbus.Flags, args ...interface{}) (uint32, map[string]dbus.Variant, error) { + flags dbus.Flags, args ...interface{}, +) (uint32, map[string]dbus.Variant, error) { ch := make(chan *dbus.Signal, 512) bus.Signal(ch) defer bus.RemoveSignal(ch) diff --git a/inputcontrol/controller_uinput.go b/inputcontrol/controller_uinput.go index df51e65..2dd9bab 100644 --- a/inputcontrol/controller_uinput.go +++ b/inputcontrol/controller_uinput.go @@ -25,10 +25,11 @@ package inputcontrol import ( "errors" "fmt" - "github.com/bendahl/uinput" "log" "os" "time" + + "github.com/bendahl/uinput" ) const shiftKeysDelay time.Duration = 50 * time.Millisecond diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index bb37d37..f45b75b 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -32,9 +32,12 @@ package inputcontrol // return DefaultRootWindow(dpy); // } import "C" + import ( "errors" "fmt" + "maps" + "slices" "strings" "sync" "time" @@ -42,12 +45,18 @@ import ( ) const ( - keyboardMappingDelay time.Duration = 500 * time.Millisecond + keyboardMappingDelay time.Duration = 200 * time.Millisecond scrollDiv int = 20 ) -var modifierIndices [6]uint = [...]uint{C.ShiftMapIndex, C.Mod1MapIndex, - C.Mod2MapIndex, C.Mod3MapIndex, C.Mod4MapIndex, C.Mod5MapIndex} +var modifierIndexes = [...]int{ + C.ShiftMapIndex, + C.Mod1MapIndex, + C.Mod2MapIndex, + C.Mod3MapIndex, + C.Mod4MapIndex, + C.Mod5MapIndex, +} type x11Controller struct { display *C.Display @@ -63,13 +72,15 @@ func InitX11Controller() (Controller, error) { display := C.XOpenDisplay(nil) if display == nil { return nil, &UnsupportedPlatformError{ - errors.New("failed to connect to X server")} + errors.New("failed to connect to X server"), + } } p := &x11Controller{display: display} if p.xIsXwayland() { p.Close() return nil, &UnsupportedPlatformError{ - errors.New("X server is Xwayland")} + errors.New("X server is Xwayland"), + } } return p, nil } @@ -113,53 +124,68 @@ func (p *x11Controller) Close() error { return nil } -func (p *x11Controller) findEmptyKeycodeLocked() (C.KeyCode, C.int, error) { +func (p *x11Controller) findEmptyKeycodeLocked() (keycode C.KeyCode, keysymsPerKeycode int, err error) { var minKeycodes, maxKeycodes C.int C.XDisplayKeycodes(p.display, &minKeycodes, &maxKeycodes) - var keysymsPerKeycode C.int - keysyms := C.XGetKeyboardMapping(p.display, C.KeyCode(minKeycodes), - maxKeycodes-minKeycodes+1, &keysymsPerKeycode) + var keysymsPerKeycodeC C.int + keysyms := C.XGetKeyboardMapping( + p.display, + C.KeyCode(minKeycodes), + maxKeycodes-minKeycodes+1, + &keysymsPerKeycodeC, + ) if keysyms == nil { return 0, 0, errors.New("failed to get keyboard mapping") } defer C.XFree(unsafe.Pointer(keysyms)) + keysymsPerKeycode = int(keysymsPerKeycodeC) keycodes: - for keycode := C.KeyCode(minKeycodes); keycode <= C.KeyCode(maxKeycodes); keycode++ { - for i := 0; i < int(keysymsPerKeycode); i++ { - keysymsIndex := int(keycode- - C.KeyCode(minKeycodes))*int(keysymsPerKeycode) + i - keysym := *(*C.KeySym)(unsafe.Pointer(uintptr(unsafe.Pointer(keysyms)) + - uintptr(keysymsIndex)*unsafe.Sizeof(*keysyms))) + for keycode := int(minKeycodes); keycode <= int(maxKeycodes); keycode++ { + for i := range keysymsPerKeycode { + keysymsIndex := (keycode-int(minKeycodes))*keysymsPerKeycode + i + keysym := *(*C.KeySym)(unsafe.Pointer( + uintptr(unsafe.Pointer(keysyms)) + + uintptr(keysymsIndex)*unsafe.Sizeof(*keysyms), + )) if keysym != 0 { continue keycodes } } - return keycode, keysymsPerKeycode, nil + return C.KeyCode(keycode), keysymsPerKeycode, nil } return 0, 0, errors.New("no empty keycode found") } -func (p *x11Controller) changeKeyMappingLocked(keysymsPerKeycode C.int, - keycode C.KeyCode, keysym Keysym) { +func (p *x11Controller) changeKeyMappingLocked( + keycode C.KeyCode, keysymsPerKeycode int, keysym Keysym, +) { keycodeMapping := make([]C.KeySym, keysymsPerKeycode) for i := range keycodeMapping { keycodeMapping[i] = C.KeySym(keysym) } - C.XChangeKeyboardMapping(p.display, C.int(keycode), keysymsPerKeycode, - (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1) - C.XFlush(p.display) + C.XChangeKeyboardMapping( + p.display, + C.int(keycode), + C.int(keysymsPerKeycode), + (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), + 1, + ) + C.XSync(p.display, C.False) } func (p *x11Controller) getModKeycodesLocked() map[uint]C.KeyCode { modKeymap := C.XGetModifierMapping(p.display) defer C.XFreeModifiermap(modKeymap) modKeycodes := make(map[uint]C.KeyCode) - for _, modIndex := range modifierIndices { - for i := 0; i < int(modKeymap.max_keypermod); i++ { - keycode := *(*C.KeyCode)(unsafe.Pointer(uintptr(unsafe.Pointer(modKeymap.modifiermap)) + - uintptr(uint(modIndex)*uint(modKeymap.max_keypermod)+uint(i)))) + for _, modIndex := range modifierIndexes { + for i := range int(modKeymap.max_keypermod) { + keycodeIndex := modIndex*int(modKeymap.max_keypermod) + i + keycode := *(*C.KeyCode)(unsafe.Pointer( + uintptr(unsafe.Pointer(modKeymap.modifiermap)) + + uintptr(keycodeIndex)*unsafe.Sizeof(*modKeymap.modifiermap), + )) if keycode != 0 { - modKeycodes[1< /dev/null; then + echo "Error: Go is not installed. Please install Go first." + return 1 + fi + + echo "Building remote-touchpad binary..." + go build -tags portal,uinput,x11 -o remote-touchpad + + if [ ! -f "remote-touchpad" ]; then + echo "Error: Failed to build remote-touchpad binary" + return 1 + fi + + # Setup directories + mkdir -p ~/.local/bin ~/.config/systemd/user + + echo "Installing service files..." + cp remote-touchpad ~/.local/bin/ || echo "Error: cp to ~/.local/bin failed" + cp remote-touchpad.service ~/.config/systemd/user/ || echo "Error: cp to ~/.config/systemd/user failed" + + echo "Enabling and starting service..." + systemctl --user daemon-reload + systemctl --user enable --now ${SERVICE_NAME}.service + + echo "✓ remote-touchpad user service installed and started" + echo "" + echo "Use './rts status' to check the service" + echo "Use './rts url' to get the connection URL" +} + +start() { + systemctl --user start ${SERVICE_NAME}.service + echo "Started remote-touchpad service" +} + +stop() { + systemctl --user stop ${SERVICE_NAME}.service + echo "Stopped remote-touchpad service" +} + +status() { + systemctl --user status ${SERVICE_NAME}.service --no-pager +} + +restart() { + systemctl --user restart ${SERVICE_NAME}.service + echo "Restarted remote-touchpad service" +} + +url() { + # Extract URL from service logs + echo "Fetching remote-touchpad URL..." + local url=$(journalctl --user -u ${SERVICE_NAME}.service -n 100 --no-pager 2>/dev/null | grep -oE "http://[^ ]+|https://[^ ]+" | tail -1) + if [ -n "$url" ]; then + echo "Remote Touchpad URL: $url" + # Generate QR code if qrencode is available + if command -v qrencode &> /dev/null; then + echo "Generating QR code..." + echo "$url" | qrencode -t ansiutf8 + else + echo "QR code generation requires qrencode. Install it with:" + echo " sudo apt-get install qrencode # Debian/Ubuntu" + echo " sudo dnf install qrencode # Fedora" + echo " brew install qrencode # macOS" + fi + else + echo "Could not find URL in service logs. Is the service running?" + fi +} + +log() { + echo "Viewing remote-touchpad service logs..." + journalctl --user -u ${SERVICE_NAME}.service --no-pager +} + +uninstall() { + echo "Uninstalling remote-touchpad..." + systemctl --user stop ${SERVICE_NAME}.service + systemctl --user disable ${SERVICE_NAME}.service + rm -f ~/.config/systemd/user/${SERVICE_NAME}.service + rm -f ~/.local/bin/remote-touchpad + systemctl --user daemon-reload + echo "✓ remote-touchpad uninstalled" +} + +help() { + echo "Remote Touchpad CLI Wrapper" + echo "" + echo "Usage: rts [command]" + echo "" + echo "Commands:" + echo " install - Install remote-touchpad as a systemd user service" + echo " start - Start the remote-touchpad service" + echo " stop - Stop the remote-touchpad service" + echo " status - Check service status" + echo " restart - Restart the service" + echo " url - Show the remote-touchpad URL" + echo " log - View service logs" + echo " uninstall - Uninstall the service" + echo " help - Show this help message" +} + +# Main command handler +case "$1" in + install) + install + ;; + start) + start + ;; + stop) + stop + ;; + status) + status + ;; + restart) + restart + ;; + url) + url + ;; + log) + log + ;; + uninstall) + uninstall + ;; + help|--help|-h|"") + help + ;; + *) + echo "Unknown command: $1" + help + exit 1 + ;; +esac diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 677f302..6d23326 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,17 +1,21 @@ -# Build by running "snapcraft". +# Build by running `snapcraft`. # WARNING: # Snapcraft uses caching for already build steps but it's buggy and can cause strange problems. -# Clean the cache by running "snapcraft clean". +# Clean the cache by running `snapcraft clean`. name: remote-touchpad license: GPL-3.0+ grade: stable adopt-info: remote-touchpad -base: core22 +base: core24 confinement: strict +platforms: + amd64: + arm64: + apps: remote-touchpad: command: bin/remote-touchpad @@ -48,10 +52,6 @@ parts: - libxt6 - libxrandr2 - libxtst6 - override-pull: | - craftctl default - # WORKAROUND: Point icon directly to SVG otherwise snapcraft can't find it - sed -e 's|Icon=com.github.unrud.RemoteTouchpad|Icon=/usr/share/icons/hicolor/scalable/apps/com.github.unrud.RemoteTouchpad.svg|' -i desktop/com.github.unrud.RemoteTouchpad.desktop override-build: | craftctl default install -Dm0755 -t "${CRAFT_PART_INSTALL}/bin" desktop/remote-touchpad-wait-on-error