add support for Flatpak's RemoteDesktop portal

This commit is contained in:
Unrud 2018-07-31 20:19:12 +02:00
parent c1509ac8fa
commit 71d7cd42b9
6 changed files with 309 additions and 3 deletions

View file

@ -3,7 +3,7 @@
Control mouse and keyboard from the webbrowser of a smartphone (or any other device with a touchscreen). 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. 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 ## Installation

View file

@ -11,7 +11,7 @@
To take control open the displayed URL or scan the QR code. To take control open the displayed URL or scan the QR code.
</p> </p>
<p> <p>
Supports Windows and X11. Supports Flatpak&apos;s RemoteDesktop portal (experimental), Windows and X11.
</p> </p>
</description> </description>
<url type="bugtracker">https://github.com/Unrud/remote-touchpad/issues</url> <url type="bugtracker">https://github.com/Unrud/remote-touchpad/issues</url>

View file

@ -36,6 +36,12 @@
"commit" : "38087fe4dafb822e541b3f7955075cc1c30bd294", "commit" : "38087fe4dafb822e541b3f7955075cc1c30bd294",
"dest" : "src/github.com/elazarl/go-bindata-assetfs" "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", "type" : "git",
"path" : "..", "path" : "..",
@ -45,7 +51,7 @@
], ],
"buildsystem" : "simple", "buildsystem" : "simple",
"build-commands" : [ "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 remote-touchpad",
"install -Dm0755 -t /app/bin src/github.com/unrud/remote-touchpad/desktop/remote-touchpad-wait-on-error", "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", "install -Dm0644 -t /app/share/appdata src/github.com/unrud/remote-touchpad/desktop/remote-touchpad.appdata.xml",

View file

@ -26,6 +26,7 @@ type PluginInfo struct {
var Plugins []PluginInfo = []PluginInfo{ var Plugins []PluginInfo = []PluginInfo{
{"X11", InitX11Plugin}, {"X11", InitX11Plugin},
{"RemoteDesktop portal", InitPortalPlugin},
{"Windows", InitWindowsPlugin}, {"Windows", InitWindowsPlugin},
} }

271
plugin_portal.go Normal file
View file

@ -0,0 +1,271 @@
// +build portal
/*
* Copyright (c) 2018 Unrud<unrud@outlook.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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)
}

28
plugin_portal_dummy.go Normal file
View file

@ -0,0 +1,28 @@
// +build !portal
/*
* Copyright (c) 2018 Unrud<unrud@outlook.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package main
import "errors"
func InitPortalPlugin() (Plugin, error) {
return nil, UnsupportedPlatformError{errors.New("disabled")}
}