diff --git a/README.md b/README.md index 6e91f36..a54ab84 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ iPad-style floating on-screen keyboard for Plasma Wayland. Qt 6, C++ core, QML U - Ctrl/Alt/Super stick like shift (tap, double-tap, hold) and turn keys into shortcuts; a tapped or held shift joins them (Shift+F5, Shift+Tab, Shift+arrows), auto-shift and caps lock don't - Optional PC key blocks: function row, navigation (ins/home/pg up, del/end/pg dn, arrows), numeric keypad; any mix, typing keys included (⚙) - Floating: drag the bottom bar, resize from the corner; geometry is remembered per screen +- Follows Plasma's virtual keyboard setting (tray toggle): off never pops up on focus, the default only after touch or a pen; "Always Show" and D-Bus `Show`/`Toggle` still show it - See-through background (opacity slider in ⚙) with optional blur behind it (KWin's blur effect) ## Build & test diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7006eae..6dcea70 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ qt_add_executable(sloposk app/DevHooks.h app/DevHooks.cpp app/DBusControl.h app/DBusControl.cpp app/Checks.h app/Checks.cpp + app/KWinVirtualKeyboard.h app/KWinVirtualKeyboard.cpp app/QuitOnSignal.h app/QuitOnSignal.cpp app/SystemLayouts.h app/SystemLayouts.cpp backend/InputMethodProtocol.h diff --git a/src/app/KWinVirtualKeyboard.cpp b/src/app/KWinVirtualKeyboard.cpp new file mode 100644 index 0000000..6ee6faa --- /dev/null +++ b/src/app/KWinVirtualKeyboard.cpp @@ -0,0 +1,30 @@ +#include "KWinVirtualKeyboard.h" + +#include +#include +#include +#include + +namespace { +const QString Service = QStringLiteral("org.kde.KWin"); +const QString Path = QStringLiteral("/VirtualKeyboard"); +const QString Interface = QStringLiteral("org.kde.kwin.VirtualKeyboard"); +} // namespace + +KWinVirtualKeyboard::KWinVirtualKeyboard(QObject *parent) + : QObject(parent) +{ + QDBusConnection::sessionBus().connect(Service, Path, Interface, QStringLiteral("modeChanged"), this, + SIGNAL(modeChanged())); +} + +void KWinVirtualKeyboard::askShowOnActive(QObject *context, std::function answer) +{ + const QDBusMessage call = QDBusMessage::createMethodCall(Service, Path, Interface, QStringLiteral("willShowOnActive")); + auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(call), context); + connect(watcher, &QDBusPendingCallWatcher::finished, context, [answer = std::move(answer)](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + answer(reply.isError() || reply.value()); + }); +} diff --git a/src/app/KWinVirtualKeyboard.h b/src/app/KWinVirtualKeyboard.h new file mode 100644 index 0000000..e670406 --- /dev/null +++ b/src/app/KWinVirtualKeyboard.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +// KWin's virtual keyboard policy (the tray toggle, System Settings → +// Virtual Keyboard): KWin activates the input method for every text field +// and the policy only gates its input panel, which sloposk doesn't use, so +// sloposk asks it over org.kde.KWin /VirtualKeyboard instead. +class KWinVirtualKeyboard : public QObject +{ + Q_OBJECT + +public: + explicit KWinVirtualKeyboard(QObject *parent = nullptr); + + // Calls `answer` with whether a field focused now should show the + // keyboard: never when it's off, only after touch or a pen in the + // default mode. True if KWin can't be asked. + void askShowOnActive(QObject *context, std::function answer); + +Q_SIGNALS: + void modeChanged(); +}; diff --git a/src/main.cpp b/src/main.cpp index c3054e4..d183a87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "app/Checks.h" #include "app/DBusControl.h" #include "app/DevHooks.h" +#include "app/KWinVirtualKeyboard.h" #include "app/QuitOnSignal.h" #include "app/SystemLayouts.h" #include "backend/KWinInputMethod.h" @@ -89,6 +90,27 @@ void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settin [&] { settings.save(placement.screenName(), placement.geometry()); }); } +// A focused field shows the keyboard only if KWin's policy wants one now; +// asked before showing, so a "no" doesn't flash the keyboard. +void connectFieldFocus(VisibilityController &visibility, KWinInputMethod &backend, KWinVirtualKeyboard &policy) +{ + QObject::connect(&backend, &TextBackend::activeChanged, &visibility, [&] { + if (!backend.isActive()) { + visibility.setFieldActive(false); + return; + } + policy.askShowOnActive(&visibility, [&](bool show) { + if (!backend.isActive()) + return; // left the field before KWin answered + visibility.setShowOnField(show); + visibility.setFieldActive(true); + }); + }); + QObject::connect(&policy, &KWinVirtualKeyboard::modeChanged, &visibility, [&] { + policy.askShowOnActive(&visibility, [&](bool show) { visibility.setShowOnField(show); }); + }); +} + void connectVisibility(VisibilityController &visibility, KWinInputMethod &backend, const Settings &settings, KeyboardController &controller, FloatingWindow &placement) { @@ -99,8 +121,6 @@ void connectVisibility(VisibilityController &visibility, KWinInputMethod &backen // work) there is no focus signal: stay visible so it can be tried out. const auto updateStandalone = [&] { visibility.setStandalone(!backend.isAvailable()); }; QObject::connect(&backend, &KWinInputMethod::availableChanged, &visibility, updateStandalone); - QObject::connect(&backend, &TextBackend::activeChanged, &visibility, - [&] { visibility.setFieldActive(backend.isActive()); }); QObject::connect(&controller, &KeyboardController::dismissRequested, &visibility, &VisibilityController::hide); QObject::connect(&visibility, &VisibilityController::visibleChanged, &placement, [&](bool visible) { visible ? placement.show() : placement.hide(); }); @@ -175,6 +195,8 @@ int main(int argc, char *argv[]) connectKeySettings(settings, model, placement, *window, rebuildLayout); VisibilityController visibility; + KWinVirtualKeyboard virtualKeyboardPolicy; + connectFieldFocus(visibility, backend, virtualKeyboardPolicy); connectVisibility(visibility, backend, settings, controller, placement); DBusControl dbus(visibility); diff --git a/src/window/VisibilityController.cpp b/src/window/VisibilityController.cpp index d05b644..31240d5 100644 --- a/src/window/VisibilityController.cpp +++ b/src/window/VisibilityController.cpp @@ -7,6 +7,12 @@ void VisibilityController::setFieldActive(bool active) update(); } +void VisibilityController::setShowOnField(bool show) +{ + m_showOnField = show; + update(); +} + void VisibilityController::setStandalone(bool standalone) { m_standalone = standalone; @@ -40,7 +46,7 @@ void VisibilityController::toggle() void VisibilityController::update() { - const bool visible = m_override.value_or(m_alwaysVisible || m_standalone || m_fieldActive); + const bool visible = m_override.value_or(m_alwaysVisible || m_standalone || (m_fieldActive && m_showOnField)); if (visible == m_visible) return; m_visible = visible; diff --git a/src/window/VisibilityController.h b/src/window/VisibilityController.h index c0fbcdd..a32020d 100644 --- a/src/window/VisibilityController.h +++ b/src/window/VisibilityController.h @@ -4,7 +4,7 @@ #include // Decides whether the keyboard is shown: -// - while a text field is focused +// - while a text field is focused, if the system wants a keyboard for it // - always, if the user wants it (to type into any window) or when running // standalone (no input-method connection) // - show()/hide()/toggle() override that until the focus changes, also @@ -20,6 +20,8 @@ public: // Called on every focus change, even when a field follows a field. void setFieldActive(bool active); + // KWin's policy (off, or touch only) for fields focused from now on. + void setShowOnField(bool show); void setStandalone(bool standalone); void setAlwaysVisible(bool always); @@ -34,6 +36,7 @@ private: void update(); bool m_fieldActive = false; + bool m_showOnField = true; bool m_standalone = false; bool m_alwaysVisible = false; std::optional m_override; diff --git a/tests/tst_visibility.cpp b/tests/tst_visibility.cpp index 320bd9a..c37c836 100644 --- a/tests/tst_visibility.cpp +++ b/tests/tst_visibility.cpp @@ -16,6 +16,19 @@ private Q_SLOTS: QVERIFY(!v.isVisible()); } + void fieldShowsOnlyWhenTheSystemWantsIt() + { + VisibilityController v; + v.setShowOnField(false); + v.setFieldActive(true); + QVERIFY(!v.isVisible()); + v.show(); // explicit requests still work + QVERIFY(v.isVisible()); + v.setShowOnField(true); + v.setFieldActive(true); + QVERIFY(v.isVisible()); + } + void dismissLastsUntilFocusChanges() { VisibilityController v;