Add Xorg Remote controller capture with a core F11 escape
The Xorg command owner gains an exclusive Remote capture mode. Native transitions pass through the repeat normalizer and capture router; forwarded keys leave as remote.input events, Remote's own shortcuts are dispatched locally, and F11 ends capture on the X thread. Reader policy, overflow or inconsistent input also end it. Ending releases forwarded keys first and keeps the grab until held keys are released. Local key echo is suppressed during capture. The app attaches the owner to sessions installed with keyboard-capture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d5327adf4
commit
c234cb82a8
18 files changed
+950
-11
No files matched your search
@@ -17,6 +17,49 @@ type RemoteCaptureKey struct {
|
||||
Symbol uint32
|
||||
Pressed bool
|
||||
Repeat bool
|
||||
// Unsupported marks a fresh press the Remote key vocabulary cannot name.
|
||||
// It is consumed locally with its repeats and release, never forwarded.
|
||||
Unsupported bool
|
||||
}
|
||||
|
||||
// RemoteCaptureEvent is one item from a native capture owner to its reader
|
||||
// consumer. Exactly one of Key or Ended is set. Name is present only on a
|
||||
// fresh press. Ended reports why capture stopped: "escape", "requested" or
|
||||
// "local" (reader policy, overflow or an inconsistent native stream); no
|
||||
// further events follow it.
|
||||
type RemoteCaptureEvent struct {
|
||||
Key *RemoteCaptureTransition
|
||||
Ended string
|
||||
}
|
||||
|
||||
type RemoteCaptureTransition struct {
|
||||
Code uint8
|
||||
Name string
|
||||
Pressed bool
|
||||
Repeat bool
|
||||
}
|
||||
|
||||
// RemoteCaptureEscape is the core escape chord: F11 with no modifiers. It is
|
||||
// handled by the native owner without waiting for the network or a Tentacle.
|
||||
var RemoteCaptureEscape = CommandChord{Symbol: 0xffc8}
|
||||
|
||||
// RemoteCaptureKeyName returns the canonical Remote key name for a
|
||||
// group-zero, level-zero keysym name, or "" when it has none.
|
||||
func RemoteCaptureKeyName(keysym string) string {
|
||||
switch keysym {
|
||||
case "Prior":
|
||||
keysym = "Page_Up"
|
||||
case "Next":
|
||||
keysym = "Page_Down"
|
||||
case "KP_Prior":
|
||||
keysym = "KP_Page_Up"
|
||||
case "KP_Next":
|
||||
keysym = "KP_Page_Down"
|
||||
}
|
||||
if !ValidRemoteKeyName(keysym) {
|
||||
return ""
|
||||
}
|
||||
return keysym
|
||||
}
|
||||
|
||||
// RemoteCaptureResult is the routing decision for one captured transition.
|
||||
@@ -164,6 +207,10 @@ func (router *RemoteCaptureRouter) Route(key RemoteCaptureKey) (RemoteCaptureRes
|
||||
if len(router.order) >= RemoteHeldKeyLimit {
|
||||
return router.fail(ErrRemoteKeyLimit)
|
||||
}
|
||||
if key.Unsupported {
|
||||
router.hold(code, remoteCaptureLocal)
|
||||
return RemoteCaptureResult{}, nil
|
||||
}
|
||||
if mask, modifier := remoteModifierMask(key.Symbol); modifier || router.cthulhu[key.Symbol] {
|
||||
router.hold(code, remoteCaptureForwarded)
|
||||
router.modifier[code] = true
|
||||
|
||||
@@ -305,3 +305,19 @@ func TestBehaviorRemoteCaptureComposesWithNormalizer(t *testing.T) {
|
||||
t.Fatalf("commands = %v", commands)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBehaviorRemoteCaptureConsumesUnsupportedKeys(t *testing.T) {
|
||||
router := newTestCaptureRouter(t, nil)
|
||||
runCaptureSteps(t, router, []captureStep{
|
||||
{press(codeShift, leftShiftKeySymbol), remote(down(codeShift))},
|
||||
{RemoteCaptureKey{Keycode: 121, Symbol: 0x1008ff12, Pressed: true, Unsupported: true}, RemoteCaptureResult{}},
|
||||
{hold(121, 0x1008ff12), RemoteCaptureResult{}},
|
||||
{release(121), RemoteCaptureResult{}},
|
||||
{release(codeShift), remote(up(codeShift))},
|
||||
})
|
||||
for keysym, want := range map[string]string{"Prior": "Page_Up", "KP_Next": "KP_Page_Down", "a": "a", "A": "", "XF86AudioMute": "", "ISO_Level3_Shift": ""} {
|
||||
if got := RemoteCaptureKeyName(keysym); got != want {
|
||||
t.Fatalf("%s = %q, want %q", keysym, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user