From 6d3514d331d55394052829a1ee6b76f5f0babebe Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Wed, 28 Oct 2026 09:48:39 +0800 Subject: [PATCH] sddm-theme: restore Qt Virtual Keyboard fallback on X11 SDDM 0.21 still runs its greeter on X11 and sets QT_IM_MODULE=qtvirtualkeyboard, but since b1eed7f7 the theme only forwards to Keyboards.KWinVirtualKeyboard, which only exists under a running KWin. Qt Virtual Keyboard stays loaded as the input method, and because the theme no longer instantiates its input panel, Qt creates its own DesktopInputPanel, which pops up whenever a text field gains focus, a plain mouse click included. The keyboard button was dead there as well, because KWinVirtualKeyboard.available is false and its visible property never becomes true. Bring the X11 fallback back as a theme-private component: on X11 VirtualKeyboard.qml loads the Qt Virtual Keyboard panel that plasma-workspace dropped in f1c83d15 (instantiating QtVK.InputPanel is what keeps Qt from creating its own panel), on Wayland it keeps forwarding to Keyboards.KWinVirtualKeyboard. The button is hidden when no virtual keyboard is available at all. The panel itself carries no animation: it is shown and hidden by animating the loader that holds it, which slides it below the bottom edge while fading it out. Keeping the animation in one place matters because Qt Virtual Keyboard's own hide key turns InputMethod.visible off before the theme is notified, and a panel that hid itself as soon as it became inactive would disappear in a single frame instead of sliding out. The panel is only deactivated once it is fully off-screen, so that hiding it does not cut the animation short. BUG: 527138 --- sddm-theme/Main.qml | 32 +++--- sddm-theme/QtVirtualKeyboard.qml | 28 +++++ sddm-theme/VirtualKeyboard.qml | 183 +++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 15 deletions(-) create mode 100644 sddm-theme/QtVirtualKeyboard.qml create mode 100644 sddm-theme/VirtualKeyboard.qml diff --git a/sddm-theme/Main.qml b/sddm-theme/Main.qml index ab75d3db99..99a2b479b7 100644 --- a/sddm-theme/Main.qml +++ b/sddm-theme/Main.qml @@ -12,7 +12,6 @@ import Qt5Compat.GraphicalEffects import org.kde.plasma.components as PlasmaComponents3 import org.kde.plasma.private.keyboardindicator as KeyboardIndicator -import org.kde.plasma.workspace.keyboardlayout as Keyboards import org.kde.kirigami as Kirigami import org.kde.breeze.components @@ -65,7 +64,7 @@ Item { anchors.fill: parent property bool uiVisible: true - property bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || Keyboards.KWinVirtualKeyboard.visible || config.type !== "image" + property bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || inputPanel.keyboardVisible || config.type !== "image" hoverEnabled: true drag.filterChildren: true @@ -207,7 +206,7 @@ Item { return parts.join(" • "); } - actionItemsVisible: !Keyboards.KWinVirtualKeyboard.visible + actionItemsVisible: !inputPanel.keyboardVisible actionItems: [ ActionButton { icon.name: "system-hibernate" @@ -315,6 +314,17 @@ Item { } } + VirtualKeyboard { + id: inputPanel + + z: 1 + + screenRoot: root + mainStack: mainStack + mainBlock: userListComponent + passwordField: userListComponent.mainPasswordBox + } + Component { id: userPromptComponent Login { @@ -341,7 +351,7 @@ Item { sddm.login(username, password, sessionButton.currentIndex) } - actionItemsVisible: !Keyboards.KWinVirtualKeyboard.visible + actionItemsVisible: !inputPanel.keyboardVisible actionItems: [ ActionButton { icon.name: "system-suspend" @@ -438,17 +448,9 @@ Item { id: virtualKeyboardButton text: i18ndc("plasma-desktop-sddm-theme", "Button to show/hide virtual keyboard", "Virtual Keyboard") - icon.name: Keyboards.KWinVirtualKeyboard.visible ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" - onClicked: { - if (Keyboards.KWinVirtualKeyboard.visible) { - Keyboards.KWinVirtualKeyboard.active = false; - } else { - // Otherwise the password field loses focus and on-screen keyboard - // keystrokes get eaten - userListComponent.mainPasswordBox.forceActiveFocus(); - Keyboards.KWinVirtualKeyboard.forceActivate(); - } - } + icon.name: inputPanel.keyboardVisible ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" + onClicked: inputPanel.toggle() + visible: inputPanel.available Layout.fillHeight: true containmentMask: Item { diff --git a/sddm-theme/QtVirtualKeyboard.qml b/sddm-theme/QtVirtualKeyboard.qml new file mode 100644 index 0000000000..bdc3d2f9d1 --- /dev/null +++ b/sddm-theme/QtVirtualKeyboard.qml @@ -0,0 +1,28 @@ +/* + SPDX-FileCopyrightText: 2017 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.VirtualKeyboard as QtVK + +/* + * Qt Virtual Keyboard's input panel. Instantiating it is what keeps Qt from + * creating its own DesktopInputPanel, which pops up whenever a text field + * gains focus, mouse clicks included. + * + * The panel has no animation of its own: VirtualKeyboard.qml slides and fades + * the loader instead. That way the keyboard also animates out when Qt hides + * the input method on its own, which its built-in hide key does. + */ +QtVK.InputPanel { + id: inputPanel + property bool activated: false + active: activated && InputMethod.visible + // Stay visible while the loader slides the panel off-screen. Hiding it as + // soon as InputMethod.visible turns false, which Qt Virtual Keyboard's own + // hide key does, makes the keyboard disappear in a single frame. + visible: activated + width: parent.width +} diff --git a/sddm-theme/VirtualKeyboard.qml b/sddm-theme/VirtualKeyboard.qml new file mode 100644 index 0000000000..dfd673e106 --- /dev/null +++ b/sddm-theme/VirtualKeyboard.qml @@ -0,0 +1,183 @@ +/* + SPDX-FileCopyrightText: 2016 David Edmundson + SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez + SPDX-FileCopyrightText: 2023 ivan tkachenko + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.Templates as T + +import org.kde.kirigami as Kirigami +import org.kde.plasma.workspace.keyboardlayout as Keyboards + +/* + * Shows and hides the virtual keyboard and keeps the login form above it. + * + * On Wayland the keyboard is drawn by KWin, so all we do is forward to + * Keyboards.KWinVirtualKeyboard. On X11, which is still what SDDM uses by + * default, there is no KWin to talk to, so the greeter falls back to Qt + * Virtual Keyboard instead. + * + * Instantiating Qt Virtual Keyboard's input panel is also what keeps Qt from + * creating its own DesktopInputPanel, which pops up whenever a text field + * gains focus, mouse clicks included. See + * https://bugs.kde.org/show_bug.cgi?id=527138 + */ +Loader { + id: loader + + // Usually, anchors on a top-level component is a bad idea, but this is a + // tightly integrated component sharing the screen with the theme. + anchors { + left: parent.left + right: parent.right + } + + required property Item screenRoot + required property T.StackView mainStack + required property /* MainBlock | Login */ Item mainBlock + required property Item passwordField + + // Whether the keyboard is currently on screen. + readonly property bool keyboardVisible: active ? state === "visible" : Keyboards.KWinVirtualKeyboard.visible + // Whether this platform has a virtual keyboard at all. + readonly property bool available: active ? status === Loader.Ready : Keyboards.KWinVirtualKeyboard.available + readonly property bool keyboardActive: (item as QtVirtualKeyboard)?.active ?? false + + active: !Qt.platform.pluginName.includes("wayland") + source: Qt.resolvedUrl("QtVirtualKeyboard.qml") + + function toggle() { + if (!active) { + if (Keyboards.KWinVirtualKeyboard.visible) { + Keyboards.KWinVirtualKeyboard.active = false; + } else { + // Otherwise the password field loses focus and on-screen + // keyboard keystrokes get eaten + passwordField.forceActiveFocus(); + Keyboards.KWinVirtualKeyboard.forceActivate(); + } + return; + } + if (!item) { + return; + } + if (state === "hidden") { + // Otherwise the password field loses focus and on-screen keyboard + // keystrokes get eaten + passwordField.forceActiveFocus(); + state = "visible"; + } else { + state = "hidden"; + } + } + + onKeyboardActiveChanged: { + // The keyboard was hidden by something other than the button, e.g. Qt + // Virtual Keyboard's own hide key, which turns InputMethod.visible off + // before the theme is notified. Animate it out. + if (!keyboardActive && state === "visible") { + state = "hidden"; + } + } + + state: "hidden" + + states: [ + State { + name: "visible" + PropertyChanges { + mainStack.y: Math.min(0, screenRoot.height - loader.height - mainBlock.visibleBoundary) + } + PropertyChanges { + loader.y: screenRoot.height - loader.height + loader.opacity: 1 + } + }, + State { + name: "hidden" + PropertyChanges { + mainStack.y: 0 + } + PropertyChanges { + loader.y: screenRoot.height + loader.opacity: 0 + } + } + ] + + transitions: [ + Transition { + from: "hidden" + to: "visible" + SequentialAnimation { + ScriptAction { + script: { + loader.item.activated = true; + Qt.inputMethod.show(); + } + } + // The loader slides the keyboard in from below the screen + // while fading it in, and moves the form out of the way + ParallelAnimation { + NumberAnimation { + target: loader.mainStack + property: "y" + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: loader + property: "y" + duration: Kirigami.Units.longDuration + easing.type: Easing.OutQuad + } + NumberAnimation { + target: loader + property: "opacity" + duration: Kirigami.Units.longDuration + easing.type: Easing.OutQuad + } + } + } + }, + Transition { + from: "visible" + to: "hidden" + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + target: loader.mainStack + property: "y" + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + // Nothing animates the keyboard out of the way, so slide + // it off-screen and fade it out ourselves + NumberAnimation { + target: loader + property: "y" + duration: Kirigami.Units.longDuration + easing.type: Easing.InQuad + } + NumberAnimation { + target: loader + property: "opacity" + duration: Kirigami.Units.longDuration + easing.type: Easing.InQuad + } + } + // Deactivate only once the keyboard has slid out, otherwise it + // vanishes before the animation starts + ScriptAction { + script: { + loader.item.activated = false; + Qt.inputMethod.hide(); + } + } + } + } + ] +}