Add quarantined Xorg destination-key ownership and cleanup
This commit is contained in:
1 parent
792cac5d0b
commit
cd9ae05bbe
10 files changed
+988
-29
No files matched your search
@@ -0,0 +1,152 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user