From a30bed46242a15c0219b09b353568e2d1be8e323 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sat, 11 Jul 2026 20:20:03 +0200 Subject: [PATCH 01/12] Snap: Update to core24 --- snap/snapcraft.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 677f302..8cecb86 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,15 +1,15 @@ -# 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 apps: @@ -48,10 +48,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 From 996edb05017136ab0a72e6b364b60dccfadf8a33 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sat, 11 Jul 2026 20:59:03 +0200 Subject: [PATCH 02/12] Snap: Specifiy supported platforms --- snap/snapcraft.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8cecb86..6d23326 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,6 +12,10 @@ adopt-info: remote-touchpad base: core24 confinement: strict +platforms: + amd64: + arm64: + apps: remote-touchpad: command: bin/remote-touchpad From 1aa8f92cc8755dc55b4bbc584129d0b3567100b7 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 2 Aug 2026 11:15:23 +0200 Subject: [PATCH 03/12] Use range over integers --- inputcontrol/controller_x11.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index bb37d37..093ea2b 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -125,7 +125,7 @@ func (p *x11Controller) findEmptyKeycodeLocked() (C.KeyCode, C.int, error) { defer C.XFree(unsafe.Pointer(keysyms)) keycodes: for keycode := C.KeyCode(minKeycodes); keycode <= C.KeyCode(maxKeycodes); keycode++ { - for i := 0; i < int(keysymsPerKeycode); i++ { + for i := range int(keysymsPerKeycode) { keysymsIndex := int(keycode- C.KeyCode(minKeycodes))*int(keysymsPerKeycode) + i keysym := *(*C.KeySym)(unsafe.Pointer(uintptr(unsafe.Pointer(keysyms)) + @@ -155,7 +155,7 @@ func (p *x11Controller) getModKeycodesLocked() map[uint]C.KeyCode { defer C.XFreeModifiermap(modKeymap) modKeycodes := make(map[uint]C.KeyCode) for _, modIndex := range modifierIndices { - for i := 0; i < int(modKeymap.max_keypermod); i++ { + for i := range int(modKeymap.max_keypermod) { keycode := *(*C.KeyCode)(unsafe.Pointer(uintptr(unsafe.Pointer(modKeymap.modifiermap)) + uintptr(uint(modIndex)*uint(modKeymap.max_keypermod)+uint(i)))) if keycode != 0 { @@ -354,7 +354,7 @@ func (p *x11Controller) PointerScroll(deltaHorizontal, deltaVertical int, finish buttonHorizontal = 6 stepsHorizontal = -stepsHorizontal } - for i := 0; i < stepsHorizontal; i++ { + for range stepsHorizontal { if err := p.sendButton(buttonHorizontal, true); err != nil { return err } @@ -367,7 +367,7 @@ func (p *x11Controller) PointerScroll(deltaHorizontal, deltaVertical int, finish buttonVertical = 4 stepsVertical = -stepsVertical } - for i := 0; i < stepsVertical; i++ { + for range stepsVertical { if err := p.sendButton(buttonVertical, true); err != nil { return err } From 85b6d6ea9d6669d0eac240db50a2b45df37450f9 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 2 Aug 2026 12:40:05 +0200 Subject: [PATCH 04/12] X11: Use XSync to improve reliability --- inputcontrol/controller_x11.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index 093ea2b..5ba9179 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -147,7 +147,7 @@ func (p *x11Controller) changeKeyMappingLocked(keysymsPerKeycode C.int, } C.XChangeKeyboardMapping(p.display, C.int(keycode), keysymsPerKeycode, (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1) - C.XFlush(p.display) + C.XSync(p.display, C.False) } func (p *x11Controller) getModKeycodesLocked() map[uint]C.KeyCode { @@ -236,7 +236,6 @@ func (p *x11Controller) keyboardKeys(keys []Keysym) error { var root, child C.Window var rootX, rootY, x, y C.int var activeMods C.uint - C.XSync(p.display, C.False) C.XQueryPointer(p.display, rootWindow, &root, &child, &rootX, &rootY, &x, &y, &activeMods) keycode, mods := p.findKeycodeLocked(keyboard, modKeycodes, activeMods, @@ -265,7 +264,7 @@ func (p *x11Controller) keyboardKeys(keys []Keysym) error { C.XTestFakeKeyEvent(p.display, C.uint(keycode), C.False, 0) p.sendModsLocked(modKeycodes, pressMods, false) p.sendModsLocked(modKeycodes, releaseMods, true) - C.XFlush(p.display) + C.XSync(p.display, C.False) if keycode == emptyKeycode { // race condition! time.Sleep(keyboardMappingDelay) @@ -309,7 +308,7 @@ func (p *x11Controller) sendButton(button uint, press bool) error { pressC = C.True } C.XTestFakeButtonEvent(p.display, C.uint(button), pressC, 0) - C.XFlush(p.display) + C.XSync(p.display, C.False) return nil } @@ -333,7 +332,7 @@ func (p *x11Controller) PointerMove(deltaX, deltaY int) error { return errors.New("X server connection closed") } C.XTestFakeRelativeMotionEvent(p.display, C.int(deltaX), C.int(deltaY), 0) - C.XFlush(p.display) + C.XSync(p.display, C.False) return nil } From 6ef2be87767de56e6465f742d3eeb9033c0aa405 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 2 Aug 2026 22:12:27 +0200 Subject: [PATCH 05/12] Format code --- host.go | 3 ++- inputcontrol/controller.go | 6 +++-- inputcontrol/controller_portal.go | 43 ++++++++++++++++++++----------- inputcontrol/controller_uinput.go | 3 ++- inputcontrol/controller_x11.go | 22 +++++++++++----- main.go | 7 ++--- 6 files changed, 55 insertions(+), 29 deletions(-) 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 6f38407..99ecf8b 100644 --- a/inputcontrol/controller_portal.go +++ b/inputcontrol/controller_portal.go @@ -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) { @@ -267,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 5ba9179..179eafc 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -32,6 +32,7 @@ package inputcontrol // return DefaultRootWindow(dpy); // } import "C" + import ( "errors" "fmt" @@ -46,8 +47,10 @@ const ( scrollDiv int = 20 ) -var modifierIndices [6]uint = [...]uint{C.ShiftMapIndex, C.Mod1MapIndex, - C.Mod2MapIndex, C.Mod3MapIndex, C.Mod4MapIndex, C.Mod5MapIndex} +var modifierIndices [6]uint = [...]uint{ + C.ShiftMapIndex, C.Mod1MapIndex, + C.Mod2MapIndex, C.Mod3MapIndex, C.Mod4MapIndex, C.Mod5MapIndex, +} type x11Controller struct { display *C.Display @@ -63,13 +66,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 } @@ -140,7 +145,8 @@ keycodes: } func (p *x11Controller) changeKeyMappingLocked(keysymsPerKeycode C.int, - keycode C.KeyCode, keysym Keysym) { + keycode C.KeyCode, keysym Keysym, +) { keycodeMapping := make([]C.KeySym, keysymsPerKeycode) for i := range keycodeMapping { keycodeMapping[i] = C.KeySym(keysym) @@ -169,7 +175,8 @@ func (p *x11Controller) getModKeycodesLocked() map[uint]C.KeyCode { func (p *x11Controller) findKeycodeLocked(keyboard C.XkbDescPtr, modKeycodes map[uint]C.KeyCode, activeMods C.uint, - keysym Keysym) (C.KeyCode, C.uint) { + keysym Keysym, +) (C.KeyCode, C.uint) { keycode := C.XKeysymToKeycode(p.display, C.KeySym(keysym)) if keycode == 0 { return 0, 0 @@ -204,7 +211,8 @@ func (p *x11Controller) findKeycodeLocked(keyboard C.XkbDescPtr, } func (p *x11Controller) sendModsLocked(modKeycodes map[uint]C.KeyCode, mods C.uint, - press bool) { + press bool, +) { var pressC C.int = C.False if press { pressC = C.True diff --git a/main.go b/main.go index d4d43e9..dc6ad1a 100644 --- a/main.go +++ b/main.go @@ -27,9 +27,6 @@ import ( "errors" "flag" "fmt" - "github.com/unrud/remote-touchpad/inputcontrol" - "github.com/unrud/remote-touchpad/terminal" - "golang.org/x/net/websocket" "log" mathrand "math/rand" "net" @@ -39,6 +36,10 @@ import ( "strings" "time" "unicode/utf8" + + "github.com/unrud/remote-touchpad/inputcontrol" + "github.com/unrud/remote-touchpad/terminal" + "golang.org/x/net/websocket" ) const ( From 346d9cf3752e1dca5546eb59e860a85ddf215f7f Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 3 Aug 2026 00:32:33 +0200 Subject: [PATCH 06/12] X11: Cleanup code --- inputcontrol/controller_x11.go | 115 ++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index 179eafc..8e40c5c 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -47,9 +47,13 @@ const ( 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 { @@ -118,41 +122,52 @@ 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 := range int(keysymsPerKeycode) { - 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.XChangeKeyboardMapping( + p.display, + C.int(keycode), + C.int(keysymsPerKeycode), + (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), + 1, + ) C.XSync(p.display, C.False) } @@ -160,12 +175,15 @@ 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 _, modIndex := range modifierIndexes { for i := range int(modKeymap.max_keypermod) { - keycode := *(*C.KeyCode)(unsafe.Pointer(uintptr(unsafe.Pointer(modKeymap.modifiermap)) + - uintptr(uint(modIndex)*uint(modKeymap.max_keypermod)+uint(i)))) + 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< Date: Mon, 3 Aug 2026 00:32:34 +0200 Subject: [PATCH 07/12] X11: Minimize mod changes --- inputcontrol/controller_x11.go | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index 8e40c5c..2206df4 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -36,6 +36,8 @@ import "C" import ( "errors" "fmt" + "maps" + "slices" "strings" "sync" "time" @@ -198,31 +200,19 @@ func (p *x11Controller) findKeycodeLocked( if keycode == 0 { return 0, 0 } - var alwaysActiveMods uint - for _, modIndex := range modifierIndexes { - mod := uint(1) << modIndex - if _, modAvailable := modKeycodes[mod]; !modAvailable { - alwaysActiveMods |= activeMods & mod - } - } - _, shiftModAvailable := modKeycodes[C.ShiftMask] - for _, modIndex := range modifierIndexes { - var mod uint - if modIndex != C.ShiftMapIndex { - mod = 1 << modIndex - } - for _, shiftMod := range [...]uint{0, C.ShiftMask} { - if shiftMod != 0 && !shiftModAvailable { - continue - } - mods := alwaysActiveMods | shiftMod | mod - var retMods C.uint - var retKeysym C.KeySym - C.XkbTranslateKeyCode(keyboard, keycode, C.uint(mods), &retMods, &retKeysym) - if retKeysym == C.KeySym(keysym) { - return keycode, mods + for activeModBitmap := range uint(1) << len(modKeycodes) { + mods := activeMods + for i, mod := range slices.Sorted(maps.Keys(modKeycodes)) { + if activeModBitmap&(1< Date: Mon, 3 Aug 2026 00:32:34 +0200 Subject: [PATCH 08/12] X11: Reduce delay when mapping new keys Reliability is improved by XSync --- inputcontrol/controller_x11.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/inputcontrol/controller_x11.go b/inputcontrol/controller_x11.go index 2206df4..f45b75b 100644 --- a/inputcontrol/controller_x11.go +++ b/inputcontrol/controller_x11.go @@ -45,7 +45,7 @@ import ( ) const ( - keyboardMappingDelay time.Duration = 500 * time.Millisecond + keyboardMappingDelay time.Duration = 200 * time.Millisecond scrollDiv int = 20 ) @@ -273,8 +273,6 @@ func (p *x11Controller) keyboardKeys(keys []Keysym) error { } keycode = emptyKeycode p.changeKeyMappingLocked(keycode, keysymsPerKeycode, keysym) - // race condition! - time.Sleep(keyboardMappingDelay) } else { pressMods = mods & ^activeMods releaseMods = activeMods & ^mods From 9764d1975d9aa68e77510105117c543b4350f944 Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 3 Aug 2026 00:32:34 +0200 Subject: [PATCH 09/12] Update dependencies --- flatpak/com.github.unrud.RemoteTouchpad.yaml | 12 ++++++------ flatpak/modules.txt | 10 +++++----- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/flatpak/com.github.unrud.RemoteTouchpad.yaml b/flatpak/com.github.unrud.RemoteTouchpad.yaml index 48eef40..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.2.1 - commit: 20d95a3d9a57a5cb72cbdafb3fd9ecb6d2ccd038 + tag: v5.2.2 + commit: a8ac15ba63645f02ffd57f4b443203279ab40b30 dest: vendor/github.com/godbus/dbus/v5 - type: git url: https://github.com/skip2/go-qrcode @@ -26,13 +26,13 @@ modules: dest: vendor/github.com/skip2/go-qrcode - type: git url: https://github.com/golang/net - tag: v0.48.0 - commit: 35e1306bddd863f360fb94480c5fed84229953f0 + tag: v0.57.0 + commit: b8f09f6f062ceb4531b7af4bd17a5c8fe9c4b2b5 dest: vendor/golang.org/x/net - type: git url: https://github.com/golang/sys - tag: v0.39.0 - commit: 08e54827f6706016347e1e4f4866b84126842b20 + tag: v0.47.0 + commit: 9e7e939dcafac07e8ab4cffa6e5fc74908413f00 dest: vendor/golang.org/x/sys - type: file path: modules.txt diff --git a/flatpak/modules.txt b/flatpak/modules.txt index fec832d..6092904 100644 --- a/flatpak/modules.txt +++ b/flatpak/modules.txt @@ -1,7 +1,7 @@ # github.com/bendahl/uinput v1.7.0 ## explicit; go 1.13 github.com/bendahl/uinput -# github.com/godbus/dbus/v5 v5.2.1 +# 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 @@ -9,9 +9,9 @@ github.com/godbus/dbus/v5 github.com/skip2/go-qrcode github.com/skip2/go-qrcode/bitset github.com/skip2/go-qrcode/reedsolomon -# golang.org/x/net v0.48.0 -## explicit; go 1.24.0 +# golang.org/x/net v0.57.0 +## explicit; go 1.25.0 golang.org/x/net/websocket -# golang.org/x/sys v0.39.0 -## explicit; go 1.24.0 +# 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 ea1c480..5afd19d 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.25.5 require ( github.com/bendahl/uinput v1.7.0 - github.com/godbus/dbus/v5 v5.2.1 + github.com/godbus/dbus/v5 v5.2.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - golang.org/x/net v0.48.0 + golang.org/x/net v0.57.0 ) -require golang.org/x/sys v0.39.0 // indirect +require golang.org/x/sys v0.47.0 // indirect diff --git a/go.sum b/go.sum index 801ae05..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.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= -github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +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/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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= From a77f3aa8ecf8b38f789d7a8ddefcb9cc4407d0fc Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 3 Aug 2026 00:32:35 +0200 Subject: [PATCH 10/12] Bump version to 1.5.4 --- CHANGELOG.md | 5 +++++ .../com.github.unrud.RemoteTouchpad.metainfo.xml | 16 ++++++++++++---- main.go | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3ec422..e59454c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.5.4 (2026-08-02) + +* Improve X11 keyboard input +* Update dependencies + ## 1.5.3 (2025-12-24) * Update dependencies diff --git a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml index 14b1574..113ef4d 100644 --- a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml +++ b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml @@ -23,6 +23,14 @@ Unrud + + +
    +
  • Improve X11 keyboard input
  • +
  • Update dependencies
  • +
+
+
    @@ -365,16 +373,16 @@ - https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.3/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.3/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.3/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.3/screenshots/4.png + https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/4.png diff --git a/main.go b/main.go index dc6ad1a..f8db693 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ const ( authenticationRateBurst int = 10 challengeLength int = 8 defaultBind string = ":0" - version string = "1.5.3" + version string = "1.5.4" prettyAppName string = "Remote Touchpad" ) From ed7e8bae543324bdd6faf0fa614142b471eb5770 Mon Sep 17 00:00:00 2001 From: Luka Jankovic Date: Fri, 14 Aug 2026 21:59:58 +0200 Subject: [PATCH 11/12] Add systemd user service and CLI wrapper for remote-touchpad This commit introduces a complete systemd user service setup for remote-touchpad, eliminating the need for users to manually manage the binary process. Key additions: - New rts CLI wrapper script providing a user-friendly interface for service management without requiring root access - Systemd service file for proper background operation with restart policies and dependency management - Service management commands: install, start, stop, status, restart, and uninstall - URL extraction and QR code generation via the url command - Service log viewing via the log command The installation process now: 1. Verifies Go is installed 2. Builds the remote-touchpad binary with appropriate tags 3. Installs service files to ~/.config/systemd/user/ 4. Installs the binary to ~/.local/bin/ 5. Enables and starts the service automatically The README has been updated with comprehensive installation instructions for the new user service approach. --- README.md | 29 ++++++++ remote-touchpad.service | 12 ++++ rts | 149 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 remote-touchpad.service create mode 100755 rts 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/remote-touchpad.service b/remote-touchpad.service new file mode 100644 index 0000000..76f5a74 --- /dev/null +++ b/remote-touchpad.service @@ -0,0 +1,12 @@ +[Unit] +Description=Remote Touchpad Service +After=network.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/remote-touchpad +Restart=always +RestartSec=5s + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/rts b/rts new file mode 100755 index 0000000..84fbe46 --- /dev/null +++ b/rts @@ -0,0 +1,149 @@ +#!/bin/bash + +# Remote Touchpad CLI Wrapper +# Provides easy access to remote-touchpad service + +SERVICE_NAME="remote-touchpad" +SERVICE_USER="$USER" + +install() { + echo "Installing remote-touchpad as a user systemd service..." + + # Check if Go is installed + if ! command -v go &> /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 From 2ac00c3b483720085e7fb686967490f9089c9e62 Mon Sep 17 00:00:00 2001 From: Luka Jankovic Date: Fri, 14 Aug 2026 22:01:02 +0200 Subject: [PATCH 12/12] Updated gitignore --- .gitignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .gitignore 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