Files

153 lines
4.6 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package xorg
/*
#include "commands_native.h"
#include <X11/XKBlib.h>
#include <X11/extensions/XTest.h>
#include <stdlib.h>
static Display *remote_key_open(void) {
Display *display = XOpenDisplay(NULL);
if (!display) return NULL;
int event, error, major, minor;
if (!XTestQueryExtension(display, &event, &error, &major, &minor)) {
XCloseDisplay(display);
return NULL;
}
return display;
}
static int remote_key_resolve(Display *display, const char *name) {
KeySym wanted = XStringToKeysym(name);
if (wanted == NoSymbol) return 0;
int result = 0;
cthulhu_x11_begin_read(display);
XkbStateRec state;
XkbDescPtr map = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
// Multi-group and level-three policy is not implemented. Never guess a
// physical key from an inactive group's symbol or a shifted-only symbol.
if (map && XkbGetState(display, XkbUseCoreKbd, &state) == Success && state.group == 0) {
unsigned int modifiers = state.mods & ~(ShiftMask | LockMask);
for (int code = map->min_key_code; code <= map->max_key_code; code++) {
unsigned int consumed;
KeySym actual;
if (XkbTranslateKeyCode(map, code, modifiers, &consumed, &actual) && actual == wanted) {
if (result) { result = 0; break; } // Ambiguous destination.
result = code;
}
}
}
if (map) XkbFreeKeyboard(map, XkbAllComponentsMask, True);
if (cthulhu_x11_end_read(display)) result = 0;
return result;
}
static int remote_key_held(Display *display, unsigned int code) {
char held[32];
cthulhu_x11_begin_read(display);
int success = XQueryKeymap(display, held);
int error = cthulhu_x11_end_read(display);
if (!success || error) return -1;
return !!((unsigned char)held[code / 8] & (1u << (code % 8)));
}
static int remote_key_set(Display *display, unsigned int code, int pressed) {
cthulhu_x11_begin_read(display);
int success = XTestFakeKeyEvent(display, code, pressed, CurrentTime);
int error = cthulhu_x11_end_read(display);
return success && !error;
}
*/
import "C"
import (
"unsafe"
"git.stormux.org/storm/cthulhu/internal/input"
)
// Quarantined native prerequisite, with no production caller. All methods
// require one serial caller on a locked OS thread. Xlib can block: the future
// production owner must live in an independent helper, never the reader loop.
type remoteKeyTarget struct {
backend *remoteKeyBackend
ledger *input.RemoteKeyLedger
}
type remoteKeyBackend struct{ display *C.Display }
func openRemoteKeyTarget() (*remoteKeyTarget, error) {
display := C.remote_key_open()
if display == nil {
return nil, input.ErrRemoteKeyUnavailable
}
backend := &remoteKeyBackend{display: display}
ledger, err := input.NewRemoteKeyLedger(backend)
if err != nil {
C.XCloseDisplay(display)
return nil, err
}
return &remoteKeyTarget{backend: backend, ledger: ledger}, nil
}
func (backend *remoteKeyBackend) Resolve(name string) (uint16, error) {
if backend.display == nil || !input.ValidRemoteKeyName(name) {
return 0, input.ErrRemoteKeyUnavailable
}
value := C.CString(name)
defer C.free(unsafe.Pointer(value))
code := C.remote_key_resolve(backend.display, value)
if code < 8 || code > 255 {
return 0, input.ErrRemoteKeyUnavailable
}
return uint16(code), nil
}
func (backend *remoteKeyBackend) Held(code uint16) (bool, error) {
if backend.display == nil || code < 8 || code > 255 {
return false, input.ErrRemoteKeyBackend
}
held := C.remote_key_held(backend.display, C.uint(code))
if held < 0 {
return false, input.ErrRemoteKeyBackend
}
return held != 0, nil
}
func (backend *remoteKeyBackend) Set(code uint16, pressed bool) error {
if backend.display == nil || code < 8 || code > 255 {
return input.ErrRemoteKeyBackend
}
var down C.int
if pressed {
down = 1
}
if C.remote_key_set(backend.display, C.uint(code), down) == 0 {
return input.ErrRemoteKeyBackend
}
return nil
}
func (target *remoteKeyTarget) apply(name string, pressed bool) error {
return target.ledger.Apply(name, pressed)
}
func (target *remoteKeyTarget) retire() error { return target.ledger.Retire() }
// Failed cleanup keeps the connection and ledger available for explicit retry.
// Closing a connection alone is not XTEST key cleanup.
func (target *remoteKeyTarget) close() error {
if err := target.retire(); err != nil {
return err
}
if target.backend.display != nil {
C.XCloseDisplay(target.backend.display)
target.backend.display = nil
}
return nil
}