Dismiss Wayland internal UI on suspension and retain key repeats
This commit is contained in:
1 parent
0d5a5f7ac1
commit
12cadda7c1
5 files changed
+77
-5
No files matched your search
@@ -83,6 +83,14 @@ func (owner *keyboardOwner) requested_policy() keyboardPolicy {
|
||||
if !keyboard.enabled.Load() || keyboard.secure.Load() {
|
||||
owner.learning = false
|
||||
keyboard.learn.Store(false)
|
||||
if owner.uiSession != 0 {
|
||||
session := owner.uiSession
|
||||
owner.dismiss_internal_ui()
|
||||
select {
|
||||
case keyboard.commands <- input.CommandEvent{Command: input.InternalUIDismiss, UISession: session}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return keyboardPolicy{enabled: keyboard.enabled.Load(), browse: keyboard.browse.Load(), button: keyboard.nativeButton.Load(), secure: keyboard.secure.Load(), learn: owner.learning, bypass: keyboard.bypass.Load(), ui: owner.uiSession != 0}
|
||||
}
|
||||
|
||||
@@ -615,6 +615,61 @@ func TestNativeInternalUIEndWaitsForHeldKeyRelease(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeInternalUIRepeatsKeys(t *testing.T) {
|
||||
monitor := start_monitor(t)
|
||||
keyboard := start_keyboard(t, monitor)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
session, err := keyboard.BeginInternalUI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
monitor.key(t, 0xff08, 22, 0, false)
|
||||
monitor.key(t, 0xff08, 22, 0, false)
|
||||
for i := 0; i < 2; i++ {
|
||||
if event := await_command(t, keyboard, input.InternalUIKey); event.UISession != session || event.Chord.Symbol != 0xff08 {
|
||||
t.Fatalf("Backspace repeat %d = %+v", i, event)
|
||||
}
|
||||
}
|
||||
monitor.key(t, 0xff08, 22, 0, true)
|
||||
if err := keyboard.EndInternalUI(ctx, session); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeInternalUISuspensionDismissesBeforeHeldRelease(t *testing.T) {
|
||||
monitor := start_monitor(t)
|
||||
keyboard := start_keyboard(t, monitor)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
session, err := keyboard.BeginInternalUI(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
monitor.key(t, 0xff0d, 36, 0, false)
|
||||
await_command(t, keyboard, input.InternalUIKey)
|
||||
keyboard.SetCommandGrabs(false)
|
||||
if event := await_command(t, keyboard, input.InternalUIDismiss); event.UISession != session {
|
||||
t.Fatalf("suspension dismiss = %+v", event)
|
||||
}
|
||||
if keyboard.InternalUIActive() {
|
||||
t.Fatal("internal UI stayed active after command grabs were suspended")
|
||||
}
|
||||
if grabbed, _, _, _ := monitor.grab_state(); !grabbed {
|
||||
t.Fatal("keyboard ungrabbed while Enter was held")
|
||||
}
|
||||
monitor.key(t, 'a', 38, 0, false)
|
||||
monitor.key(t, 'a', 38, 0, true)
|
||||
monitor.key(t, 0xff0d, 36, 0, true)
|
||||
wait_snapshot(t, monitor, func(state MonitorSnapshot) bool { return !state.grabbed && len(state.grabs) == 0 })
|
||||
if len(keyboard.commands) != 0 || len(keyboard.events) != 0 {
|
||||
t.Fatal("keys from the closing UI reached command routing or monitoring")
|
||||
}
|
||||
if err := keyboard.EndInternalUI(ctx, session); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeShowActionsCapturePreservesHeldReaderModifier(t *testing.T) {
|
||||
monitor := start_monitor(t)
|
||||
keyboard := start_keyboard(t, monitor)
|
||||
|
||||
@@ -255,14 +255,23 @@ func (owner *keyboardOwner) handle_key(key keyEvent) error {
|
||||
if owner.policy.enabled && (key.released || key.state <= 0xffff) {
|
||||
consumed, command = owner.router.Handle(symbol, key.code, uint16(key.state), !key.released)
|
||||
}
|
||||
if !keyboard.enabled.Load() {
|
||||
consumed, command = false, ""
|
||||
}
|
||||
if key.released {
|
||||
delete(owner.captured, key.code)
|
||||
} else if consumed || owner.learning || owner.uiSession != 0 {
|
||||
owner.captured[key.code] = true
|
||||
}
|
||||
keyboard.active.Store(len(owner.captured) != 0 || owner.learning || owner.uiSession != 0)
|
||||
// The compositor can still hold an exclusive grab while the opening
|
||||
// chord drains. Keys in that interval belong to the closing UI, not
|
||||
// ordinary command routing or monitoring.
|
||||
if owner.policy.ui && owner.uiSession == 0 || !keyboard.enabled.Load() && owner.uiSession != 0 {
|
||||
return nil
|
||||
}
|
||||
if owner.uiSession != 0 {
|
||||
return owner.handle_ui_key(key, symbol, chord, repeated, timestamp)
|
||||
return owner.handle_ui_key(key, symbol, chord, timestamp)
|
||||
}
|
||||
if !key.released && !repeated && owner.learning {
|
||||
command = input.LearnKey
|
||||
|
||||
@@ -106,8 +106,8 @@ func (owner *keyboardOwner) dismiss_internal_ui() {
|
||||
// handle_ui_key consumes one key for the active session. Reader modifiers
|
||||
// and their chords never become field contents; Escape reports the key
|
||||
// and dismisses the session, matching the Xorg owner.
|
||||
func (owner *keyboardOwner) handle_ui_key(key keyEvent, symbol uint32, chord input.CommandChord, repeated bool, timestamp uint32) error {
|
||||
if key.released || repeated {
|
||||
func (owner *keyboardOwner) handle_ui_key(key keyEvent, symbol uint32, chord input.CommandChord, timestamp uint32) error {
|
||||
if key.released {
|
||||
return nil
|
||||
}
|
||||
if owner.router.IsModifier(key.symbol) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import "strings"
|
||||
|
||||
const Name = "Cthulhu"
|
||||
|
||||
const Version = "2026.10.31"
|
||||
const Channel = "master"
|
||||
const Version = "2026.11.11"
|
||||
const Channel = "hunter"
|
||||
|
||||
var Build = Version
|
||||
|
||||
|
||||
Reference in new issue
Block a user