remote-touchpad/plugin_x11.go

222 lines
5.9 KiB
Go

// +build x11
/*
* 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
// #cgo LDFLAGS: -lX11 -lXtst
// #include <X11/Xlib.h>
// #include <X11/Intrinsic.h>
// #include <X11/extensions/XTest.h>
import "C"
import (
"errors"
"fmt"
"os"
"sync"
"time"
"unsafe"
)
const (
typingDelay time.Duration = 250 * time.Millisecond
scrollDiv int = 20
)
type x11Plugin struct {
display *C.Display
lock sync.Mutex
scrollHorizontal, scrollVertical int
}
func InitX11Plugin() (Plugin, 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 &x11Plugin{display: display}, nil
}
func (p *x11Plugin) 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 *x11Plugin) KeyboardText(text string) error {
p.lock.Lock()
defer p.lock.Unlock()
if p.display == nil {
return errors.New("X server connection closed")
}
if text == "" {
return nil
}
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 errors.New("failed to get keyboard mapping")
}
defer C.XFree(unsafe.Pointer(keysyms))
emptyKeycode := C.KeyCode(0)
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
}
}
emptyKeycode = keycode
break
}
if emptyKeycode == 0 {
return errors.New("no empty keycode found")
}
keycodeMapping := make([]C.KeySym, keysymsPerKeycode)
defer func() {
for i := range keycodeMapping {
keycodeMapping[i] = 0
}
C.XChangeKeyboardMapping(p.display, C.int(emptyKeycode), keysymsPerKeycode,
(*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1)
C.XFlush(p.display)
}()
for _, runeValue := range text {
keysym, err := runeToKeysym(runeValue)
if err != nil {
return err
}
for i := range keycodeMapping {
keycodeMapping[i] = C.KeySym(keysym)
}
C.XChangeKeyboardMapping(p.display, C.int(emptyKeycode), keysymsPerKeycode,
(*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1)
C.XTestFakeKeyEvent(p.display, C.uint(emptyKeycode), C.True, 0)
C.XTestFakeKeyEvent(p.display, C.uint(emptyKeycode), C.False, 0)
// race condition!
C.XFlush(p.display)
time.Sleep(typingDelay)
}
return nil
}
func (p *x11Plugin) 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 *x11Plugin) 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 *x11Plugin) 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 *x11Plugin) PointerScroll(deltaHorizontal, deltaVertical int) error {
p.lock.Lock()
stepsHorizontal := (p.scrollHorizontal + deltaHorizontal) / scrollDiv
stepsVertical := (p.scrollVertical + deltaVertical) / scrollDiv
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
}
func (p *x11Plugin) PointerScrollFinish() error {
p.lock.Lock()
defer p.lock.Unlock()
p.scrollHorizontal = 0
p.scrollVertical = 0
return nil
}