// +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 // #include 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 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) 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 *x11Plugin) 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 *x11Plugin) 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< 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 }