Reorganize
This commit is contained in:
parent
e29982dc00
commit
8a2a29c9d6
40 changed files with 39 additions and 37 deletions
87
inputcontrol/controller.go
Normal file
87
inputcontrol/controller.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
import "sort"
|
||||
|
||||
type PointerButton int
|
||||
type Key int
|
||||
|
||||
const (
|
||||
PointerButtonLeft PointerButton = iota
|
||||
PointerButtonRight
|
||||
PointerButtonMiddle
|
||||
PointerButtonLimit
|
||||
)
|
||||
|
||||
const (
|
||||
KeyVolumeMute Key = iota
|
||||
KeyVolumeDown
|
||||
KeyVolumeUp
|
||||
KeyMediaPlayPause
|
||||
KeyMediaPrevTrack
|
||||
KeyMediaNextTrack
|
||||
KeyBrowserBack
|
||||
KeyBrowserForward
|
||||
KeySuper
|
||||
KeyLeft
|
||||
KeyRight
|
||||
KeyUp
|
||||
KeyDown
|
||||
KeyHome
|
||||
KeyEnd
|
||||
KeyBackSpace
|
||||
KeyDelete
|
||||
KeyReturn
|
||||
KeyLimit
|
||||
)
|
||||
|
||||
type ControllerInfo struct {
|
||||
Name string
|
||||
Init func() (Controller, error)
|
||||
|
||||
priority int
|
||||
}
|
||||
|
||||
var Controllers []ControllerInfo
|
||||
|
||||
func RegisterController(name string, init func() (Controller, error), priority int) {
|
||||
Controllers = append(Controllers, ControllerInfo{name, init, priority})
|
||||
sort.SliceStable(Controllers, func(i, j int) bool {
|
||||
return Controllers[i].priority < Controllers[j].priority
|
||||
})
|
||||
}
|
||||
|
||||
type UnsupportedPlatformError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e UnsupportedPlatformError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
type Controller interface {
|
||||
Close() error
|
||||
KeyboardText(text string) error
|
||||
KeyboardKey(key Key) error
|
||||
PointerButton(button PointerButton, press bool) error
|
||||
PointerMove(deltaX, deltaY int) error
|
||||
PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error
|
||||
}
|
||||
288
inputcontrol/controller_portal.go
Normal file
288
inputcontrol/controller_portal.go
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
//go:build portal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
deviceKeyboard uint32 = 1
|
||||
devicePointer uint32 = 2
|
||||
|
||||
btnReleased uint32 = 0
|
||||
btnPressed uint32 = 1
|
||||
|
||||
// linux/input-event-codes.h
|
||||
btnLeft int32 = 0x110
|
||||
btnRight int32 = 0x111
|
||||
btnMiddle int32 = 0x112
|
||||
)
|
||||
|
||||
type portalController struct {
|
||||
bus *dbus.Conn
|
||||
remoteDesktop dbus.BusObject
|
||||
sessionHandle dbus.ObjectPath
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterController("RemoteDesktop portal", InitPortalController, 1)
|
||||
}
|
||||
|
||||
func InitPortalController() (Controller, 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 &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) {
|
||||
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 *portalController) 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 *portalController) keyboardKeys(keys []Keysym) 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 _, keysym := range keys {
|
||||
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 *portalController) KeyboardText(text string) error {
|
||||
keys := make([]Keysym, 0, len(text))
|
||||
for _, runeValue := range text {
|
||||
keysym, err := RuneToKeysym(runeValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys = append(keys, keysym)
|
||||
}
|
||||
return p.keyboardKeys(keys)
|
||||
}
|
||||
|
||||
func (p *portalController) KeyboardKey(key Key) error {
|
||||
keysym, err := KeyToKeysym(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := [...]Keysym{keysym}
|
||||
return p.keyboardKeys(keys[:])
|
||||
}
|
||||
|
||||
func (p *portalController) PointerButton(button PointerButton, 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 == PointerButtonLeft {
|
||||
btn = btnLeft
|
||||
} else if button == PointerButtonMiddle {
|
||||
btn = btnMiddle
|
||||
} else if button == PointerButtonRight {
|
||||
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 *portalController) 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 *portalController) PointerScroll(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
|
||||
}
|
||||
245
inputcontrol/controller_windows.go
Normal file
245
inputcontrol/controller_windows.go
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
//go:build windows
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
inputMouse uintptr = 0x0
|
||||
inputKeyboard uintptr = 0x1
|
||||
|
||||
keyeventfKeyup uint32 = 0x2
|
||||
keyeventfUnicode uint32 = 0x4
|
||||
|
||||
vkBack uint16 = 0x8
|
||||
vkReturn uint16 = 0xD
|
||||
vkEnd uint16 = 0x23
|
||||
vkHome uint16 = 0x24
|
||||
vkLeft uint16 = 0x25
|
||||
vkUp uint16 = 0x26
|
||||
vkRight uint16 = 0x27
|
||||
vkDown uint16 = 0x28
|
||||
vkDelete uint16 = 0x2E
|
||||
vkLwin uint16 = 0x5B
|
||||
vkBrowserBack uint16 = 0xA6
|
||||
vkBrowserForward uint16 = 0xA7
|
||||
vkVolumeMute uint16 = 0xAD
|
||||
vkVolumeDown uint16 = 0xAE
|
||||
vkVolumeUp uint16 = 0xAF
|
||||
vkMediaNextTrack uint16 = 0xB0
|
||||
vkMediaPrevTrack uint16 = 0xB1
|
||||
vkMediaPlayPause uint16 = 0xB3
|
||||
|
||||
mouseeventfMove uint32 = 0x1
|
||||
mouseeventfLeftdown uint32 = 0x2
|
||||
mouseeventfLeftup uint32 = 0x4
|
||||
mouseeventfRightdown uint32 = 0x8
|
||||
mouseeventfRightup uint32 = 0x10
|
||||
mouseeventfMiddledown uint32 = 0x20
|
||||
mouseeventfMiddleup uint32 = 0x40
|
||||
mouseeventfWheel uint32 = 0x800
|
||||
mouseeventfHwheel uint32 = 0x1000
|
||||
|
||||
scrollMult int = 6
|
||||
)
|
||||
|
||||
var (
|
||||
user32DLL = syscall.NewLazyDLL("user32.dll")
|
||||
sendInputProc = user32DLL.NewProc("SendInput")
|
||||
)
|
||||
|
||||
type mouseInput struct {
|
||||
typ uintptr // HACK: padded uint32
|
||||
|
||||
dx, dy int32
|
||||
mouseData, dwFlags, time uint32
|
||||
dwExtraInfo uintptr
|
||||
}
|
||||
|
||||
type keybdInput struct {
|
||||
typ uintptr // HACK: padded uint32
|
||||
|
||||
wVk, wScan uint16
|
||||
dwFlags, time uint32
|
||||
dwExtraInfo uintptr
|
||||
|
||||
padding [8]byte
|
||||
}
|
||||
|
||||
type windowsController struct{}
|
||||
|
||||
func init() {
|
||||
RegisterController("Windows", InitWindowsController, 0)
|
||||
}
|
||||
|
||||
func InitWindowsController() (Controller, error) {
|
||||
p := &windowsController{}
|
||||
if err := sendInputProc.Find(); err != nil {
|
||||
return nil, UnsupportedPlatformError{err}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *windowsController) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsController) sendInput(inputs []keybdInput) error {
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if r, _, err := sendInputProc.Call(uintptr(len(inputs)),
|
||||
uintptr(unsafe.Pointer(&inputs[0])),
|
||||
unsafe.Sizeof(inputs[0])); int(r) != len(inputs) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsController) KeyboardText(text string) error {
|
||||
inputs := make([]keybdInput, 0, len(text)*2)
|
||||
for _, runeValue := range text {
|
||||
in := keybdInput{typ: inputKeyboard, wScan: uint16(runeValue), dwFlags: keyeventfUnicode}
|
||||
inputs = append(inputs, in)
|
||||
in.dwFlags |= keyeventfKeyup
|
||||
inputs = append(inputs, in)
|
||||
}
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.sendInput(inputs)
|
||||
}
|
||||
|
||||
func (p *windowsController) KeyboardKey(key Key) error {
|
||||
input := keybdInput{typ: inputKeyboard}
|
||||
if key == KeyBackSpace {
|
||||
input.wVk = vkBack
|
||||
} else if key == KeyReturn {
|
||||
input.wVk = vkReturn
|
||||
} else if key == KeyEnd {
|
||||
input.wVk = vkEnd
|
||||
} else if key == KeyHome {
|
||||
input.wVk = vkHome
|
||||
} else if key == KeyLeft {
|
||||
input.wVk = vkLeft
|
||||
} else if key == KeyUp {
|
||||
input.wVk = vkUp
|
||||
} else if key == KeyRight {
|
||||
input.wVk = vkRight
|
||||
} else if key == KeyDown {
|
||||
input.wVk = vkDown
|
||||
} else if key == KeyDelete {
|
||||
input.wVk = vkDelete
|
||||
} else if key == KeySuper {
|
||||
input.wVk = vkLwin
|
||||
} else if key == KeyBrowserBack {
|
||||
input.wVk = vkBrowserBack
|
||||
} else if key == KeyBrowserForward {
|
||||
input.wVk = vkBrowserForward
|
||||
} else if key == KeyVolumeMute {
|
||||
input.wVk = vkVolumeMute
|
||||
} else if key == KeyVolumeDown {
|
||||
input.wVk = vkVolumeDown
|
||||
} else if key == KeyVolumeUp {
|
||||
input.wVk = vkVolumeUp
|
||||
} else if key == KeyMediaNextTrack {
|
||||
input.wVk = vkMediaNextTrack
|
||||
} else if key == KeyMediaPrevTrack {
|
||||
input.wVk = vkMediaPrevTrack
|
||||
} else if key == KeyMediaPlayPause {
|
||||
input.wVk = vkMediaPlayPause
|
||||
} else {
|
||||
return errors.New("key not mapped to virtual-key code")
|
||||
}
|
||||
inputs := [...]keybdInput{input, input}
|
||||
inputs[1].dwFlags |= keyeventfKeyup
|
||||
return p.sendInput(inputs[:])
|
||||
}
|
||||
|
||||
func (p *windowsController) PointerButton(button PointerButton, press bool) error {
|
||||
input := mouseInput{typ: inputMouse}
|
||||
if button == PointerButtonLeft && press {
|
||||
input.dwFlags = mouseeventfLeftdown
|
||||
} else if button == PointerButtonLeft {
|
||||
input.dwFlags = mouseeventfLeftup
|
||||
} else if button == PointerButtonMiddle && press {
|
||||
input.dwFlags = mouseeventfMiddledown
|
||||
} else if button == PointerButtonMiddle {
|
||||
input.dwFlags = mouseeventfMiddleup
|
||||
} else if button == PointerButtonRight && press {
|
||||
input.dwFlags = mouseeventfRightdown
|
||||
} else if button == PointerButtonRight {
|
||||
input.dwFlags = mouseeventfRightup
|
||||
} else {
|
||||
return errors.New("unsupported pointer button")
|
||||
}
|
||||
if r, _, err := sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)),
|
||||
unsafe.Sizeof(input)); int(r) != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsController) PointerMove(deltaX, deltaY int) error {
|
||||
input := mouseInput{
|
||||
typ: inputMouse,
|
||||
dx: int32(deltaX),
|
||||
dy: int32(deltaY),
|
||||
dwFlags: mouseeventfMove,
|
||||
}
|
||||
if r, _, err := sendInputProc.Call(1, uintptr(unsafe.Pointer(&input)),
|
||||
unsafe.Sizeof(input)); int(r) != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsController) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
|
||||
inputs := make([]mouseInput, 0, 2)
|
||||
if deltaHorizontal != 0 {
|
||||
inputs = append(inputs, mouseInput{
|
||||
typ: inputMouse,
|
||||
dwFlags: mouseeventfHwheel,
|
||||
mouseData: uint32(deltaHorizontal * scrollMult),
|
||||
})
|
||||
}
|
||||
if deltaVertical != 0 {
|
||||
inputs = append(inputs, mouseInput{
|
||||
typ: inputMouse,
|
||||
dwFlags: mouseeventfWheel,
|
||||
mouseData: uint32(-deltaVertical * scrollMult),
|
||||
})
|
||||
}
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if r, _, err := sendInputProc.Call(uintptr(len(inputs)),
|
||||
uintptr(unsafe.Pointer(&inputs[0])),
|
||||
unsafe.Sizeof(inputs[0])); int(r) != len(inputs) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
345
inputcontrol/controller_x11.go
Normal file
345
inputcontrol/controller_x11.go
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
//go:build x11
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
// #cgo LDFLAGS: -lX11 -lXtst
|
||||
// #include <X11/Xlib.h>
|
||||
// #include <X11/Intrinsic.h>
|
||||
// #include <X11/extensions/XTest.h>
|
||||
// #include <X11/XKBlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
keyboardMappingDelay time.Duration = 500 * time.Millisecond
|
||||
scrollDiv int = 20
|
||||
)
|
||||
|
||||
var modifierIndices [6]uint = [...]uint{C.ShiftMapIndex, C.Mod1MapIndex,
|
||||
C.Mod2MapIndex, C.Mod3MapIndex, C.Mod4MapIndex, C.Mod5MapIndex}
|
||||
|
||||
type x11Controller struct {
|
||||
display *C.Display
|
||||
lock sync.Mutex
|
||||
scrollHorizontal, scrollVertical int
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterController("X11", InitX11Controller, 0)
|
||||
}
|
||||
|
||||
func InitX11Controller() (Controller, error) {
|
||||
sessionType := os.Getenv("XDG_SESSION_TYPE")
|
||||
if sessionType != "" && sessionType != "x11" {
|
||||
return nil, UnsupportedPlatformError{errors.New(fmt.Sprintf(
|
||||
"unsupported session type '%v'", sessionType))}
|
||||
}
|
||||
display := C.XOpenDisplay(nil)
|
||||
if display == nil {
|
||||
return nil, UnsupportedPlatformError{
|
||||
errors.New("failed to connect to X server")}
|
||||
}
|
||||
return &x11Controller{display: display}, nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) Close() error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.display == nil {
|
||||
return errors.New("X server connection closed")
|
||||
}
|
||||
C.XCloseDisplay(p.display)
|
||||
p.display = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) findEmptyKeycodeLocked() (C.KeyCode, C.int, 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)
|
||||
if keysyms == nil {
|
||||
return 0, 0, errors.New("failed to get keyboard mapping")
|
||||
}
|
||||
defer C.XFree(unsafe.Pointer(keysyms))
|
||||
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)))
|
||||
if keysym != 0 {
|
||||
continue keycodes
|
||||
}
|
||||
}
|
||||
return keycode, keysymsPerKeycode, nil
|
||||
}
|
||||
return 0, 0, errors.New("no empty keycode found")
|
||||
}
|
||||
|
||||
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), 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 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<<uint(modIndex)] = keycode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return modKeycodes
|
||||
}
|
||||
|
||||
func (p *x11Controller) findKeycodeLocked(keyboard C.XkbDescPtr,
|
||||
modKeycodes map[uint]C.KeyCode, activeMods C.uint,
|
||||
keysym Keysym) (C.KeyCode, C.uint) {
|
||||
keycode := C.XKeysymToKeycode(p.display, C.KeySym(keysym))
|
||||
if keycode == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
var alwaysActiveMods C.uint
|
||||
for modIndex := uint(0); modIndex < 8; modIndex++ {
|
||||
mod := uint(1) << modIndex
|
||||
if _, modAvailable := modKeycodes[mod]; !modAvailable {
|
||||
alwaysActiveMods |= activeMods & C.uint(mod)
|
||||
}
|
||||
}
|
||||
_, shiftModAvailable := modKeycodes[C.ShiftMask]
|
||||
for _, modIndex := range modifierIndices {
|
||||
var mod C.uint
|
||||
if modIndex != C.ShiftMapIndex {
|
||||
mod = 1 << modIndex
|
||||
}
|
||||
for _, shiftMod := range [...]C.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, mods, &retMods, &retKeysym)
|
||||
if retKeysym == C.KeySym(keysym) {
|
||||
return keycode, mods
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (p *x11Controller) sendModsLocked(modKeycodes map[uint]C.KeyCode, mods C.uint,
|
||||
press bool) {
|
||||
var pressC C.int = C.False
|
||||
if press {
|
||||
pressC = C.True
|
||||
}
|
||||
for mod, keycode := range modKeycodes {
|
||||
if mods&C.uint(mod) != 0 {
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), pressC, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *x11Controller) keyboardKeys(keys []Keysym) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.display == nil {
|
||||
return errors.New("X server connection closed")
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
rootWindow := C.XDefaultRootWindow(p.display)
|
||||
modKeycodes := p.getModKeycodesLocked()
|
||||
keyboard := C.XkbGetKeyboard(p.display,
|
||||
C.XkbCompatMapMask|C.XkbGeometryMask, C.XkbUseCoreKbd)
|
||||
defer C.XkbFreeKeyboard(keyboard, C.XkbAllComponentsMask, C.True)
|
||||
var emptyKeycode C.KeyCode
|
||||
var keysymsPerKeycode C.int
|
||||
for _, keysym := range keys {
|
||||
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,
|
||||
keysym)
|
||||
var pressMods, releaseMods C.uint
|
||||
if keycode == 0 {
|
||||
if emptyKeycode == 0 {
|
||||
var err error
|
||||
emptyKeycode, keysymsPerKeycode, err = p.findEmptyKeycodeLocked()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.changeKeyMappingLocked(keysymsPerKeycode, emptyKeycode, 0)
|
||||
}
|
||||
keycode = emptyKeycode
|
||||
p.changeKeyMappingLocked(keysymsPerKeycode, keycode, keysym)
|
||||
// race condition!
|
||||
time.Sleep(keyboardMappingDelay)
|
||||
} else {
|
||||
pressMods = mods & ^activeMods
|
||||
releaseMods = activeMods & ^mods
|
||||
}
|
||||
p.sendModsLocked(modKeycodes, releaseMods, false)
|
||||
p.sendModsLocked(modKeycodes, pressMods, true)
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), C.True, 0)
|
||||
C.XTestFakeKeyEvent(p.display, C.uint(keycode), C.False, 0)
|
||||
p.sendModsLocked(modKeycodes, pressMods, false)
|
||||
p.sendModsLocked(modKeycodes, releaseMods, true)
|
||||
C.XFlush(p.display)
|
||||
if keycode == emptyKeycode {
|
||||
// race condition!
|
||||
time.Sleep(keyboardMappingDelay)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) KeyboardText(text string) error {
|
||||
keys := make([]Keysym, 0, len(text))
|
||||
for _, runeValue := range text {
|
||||
keysym, err := RuneToKeysym(runeValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys = append(keys, keysym)
|
||||
}
|
||||
return p.keyboardKeys(keys)
|
||||
}
|
||||
|
||||
func (p *x11Controller) KeyboardKey(key Key) error {
|
||||
keysym, err := KeyToKeysym(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := [...]Keysym{keysym}
|
||||
return p.keyboardKeys(keys[:])
|
||||
}
|
||||
|
||||
func (p *x11Controller) sendButton(button uint, press bool) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.display == nil {
|
||||
return errors.New("X server connection closed")
|
||||
}
|
||||
if button == 0 || button > 9 {
|
||||
return errors.New("unsupported pointer button")
|
||||
}
|
||||
var pressC C.int = C.False
|
||||
if press {
|
||||
pressC = C.True
|
||||
}
|
||||
C.XTestFakeButtonEvent(p.display, C.uint(button), pressC, 0)
|
||||
C.XFlush(p.display)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) PointerButton(button PointerButton, press bool) error {
|
||||
if button == PointerButtonLeft {
|
||||
return p.sendButton(1, press)
|
||||
}
|
||||
if button == PointerButtonRight {
|
||||
return p.sendButton(3, press)
|
||||
}
|
||||
if button == PointerButtonMiddle {
|
||||
return p.sendButton(2, press)
|
||||
}
|
||||
return errors.New("unsupported pointer button")
|
||||
}
|
||||
|
||||
func (p *x11Controller) PointerMove(deltaX, deltaY int) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.display == nil {
|
||||
return errors.New("X server connection closed")
|
||||
}
|
||||
C.XTestFakeRelativeMotionEvent(p.display, C.int(deltaX), C.int(deltaY), 0)
|
||||
C.XFlush(p.display)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *x11Controller) PointerScroll(deltaHorizontal, deltaVertical int, finish bool) error {
|
||||
p.lock.Lock()
|
||||
stepsHorizontal := (p.scrollHorizontal + deltaHorizontal) / scrollDiv
|
||||
stepsVertical := (p.scrollVertical + deltaVertical) / scrollDiv
|
||||
if finish {
|
||||
p.scrollHorizontal = 0
|
||||
p.scrollVertical = 0
|
||||
} else {
|
||||
p.scrollHorizontal = (p.scrollHorizontal + deltaHorizontal) % scrollDiv
|
||||
p.scrollVertical = (p.scrollVertical + deltaVertical) % scrollDiv
|
||||
}
|
||||
p.lock.Unlock()
|
||||
var buttonHorizontal uint = 7
|
||||
if stepsHorizontal < 0 {
|
||||
buttonHorizontal = 6
|
||||
stepsHorizontal = -stepsHorizontal
|
||||
}
|
||||
for i := 0; i < stepsHorizontal; i++ {
|
||||
if err := p.sendButton(buttonHorizontal, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.sendButton(buttonHorizontal, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var buttonVertical uint = 5
|
||||
if stepsVertical < 0 {
|
||||
buttonVertical = 4
|
||||
stepsVertical = -stepsVertical
|
||||
}
|
||||
for i := 0; i < stepsVertical; i++ {
|
||||
if err := p.sendButton(buttonVertical, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.sendButton(buttonVertical, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
207
inputcontrol/keysyms.generated.go
Normal file
207
inputcontrol/keysyms.generated.go
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// Code generated by keysyms.generator.go. DO NOT EDIT.
|
||||
//go:build portal || x11
|
||||
|
||||
package inputcontrol
|
||||
|
||||
var keysymsMap = map[rune]Keysym{
|
||||
0x0008: 0x0000ff08,
|
||||
0x0009: 0x0000ff09,
|
||||
0x000a: 0x0000ff0a,
|
||||
0x000b: 0x0000ff0b,
|
||||
0x000d: 0x0000ff0d,
|
||||
0x0013: 0x0000ff13,
|
||||
0x0014: 0x0000ff14,
|
||||
0x0015: 0x0000ff15,
|
||||
0x001b: 0x0000ff1b,
|
||||
0x0020: 0x00000020,
|
||||
0x0021: 0x00000021,
|
||||
0x0022: 0x00000022,
|
||||
0x0023: 0x00000023,
|
||||
0x0024: 0x00000024,
|
||||
0x0025: 0x00000025,
|
||||
0x0026: 0x00000026,
|
||||
0x0027: 0x00000027,
|
||||
0x0028: 0x00000028,
|
||||
0x0029: 0x00000029,
|
||||
0x002a: 0x0000002a,
|
||||
0x002b: 0x0000002b,
|
||||
0x002c: 0x0000002c,
|
||||
0x002d: 0x0000002d,
|
||||
0x002e: 0x0000002e,
|
||||
0x002f: 0x0000002f,
|
||||
0x0030: 0x00000030,
|
||||
0x0031: 0x00000031,
|
||||
0x0032: 0x00000032,
|
||||
0x0033: 0x00000033,
|
||||
0x0034: 0x00000034,
|
||||
0x0035: 0x00000035,
|
||||
0x0036: 0x00000036,
|
||||
0x0037: 0x00000037,
|
||||
0x0038: 0x00000038,
|
||||
0x0039: 0x00000039,
|
||||
0x003a: 0x0000003a,
|
||||
0x003b: 0x0000003b,
|
||||
0x003c: 0x0000003c,
|
||||
0x003d: 0x0000003d,
|
||||
0x003e: 0x0000003e,
|
||||
0x003f: 0x0000003f,
|
||||
0x0040: 0x00000040,
|
||||
0x0041: 0x00000041,
|
||||
0x0042: 0x00000042,
|
||||
0x0043: 0x00000043,
|
||||
0x0044: 0x00000044,
|
||||
0x0045: 0x00000045,
|
||||
0x0046: 0x00000046,
|
||||
0x0047: 0x00000047,
|
||||
0x0048: 0x00000048,
|
||||
0x0049: 0x00000049,
|
||||
0x004a: 0x0000004a,
|
||||
0x004b: 0x0000004b,
|
||||
0x004c: 0x0000004c,
|
||||
0x004d: 0x0000004d,
|
||||
0x004e: 0x0000004e,
|
||||
0x004f: 0x0000004f,
|
||||
0x0050: 0x00000050,
|
||||
0x0051: 0x00000051,
|
||||
0x0052: 0x00000052,
|
||||
0x0053: 0x00000053,
|
||||
0x0054: 0x00000054,
|
||||
0x0055: 0x00000055,
|
||||
0x0056: 0x00000056,
|
||||
0x0057: 0x00000057,
|
||||
0x0058: 0x00000058,
|
||||
0x0059: 0x00000059,
|
||||
0x005a: 0x0000005a,
|
||||
0x005b: 0x0000005b,
|
||||
0x005c: 0x0000005c,
|
||||
0x005d: 0x0000005d,
|
||||
0x005e: 0x0000005e,
|
||||
0x005f: 0x0000005f,
|
||||
0x0060: 0x00000060,
|
||||
0x0061: 0x00000061,
|
||||
0x0062: 0x00000062,
|
||||
0x0063: 0x00000063,
|
||||
0x0064: 0x00000064,
|
||||
0x0065: 0x00000065,
|
||||
0x0066: 0x00000066,
|
||||
0x0067: 0x00000067,
|
||||
0x0068: 0x00000068,
|
||||
0x0069: 0x00000069,
|
||||
0x006a: 0x0000006a,
|
||||
0x006b: 0x0000006b,
|
||||
0x006c: 0x0000006c,
|
||||
0x006d: 0x0000006d,
|
||||
0x006e: 0x0000006e,
|
||||
0x006f: 0x0000006f,
|
||||
0x0070: 0x00000070,
|
||||
0x0071: 0x00000071,
|
||||
0x0072: 0x00000072,
|
||||
0x0073: 0x00000073,
|
||||
0x0074: 0x00000074,
|
||||
0x0075: 0x00000075,
|
||||
0x0076: 0x00000076,
|
||||
0x0077: 0x00000077,
|
||||
0x0078: 0x00000078,
|
||||
0x0079: 0x00000079,
|
||||
0x007a: 0x0000007a,
|
||||
0x007b: 0x0000007b,
|
||||
0x007c: 0x0000007c,
|
||||
0x007d: 0x0000007d,
|
||||
0x007e: 0x0000007e,
|
||||
0x00a0: 0x000000a0,
|
||||
0x00a1: 0x000000a1,
|
||||
0x00a2: 0x000000a2,
|
||||
0x00a3: 0x000000a3,
|
||||
0x00a4: 0x000000a4,
|
||||
0x00a5: 0x000000a5,
|
||||
0x00a6: 0x000000a6,
|
||||
0x00a7: 0x000000a7,
|
||||
0x00a8: 0x000000a8,
|
||||
0x00a9: 0x000000a9,
|
||||
0x00aa: 0x000000aa,
|
||||
0x00ab: 0x000000ab,
|
||||
0x00ac: 0x000000ac,
|
||||
0x00ad: 0x000000ad,
|
||||
0x00ae: 0x000000ae,
|
||||
0x00af: 0x000000af,
|
||||
0x00b0: 0x000000b0,
|
||||
0x00b1: 0x000000b1,
|
||||
0x00b2: 0x000000b2,
|
||||
0x00b3: 0x000000b3,
|
||||
0x00b4: 0x000000b4,
|
||||
0x00b5: 0x000000b5,
|
||||
0x00b6: 0x000000b6,
|
||||
0x00b7: 0x000000b7,
|
||||
0x00b8: 0x000000b8,
|
||||
0x00b9: 0x000000b9,
|
||||
0x00ba: 0x000000ba,
|
||||
0x00bb: 0x000000bb,
|
||||
0x00bc: 0x000000bc,
|
||||
0x00bd: 0x000000bd,
|
||||
0x00be: 0x000000be,
|
||||
0x00bf: 0x000000bf,
|
||||
0x00c0: 0x000000c0,
|
||||
0x00c1: 0x000000c1,
|
||||
0x00c2: 0x000000c2,
|
||||
0x00c3: 0x000000c3,
|
||||
0x00c4: 0x000000c4,
|
||||
0x00c5: 0x000000c5,
|
||||
0x00c6: 0x000000c6,
|
||||
0x00c7: 0x000000c7,
|
||||
0x00c8: 0x000000c8,
|
||||
0x00c9: 0x000000c9,
|
||||
0x00ca: 0x000000ca,
|
||||
0x00cb: 0x000000cb,
|
||||
0x00cc: 0x000000cc,
|
||||
0x00cd: 0x000000cd,
|
||||
0x00ce: 0x000000ce,
|
||||
0x00cf: 0x000000cf,
|
||||
0x00d0: 0x000000d0,
|
||||
0x00d1: 0x000000d1,
|
||||
0x00d2: 0x000000d2,
|
||||
0x00d3: 0x000000d3,
|
||||
0x00d4: 0x000000d4,
|
||||
0x00d5: 0x000000d5,
|
||||
0x00d6: 0x000000d6,
|
||||
0x00d7: 0x000000d7,
|
||||
0x00d8: 0x000000d8,
|
||||
0x00d9: 0x000000d9,
|
||||
0x00da: 0x000000da,
|
||||
0x00db: 0x000000db,
|
||||
0x00dc: 0x000000dc,
|
||||
0x00dd: 0x000000dd,
|
||||
0x00de: 0x000000de,
|
||||
0x00df: 0x000000df,
|
||||
0x00e0: 0x000000e0,
|
||||
0x00e1: 0x000000e1,
|
||||
0x00e2: 0x000000e2,
|
||||
0x00e3: 0x000000e3,
|
||||
0x00e4: 0x000000e4,
|
||||
0x00e5: 0x000000e5,
|
||||
0x00e6: 0x000000e6,
|
||||
0x00e7: 0x000000e7,
|
||||
0x00e8: 0x000000e8,
|
||||
0x00e9: 0x000000e9,
|
||||
0x00ea: 0x000000ea,
|
||||
0x00eb: 0x000000eb,
|
||||
0x00ec: 0x000000ec,
|
||||
0x00ed: 0x000000ed,
|
||||
0x00ee: 0x000000ee,
|
||||
0x00ef: 0x000000ef,
|
||||
0x00f0: 0x000000f0,
|
||||
0x00f1: 0x000000f1,
|
||||
0x00f2: 0x000000f2,
|
||||
0x00f3: 0x000000f3,
|
||||
0x00f4: 0x000000f4,
|
||||
0x00f5: 0x000000f5,
|
||||
0x00f6: 0x000000f6,
|
||||
0x00f7: 0x000000f7,
|
||||
0x00f8: 0x000000f8,
|
||||
0x00f9: 0x000000f9,
|
||||
0x00fa: 0x000000fa,
|
||||
0x00fb: 0x000000fb,
|
||||
0x00fc: 0x000000fc,
|
||||
0x00fd: 0x000000fd,
|
||||
0x00fe: 0x000000fe,
|
||||
0x00ff: 0x000000ff,
|
||||
}
|
||||
126
inputcontrol/keysyms.generator.go
Normal file
126
inputcontrol/keysyms.generator.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
//go:build ignore
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Keysym int32
|
||||
|
||||
const (
|
||||
keysymdefHeader string = "/usr/include/X11/keysymdef.h"
|
||||
output string = "keysyms.generated.go"
|
||||
maxMappedUnicode rune = 0xff
|
||||
)
|
||||
|
||||
var overrideKeysyms = map[string]rune{
|
||||
"XK_BackSpace": 0x08,
|
||||
"XK_Tab": 0x09,
|
||||
"XK_Linefeed": 0x0a,
|
||||
"XK_Clear": 0x0b,
|
||||
"XK_Return": 0x0d,
|
||||
"XK_Pause": 0x13,
|
||||
"XK_Scroll_Lock": 0x14,
|
||||
"XK_Sys_Req": 0x15,
|
||||
"XK_Escape": 0x1b,
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, err := os.Open(keysymdefHeader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader := bufio.NewReader(f)
|
||||
re := regexp.MustCompile("^\\#define " +
|
||||
"(XK_[a-zA-Z_0-9]+)\\s+" + // keysymName
|
||||
"0x([0-9a-f]+)\\s*" + // keysym
|
||||
"(?:/\\*\\s*(?:U\\+([0-9A-F]{4,6}))?.*\\*/\\s*)?" + // keysymUnicode (optional)
|
||||
"$")
|
||||
keysymsMap := make(map[rune]Keysym)
|
||||
for {
|
||||
l, err := reader.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
submatches := re.FindStringSubmatch(l)
|
||||
if len(submatches) == 0 {
|
||||
continue
|
||||
}
|
||||
keysymName := submatches[1]
|
||||
keysymTemp, err := strconv.ParseInt(submatches[2], 16, 32)
|
||||
keysym := Keysym(keysymTemp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
unicode, found := overrideKeysyms[keysymName]
|
||||
if !found {
|
||||
if len(submatches[3]) == 0 {
|
||||
continue
|
||||
}
|
||||
unicodeTemp, err := strconv.ParseInt(submatches[3], 16, 32)
|
||||
unicode = rune(unicodeTemp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
if unicode > maxMappedUnicode {
|
||||
continue
|
||||
}
|
||||
if _, found := keysymsMap[unicode]; found {
|
||||
continue
|
||||
}
|
||||
keysymsMap[unicode] = keysym
|
||||
}
|
||||
content := "// Code generated by keysyms.generator.go. DO NOT EDIT.\n" +
|
||||
"//go:build portal || x11\n\n" +
|
||||
"package inputcontrol\n\n" +
|
||||
"var keysymsMap = map[rune]Keysym{\n"
|
||||
keys := make([]rune, 0)
|
||||
for key := range keysymsMap {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||||
for _, unicode := range keys {
|
||||
keysym := keysymsMap[unicode]
|
||||
content += fmt.Sprintf("\t0x%04x: 0x%08x,\n", unicode, keysym)
|
||||
}
|
||||
content += "}\n"
|
||||
o, err := os.Create(output)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer o.Close()
|
||||
if _, err := o.WriteString(content); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
124
inputcontrol/keysyms.go
Normal file
124
inputcontrol/keysyms.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
//go:build portal || x11
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad 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 Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package inputcontrol
|
||||
|
||||
//go:generate go run keysyms.generator.go
|
||||
|
||||
import "errors"
|
||||
|
||||
type Keysym int32
|
||||
|
||||
const (
|
||||
// X11/keysymdef.h
|
||||
xkBackSpace Keysym = 0xff08
|
||||
xkReturn Keysym = 0xff0D
|
||||
xkDelete Keysym = 0xffff
|
||||
xkHome Keysym = 0xff50
|
||||
xkLeft Keysym = 0xff51
|
||||
xkUp Keysym = 0xff52
|
||||
xkRight Keysym = 0xff53
|
||||
xkDown Keysym = 0xff54
|
||||
xkEnd Keysym = 0xff57
|
||||
xkSuperL Keysym = 0xffeb
|
||||
// X11/XF86keysym.h
|
||||
xf86xkAudioLowerVolume Keysym = 0x1008ff11
|
||||
xf86xkAudioMute Keysym = 0x1008ff12
|
||||
xf86xkAudioRaiseVolume Keysym = 0x1008ff13
|
||||
xf86xkAudioPlay Keysym = 0x1008ff14
|
||||
xf86xkAudioPrev Keysym = 0x1008ff16
|
||||
xf86xkAudioNext Keysym = 0x1008ff17
|
||||
xf86xkBack Keysym = 0x1008ff26
|
||||
xf86xkForward Keysym = 0x1008ff27
|
||||
)
|
||||
|
||||
func RuneToKeysym(runeValue rune) (Keysym, error) {
|
||||
if runeValue == '\n' {
|
||||
runeValue = '\r'
|
||||
}
|
||||
keysym, found := keysymsMap[runeValue]
|
||||
if !found {
|
||||
if runeValue < 0x100 || runeValue > 0x10ffff {
|
||||
return 0, errors.New("rune not mappend to keysym and " +
|
||||
"out of range for direct unicode mapping")
|
||||
}
|
||||
keysym = Keysym(0x01000000 + runeValue)
|
||||
}
|
||||
return keysym, nil
|
||||
}
|
||||
|
||||
func KeyToKeysym(key Key) (Keysym, error) {
|
||||
if key == KeyBackSpace {
|
||||
return xkBackSpace, nil
|
||||
}
|
||||
if key == KeyReturn {
|
||||
return xkReturn, nil
|
||||
}
|
||||
if key == KeyDelete {
|
||||
return xkDelete, nil
|
||||
}
|
||||
if key == KeyHome {
|
||||
return xkHome, nil
|
||||
}
|
||||
if key == KeyLeft {
|
||||
return xkLeft, nil
|
||||
}
|
||||
if key == KeyUp {
|
||||
return xkUp, nil
|
||||
}
|
||||
if key == KeyRight {
|
||||
return xkRight, nil
|
||||
}
|
||||
if key == KeyDown {
|
||||
return xkDown, nil
|
||||
}
|
||||
if key == KeyEnd {
|
||||
return xkEnd, nil
|
||||
}
|
||||
if key == KeySuper {
|
||||
return xkSuperL, nil
|
||||
}
|
||||
if key == KeyVolumeMute {
|
||||
return xf86xkAudioMute, nil
|
||||
}
|
||||
if key == KeyVolumeDown {
|
||||
return xf86xkAudioLowerVolume, nil
|
||||
}
|
||||
if key == KeyVolumeUp {
|
||||
return xf86xkAudioRaiseVolume, nil
|
||||
}
|
||||
if key == KeyMediaPlayPause {
|
||||
return xf86xkAudioPlay, nil
|
||||
}
|
||||
if key == KeyMediaPrevTrack {
|
||||
return xf86xkAudioPrev, nil
|
||||
}
|
||||
if key == KeyMediaNextTrack {
|
||||
return xf86xkAudioNext, nil
|
||||
}
|
||||
if key == KeyBrowserBack {
|
||||
return xf86xkBack, nil
|
||||
}
|
||||
if key == KeyBrowserForward {
|
||||
return xf86xkForward, nil
|
||||
}
|
||||
return 0, errors.New("key not mapped to keysym")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue