Follow Plasma's virtual keyboard setting
KWin activates the input method for every text field; its policy (the tray toggle) only gates the input panel, which sloposk doesn't use. sloposk now asks KWin over D-Bus whether a focused field should show the keyboard: never when off, only after touch or a pen by default. "Always Show" and D-Bus Show/Toggle still show it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
- 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 (⚙)
|
- 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
|
- 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)
|
- See-through background (opacity slider in ⚙) with optional blur behind it (KWin's blur effect)
|
||||||
|
|
||||||
## Build & test
|
## Build & test
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ qt_add_executable(sloposk
|
|||||||
app/DevHooks.h app/DevHooks.cpp
|
app/DevHooks.h app/DevHooks.cpp
|
||||||
app/DBusControl.h app/DBusControl.cpp
|
app/DBusControl.h app/DBusControl.cpp
|
||||||
app/Checks.h app/Checks.cpp
|
app/Checks.h app/Checks.cpp
|
||||||
|
app/KWinVirtualKeyboard.h app/KWinVirtualKeyboard.cpp
|
||||||
app/QuitOnSignal.h app/QuitOnSignal.cpp
|
app/QuitOnSignal.h app/QuitOnSignal.cpp
|
||||||
app/SystemLayouts.h app/SystemLayouts.cpp
|
app/SystemLayouts.h app/SystemLayouts.cpp
|
||||||
backend/InputMethodProtocol.h
|
backend/InputMethodProtocol.h
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "KWinVirtualKeyboard.h"
|
||||||
|
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
#include <QDBusPendingReply>
|
||||||
|
|
||||||
|
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<void(bool)> 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<bool> reply = *watcher;
|
||||||
|
answer(reply.isError() || reply.value());
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// 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<void(bool)> answer);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void modeChanged();
|
||||||
|
};
|
||||||
+24
-2
@@ -1,6 +1,7 @@
|
|||||||
#include "app/Checks.h"
|
#include "app/Checks.h"
|
||||||
#include "app/DBusControl.h"
|
#include "app/DBusControl.h"
|
||||||
#include "app/DevHooks.h"
|
#include "app/DevHooks.h"
|
||||||
|
#include "app/KWinVirtualKeyboard.h"
|
||||||
#include "app/QuitOnSignal.h"
|
#include "app/QuitOnSignal.h"
|
||||||
#include "app/SystemLayouts.h"
|
#include "app/SystemLayouts.h"
|
||||||
#include "backend/KWinInputMethod.h"
|
#include "backend/KWinInputMethod.h"
|
||||||
@@ -89,6 +90,27 @@ void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settin
|
|||||||
[&] { settings.save(placement.screenName(), placement.geometry()); });
|
[&] { 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,
|
void connectVisibility(VisibilityController &visibility, KWinInputMethod &backend, const Settings &settings,
|
||||||
KeyboardController &controller, FloatingWindow &placement)
|
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.
|
// work) there is no focus signal: stay visible so it can be tried out.
|
||||||
const auto updateStandalone = [&] { visibility.setStandalone(!backend.isAvailable()); };
|
const auto updateStandalone = [&] { visibility.setStandalone(!backend.isAvailable()); };
|
||||||
QObject::connect(&backend, &KWinInputMethod::availableChanged, &visibility, updateStandalone);
|
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(&controller, &KeyboardController::dismissRequested, &visibility, &VisibilityController::hide);
|
||||||
QObject::connect(&visibility, &VisibilityController::visibleChanged, &placement,
|
QObject::connect(&visibility, &VisibilityController::visibleChanged, &placement,
|
||||||
[&](bool visible) { visible ? placement.show() : placement.hide(); });
|
[&](bool visible) { visible ? placement.show() : placement.hide(); });
|
||||||
@@ -175,6 +195,8 @@ int main(int argc, char *argv[])
|
|||||||
connectKeySettings(settings, model, placement, *window, rebuildLayout);
|
connectKeySettings(settings, model, placement, *window, rebuildLayout);
|
||||||
|
|
||||||
VisibilityController visibility;
|
VisibilityController visibility;
|
||||||
|
KWinVirtualKeyboard virtualKeyboardPolicy;
|
||||||
|
connectFieldFocus(visibility, backend, virtualKeyboardPolicy);
|
||||||
connectVisibility(visibility, backend, settings, controller, placement);
|
connectVisibility(visibility, backend, settings, controller, placement);
|
||||||
|
|
||||||
DBusControl dbus(visibility);
|
DBusControl dbus(visibility);
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ void VisibilityController::setFieldActive(bool active)
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VisibilityController::setShowOnField(bool show)
|
||||||
|
{
|
||||||
|
m_showOnField = show;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void VisibilityController::setStandalone(bool standalone)
|
void VisibilityController::setStandalone(bool standalone)
|
||||||
{
|
{
|
||||||
m_standalone = standalone;
|
m_standalone = standalone;
|
||||||
@@ -40,7 +46,7 @@ void VisibilityController::toggle()
|
|||||||
|
|
||||||
void VisibilityController::update()
|
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)
|
if (visible == m_visible)
|
||||||
return;
|
return;
|
||||||
m_visible = visible;
|
m_visible = visible;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
// Decides whether the keyboard is shown:
|
// 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
|
// - always, if the user wants it (to type into any window) or when running
|
||||||
// standalone (no input-method connection)
|
// standalone (no input-method connection)
|
||||||
// - show()/hide()/toggle() override that until the focus changes, also
|
// - 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.
|
// Called on every focus change, even when a field follows a field.
|
||||||
void setFieldActive(bool active);
|
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 setStandalone(bool standalone);
|
||||||
void setAlwaysVisible(bool always);
|
void setAlwaysVisible(bool always);
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ private:
|
|||||||
void update();
|
void update();
|
||||||
|
|
||||||
bool m_fieldActive = false;
|
bool m_fieldActive = false;
|
||||||
|
bool m_showOnField = true;
|
||||||
bool m_standalone = false;
|
bool m_standalone = false;
|
||||||
bool m_alwaysVisible = false;
|
bool m_alwaysVisible = false;
|
||||||
std::optional<bool> m_override;
|
std::optional<bool> m_override;
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ private Q_SLOTS:
|
|||||||
QVERIFY(!v.isVisible());
|
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()
|
void dismissLastsUntilFocusChanges()
|
||||||
{
|
{
|
||||||
VisibilityController v;
|
VisibilityController v;
|
||||||
|
|||||||
Reference in New Issue
Block a user