//go:build x11 /* * Copyright (c) 2018 Unrud * * 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 . */ package inputcontrol // #cgo LDFLAGS: -lX11 -lXrandr -lXtst // #include // #include // #include // #include // #include // #include // Window MacroDefaultRootWindow(Display *dpy) { // return DefaultRootWindow(dpy); // } import "C" import ( "errors" "fmt" "strings" "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) { display := C.XOpenDisplay(nil) if display == nil { return nil, &UnsupportedPlatformError{ 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")} } return p, nil } func (p *x11Controller) xIsXwayland() bool { // Detection method from https://gitlab.freedesktop.org/xorg/app/xisxwayland/-/blob/xisxwayland-2/xisxwayland.c var opcode, event, error, major, minor C.int xwaylandExtensionName := C.CString("XWAYLAND") defer C.free(unsafe.Pointer(xwaylandExtensionName)) if C.XQueryExtension(p.display, xwaylandExtensionName, &opcode, &event, &error) != 0 { return true } if C.XRRQueryExtension(p.display, &event, &error) == 0 || C.XRRQueryVersion(p.display, &major, &minor) == 0 { return false } resources := C.XRRGetScreenResourcesCurrent(p.display, C.MacroDefaultRootWindow(p.display)) if resources == nil { return false } defer C.XRRFreeScreenResources(resources) if resources.noutput < 1 { return false } output := C.XRRGetOutputInfo(p.display, resources, *resources.outputs) if output == nil { return false } defer C.XRRFreeOutputInfo(output) return strings.HasPrefix(C.GoString(output.name), "XWAYLAND") } 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 := range int(keysymsPerKeycode) { 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 := range int(modKeymap.max_keypermod) { keycode := *(*C.KeyCode)(unsafe.Pointer(uintptr(unsafe.Pointer(modKeymap.modifiermap)) + uintptr(uint(modIndex)*uint(modKeymap.max_keypermod)+uint(i)))) if keycode != 0 { modKeycodes[1< 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 { switch button { case PointerButtonLeft: return p.sendButton(1, press) case PointerButtonRight: return p.sendButton(3, press) case PointerButtonMiddle: return p.sendButton(2, press) default: return fmt.Errorf("unsupported pointer button: %#v", 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 range stepsHorizontal { 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 range stepsVertical { if err := p.sendButton(buttonVertical, true); err != nil { return err } if err := p.sendButton(buttonVertical, false); err != nil { return err } } return nil }