mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
dafda5bd5c
GDK3 (used by Firefox/Camoufox) selects XI2 for the seat keyboard at startup and ignores core-protocol KeyPress events. XTestFakeKeyEvent produces core events, so typed characters from the Neko viewer silently never reach Firefox. Fix: discover the "Virtual core XTEST keyboard" XInput1 device at startup, cache it, and dispatch key events via XTestFakeDeviceKeyEvent which goes through the XI path. Falls back to the old core XTest path if the device isn't found. Requires -lXi (libxi-dev at build, libxi6 at runtime).
352 lines
6.8 KiB
Go
352 lines
6.8 KiB
Go
package xorg
|
|
|
|
/*
|
|
#cgo LDFLAGS: -lX11 -lXrandr -lXtst -lXfixes -lXi -lxcvt
|
|
|
|
#include "xorg.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/m1k1o/neko/server/pkg/types"
|
|
)
|
|
|
|
//go:generate ./keysymdef.sh
|
|
|
|
type KbdMod uint8
|
|
|
|
const (
|
|
KbdModShift KbdMod = C.ShiftMask
|
|
KbdModCapsLock KbdMod = C.LockMask
|
|
KbdModControl KbdMod = C.ControlMask
|
|
KbdModAlt KbdMod = C.Mod1Mask
|
|
KbdModNumLock KbdMod = C.Mod2Mask
|
|
KbdModMeta KbdMod = C.Mod3Mask
|
|
KbdModSuper KbdMod = C.Mod4Mask
|
|
KbdModAltGr KbdMod = C.Mod5Mask
|
|
)
|
|
|
|
type ScreenConfiguration struct {
|
|
Width int
|
|
Height int
|
|
Rates map[int]int16
|
|
}
|
|
|
|
var ScreenConfigurations = make(map[int]ScreenConfiguration)
|
|
|
|
var debounce_button = make(map[uint32]time.Time)
|
|
var debounce_key = make(map[uint32]time.Time)
|
|
var mu = sync.Mutex{}
|
|
|
|
func GetScreenConfigurations() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
C.XGetScreenConfigurations()
|
|
}
|
|
|
|
func DisplayOpen(display string) bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
displayUnsafe := C.CString(display)
|
|
defer C.free(unsafe.Pointer(displayUnsafe))
|
|
|
|
ok := C.XDisplayOpen(displayUnsafe)
|
|
return int(ok) == 1
|
|
}
|
|
|
|
func DisplayClose() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
C.XDisplayClose()
|
|
}
|
|
|
|
func Move(x, y int) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
C.XMove(C.int(x), C.int(y))
|
|
}
|
|
|
|
func GetCursorPosition() (int, int) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
var x C.int
|
|
var y C.int
|
|
C.XCursorPosition(&x, &y)
|
|
|
|
return int(x), int(y)
|
|
}
|
|
|
|
func Scroll(deltaX, deltaY int, controlKey bool) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if controlKey {
|
|
C.XSetKeyboardModifier(C.uchar(C.ControlMask), 1)
|
|
defer C.XSetKeyboardModifier(C.uchar(C.ControlMask), 0)
|
|
}
|
|
|
|
C.XScroll(C.int(deltaX), C.int(deltaY))
|
|
}
|
|
|
|
func ButtonDown(code uint32) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := debounce_button[code]; ok {
|
|
return fmt.Errorf("debounced button %v", code)
|
|
}
|
|
|
|
debounce_button[code] = time.Now()
|
|
|
|
C.XButton(C.uint(code), C.int(1))
|
|
return nil
|
|
}
|
|
|
|
func KeyDown(code uint32) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := debounce_key[code]; ok {
|
|
return fmt.Errorf("debounced key %v", code)
|
|
}
|
|
|
|
debounce_key[code] = time.Now()
|
|
|
|
C.XKey(C.KeySym(code), C.int(1))
|
|
return nil
|
|
}
|
|
|
|
func ButtonUp(code uint32) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := debounce_button[code]; !ok {
|
|
return fmt.Errorf("debounced button %v", code)
|
|
}
|
|
|
|
delete(debounce_button, code)
|
|
|
|
C.XButton(C.uint(code), C.int(0))
|
|
return nil
|
|
}
|
|
|
|
func KeyUp(code uint32) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := debounce_key[code]; !ok {
|
|
return fmt.Errorf("debounced key %v", code)
|
|
}
|
|
|
|
delete(debounce_key, code)
|
|
|
|
C.XKey(C.KeySym(code), C.int(0))
|
|
return nil
|
|
}
|
|
|
|
func ResetKeys() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
for code := range debounce_button {
|
|
C.XButton(C.uint(code), C.int(0))
|
|
delete(debounce_button, code)
|
|
}
|
|
|
|
for code := range debounce_key {
|
|
C.XKey(C.KeySym(code), C.int(0))
|
|
delete(debounce_key, code)
|
|
}
|
|
}
|
|
|
|
func CheckKeys(duration time.Duration) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
t := time.Now()
|
|
for code, start := range debounce_button {
|
|
if t.Sub(start) < duration {
|
|
continue
|
|
}
|
|
|
|
C.XButton(C.uint(code), C.int(0))
|
|
delete(debounce_button, code)
|
|
}
|
|
|
|
for code, start := range debounce_key {
|
|
if t.Sub(start) < duration {
|
|
continue
|
|
}
|
|
|
|
C.XKey(C.KeySym(code), C.int(0))
|
|
delete(debounce_key, code)
|
|
}
|
|
}
|
|
|
|
// set screen configuration, create new one if not exists
|
|
func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
// round width to 8, because of Xorg
|
|
s.Width = s.Width - (s.Width % 8)
|
|
|
|
// if rate is 0, set it to 60
|
|
if s.Rate == 0 {
|
|
s.Rate = 60
|
|
}
|
|
|
|
// convert variables to C types
|
|
c_width, c_height, c_rate := C.int(s.Width), C.int(s.Height), C.short(s.Rate)
|
|
|
|
// if screen configuration already exists, just set it
|
|
status := C.XSetScreenConfiguration(c_width, c_height, c_rate)
|
|
if status != C.RRSetConfigSuccess {
|
|
// create new screen configuration
|
|
C.XCreateScreenMode(c_width, c_height, c_rate)
|
|
|
|
// screen configuration should exist now, set it
|
|
status = C.XSetScreenConfiguration(c_width, c_height, c_rate)
|
|
}
|
|
|
|
var err error
|
|
|
|
// if screen configuration was not set successfully, return error
|
|
if status != C.RRSetConfigSuccess {
|
|
err = fmt.Errorf("unknown screen configuration %s", s.String())
|
|
}
|
|
|
|
// if specified rate is not supported a BadValue error is returned
|
|
if status == C.BadValue {
|
|
err = fmt.Errorf("unsupported screen rate %d", s.Rate)
|
|
}
|
|
|
|
return s, err
|
|
}
|
|
|
|
func GetScreenSize() types.ScreenSize {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
c_width, c_height, c_rate := C.int(0), C.int(0), C.short(0)
|
|
C.XGetScreenConfiguration(&c_width, &c_height, &c_rate)
|
|
|
|
return types.ScreenSize{
|
|
Width: int(c_width),
|
|
Height: int(c_height),
|
|
Rate: int16(c_rate),
|
|
}
|
|
}
|
|
|
|
func SetKeyboardModifier(mod KbdMod, active bool) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
num := C.int(0)
|
|
if active {
|
|
num = C.int(1)
|
|
}
|
|
|
|
C.XSetKeyboardModifier(C.uchar(mod), num)
|
|
}
|
|
|
|
func GetKeyboardModifiers() KbdMod {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
return KbdMod(C.XGetKeyboardModifiers())
|
|
}
|
|
|
|
func GetCursorImage() *types.CursorImage {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
cur := C.XGetCursorImage()
|
|
defer C.XFree(unsafe.Pointer(cur))
|
|
|
|
width := int(cur.width)
|
|
height := int(cur.height)
|
|
|
|
// Xlib stores 32-bit data in longs, even if longs are 64-bits on 64-bit systems.
|
|
ptrSize := strconv.IntSize / 8
|
|
pixels := C.GoBytes(unsafe.Pointer(cur.pixels), C.int(width*height*ptrSize))
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
for y := 0; y < height; y++ {
|
|
for x := 0; x < width; x++ {
|
|
pos := ((y * width) + x) * ptrSize
|
|
|
|
img.SetRGBA(x, y, color.RGBA{
|
|
A: pixels[pos+3],
|
|
R: pixels[pos+2],
|
|
G: pixels[pos+1],
|
|
B: pixels[pos+0],
|
|
})
|
|
}
|
|
}
|
|
|
|
return &types.CursorImage{
|
|
Width: uint16(width),
|
|
Height: uint16(height),
|
|
Xhot: uint16(cur.xhot),
|
|
Yhot: uint16(cur.yhot),
|
|
Serial: uint64(cur.cursor_serial),
|
|
Image: img,
|
|
}
|
|
}
|
|
|
|
func GetScreenshotImage() *image.RGBA {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
var w, h C.int
|
|
pixelsUnsafe := C.XGetScreenshot(&w, &h)
|
|
pixels := C.GoBytes(unsafe.Pointer(pixelsUnsafe), w*h*3)
|
|
defer C.free(unsafe.Pointer(pixelsUnsafe))
|
|
|
|
width := int(w)
|
|
height := int(h)
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
for row := 0; row < height; row++ {
|
|
for col := 0; col < width; col++ {
|
|
pos := ((row * width) + col) * 3
|
|
|
|
img.SetRGBA(col, row, color.RGBA{
|
|
R: uint8(pixels[pos]),
|
|
G: uint8(pixels[pos+1]),
|
|
B: uint8(pixels[pos+2]),
|
|
A: 0xFF,
|
|
})
|
|
}
|
|
}
|
|
|
|
return img
|
|
}
|
|
|
|
//export goCreateScreenSize
|
|
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
|
|
ScreenConfigurations[int(index)] = ScreenConfiguration{
|
|
Width: int(width),
|
|
Height: int(height),
|
|
Rates: make(map[int]int16),
|
|
}
|
|
}
|
|
|
|
//export goSetScreenRates
|
|
func goSetScreenRates(index C.int, rate_index C.int, rateC C.short) {
|
|
ScreenConfigurations[int(index)].Rates[int(rate_index)] = int16(rateC)
|
|
}
|