153 lines
4.6 KiB
Go
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
|
|
}
|