diff --git a/core/internal/qmlchecks/lockscreen_input_test.go b/core/internal/qmlchecks/lockscreen_input_test.go index 77f924c9e..c7e2f168d 100644 --- a/core/internal/qmlchecks/lockscreen_input_test.go +++ b/core/internal/qmlchecks/lockscreen_input_test.go @@ -22,6 +22,16 @@ func TestLockScreenPasswordFieldBypassesTextInputIME(t *testing.T) { if !strings.Contains(content, "Keys.onPressed") || !strings.Contains(content, "event.text") { t.Fatalf("passwordField should handle physical key text manually instead of relying on a text input control") } + + // Wayland IMEs commit unconsumed printable keys as text-input text rather + // than forwarding raw keys, so the lock screen needs an IME commit sink + // alongside raw key handling. + if !strings.Contains(content, "id: imeCommitSink") { + t.Fatalf("passwordField must keep the imeCommitSink TextInput so IME-routed keyboards can type (#2950)") + } + if !strings.Contains(content, "Qt.ImhSensitiveData") { + t.Fatalf("imeCommitSink must advertise hidden-text hints so IMEs treat it as a password field") + } } func TestLockScreenPamSupportsManagedAndSystemPolicies(t *testing.T) { diff --git a/quickshell/Modules/Lock/LockScreenContent.qml b/quickshell/Modules/Lock/LockScreenContent.qml index 7bf76a583..fcde1163b 100644 --- a/quickshell/Modules/Lock/LockScreenContent.qml +++ b/quickshell/Modules/Lock/LockScreenContent.qml @@ -932,7 +932,9 @@ Item { pam.passwd.start(); } } - Keys.onPressed: event => { + Keys.onPressed: event => handleKey(event) + + function handleKey(event) { if (demoMode) { return; } @@ -1042,6 +1044,36 @@ Item { } } + // Wayland IMEs commit unconsumed printable keys as text-input text + // (ibus ibuswaylandim.c) instead of forwarding raw keys, so an active + // text input must exist to receive them; the hidden-text hints put + // fcitx5 into plain keyboard passthrough (CapabilityFlag::Password). + // Raw keys stay in handleKey (#2950). + TextInput { + id: imeCommitSink + + focus: true + width: 1 + height: 1 + opacity: 0 + echoMode: TextInput.Password + inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + Keys.onPressed: event => { + passwordField.handleKey(event); + if (!event.accepted && (event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier))) + event.accepted = true; + } + onTextChanged: { + if (text.length === 0) + return; + const committed = text; + text = ""; + if (demoMode || root.unlocking || pam.passwd.active) + return; + passwordField.insertText(committed); + } + } + Component.onCompleted: { if (!demoMode) { forceActiveFocus();