diff --git a/README.md b/README.md
index 7fab61a..62b2379 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@ iPad-style floating on-screen keyboard for Plasma Wayland. Qt 6, C++ core, QML U
- Flick a key down for its secondary symbol; long-press for accented alternates
- iOS shift: tap = one letter, double-tap = caps lock, hold = shift while held
+- Works everywhere, not just in text fields: with none focused, keys reach the focused window like a hardware keyboard (shortcuts, terminals, the desktop); "Always Show" in ⚙ keeps it up
- Letters follow the system keyboard layout; the globe key switches it
- Auto-capitalization, double-space → ". " (both optional, ⚙ on the bottom bar), backspace repeat → word delete
- Long-press space = trackpad; slide from `.?123` onto a symbol and release
@@ -60,7 +61,7 @@ Everything else is `layouts/template.json`; `~/.local/share/sloposk/template.jso
## Settings
-`~/.config/sloposk/sloposk.conf`, also editable from the ⚙ panel: `typing/autoCapitalize`, `typing/doubleSpacePeriod`, `layout/visibleSections` (`main` plus section ids), `layout/fullKeyboard`.
+`~/.config/sloposk/sloposk.conf`, also editable from the ⚙ panel: `typing/autoCapitalize`, `typing/doubleSpacePeriod`, `layout/visibleSections` (`main` plus section ids), `layout/fullKeyboard`, `window/alwaysVisible`.
## Architecture
@@ -68,7 +69,7 @@ Everything else is `layouts/template.json`; `~/.local/share/sloposk/template.jso
qml/ view only
input/ touch → PointerTracker gestures → KeyboardController rules
engine/ key output → text ops (DirectEngine; prediction/swipe later)
-backend/ text ops → compositor (KWin input-method-v1); the compositor's keymap
+backend/ text ops → compositor (KWin input-method-v1, or fake input with no text field); the compositor's keymap
layout/ template JSON + keymap letters → Layout → LayoutModel (geometry, hit testing)
window/ floating layer-shell placement, visibility, saved geometry
app/ D-Bus control and system layouts, dev hooks
@@ -76,3 +77,5 @@ settings/ persisted user preferences
```
`sloposk_core` (everything compositor-independent, plus the `Sloposk` QML module) is a static library shared by the app and the tests.
+
+`protocols/fake-input.xml` is KDE's `org_kde_kwin_fake_input` (LGPL-2.1-or-later), vendored from plasma-wayland-protocols.
diff --git a/data/org.sloposk.keyboard.desktop b/data/org.sloposk.keyboard.desktop
index a7270a4..e12d0d4 100644
--- a/data/org.sloposk.keyboard.desktop
+++ b/data/org.sloposk.keyboard.desktop
@@ -6,3 +6,5 @@ Type=Application
Icon=input-keyboard-virtual
NoDisplay=true
X-KDE-Wayland-VirtualKeyboard=true
+# Key events for windows without a text field (fake input is restricted).
+X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input
diff --git a/protocols/fake-input.xml b/protocols/fake-input.xml
new file mode 100644
index 0000000..33734c0
--- /dev/null
+++ b/protocols/fake-input.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+ This interface allows other processes to provide fake input events.
+ Purpose is on the one hand side to provide testing facilities like XTest on X11.
+ But also to support use case like kdeconnect's mouse pad interface.
+
+ A compositor should not trust the input received from this interface.
+ Clients should not expect that the compositor honors the requests from this
+ interface.
+
+ Warning! The protocol described in this file is a desktop environment
+ implementation detail. Regular clients must not use this protocol.
+ Backward incompatible changes may be added without bumping the major
+ version of the extension.
+
+
+
+ A client should use this request to tell the compositor why it wants to
+ use this interface. The compositor might use the information to decide
+ whether it wants to grant the request. The data might also be passed to
+ the user to decide whether the application should get granted access to
+ this very privileged interface.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A client should use this request to send touch down event at specific
+ coordinates.
+
+
+
+
+
+
+
+ A client should use this request to send touch motion to specific position.
+
+
+
+
+
+
+
+ A client should use this request to send touch up event.
+
+
+
+
+
+ A client should use this request to cancel the current
+ touch event.
+
+
+
+
+ A client should use this request to send touch frame event.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/qml/SettingsPanel.qml b/qml/SettingsPanel.qml
index 6c54c2b..efc29c0 100644
--- a/qml/SettingsPanel.qml
+++ b/qml/SettingsPanel.qml
@@ -32,6 +32,14 @@ Rectangle {
onToggled: checked => panel.settings.autoCapitalize = checked
}
+ ToggleRow {
+ width: parent.width
+ text: qsTr("Always Show")
+ detail: qsTr("Type into any window, not just text fields")
+ checked: panel.settings.alwaysVisible
+ onToggled: checked => panel.settings.alwaysVisible = checked
+ }
+
ToggleRow {
width: parent.width
text: qsTr("“.” Shortcut")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6b3f034..65d15dd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -78,12 +78,14 @@ qt_add_executable(sloposk
backend/InputMethodProtocol.h
backend/KWinInputMethod.h backend/KWinInputMethod.cpp
backend/KWinInputContext.h backend/KWinInputContext.cpp
+ backend/KWinFakeInput.h backend/KWinFakeInput.cpp
backend/WaylandKeymap.h backend/WaylandKeymap.cpp
window/FloatingWindow.h window/FloatingWindow.cpp
)
qt6_generate_wayland_protocol_client_sources(sloposk PRIVATE_CODE FILES
${WAYLAND_PROTOCOLS_DIR}/unstable/input-method/input-method-unstable-v1.xml
+ ${PROJECT_SOURCE_DIR}/protocols/fake-input.xml
)
# Generated code lacks the core-protocol include (see backend/InputMethodProtocol.h).
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/qwayland-input-method-unstable-v1.cpp
diff --git a/src/backend/KWinFakeInput.cpp b/src/backend/KWinFakeInput.cpp
new file mode 100644
index 0000000..5e58a50
--- /dev/null
+++ b/src/backend/KWinFakeInput.cpp
@@ -0,0 +1,60 @@
+#include "KWinFakeInput.h"
+
+#include
+
+namespace {
+
+constexpr uint32_t Released = 0;
+constexpr uint32_t Pressed = 1;
+
+// Modifiers are held with real keys (evdev codes) so they combine with any keysym.
+struct ModifierKey {
+ Qt::KeyboardModifier flag;
+ uint32_t evdevCode;
+};
+constexpr ModifierKey ModifierKeys[] = {
+ {Qt::ControlModifier, 29}, // KEY_LEFTCTRL
+ {Qt::ShiftModifier, 42}, // KEY_LEFTSHIFT
+ {Qt::AltModifier, 56}, // KEY_LEFTALT
+ {Qt::MetaModifier, 125}, // KEY_LEFTMETA
+};
+
+} // namespace
+
+KWinFakeInput::KWinFakeInput()
+ : QWaylandClientExtensionTemplate(6) // 6: keyboard_keysym
+{
+ connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
+ if (isActive())
+ authenticate(QStringLiteral("sloposk"), QStringLiteral("On-screen keyboard: type into any window"));
+ });
+ initialize();
+}
+
+KWinFakeInput::~KWinFakeInput()
+{
+ if (isActive())
+ destroy();
+}
+
+void KWinFakeInput::sendKeysym(quint32 keysym, Qt::KeyboardModifiers modifiers)
+{
+ if (!isActive())
+ return;
+ for (const ModifierKey &key : ModifierKeys) {
+ if (modifiers & key.flag)
+ keyboard_key(key.evdevCode, Pressed);
+ }
+ keyboard_keysym(keysym, Pressed);
+ keyboard_keysym(keysym, Released);
+ for (const ModifierKey &key : ModifierKeys) {
+ if (modifiers & key.flag)
+ keyboard_key(key.evdevCode, Released);
+ }
+}
+
+void KWinFakeInput::typeText(const QString &text)
+{
+ for (const char32_t ucs : text.toUcs4())
+ sendKeysym(xkb_utf32_to_keysym(ucs));
+}
diff --git a/src/backend/KWinFakeInput.h b/src/backend/KWinFakeInput.h
new file mode 100644
index 0000000..7acb1a6
--- /dev/null
+++ b/src/backend/KWinFakeInput.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include
+#include
+
+#include "qwayland-fake-input.h"
+
+// Key events for whatever window has focus, as from a hardware keyboard:
+// KWin's fake-input protocol, which it offers to the input-method process.
+// Used when no text field is focused, so shortcuts, terminals and games work.
+class KWinFakeInput : public QWaylandClientExtensionTemplate, public QtWayland::org_kde_kwin_fake_input
+{
+ Q_OBJECT
+
+public:
+ KWinFakeInput();
+ ~KWinFakeInput() override;
+
+ // Presses and releases `keysym` with `modifiers` held. KWin finds the key
+ // (and any shift level) in the current keymap.
+ void sendKeysym(quint32 keysym, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
+ // Types each character as its keysym.
+ void typeText(const QString &text);
+};
diff --git a/src/backend/KWinInputContext.cpp b/src/backend/KWinInputContext.cpp
index 274b466..e9789f9 100644
--- a/src/backend/KWinInputContext.cpp
+++ b/src/backend/KWinInputContext.cpp
@@ -40,6 +40,7 @@ KWinInputContext::KWinInputContext(::zwp_input_method_context_v1 *object, QObjec
: QObject(parent)
, QtWayland::zwp_input_method_context_v1(object)
{
+ m_context.field = true;
announceModifiers();
}
diff --git a/src/backend/KWinInputMethod.cpp b/src/backend/KWinInputMethod.cpp
index 88ac945..a2a4bab 100644
--- a/src/backend/KWinInputMethod.cpp
+++ b/src/backend/KWinInputMethod.cpp
@@ -1,6 +1,8 @@
#include "KWinInputMethod.h"
#include "KWinInputContext.h"
+#include
+
KWinInputMethodGlobal::KWinInputMethodGlobal()
: QWaylandClientExtensionTemplate(1)
{
@@ -46,12 +48,20 @@ void KWinInputMethod::commitText(const QString &text)
{
if (m_context)
m_context->commit(text);
+ else
+ m_fakeInput.typeText(text);
}
void KWinInputMethod::replaceBeforeCursor(const QString &before, const QString &text)
{
- if (m_context)
+ if (m_context) {
m_context->replaceBeforeCursor(before, text);
+ return;
+ }
+ // Key events are one ordered stream, so plain Backspaces are safe here.
+ for (qsizetype i = 0; i < before.toUcs4().size(); ++i)
+ m_fakeInput.sendKeysym(XKB_KEY_BackSpace);
+ m_fakeInput.typeText(text);
}
void KWinInputMethod::setPreedit(const QString &text)
@@ -64,6 +74,8 @@ void KWinInputMethod::sendKeysym(quint32 keysym, Qt::KeyboardModifiers modifiers
{
if (m_context)
m_context->sendKeysym(keysym, modifiers);
+ else
+ m_fakeInput.sendKeysym(keysym, modifiers);
}
void KWinInputMethod::activate(::zwp_input_method_context_v1 *context)
diff --git a/src/backend/KWinInputMethod.h b/src/backend/KWinInputMethod.h
index d4acc2a..b2999ca 100644
--- a/src/backend/KWinInputMethod.h
+++ b/src/backend/KWinInputMethod.h
@@ -1,5 +1,6 @@
#pragma once
+#include "KWinFakeInput.h"
#include "TextBackend.h"
#include "InputMethodProtocol.h"
@@ -28,6 +29,9 @@ protected:
void zwp_input_method_v1_deactivate(::zwp_input_method_context_v1 *context) override;
};
+// Types into the focused text field through the input method; with none
+// focused, sends key events to the focused window through fake input, so the
+// keyboard works everywhere (shortcuts, terminals, the desktop).
class KWinInputMethod : public TextBackend
{
Q_OBJECT
@@ -55,5 +59,6 @@ private:
void deactivate();
KWinInputMethodGlobal m_global;
+ KWinFakeInput m_fakeInput;
std::unique_ptr m_context;
};
diff --git a/src/backend/TextContext.h b/src/backend/TextContext.h
index a7d570b..b59a146 100644
--- a/src/backend/TextContext.h
+++ b/src/backend/TextContext.h
@@ -23,6 +23,7 @@ struct TextContext {
Multiline = 0x200,
};
+ bool field = false; // a text field is focused; without one, keys go to the window as from a keyboard
bool hasSurroundingText = false;
QString textBeforeCursor;
Purpose purpose = Purpose::Normal;
@@ -33,7 +34,7 @@ struct TextContext {
{
const bool prosePurpose = purpose == Purpose::Normal || purpose == Purpose::Alpha
|| purpose == Purpose::Name;
- return prosePurpose && !(hints & (HiddenText | SensitiveData));
+ return field && prosePurpose && !(hints & (HiddenText | SensitiveData));
}
bool allowsAutoCapitalization() const { return isProse() && !(hints & Lowercase); }
diff --git a/src/input/KeyboardController.cpp b/src/input/KeyboardController.cpp
index 1cb298a..8ae6643 100644
--- a/src/input/KeyboardController.cpp
+++ b/src/input/KeyboardController.cpp
@@ -141,12 +141,11 @@ void KeyboardController::moveCursor(QPoint steps)
repeat(qMax(0, -steps.y()), XKB_KEY_Up);
}
-void KeyboardController::resetContext()
+void KeyboardController::resetContext(const TextContext &context)
{
m_deadKeys.cancel();
- m_context = {};
m_textBefore.clear();
- autoCapitalize();
+ setContext(context);
}
void KeyboardController::setContext(const TextContext &context)
diff --git a/src/input/KeyboardController.h b/src/input/KeyboardController.h
index ae76a5d..92f83f5 100644
--- a/src/input/KeyboardController.h
+++ b/src/input/KeyboardController.h
@@ -53,8 +53,9 @@ public:
// Space-bar trackpad: move the text cursor by whole steps.
void moveCursor(QPoint steps);
- // A field was focused or unfocused: forget what we knew about the text.
- void resetContext();
+ // A field was focused (context.field) or unfocused: forget what we knew
+ // about the text and start over from what it reported.
+ void resetContext(const TextContext &context);
// The focused field reported new text/content type.
void setContext(const TextContext &context);
void setOptions(const TypingOptions &options);
diff --git a/src/main.cpp b/src/main.cpp
index 9e8fe37..e05f918 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -85,9 +85,12 @@ void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settin
[&] { settings.save(placement.screenName(), placement.geometry()); });
}
-void connectVisibility(VisibilityController &visibility, KWinInputMethod &backend,
+void connectVisibility(VisibilityController &visibility, KWinInputMethod &backend, const Settings &settings,
KeyboardController &controller, FloatingWindow &placement)
{
+ visibility.setAlwaysVisible(settings.alwaysVisible());
+ QObject::connect(&settings, &Settings::alwaysVisibleChanged, &visibility,
+ [&] { visibility.setAlwaysVisible(settings.alwaysVisible()); });
// Without the input-method global (run outside KWin's control, e.g. for UI
// work) there is no focus signal: stay visible so it can be tried out.
const auto updateStandalone = [&] { visibility.setStandalone(!backend.isAvailable()); };
@@ -102,7 +105,8 @@ void connectVisibility(VisibilityController &visibility, KWinInputMethod &backen
void connectTextContext(KWinInputMethod &backend, KeyboardController &controller)
{
- QObject::connect(&backend, &TextBackend::activeChanged, &controller, &KeyboardController::resetContext);
+ QObject::connect(&backend, &TextBackend::activeChanged, &controller,
+ [&] { controller.resetContext(backend.context()); });
QObject::connect(&backend, &TextBackend::contextChanged, &controller,
[&] { controller.setContext(backend.context()); });
}
@@ -158,7 +162,7 @@ int main(int argc, char *argv[])
connectKeySettings(settings, model, placement, *window, rebuildLayout);
VisibilityController visibility;
- connectVisibility(visibility, backend, controller, placement);
+ connectVisibility(visibility, backend, settings, controller, placement);
DBusControl dbus(visibility);
if (!dbus.registerOnSessionBus())
diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp
index 1370cb5..40f6c3b 100644
--- a/src/settings/Settings.cpp
+++ b/src/settings/Settings.cpp
@@ -9,6 +9,7 @@ constexpr auto AutoCapitalizeKey = "typing/autoCapitalize";
constexpr auto DoubleSpacePeriodKey = "typing/doubleSpacePeriod";
constexpr auto VisibleSectionsKey = "layout/visibleSections";
constexpr auto FullKeyboardKey = "layout/fullKeyboard";
+constexpr auto AlwaysVisibleKey = "window/alwaysVisible";
}
Settings::Settings(QObject *parent)
@@ -22,6 +23,16 @@ Settings::Settings(QObject *parent)
if (m_visibleSections.isEmpty())
m_visibleSections = {MainSectionId};
m_fullKeyboard = store.value(FullKeyboardKey, false).toBool();
+ m_alwaysVisible = store.value(AlwaysVisibleKey, false).toBool();
+}
+
+void Settings::setAlwaysVisible(bool always)
+{
+ if (always == m_alwaysVisible)
+ return;
+ m_alwaysVisible = always;
+ QSettings().setValue(AlwaysVisibleKey, always);
+ Q_EMIT alwaysVisibleChanged();
}
void Settings::setFullKeyboard(bool full)
diff --git a/src/settings/Settings.h b/src/settings/Settings.h
index e7af02a..d2a2e3b 100644
--- a/src/settings/Settings.h
+++ b/src/settings/Settings.h
@@ -19,6 +19,8 @@ class Settings : public QObject
Q_PROPERTY(QStringList visibleSections READ visibleSections NOTIFY visibleSectionsChanged)
// iPad Pro-style full keyboard (number row, tab, ctrl/alt/super) instead of the compact one.
Q_PROPERTY(bool fullKeyboard READ fullKeyboard WRITE setFullKeyboard NOTIFY fullKeyboardChanged)
+ // Shown even without a focused text field, to type into any window.
+ Q_PROPERTY(bool alwaysVisible READ alwaysVisible WRITE setAlwaysVisible NOTIFY alwaysVisibleChanged)
public:
explicit Settings(QObject *parent = nullptr);
@@ -36,11 +38,14 @@ public:
bool fullKeyboard() const { return m_fullKeyboard; }
void setFullKeyboard(bool full);
+ bool alwaysVisible() const { return m_alwaysVisible; }
+ void setAlwaysVisible(bool always);
Q_SIGNALS:
void typingOptionsChanged();
void visibleSectionsChanged();
void fullKeyboardChanged();
+ void alwaysVisibleChanged();
private:
void setTypingOptions(const TypingOptions &options);
@@ -48,4 +53,5 @@ private:
TypingOptions m_typing;
QStringList m_visibleSections;
bool m_fullKeyboard = false;
+ bool m_alwaysVisible = false;
};
diff --git a/src/window/VisibilityController.cpp b/src/window/VisibilityController.cpp
index 301e4ad..2384c05 100644
--- a/src/window/VisibilityController.cpp
+++ b/src/window/VisibilityController.cpp
@@ -15,6 +15,13 @@ void VisibilityController::setStandalone(bool standalone)
update();
}
+void VisibilityController::setAlwaysVisible(bool always)
+{
+ m_alwaysVisible = always;
+ m_override.reset();
+ update();
+}
+
void VisibilityController::show()
{
m_override = true;
@@ -35,7 +42,7 @@ void VisibilityController::toggle()
void VisibilityController::update()
{
- const bool visible = m_override.value_or(m_standalone || m_fieldActive);
+ const bool visible = m_override.value_or(m_alwaysVisible || m_standalone || m_fieldActive);
if (visible == m_visible)
return;
m_visible = visible;
diff --git a/src/window/VisibilityController.h b/src/window/VisibilityController.h
index 5aad089..19e48bd 100644
--- a/src/window/VisibilityController.h
+++ b/src/window/VisibilityController.h
@@ -5,7 +5,8 @@
// Decides whether the keyboard is shown:
// - while a text field is focused
-// - always, when running standalone (no input-method connection)
+// - 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
class VisibilityController : public QObject
{
@@ -18,6 +19,7 @@ public:
void setFieldActive(bool active);
void setStandalone(bool standalone);
+ void setAlwaysVisible(bool always);
void show();
void hide();
@@ -31,6 +33,7 @@ private:
bool m_fieldActive = false;
bool m_standalone = false;
+ bool m_alwaysVisible = false;
std::optional m_override;
bool m_visible = false;
};
diff --git a/tests/tst_controller.cpp b/tests/tst_controller.cpp
index f927734..e347ec3 100644
--- a/tests/tst_controller.cpp
+++ b/tests/tst_controller.cpp
@@ -10,6 +10,10 @@ using namespace Qt::StringLiterals;
namespace {
+const TextContext Field{.field = true};
+// A field with some text already: no auto-shift until a test asks for it.
+const TextContext MidSentence{.field = true, .hasSurroundingText = true, .textBeforeCursor = u"so"_s};
+
KeyDef makeKey(KeyType type, const QString &label = {})
{
KeyDef k;
@@ -44,6 +48,7 @@ private Q_SLOTS:
model.setLayout(TestLayouts::build("us"));
model.setVisibleSections({MainSectionId});
controller = std::make_unique(engine, model);
+ controller->resetContext(MidSentence); // most tests type into a text field
}
void typesLowercase()
@@ -103,7 +108,7 @@ private Q_SLOTS:
void capitalizesAtSentenceStart()
{
- controller->resetContext();
+ controller->resetContext(Field);
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Once);
for (const char *c : {"h", "i", ".", " "})
tap(makeKey(KeyType::Character, QString::fromLatin1(c)));
@@ -114,7 +119,7 @@ private Q_SLOTS:
void noCapitalizationInPasswordFields()
{
- TextContext context;
+ TextContext context = Field;
context.purpose = TextContext::Purpose::Password;
controller->setContext(context);
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Off);
@@ -122,7 +127,7 @@ private Q_SLOTS:
void fieldTextDrivesCapitalization()
{
- TextContext context;
+ TextContext context = Field;
context.hasSurroundingText = true;
context.textBeforeCursor = u"Done! "_s;
controller->setContext(context);
@@ -152,12 +157,12 @@ private Q_SLOTS:
tap(makeKey(KeyType::Character, u"a"_s));
tap(space);
tap(space);
- QCOMPARE(engine.output, u"A ");
+ QCOMPARE(engine.output, u"a ");
}
void autoCapitalizationCanBeTurnedOff()
{
- controller->resetContext();
+ controller->resetContext(Field);
controller->setOptions({.autoCapitalize = false});
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Off);
tap(makeKey(KeyType::Character, u"a"_s));
@@ -166,7 +171,7 @@ private Q_SLOTS:
void doubleTapShiftLocksAtSentenceStart()
{
- controller->resetContext(); // a field was focused
+ controller->resetContext(Field); // a field was focused
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Once); // auto-shift
const KeyDef shift = makeKey(KeyType::Shift);
controller->keyDown(shift, 5000);
@@ -281,7 +286,7 @@ private Q_SLOTS:
void autoShiftAndCapsLockDontApplyToFunctionKeys()
{
- controller->resetContext(); // sentence start: auto-shift on
+ controller->resetContext(Field); // sentence start: auto-shift on
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Once);
tap(keysymKey(XKB_KEY_Left));
QCOMPARE(engine.lastModifiers, Qt::NoModifier);
@@ -303,10 +308,10 @@ private Q_SLOTS:
lj.shifted = u"2"_s;
KeyDef one = makeKey(KeyType::Character, u"+"_s);
one.shifted = u"1"_s;
- controller->resetContext(); // sentence start
+ controller->resetContext(Field); // sentence start
tap(one);
QCOMPARE(engine.output, u"+"); // not the shifted level
- controller->resetContext();
+ controller->resetContext(Field);
tap(lj);
QCOMPARE(engine.output, u"+Ľ");
tap(makeKey(KeyType::Shift)); // the user's shift does pick it
@@ -325,6 +330,17 @@ private Q_SLOTS:
QVERIFY(engine.output.endsWith(u"é"_s));
}
+ void noTextRulesWithoutAField()
+ {
+ controller->resetContext({}); // keys go to a window, not a text field
+ QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Off);
+ const KeyDef space = makeKey(KeyType::Space);
+ tap(makeKey(KeyType::Character, u"a"_s));
+ tap(space);
+ tap(space);
+ QCOMPARE(engine.output, u"a ");
+ }
+
void dismissEmitsSignal()
{
int dismissed = 0;
diff --git a/tests/tst_visibility.cpp b/tests/tst_visibility.cpp
index f9fe7ac..a823e48 100644
--- a/tests/tst_visibility.cpp
+++ b/tests/tst_visibility.cpp
@@ -27,6 +27,18 @@ private Q_SLOTS:
QVERIFY(v.isVisible());
}
+ void alwaysVisibleShowsWithoutField()
+ {
+ VisibilityController v;
+ v.setAlwaysVisible(true);
+ QVERIFY(v.isVisible());
+ v.hide(); // dismiss key still works...
+ QVERIFY(!v.isVisible());
+ v.setFieldActive(true); // ...until the focus changes
+ v.setFieldActive(false);
+ QVERIFY(v.isVisible());
+ }
+
void toggleShowsWithoutField()
{
VisibilityController v;