diff --git a/README.md b/README.md
index 904fd5e..42ea676 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen).
To take control open the displayed URL or scan the QR code.
-Supports Windows and X11.
+Supports Flatpak's RemoteDesktop portal (experimental), Windows and X11.
## Installation
diff --git a/desktop/remote-touchpad.appdata.xml b/desktop/remote-touchpad.appdata.xml
index 5e269ed..62dd6c5 100644
--- a/desktop/remote-touchpad.appdata.xml
+++ b/desktop/remote-touchpad.appdata.xml
@@ -11,7 +11,7 @@
To take control open the displayed URL or scan the QR code.
- Supports Windows and X11.
+ Supports Flatpak's RemoteDesktop portal (experimental), Windows and X11.
https://github.com/Unrud/remote-touchpad/issues
diff --git a/flatpak/com.github.unrud.RemoteTouchpad.json b/flatpak/com.github.unrud.RemoteTouchpad.json
index 151250b..db92ec4 100644
--- a/flatpak/com.github.unrud.RemoteTouchpad.json
+++ b/flatpak/com.github.unrud.RemoteTouchpad.json
@@ -36,6 +36,12 @@
"commit" : "38087fe4dafb822e541b3f7955075cc1c30bd294",
"dest" : "src/github.com/elazarl/go-bindata-assetfs"
},
+ {
+ "type" : "git",
+ "url" : "https://anonscm.debian.org/git/pkg-go/packages/golang-dbus.git",
+ "commit" : "79d96b151df08809558c825f8a75188826e8e228",
+ "dest" : "src/anonscm.debian.org/git/pkg-go/packages/golang-dbus.git"
+ },
{
"type" : "git",
"path" : "..",
@@ -45,7 +51,7 @@
],
"buildsystem" : "simple",
"build-commands" : [
- ". /usr/lib/sdk/golang/enable.sh; env GOPATH=\"$(pwd)\" go build -tags x11 github.com/unrud/remote-touchpad",
+ ". /usr/lib/sdk/golang/enable.sh; env GOPATH=\"$(pwd)\" go build -tags 'portal x11' github.com/unrud/remote-touchpad",
"install -Dm0755 -t /app/bin remote-touchpad",
"install -Dm0755 -t /app/bin src/github.com/unrud/remote-touchpad/desktop/remote-touchpad-wait-on-error",
"install -Dm0644 -t /app/share/appdata src/github.com/unrud/remote-touchpad/desktop/remote-touchpad.appdata.xml",
diff --git a/plugin.go b/plugin.go
index 9445537..64482fc 100644
--- a/plugin.go
+++ b/plugin.go
@@ -26,6 +26,7 @@ type PluginInfo struct {
var Plugins []PluginInfo = []PluginInfo{
{"X11", InitX11Plugin},
+ {"RemoteDesktop portal", InitPortalPlugin},
{"Windows", InitWindowsPlugin},
}
diff --git a/plugin_portal.go b/plugin_portal.go
new file mode 100644
index 0000000..faa6a5d
--- /dev/null
+++ b/plugin_portal.go
@@ -0,0 +1,271 @@
+// +build portal
+
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
+ */
+
+package main
+
+import (
+ dbus "anonscm.debian.org/git/pkg-go/packages/golang-dbus.git"
+ "errors"
+ "fmt"
+ "sync"
+)
+
+const (
+ deviceKeyboard uint32 = 1
+ devicePointer uint32 = 2
+
+ btnReleased uint32 = 0
+ btnPressed uint32 = 1
+
+ btnLeft int32 = 0x110
+ btnRight int32 = 0x111
+ btnMiddle int32 = 0x112
+)
+
+type portalPlugin struct {
+ bus *dbus.Conn
+ remoteDesktop dbus.BusObject
+ sessionHandle dbus.ObjectPath
+ lock sync.RWMutex
+}
+
+func InitPortalPlugin() (Plugin, error) {
+ bus, err := dbus.SessionBusPrivate()
+ if err != nil {
+ return nil, UnsupportedPlatformError{err}
+ }
+ cleanupBus := true
+ defer func() {
+ if cleanupBus {
+ bus.Close()
+ }
+ }()
+ err = bus.Auth(nil)
+ if err != nil {
+ return nil, UnsupportedPlatformError{err}
+ }
+ err = bus.Hello()
+ if err != nil {
+ return nil, UnsupportedPlatformError{err}
+ }
+ remoteDesktop := bus.Object("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop")
+ availableDeviceTypesV, err := remoteDesktop.GetProperty(
+ "org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
+ if err != nil {
+ return nil, UnsupportedPlatformError{err}
+ }
+ availableDeviceTypes, ok := availableDeviceTypesV.Value().(uint32)
+ if !ok {
+ return nil, UnsupportedPlatformError{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")}
+ }
+ 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{err}
+ }
+ if result != 0 {
+ return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
+ "Calling 'CreateSession' failed (%v)", result))}
+ }
+ sessionHandleV, ok := outVardict["session_handle"]
+ if !ok {
+ return nil, UnsupportedPlatformError{errors.New(
+ "'session_handle' missing from 'CreateSession' return value")}
+ }
+ sessionHandleS, ok := sessionHandleV.Value().(string)
+ if !ok {
+ return nil, UnsupportedPlatformError{errors.New(
+ "unexpected 'session_handle' type in 'CreateSession' return value")}
+ }
+ sessionHandle := dbus.ObjectPath(sessionHandleS)
+ inVardict = make(map[string]dbus.Variant)
+ inVardict["type"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
+ result, outVardict, err = getResponse(bus, remoteDesktop,
+ "org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
+ if err != nil {
+ return nil, UnsupportedPlatformError{err}
+ }
+ if result != 0 {
+ return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
+ "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")
+ }
+ devicesV, ok := outVardict["devices"]
+ if !ok {
+ return nil, UnsupportedPlatformError{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 &portalPlugin{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) {
+ ch := make(chan *dbus.Signal)
+ bus.Signal(ch)
+ defer bus.RemoveSignal(ch)
+ var requestPath dbus.ObjectPath
+ if err := object.Call(method, flags, args...).Store(&requestPath); err != nil {
+ return 0, nil, err
+ }
+ for {
+ s := <-ch
+ if s.Path == requestPath && s.Name == "org.freedesktop.portal.Request.Response" {
+ if len(s.Body) != 2 {
+ return 0, nil, errors.New("unexpected 'Response' return length")
+ }
+ result, ok := s.Body[0].(uint32)
+ if !ok {
+ return 0, nil, errors.New("unexpected 'Response' return type")
+ }
+ outVardict, ok := s.Body[1].(map[string]dbus.Variant)
+ if !ok {
+ return 0, nil, errors.New("unexpected 'Response' return type")
+ }
+ return result, outVardict, nil
+ }
+ }
+}
+
+func (p *portalPlugin) Close() error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ if p.bus == nil {
+ return errors.New("dbus connection closed")
+ }
+ if err := p.bus.Close(); err != nil {
+ return err
+ }
+ p.bus = nil
+ p.remoteDesktop = nil
+ return nil
+}
+
+func (p *portalPlugin) KeyboardText(text string) error {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ if p.bus == nil {
+ return errors.New("dbus connection closed")
+ }
+ inVardict := make(map[string]dbus.Variant)
+ for _, runeValue := range text {
+ keysym := int32(0x01000000 + runeValue)
+ for _, state := range [...]uint32{btnPressed, btnReleased} {
+ if err := p.remoteDesktop.Call(
+ "org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym",
+ 0, p.sessionHandle, inVardict, keysym, state).Store(); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (p *portalPlugin) PointerButton(button uint, press bool) error {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ if p.bus == nil {
+ return errors.New("dbus connection closed")
+ }
+ var btn int32
+ if button == 1 {
+ btn = btnLeft
+ } else if button == 2 {
+ btn = btnMiddle
+ } else if button == 3 {
+ btn = btnRight
+ } else {
+ return errors.New("unsupported pointer button")
+ }
+ state := btnReleased
+ if press {
+ state = btnPressed
+ }
+ 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 *portalPlugin) PointerMove(deltaX, deltaY int) error {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ if p.bus == nil {
+ return errors.New("dbus connection closed")
+ }
+ 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 *portalPlugin) pointerScrollFull(deltaHorizontal, deltaVertical int, finish bool) error {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ if p.bus == nil {
+ return errors.New("dbus connection closed")
+ }
+ 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
+}
+
+func (p *portalPlugin) PointerScroll(deltaHorizontal, deltaVertical int) error {
+ return p.pointerScrollFull(deltaHorizontal, deltaVertical, false)
+}
+
+func (p *portalPlugin) PointerScrollFinish() error {
+ return p.pointerScrollFull(0, 0, true)
+}
diff --git a/plugin_portal_dummy.go b/plugin_portal_dummy.go
new file mode 100644
index 0000000..9148781
--- /dev/null
+++ b/plugin_portal_dummy.go
@@ -0,0 +1,28 @@
+// +build !portal
+
+/*
+ * Copyright (c) 2018 Unrud
+ *
+ * This file is part of Remote-Touchpad.
+ *
+ * Foobar is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Remote-Touchpad is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Foobar. If not, see .
+ */
+
+package main
+
+import "errors"
+
+func InitPortalPlugin() (Plugin, error) {
+ return nil, UnsupportedPlatformError{errors.New("disabled")}
+}