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
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <optional>
|
||||
|
||||
// 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<bool> m_override;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user