// +build x11 /* * 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 // #cgo LDFLAGS: -lX11 -lXtst // #include // #include // #include import "C" import ( "errors" "fmt" "os" "sync" "time" "unsafe" ) const ( keyboardMappingDelay time.Duration = 125 * 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) 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 } 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 _, keysym := range keys { for i := range keycodeMapping { keycodeMapping[i] = C.KeySym(keysym) } C.XChangeKeyboardMapping(p.display, C.int(emptyKeycode), keysymsPerKeycode, (*C.KeySym)(unsafe.Pointer(&keycodeMapping[0])), 1) // race condition! C.XFlush(p.display) time.Sleep(keyboardMappingDelay) 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(keyboardMappingDelay) } return nil } func (p *x11Plugin) 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 *x11Plugin) KeyboardKey(key Key) error { keysym, err := KeyToKeysym(key) if err != nil { return err } keys := [...]Keysym{keysym} return p.keyboardKeys(keys[:]) } 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 }