diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 9f3768a..c1ed46d 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -23,7 +23,7 @@ jobs:
with:
go-version: stable
check-latest: true
- - run: sudo apt install libxrandr-dev libxtst-dev libxt-dev
+ - run: sudo apt install libxrandr-dev libxtst-dev
if: ${{ contains(matrix.tags, 'x11') }}
- name: Go Format
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 07c477a..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-# 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 e59454c..9172f94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,31 +1,5 @@
# 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
-* Update dependencies
-
-## 1.4.8 (2024-09-01)
-
-* Update dependencies
-
-## 1.4.7 (2024-08-30)
-
-* Update dependencies
-
## 1.4.6 (2024-02-06)
* Prevent incorrect MIME types
diff --git a/README.md b/README.md
index e039950..2740e73 100644
--- a/README.md
+++ b/README.md
@@ -12,45 +12,8 @@ Supports Flatpak's RemoteDesktop portal (for Wayland), Windows and X11.
* [Snap](https://snapcraft.io/remote-touchpad)
* [Windows](https://github.com/Unrud/remote-touchpad/releases/latest)
* Golang:
- * Portal & uinput & X11:
-
- ```sh
- go install -tags portal,uinput,x11 github.com/unrud/remote-touchpad@latest
- ```
- * Windows:
-
- ```sh
- 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.
+ * Portal & uinput & X11: `go install -tags portal,uinput,x11 github.com/unrud/remote-touchpad@latest`
+ * Windows: `go install github.com/unrud/remote-touchpad@latest`
## Screenshots
diff --git a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
index 113ef4d..9a7da69 100644
--- a/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
+++ b/desktop/com.github.unrud.RemoteTouchpad.metainfo.xml
@@ -17,56 +17,8 @@
com.github.unrud.RemoteTouchpad.desktop
https://github.com/Unrud/remote-touchpad/issues
https://github.com/Unrud/remote-touchpad
- https://github.com/Unrud/remote-touchpad
- Unrud
-
- Unrud
-
+ Unrud
-
-
-
- - Improve X11 keyboard input
- - Update dependencies
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Add restore token support for the remote desktop portal
- - Update dependencies
-
-
-
-
-
-
-
-
-
-
-
-
-
- https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/1.png
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.4.6/screenshots/1.png
- https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/2.png
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.4.6/screenshots/2.png
- https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/3.png
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.4.6/screenshots/3.png
- https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.5.4/screenshots/4.png
+ https://raw.githubusercontent.com/Unrud/remote-touchpad/v1.4.6/screenshots/4.png
diff --git a/flatpak/com.github.unrud.RemoteTouchpad.yaml b/flatpak/com.github.unrud.RemoteTouchpad.yaml
index 239160e..0a89b54 100644
--- a/flatpak/com.github.unrud.RemoteTouchpad.yaml
+++ b/flatpak/com.github.unrud.RemoteTouchpad.yaml
@@ -1,6 +1,6 @@
app-id: com.github.unrud.RemoteTouchpad
runtime: org.freedesktop.Platform
-runtime-version: '25.08'
+runtime-version: '23.08'
sdk: org.freedesktop.Sdk
command: remote-touchpad
sdk-extensions:
@@ -8,17 +8,19 @@ sdk-extensions:
finish-args:
- --socket=fallback-x11
- --socket=wayland
- - --share=ipc # Required by Flathub linter
- --share=network
modules:
- name: remote-touchpad
sources:
- type: git
path: ..
+ - type: shell
+ commands:
+ - . /usr/lib/sdk/golang/enable.sh; go mod vendor -e
- type: git
url: https://github.com/godbus/dbus
- tag: v5.2.2
- commit: a8ac15ba63645f02ffd57f4b443203279ab40b30
+ tag: v5.1.0
+ commit: e523abc905595cf17fb0001a7d77eaaddfaa216d
dest: vendor/github.com/godbus/dbus/v5
- type: git
url: https://github.com/skip2/go-qrcode
@@ -26,17 +28,9 @@ modules:
dest: vendor/github.com/skip2/go-qrcode
- type: git
url: https://github.com/golang/net
- tag: v0.57.0
- commit: b8f09f6f062ceb4531b7af4bd17a5c8fe9c4b2b5
+ tag: v0.20.0
+ commit: cb5b10f0bbc51089bf49030ce3bd43bbfee08c23
dest: vendor/golang.org/x/net
- - type: git
- 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
buildsystem: simple
build-commands:
- . /usr/lib/sdk/golang/enable.sh; go build -tags portal,x11
diff --git a/flatpak/modules.txt b/flatpak/modules.txt
deleted file mode 100644
index 6092904..0000000
--- a/flatpak/modules.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-# github.com/bendahl/uinput v1.7.0
-## explicit; go 1.13
-github.com/bendahl/uinput
-# 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/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 5afd19d..b4ebadb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,10 @@
module github.com/unrud/remote-touchpad
-go 1.25.5
+go 1.20
require (
github.com/bendahl/uinput v1.7.0
- github.com/godbus/dbus/v5 v5.2.2
+ github.com/godbus/dbus/v5 v5.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
- golang.org/x/net v0.57.0
+ golang.org/x/net v0.20.0
)
-
-require golang.org/x/sys v0.47.0 // indirect
diff --git a/go.sum b/go.sum
index 783b4df..430ee54 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,8 @@
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.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
-github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
+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/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.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=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
diff --git a/host.go b/host.go
index 622b3bf..54874e5 100644
--- a/host.go
+++ b/host.go
@@ -74,8 +74,7 @@ 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 111e937..06ed8e4 100644
--- a/inputcontrol/controller.go
+++ b/inputcontrol/controller.go
@@ -21,10 +21,8 @@ package inputcontrol
import "sort"
-type (
- PointerButton int
- Key int
-)
+type PointerButton int
+type Key int
const (
PointerButtonLeft PointerButton = iota
diff --git a/inputcontrol/controller_portal.go b/inputcontrol/controller_portal.go
index 99ecf8b..7d19efc 100644
--- a/inputcontrol/controller_portal.go
+++ b/inputcontrol/controller_portal.go
@@ -22,19 +22,8 @@
package inputcontrol
import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/hkdf"
- "crypto/rand"
- "crypto/sha256"
"errors"
"fmt"
- "io"
- "log"
- "os"
- "path/filepath"
- "slices"
-
"github.com/godbus/dbus/v5"
)
@@ -45,8 +34,6 @@ const (
btnReleased uint32 = 0
btnPressed uint32 = 1
- untilRevoked uint32 = 2
-
// linux/input-event-codes.h
btnLeft int32 = 0x110
btnRight int32 = 0x111
@@ -55,7 +42,7 @@ const (
type portalController struct {
bus *dbus.Conn
- portalDesktop dbus.BusObject
+ remoteDesktop dbus.BusObject
sessionHandle dbus.ObjectPath
}
@@ -82,209 +69,85 @@ func InitPortalController() (Controller, error) {
if err != nil {
return nil, &UnsupportedPlatformError{err}
}
- portalDesktop := bus.Object("org.freedesktop.portal.Desktop",
+ remoteDesktop := bus.Object("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop")
- remoteDesktopVersionV, err := portalDesktop.GetProperty(
- "org.freedesktop.portal.RemoteDesktop.version")
- if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("getting 'version' failed: %w", err),
- }
- }
- remoteDesktopVersion, ok := remoteDesktopVersionV.Value().(uint32)
- if !ok {
- return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'version' type"),
- }
- }
- restoreTokenStore, err := func() (*secretStore, error) {
- if remoteDesktopVersion < 2 {
- return nil, nil
- }
- cacheDirectory, err := os.UserCacheDir()
- if err != nil {
- return nil, err
- }
- if err := os.MkdirAll(cacheDirectory, 0o700); err != nil {
- return nil, err
- }
- secret, err := retrieveSecret(bus)
- if err != nil {
- return nil, err
- }
- return newSecretStore(secret,
- filepath.Join(cacheDirectory, "remote-touchpad.portal-restore-token.bin"))
- }()
- if err != nil {
- log.Printf("Skipping restore token: %v", err)
- }
- availableDeviceTypesV, err := portalDesktop.GetProperty(
+ availableDeviceTypesV, err := remoteDesktop.GetProperty(
"org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("getting 'AvailableDeviceTypes' failed: %w", err),
- }
+ return nil, &UnsupportedPlatformError{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,
- map[string]dbus.Variant{"session_handle_token": dbus.MakeVariant("t")},
- ))
+ inVardict := make(map[string]dbus.Variant)
+ inVardict["session_handle_token"] = dbus.MakeVariant("t")
+ result, outVardict, err := getResponse(bus, remoteDesktop,
+ "org.freedesktop.portal.RemoteDesktop.CreateSession", 0, inVardict)
if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'CreateSession' failed: %w", err),
- }
+ return nil, &UnsupportedPlatformError{err}
}
- sessionHandleString, ok := createSessionResults["session_handle"].Value().(string)
+ if result != 0 {
+ return nil, &UnsupportedPlatformError{
+ fmt.Errorf("Calling 'CreateSession' failed (%v)", result)}
+ }
+ sessionHandleV, ok := outVardict["session_handle"]
if !ok {
return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'session_handle' type in 'CreateSession' return value"),
- }
+ errors.New("'session_handle' missing from 'CreateSession' return value")}
}
- sessionHandle := dbus.ObjectPath(sessionHandleString)
- selectDevicesOptions := map[string]dbus.Variant{
- "types": dbus.MakeVariant(deviceKeyboard | devicePointer),
- }
- if restoreTokenStore != nil {
- if restoreToken, err := restoreTokenStore.Load(); err != nil {
- if !errors.Is(err, os.ErrNotExist) {
- log.Printf("Failed to load restore token: %v", err)
- }
- } else if len(restoreToken) > 0 {
- selectDevicesOptions["restore_token"] = dbus.MakeVariant(string(restoreToken))
- }
- selectDevicesOptions["persist_mode"] = dbus.MakeVariant(untilRevoked)
- }
- _, err = checkResponse(getResponse(bus, portalDesktop,
- "org.freedesktop.portal.RemoteDesktop.SelectDevices", 0,
- sessionHandle, selectDevicesOptions,
- ))
- if err != nil {
+ sessionHandleS, ok := sessionHandleV.Value().(string)
+ if !ok {
return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'SelectDevices' failed: %w", err),
- }
+ errors.New("unexpected 'session_handle' type in 'CreateSession' return value")}
}
- startResponseStatus, startResults, err := getResponse(bus, portalDesktop,
- "org.freedesktop.portal.RemoteDesktop.Start", 0,
- sessionHandle, "", map[string]dbus.Variant{},
- )
+ sessionHandle := dbus.ObjectPath(sessionHandleS)
+ inVardict = make(map[string]dbus.Variant)
+ inVardict["types"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
+ result, outVardict, err = getResponse(bus, remoteDesktop,
+ "org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
if err != nil {
- return nil, &UnsupportedPlatformError{
- fmt.Errorf("calling 'Start' failed: %w", err),
- }
+ return nil, &UnsupportedPlatformError{err}
}
- if startResponseStatus != 0 {
+ if result != 0 {
+ return nil, &UnsupportedPlatformError{
+ fmt.Errorf("Calling 'SelectDevices' failed (%v)", result)}
+ }
+ inVardict = make(map[string]dbus.Variant)
+ result, outVardict, err = getResponse(bus, remoteDesktop,
+ "org.freedesktop.portal.RemoteDesktop.Start", 0, sessionHandle, "", inVardict)
+ if err != nil {
+ return nil, &UnsupportedPlatformError{err}
+ }
+ if result != 0 {
return nil, errors.New("keyboard or pointer access denied")
}
- if restoreToken, _ := startResults["restore_token"].Value().(string); restoreTokenStore != nil {
- if err := restoreTokenStore.Store([]byte(restoreToken)); err != nil {
- log.Printf("Failed to store restore token: %v", err)
- }
- }
- devices, ok := startResults["devices"].Value().(uint32)
+ devicesV, ok := outVardict["devices"]
if !ok {
return nil, &UnsupportedPlatformError{
- errors.New("unexpected 'devices' type in 'Start' return value"),
- }
+ errors.New("'devices' missing from 'Start' return value")}
+ }
+ devices, ok := devicesV.Value().(uint32)
+ if !ok {
+ return nil, &UnsupportedPlatformError{
+ 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
-}
-
-func retrieveSecret(bus *dbus.Conn) ([]byte, error) {
- portalDesktop := bus.Object("org.freedesktop.portal.Desktop",
- "/org/freedesktop/portal/desktop")
- secretReader, secretWriter, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- defer secretReader.Close()
- defer secretWriter.Close()
- if _, err := checkResponse(getResponse(bus, portalDesktop,
- "org.freedesktop.portal.Secret.RetrieveSecret", 0,
- dbus.UnixFD(secretWriter.Fd()), map[string]dbus.Variant{},
- )); err != nil {
- return nil, fmt.Errorf("calling 'RetrieveSecret' failed: %w", err)
- }
- if err := secretWriter.Close(); err != nil {
- return nil, err
- }
- secret, err := io.ReadAll(secretReader)
- if err != nil {
- return nil, err
- }
- if len(secret) < 16 {
- return nil, fmt.Errorf("'RetrieveSecret' returned too few bytes (%d)", len(secret))
- }
- return secret, err
-}
-
-type secretStore struct {
- aesgcm cipher.AEAD
- filename string
-}
-
-func newSecretStore(key []byte, filename string) (*secretStore, error) {
- derivedKey, err := hkdf.Key(sha256.New, key, nil, "", 32)
- if err != nil {
- return nil, err
- }
- block, err := aes.NewCipher(derivedKey)
- if err != nil {
- return nil, err
- }
- aesgcm, err := cipher.NewGCM(block)
- if err != nil {
- return nil, err
- }
- return &secretStore{
- aesgcm: aesgcm,
- filename: filename,
- }, nil
-}
-
-func (s *secretStore) Load() ([]byte, error) {
- data, err := os.ReadFile(s.filename)
- if err != nil {
- return nil, err
- }
- if len(data) < s.aesgcm.NonceSize() {
- return nil, errors.New("invalid ciphertext")
- }
- nonce := data[:s.aesgcm.NonceSize()]
- ciphertext := data[len(nonce):]
- return s.aesgcm.Open(nil, nonce, ciphertext, nil)
-}
-
-func (s *secretStore) Store(data []byte) error {
- nonce := make([]byte, s.aesgcm.NonceSize())
- if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
- return err
- }
- ciphertext := s.aesgcm.Seal(nil, nonce, data, nil)
- return os.WriteFile(s.filename, slices.Concat(nonce, ciphertext), 0o600)
+ return &portalController{bus: bus, remoteDesktop: remoteDesktop,
+ sessionHandle: sessionHandle}, nil
}
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)
@@ -296,39 +159,32 @@ func getResponse(bus *dbus.Conn, object dbus.BusObject, method string,
s := <-ch
if s.Path == requestPath && s.Name == "org.freedesktop.portal.Request.Response" {
if len(s.Body) != 2 {
- return 0, nil, fmt.Errorf("unexpected 'Response' return length (%d)", len(s.Body))
+ return 0, nil, errors.New("unexpected 'Response' return length")
}
- responseStatus, ok := s.Body[0].(uint32)
+ result, ok := s.Body[0].(uint32)
if !ok {
return 0, nil, errors.New("unexpected 'Response' return type")
}
- results, ok := s.Body[1].(map[string]dbus.Variant)
+ outVardict, ok := s.Body[1].(map[string]dbus.Variant)
if !ok {
return 0, nil, errors.New("unexpected 'Response' return type")
}
- return responseStatus, results, nil
+ return result, outVardict, nil
}
}
}
-func checkResponse(responseStatus uint32, results map[string]dbus.Variant, err error) (map[string]dbus.Variant, error) {
- if err == nil && responseStatus != 0 {
- err = fmt.Errorf("unexpected 'Response' status (%d)", responseStatus)
- }
- return results, err
-}
-
func (p *portalController) Close() error {
return p.bus.Close()
}
func (p *portalController) keyboardKeys(keys []Keysym) error {
+ inVardict := make(map[string]dbus.Variant)
for _, keysym := range keys {
for _, state := range [...]uint32{btnPressed, btnReleased} {
- if err := p.portalDesktop.Call(
- "org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0,
- p.sessionHandle, map[string]dbus.Variant{}, keysym, state,
- ).Store(); err != nil {
+ if err := p.remoteDesktop.Call(
+ "org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym",
+ 0, p.sessionHandle, inVardict, keysym, state).Store(); err != nil {
return err
}
}
@@ -373,27 +229,28 @@ func (p *portalController) PointerButton(button PointerButton, press bool) error
if press {
state = btnPressed
}
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerButton", 0,
- p.sessionHandle, map[string]dbus.Variant{}, btn, state,
- ).Store(); err != nil {
+ inVardict := make(map[string]dbus.Variant)
+ if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerButton",
+ 0, p.sessionHandle, inVardict, btn, state).Store(); err != nil {
return err
}
return nil
}
func (p *portalController) PointerMove(deltaX, deltaY int) error {
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerMotion", 0,
- p.sessionHandle, map[string]dbus.Variant{}, float64(deltaX), float64(deltaY),
- ).Store(); err != nil {
+ inVardict := make(map[string]dbus.Variant)
+ if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerMotion",
+ 0, p.sessionHandle, inVardict, float64(deltaX), float64(deltaY)).Store(); err != nil {
return err
}
return nil
}
func (p *portalController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
- if err := p.portalDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerAxis", 0,
- p.sessionHandle, map[string]dbus.Variant{"finish": dbus.MakeVariant(finish)}, float64(deltaHorizontal), float64(deltaVertical),
- ).Store(); err != nil {
+ inVardict := make(map[string]dbus.Variant)
+ inVardict["finish"] = dbus.MakeVariant(finish)
+ if err := p.remoteDesktop.Call("org.freedesktop.portal.RemoteDesktop.NotifyPointerAxis",
+ 0, p.sessionHandle, inVardict, float64(deltaHorizontal), float64(deltaVertical)).Store(); err != nil {
return err
}
return nil
diff --git a/inputcontrol/controller_uinput.go b/inputcontrol/controller_uinput.go
index 2dd9bab..df51e65 100644
--- a/inputcontrol/controller_uinput.go
+++ b/inputcontrol/controller_uinput.go
@@ -25,11 +25,10 @@ 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 f45b75b..bb37d37 100644
--- a/inputcontrol/controller_x11.go
+++ b/inputcontrol/controller_x11.go
@@ -32,12 +32,9 @@ package inputcontrol
// return DefaultRootWindow(dpy);
// }
import "C"
-
import (
"errors"
"fmt"
- "maps"
- "slices"
"strings"
"sync"
"time"
@@ -45,18 +42,12 @@ import (
)
const (
- keyboardMappingDelay time.Duration = 200 * time.Millisecond
+ keyboardMappingDelay time.Duration = 500 * time.Millisecond
scrollDiv int = 20
)
-var modifierIndexes = [...]int{
- 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
@@ -72,15 +63,13 @@ 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
}
@@ -124,68 +113,53 @@ func (p *x11Controller) Close() error {
return nil
}
-func (p *x11Controller) findEmptyKeycodeLocked() (keycode C.KeyCode, keysymsPerKeycode int, err error) {
+func (p *x11Controller) findEmptyKeycodeLocked() (C.KeyCode, C.int, error) {
var minKeycodes, maxKeycodes C.int
C.XDisplayKeycodes(p.display, &minKeycodes, &maxKeycodes)
- var keysymsPerKeycodeC C.int
- keysyms := C.XGetKeyboardMapping(
- p.display,
- C.KeyCode(minKeycodes),
- maxKeycodes-minKeycodes+1,
- &keysymsPerKeycodeC,
- )
+ var keysymsPerKeycode C.int
+ keysyms := C.XGetKeyboardMapping(p.display, C.KeyCode(minKeycodes),
+ maxKeycodes-minKeycodes+1, &keysymsPerKeycode)
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 := 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),
- ))
+ 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)))
if keysym != 0 {
continue keycodes
}
}
- return C.KeyCode(keycode), keysymsPerKeycode, nil
+ return keycode, keysymsPerKeycode, nil
}
return 0, 0, errors.New("no empty keycode found")
}
-func (p *x11Controller) changeKeyMappingLocked(
- keycode C.KeyCode, keysymsPerKeycode int, keysym Keysym,
-) {
+func (p *x11Controller) changeKeyMappingLocked(keysymsPerKeycode C.int,
+ keycode C.KeyCode, keysym Keysym) {
keycodeMapping := make([]C.KeySym, keysymsPerKeycode)
for i := range keycodeMapping {
keycodeMapping[i] = C.KeySym(keysym)
}
- C.XChangeKeyboardMapping(
- p.display,
- C.int(keycode),
- C.int(keysymsPerKeycode),
- (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])),
- 1,
- )
- C.XSync(p.display, C.False)
+ C.XChangeKeyboardMapping(p.display, C.int(keycode), keysymsPerKeycode,
+ (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1)
+ C.XFlush(p.display)
}
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 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),
- ))
+ 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))))
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 6d23326..677f302 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,21 +1,17 @@
-# 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: core24
+base: core22
confinement: strict
-platforms:
- amd64:
- arm64:
-
apps:
remote-touchpad:
command: bin/remote-touchpad
@@ -52,6 +48,10 @@ 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