Reword code comments in plain technical language
Comments only, no code changes. Replaces vague or personified phrasing with explicit statements of what the code does and why, uses bullet lists for multi-case behavior, and fixes the settings path in WindowSettings.h (~/.config/sloposk/sloposk.conf). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
// A small on/off button in the grab bar; lit while on. The icon goes inside
|
||||
// and should draw in `foreground`.
|
||||
// On/off button in the grab bar, highlighted while on.
|
||||
// Child items form the icon and should use `foreground` as their color.
|
||||
Item {
|
||||
id: toggle
|
||||
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
|
||||
// The grab bar under the floating keyboard. Moving the window is up to the
|
||||
// owner (the window manager's move, so it works for mouse and touch alike).
|
||||
// Grab bar below the keyboard. Only emits dragStarted(); the parent starts
|
||||
// the window manager's move (works for mouse and touch).
|
||||
Item {
|
||||
id: handle
|
||||
|
||||
// Narrower when the bar's buttons leave less room in the middle.
|
||||
// Set smaller when the bar's buttons take up more width.
|
||||
property real pillWidth: 120
|
||||
signal dragStarted()
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
|
||||
// The dismiss key left floating while the keyboard is shrunk; see-through so
|
||||
// it hides less. Tap to bring the keyboard back; dragging is up to the owner
|
||||
// (the window manager's move).
|
||||
// Button shown in place of the keyboard while it is collapsed. Semi-transparent
|
||||
// so it covers less. Tap emits tapped() (expand); drag emits dragStarted() and
|
||||
// the parent starts the window manager's move.
|
||||
Item {
|
||||
id: button
|
||||
|
||||
@@ -28,7 +28,7 @@ Item {
|
||||
|
||||
TapHandler {
|
||||
id: tap
|
||||
// The move ends with a release that would otherwise count as a tap.
|
||||
// Set during a drag, so the release that ends the drag isn't treated as a tap.
|
||||
property bool moved: false
|
||||
onPressedChanged: if (pressed) moved = false
|
||||
onTapped: if (!moved) button.tapped()
|
||||
|
||||
+3
-3
@@ -30,9 +30,9 @@ Item {
|
||||
return modifierModes[modifier] ?? KeyboardController.Off
|
||||
return KeyboardController.Off
|
||||
}
|
||||
// Shift shows locking with its glyph; the others turn accent-coloured.
|
||||
// Accent color for locked modifiers. Shift is excluded: its glyph shows the lock.
|
||||
readonly property bool locked: latch === KeyboardController.Locked && keyType !== "shift"
|
||||
// Character keys darken when pressed; function keys lighten (as on iPad).
|
||||
// Light background: character keys at rest, function keys while pressed or latched (as on iPad).
|
||||
readonly property bool lit: (isCharacter !== pressed) || latch !== KeyboardController.Off
|
||||
|
||||
readonly property var defaultGlyphs: ({
|
||||
@@ -46,7 +46,7 @@ Item {
|
||||
return ["⇧", "⬆", "⇪"][shiftMode]
|
||||
return label !== "" ? label : (defaultGlyphs[keyType] ?? "")
|
||||
}
|
||||
// Count code points, not UTF-16 units, so emoji glyphs aren't treated as words.
|
||||
// Multi-character label (e.g. "return"). Counts code points, not UTF-16 units, so an emoji counts as one.
|
||||
readonly property bool isWord: [...displayLabel].length > 1 && !isCharacter
|
||||
|
||||
x: keyX
|
||||
|
||||
+9
-9
@@ -12,36 +12,36 @@ Window {
|
||||
property var collapse: null // CollapseController, set from C++
|
||||
readonly property bool collapsed: collapse?.collapsed ?? false
|
||||
property bool settingsOpen: false
|
||||
// Height of the key area, for C++ keeping key sizes when rows change.
|
||||
// Read by C++ to keep key size constant when rows are added or removed.
|
||||
readonly property real keysHeight: keyboard.height
|
||||
|
||||
// Transparent room above the keyboard for long-press popups on the top row.
|
||||
// Transparent space above the keyboard for the top row's long-press popups.
|
||||
readonly property real popupMargin: 90
|
||||
// Outside this, input passes through to whatever is below the window.
|
||||
// Input outside this rect goes to the window underneath.
|
||||
readonly property rect inputRegion: collapsed
|
||||
? Qt.rect(floatingButton.x, floatingButton.y, floatingButton.width, floatingButton.height)
|
||||
: keyboard.alternates.open
|
||||
? Qt.rect(0, 0, width, height)
|
||||
: Qt.rect(panel.x, panel.y, panel.width, panel.height)
|
||||
// Empty when nothing shows through, so the compositor skips the blur.
|
||||
// Empty (no blur) when collapsed, blur is off, or the background is opaque.
|
||||
readonly property rect blurRegion: !collapsed && settings.blurBackground && settings.backgroundOpacity < 1
|
||||
? Qt.rect(panel.x, panel.y, panel.width, panel.height)
|
||||
: Qt.rect(0, 0, 0, 0)
|
||||
|
||||
width: 1000
|
||||
height: 340 + popupMargin
|
||||
// None while shrunk to the floating button.
|
||||
// No minimum while collapsed, so the window can shrink to the button.
|
||||
minimumWidth: collapsed ? 0 : 480
|
||||
minimumHeight: collapsed ? 0 : 180 + popupMargin
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
// Alt+F4 or the task manager would hide it behind the visibility
|
||||
// controller's back; the dismiss key and the tray toggle hide it instead.
|
||||
// Ignore close requests (Alt+F4, task manager): they would hide the window
|
||||
// without VisibilityController knowing. Hiding goes through the dismiss key or tray toggle.
|
||||
onClosing: close => close.accepted = false
|
||||
onInputRegionChanged: placement?.setInputRegion(inputRegion)
|
||||
onBlurRegionChanged: placement?.setBlurRegion(blurRegion, Theme.windowRadius)
|
||||
// For CollapseController.
|
||||
// Called by CollapseController.
|
||||
function dismissKeyRect() {
|
||||
return keyboard.keyRect("dismiss");
|
||||
}
|
||||
@@ -107,7 +107,7 @@ Window {
|
||||
FloatingButton {
|
||||
id: floatingButton
|
||||
readonly property rect keyRect: root.collapse?.keyRect ?? Qt.rect(0, 0, 0, 0)
|
||||
// Over the key while the window is full size, then in the shrunk window.
|
||||
// keyRect is in full-window coordinates; the clamp keeps the button inside the window once it has shrunk.
|
||||
x: Math.max(0, Math.min(keyRect.x, root.width - width))
|
||||
y: Math.max(0, Math.min(keyRect.y, root.height - height))
|
||||
width: keyRect.width
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import Sloposk
|
||||
|
||||
// One-tap switches for the most changed key options, beside the gear.
|
||||
// Icons are drawn from boxes so they don't depend on the fonts installed.
|
||||
// Toggle buttons for frequently changed key options, next to the settings button.
|
||||
// Icons are built from Rectangles, so they don't depend on installed fonts.
|
||||
Row {
|
||||
id: toggles
|
||||
|
||||
@@ -13,8 +13,8 @@ Row {
|
||||
return layoutModel.sections.some(s => s.id === id);
|
||||
}
|
||||
|
||||
// Shows or hides one of the template's optional sections; absent from
|
||||
// layouts without it.
|
||||
// Shows or hides an optional template section. Not displayed if the
|
||||
// layout has no such section.
|
||||
component SectionToggle: BarToggle {
|
||||
required property string section
|
||||
|
||||
@@ -23,7 +23,7 @@ Row {
|
||||
onToggled: checked => toggles.settings.setSectionVisible(section, checked)
|
||||
}
|
||||
|
||||
// A grid of tiny keys, optionally in a keyboard outline.
|
||||
// Icon: grid of small keys, optionally inside a keyboard outline.
|
||||
component KeyGrid: Item {
|
||||
id: keyGrid
|
||||
required property color color
|
||||
@@ -58,7 +58,7 @@ Row {
|
||||
}
|
||||
}
|
||||
|
||||
// A key outline with a label on it.
|
||||
// Icon: key outline with a text label.
|
||||
component LabelledKey: Rectangle {
|
||||
property alias label: caption.text
|
||||
property alias textColor: caption.color
|
||||
@@ -90,7 +90,7 @@ Row {
|
||||
label: qsTr("Hold Keys")
|
||||
checked: toggles.settings.holdKeys
|
||||
onToggled: checked => toggles.settings.holdKeys = checked
|
||||
// A fingertip resting on a key, holding it down.
|
||||
// Icon: finger pressing a key (hold-keys mode).
|
||||
Rectangle {
|
||||
anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom }
|
||||
width: 18
|
||||
@@ -119,7 +119,7 @@ Row {
|
||||
id: navigation
|
||||
section: "navigation"
|
||||
label: qsTr("Navigation Keys")
|
||||
// The inverted T of the arrow keys.
|
||||
// Icon: arrow keys in an inverted T.
|
||||
Repeater {
|
||||
model: [[1, 0], [0, 1], [1, 1], [2, 1]]
|
||||
Rectangle {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
|
||||
// Corner grip. Resizing is up to the owner (the window manager's resize).
|
||||
// Corner grip. Only emits dragStarted(); the parent starts the window manager's resize.
|
||||
Item {
|
||||
id: grip
|
||||
|
||||
@@ -28,7 +28,7 @@ Item {
|
||||
|
||||
DragHandler {
|
||||
target: null
|
||||
// The grab bar lies underneath and would take the drag over.
|
||||
// Prevent the grab bar underneath from taking over the drag.
|
||||
grabPermissions: PointerHandler.TakeOverForbidden
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
onActiveChanged: if (active) grip.dragStarted()
|
||||
|
||||
@@ -5,7 +5,7 @@ Item {
|
||||
id: button
|
||||
|
||||
property bool checked: false
|
||||
// Something doesn't work (see Diagnostics): a dot invites a look in settings.
|
||||
// Shows a dot when Diagnostics reports a problem.
|
||||
property bool alert: false
|
||||
signal clicked()
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import Sloposk
|
||||
|
||||
// Replaces the keys while open, over the window's own background so
|
||||
// opacity changes show live. Binds straight to Settings, which persists.
|
||||
// Shown instead of the keys while open. Drawn on the window background, so
|
||||
// opacity changes are visible immediately. Bound directly to Settings, which saves to disk.
|
||||
Flickable {
|
||||
id: panel
|
||||
|
||||
@@ -113,7 +113,7 @@ Flickable {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
// "main" is the typing keys; the rest come from the template.
|
||||
// "main" = the typing keys; other ids are template sections.
|
||||
model: [{ id: "main", name: qsTr("Typing keys") }].concat(panel.layoutModel.sections)
|
||||
|
||||
ToggleRow {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
|
||||
// A labelled slider in the iOS style; value runs from `from` to 1.
|
||||
// iOS-style labelled slider; value range is `from`..1.
|
||||
Item {
|
||||
id: row
|
||||
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ QString fakeInputProblem(const KWinFakeInput &fakeInput)
|
||||
|
||||
void watchInputMethod(Diagnostics &diagnostics, const KWinInputMethod &backend, bool launchedByKWin)
|
||||
{
|
||||
// Qt binds globals KWin already announced on construction, so the first
|
||||
// check is final unless KWin restarts.
|
||||
// Qt binds the globals KWin has already announced during construction, so
|
||||
// this result only changes if KWin restarts.
|
||||
const auto checkInputMethod = [&] {
|
||||
diagnostics.check(QStringLiteral("input-method"), launchedByKWin && backend.isAvailable(),
|
||||
tr("Not running as KWin's virtual keyboard, so it can't type into text fields: "
|
||||
|
||||
+4
-5
@@ -4,11 +4,10 @@ class Diagnostics;
|
||||
class KWinInputMethod;
|
||||
class SystemLayouts;
|
||||
|
||||
// Reports to `diagnostics` what this session doesn't allow, and keeps the
|
||||
// reports current as protocols and services come and go.
|
||||
// KWin offers the input-method global to every client but only activates
|
||||
// the process it launched, which it hands a connection: `launchedByKWin` is
|
||||
// whether WAYLAND_SOCKET was set at startup.
|
||||
// Reports missing protocols and D-Bus services to `diagnostics`, and updates
|
||||
// the reports when they appear or disappear.
|
||||
// `launchedByKWin`: WAYLAND_SOCKET was set at startup. KWin advertises the
|
||||
// input-method global to every client, but only activates the process it launched.
|
||||
void watchInputMethod(Diagnostics &diagnostics, const KWinInputMethod &backend, bool launchedByKWin);
|
||||
void watchSystemLayouts(Diagnostics &diagnostics, const SystemLayouts &layouts);
|
||||
void reportDBusControl(Diagnostics &diagnostics, bool registered);
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#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.
|
||||
// Queries KWin's virtual keyboard policy (tray toggle, System Settings →
|
||||
// Virtual Keyboard) over org.kde.KWin /VirtualKeyboard.
|
||||
// Needed because KWin activates the input method for every text field and
|
||||
// applies the policy only to the input panel surface, which sloposk doesn't use.
|
||||
class KWinVirtualKeyboard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -15,9 +15,11 @@ class KWinVirtualKeyboard : public QObject
|
||||
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.
|
||||
// Asynchronously asks KWin whether to show the keyboard for the currently
|
||||
// focused field and calls `answer` with the result:
|
||||
// - false if the virtual keyboard is disabled
|
||||
// - in the default (touch-only) mode, true only if the last input was touch or pen
|
||||
// - true if KWin can't be reached
|
||||
void askShowOnActive(QObject *context, std::function<void(bool)> answer);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
@@ -23,7 +23,7 @@ SystemLayouts::SystemLayouts(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
// Follows the service's owner, so it survives KWin restarting.
|
||||
// Watches the service owner, so it reconnects after KWin restarts.
|
||||
bus.connect(Service, Path, Interface, QStringLiteral("layoutChanged"), this, SLOT(setCurrent(uint)));
|
||||
auto *watcher = new QDBusServiceWatcher(Service, bus, QDBusServiceWatcher::WatchForOwnerChange, this);
|
||||
connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &SystemLayouts::fetchCurrent);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
// Which of the system's keyboard layouts is active, followed and switched
|
||||
// over KWin's org.kde.keyboard D-Bus interface, so the on-screen and the
|
||||
// physical keyboard always agree. Without that service it switches locally,
|
||||
// until the service appears (KWin starting late or restarting).
|
||||
// Tracks and switches the active system keyboard layout via KWin's
|
||||
// org.kde.keyboard D-Bus interface, so the on-screen and physical keyboards
|
||||
// use the same layout. While the service is missing (KWin not started yet or
|
||||
// restarting), switching only changes the local index.
|
||||
class SystemLayouts : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void currentChanged();
|
||||
// After each attempt to reach the service, and when it goes away.
|
||||
// Emitted after each attempt to reach the service, and when it disappears.
|
||||
void serviceChecked(bool available);
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
#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.
|
||||
// Sends key events to the focused window, as a hardware keyboard would, via
|
||||
// KWin's fake-input protocol (offered to the input-method process).
|
||||
// Used when no text field is focused, so shortcuts, terminals and games work.
|
||||
class KWinFakeInput : public QWaylandClientExtensionTemplate<KWinFakeInput>, public QtWayland::org_kde_kwin_fake_input
|
||||
{
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void typeText(const QString &text);
|
||||
// KWin offers the protocol only to clients whose desktop file lists it
|
||||
// (see the .desktop.in); isActive() says whether it did.
|
||||
// keyboard_keysym, which everything here uses, needs this version.
|
||||
// Minimum version: required for keyboard_keysym, which all requests here use.
|
||||
static constexpr uint32_t RequiredVersion = 6;
|
||||
bool canSendKeysyms() const;
|
||||
// The version KWin bound, which may be older than the one asked for:
|
||||
|
||||
@@ -58,7 +58,7 @@ void KWinInputMethod::replaceBeforeCursor(const QString &before, const QString &
|
||||
m_context->replaceBeforeCursor(before, text);
|
||||
return;
|
||||
}
|
||||
// Key events are one ordered stream, so plain Backspaces are safe here.
|
||||
// Key events are delivered in order, so sending Backspace keys is safe here.
|
||||
for (qsizetype i = 0; i < before.toUcs4().size(); ++i)
|
||||
m_fakeInput.sendKeysym(XKB_KEY_BackSpace);
|
||||
m_fakeInput.typeText(text);
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
// False when the compositor didn't hand us the input-method global.
|
||||
bool isAvailable() const;
|
||||
// Key events outside text fields, held keys and modifier combos.
|
||||
// Used for key events outside text fields, held keys and modifier combos.
|
||||
const KWinFakeInput &fakeInput() const { return m_fakeInput; }
|
||||
|
||||
bool isActive() const override;
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
KWinInputMethodGlobal m_global;
|
||||
KWinFakeInput m_fakeInput;
|
||||
std::unique_ptr<KWinInputContext> m_context;
|
||||
// Replaced by an activate before KWin deactivated it; kept until it does.
|
||||
// Context replaced by a new activate before KWin deactivated it. Kept
|
||||
// until KWin sends its deactivate, then destroyed.
|
||||
std::unique_ptr<KWinInputContext> m_previous;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
virtual void setKeyPressed(quint32 keysym, bool pressed) = 0;
|
||||
|
||||
Q_SIGNALS:
|
||||
// Also when focus moves straight from one field to another.
|
||||
// Also emitted when focus moves directly from one field to another.
|
||||
void activeChanged();
|
||||
void contextChanged();
|
||||
};
|
||||
|
||||
@@ -29,7 +29,8 @@ struct TextContext {
|
||||
Purpose purpose = Purpose::Normal;
|
||||
quint32 hints = 0;
|
||||
|
||||
// Prose fields get iOS conveniences; codes, addresses and secrets don't.
|
||||
// Whether typing aids (auto-capitalization, double-space period) apply:
|
||||
// yes for normal text and names, no for passwords, URLs, emails, numbers etc.
|
||||
bool isProse() const
|
||||
{
|
||||
const bool prosePurpose = purpose == Purpose::Normal || purpose == Purpose::Alpha
|
||||
@@ -37,8 +38,8 @@ struct TextContext {
|
||||
return field && prosePurpose && !(hints & (HiddenText | SensitiveData));
|
||||
}
|
||||
|
||||
// Fields that state any text preference must ask for capitals; most that
|
||||
// state none (hints 0) still want them.
|
||||
// Fields that set any text hint get auto-capitalization only if they set
|
||||
// AutoCapitalization. Fields with no hints get it by default.
|
||||
bool allowsAutoCapitalization() const
|
||||
{
|
||||
constexpr quint32 Preferences = AutoCompletion | AutoCorrection | AutoCapitalization | Lowercase
|
||||
|
||||
@@ -26,10 +26,11 @@ public:
|
||||
int selected() const { return m_selected; }
|
||||
QString selectedText() const { return m_choices.value(m_selected); }
|
||||
|
||||
// Places one key-sized cell per choice just above the key, starting over it
|
||||
// and extending right (shifted left near the edge to stay within
|
||||
// [0, boundsWidth]; lowered, over the key, to stay below minTop). The cell
|
||||
// above the finger starts selected.
|
||||
// Lays out one key-sized cell per choice in a row above the key, starting
|
||||
// at the key's x and extending right.
|
||||
// - shifted left if needed to stay within [0, boundsWidth]
|
||||
// - moved down (overlapping the key) if needed to stay below minTop
|
||||
// Selects the cell above the finger.
|
||||
void open(const QRectF &keyRect, const QStringList &choices, qreal boundsWidth, qreal minTop, qreal gap);
|
||||
void selectAt(qreal x);
|
||||
void close();
|
||||
|
||||
@@ -51,7 +51,8 @@ QString DeadKeys::press(quint32 deadKeysym)
|
||||
return composed;
|
||||
}
|
||||
case XKB_COMPOSE_CANCELLED: {
|
||||
// No sequence goes on like this: type the old accents, start anew.
|
||||
// No compose sequence continues with this key: flush the pending accents
|
||||
// and start a new sequence.
|
||||
const QString accents = m_pending;
|
||||
cancel();
|
||||
return accents + press(deadKeysym);
|
||||
|
||||
@@ -17,12 +17,13 @@ public:
|
||||
~DeadKeys();
|
||||
|
||||
bool isPending() const { return !m_pending.isEmpty(); }
|
||||
// A dead key was typed: starts or extends the sequence. Returns text to
|
||||
// type now: what two dead keys composed (´´ → ´), or the accents of a
|
||||
// sequence the new one can't continue; empty while still pending.
|
||||
// Dead key pressed: starts or extends the sequence. Returns text to type now:
|
||||
// - the result if the dead keys composed (´´ → ´)
|
||||
// - the pending accents if this key can't continue the sequence
|
||||
// - empty while the sequence is still pending
|
||||
QString press(quint32 deadKeysym);
|
||||
// `text` typed while pending: the composed character, or the accents
|
||||
// followed by `text` when nothing composes. Ends the sequence.
|
||||
// Text key pressed while a sequence is pending. Returns the composed
|
||||
// character, or the pending accents + `text` if nothing composes. Ends the sequence.
|
||||
QString compose(const QString &text);
|
||||
void cancel();
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
|
||||
class InputEngine;
|
||||
|
||||
// The "hold keys" mode: the keyboard acts like a hardware one. A key is down
|
||||
// while the finger is, so apps see held keys (games, Shift+click, the
|
||||
// system's key repeat). Shift, Ctrl, Alt, Super and AltGr are plain keys, caps lock
|
||||
// is the system's, and dead keys compose in the app; none of the touch
|
||||
// conveniences (sticky shift, auto-capitalization, accents) apply.
|
||||
// "Hold keys" mode: behaves like a hardware keyboard.
|
||||
// - a key is pressed while touched and released on lift, so apps see held
|
||||
// keys (games, Shift+click, system key repeat)
|
||||
// - Shift, Ctrl, Alt, Super and AltGr are normal keys; caps lock is the system's
|
||||
// - dead keys are sent as-is and composed by the app
|
||||
// - no sticky modifiers, auto-capitalization or long-press accents
|
||||
class HardwareKeys
|
||||
{
|
||||
public:
|
||||
@@ -25,7 +26,7 @@ public:
|
||||
// Lets go of everything still down, e.g. when the mode is turned off.
|
||||
void releaseAll();
|
||||
|
||||
// For the view: shift and AltGr shown while held, caps lock while on.
|
||||
// For the view: shift/AltGr are on while held, caps lock while active.
|
||||
KeyLevel keyLevel() const;
|
||||
bool isShiftHeld() const;
|
||||
bool isCapsLockOn() const { return m_capsLock; }
|
||||
|
||||
@@ -14,10 +14,10 @@ static_assert(int(KeyboardController::ShiftMode::Locked) == int(ShiftState::Mode
|
||||
namespace {
|
||||
// Enough history for the text rules; fields may report megabytes.
|
||||
constexpr qsizetype MaxTextBefore = 256;
|
||||
// Stands for text we can't see: neither a sentence start nor a word, so no
|
||||
// rule fires on it. For fields that don't report text, after cursor moves.
|
||||
// Placeholder for unknown text before the cursor (field doesn't report text,
|
||||
// or the cursor moved). Neither a sentence start nor a word, so no text rule triggers.
|
||||
constexpr QChar UnknownText = u'\uFFFC';
|
||||
// Like a hardware key: no word mode, a faster rate than Backspace.
|
||||
// Same as a hardware key repeat: no word mode, faster than Backspace.
|
||||
constexpr KeyRepeater::Timing KeysymRepeat{.intervalMs = 50, .wordModeAfterMs = std::numeric_limits<int>::max()};
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs)
|
||||
break;
|
||||
case KeyType::Backspace:
|
||||
if (m_deadKeys.isPending()) {
|
||||
m_deadKeys.cancel(); // backspace takes back the accent
|
||||
m_deadKeys.cancel(); // backspace discards the pending accent
|
||||
} else if (m_modifiers.active()) {
|
||||
sendKey(XKB_KEY_BackSpace);
|
||||
} else {
|
||||
@@ -156,7 +156,7 @@ void KeyboardController::keyCancelled(const KeyDef &key)
|
||||
|
||||
void KeyboardController::slideReleased(const KeyDef &target, qint64 timeMs)
|
||||
{
|
||||
holdKey(target, true); // only in hold-keys mode: the slide never pressed it
|
||||
holdKey(target, true); // hold-keys mode only: the slide target was never pressed, press it now
|
||||
keyUp(target, KeyOutput::Primary, {}, timeMs);
|
||||
m_model.setLayer(m_layerBeforeSwitch);
|
||||
}
|
||||
@@ -190,7 +190,7 @@ void KeyboardController::setContext(const TextContext &context)
|
||||
if (context.hasSurroundingText) {
|
||||
m_textBefore = context.textBeforeCursor.right(MaxTextBefore);
|
||||
} else if (m_context.hasSurroundingText) {
|
||||
// The field stopped reporting (an input-method reset): what we knew is stale.
|
||||
// The field stopped reporting text (input-method reset): drop the tracked text.
|
||||
m_deadKeys.cancel();
|
||||
m_textBefore.clear();
|
||||
}
|
||||
@@ -220,8 +220,8 @@ void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, cons
|
||||
{
|
||||
const QString base = key.type == KeyType::Space ? QStringLiteral(" ") : key.label;
|
||||
if (m_modifiers.active()) {
|
||||
// A shortcut such as Ctrl+C: the key's unshifted symbol, with modifiers;
|
||||
// for a dead key its dead keysym, as the physical key sends.
|
||||
// Shortcut such as Ctrl+C: send the key's unshifted keysym with modifiers
|
||||
// (for a dead key, its dead keysym, same as the physical key).
|
||||
m_deadKeys.cancel();
|
||||
sendKey(key.dead ? key.dead : xkb_utf32_to_keysym(base.toLower().toUcs4().value(0)));
|
||||
return;
|
||||
@@ -229,7 +229,7 @@ void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, cons
|
||||
|
||||
const KeyLevel level = keyLevel();
|
||||
if (level.altGr) {
|
||||
// Used up by this key, like a one-shot shift.
|
||||
// Clear one-shot modifiers, like a one-shot shift.
|
||||
m_modifiers.keyTyped();
|
||||
modifiersUpdated();
|
||||
}
|
||||
@@ -295,7 +295,7 @@ void KeyboardController::deleteBackward(bool wholeWord)
|
||||
autoCapitalize();
|
||||
}
|
||||
|
||||
// Only the key that started the repeat stops it; another finger's key doesn't.
|
||||
// Only releasing the key that started the repeat stops it, not another finger's key.
|
||||
void KeyboardController::stopRepeating(const KeyDef &key)
|
||||
{
|
||||
if (key.keysym == m_repeatingKeysym)
|
||||
|
||||
@@ -19,15 +19,15 @@ class LayoutModel;
|
||||
// Which of a key's outputs the gesture selected.
|
||||
enum class KeyOutput { Primary, Flick, Alternate };
|
||||
|
||||
// Keyboard rules: what each key does, shift and the other modifiers, dead
|
||||
// keys, layers and the iOS conveniences (auto-capitalization, double-space
|
||||
// period, backspace repeat). Knows nothing about touch or Wayland: it
|
||||
// receives key presses and text-field context, and talks to an InputEngine.
|
||||
// Keyboard logic: key actions, shift and other modifiers, dead keys, layers
|
||||
// and iOS-style typing aids (auto-capitalization, double-space period,
|
||||
// backspace repeat). Independent of touch and Wayland: input is key presses
|
||||
// and text-field context, output goes to an InputEngine.
|
||||
//
|
||||
// With Ctrl, Alt or Super active, keys become shortcuts (keysyms with those
|
||||
// modifiers) instead of text; AltGr instead types a key's AltGr symbol. Shift joins shortcuts and non-text keys
|
||||
// (Shift+F5, Shift+Tab, Shift+arrows) only when the user turned it on, not
|
||||
// when auto-capitalization did.
|
||||
// With Ctrl, Alt or Super active, keys send shortcuts (keysym + modifiers)
|
||||
// instead of text. AltGr types the key's AltGr symbol.
|
||||
// Shift is added to shortcuts and non-text keys (Shift+F5, Shift+Tab,
|
||||
// Shift+arrows) only if the user enabled it, not if auto-capitalization did.
|
||||
class KeyboardController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -45,19 +45,19 @@ public:
|
||||
|
||||
ShiftMode shiftMode() const;
|
||||
QVariantMap modifierModes() const;
|
||||
// Hold-keys mode: no long-press, flick or trackpad gestures, no rollover.
|
||||
// Hold-keys mode disables long-press, flick and trackpad gestures, and rollover.
|
||||
bool holdsKeys() const { return m_options.holdKeys; }
|
||||
|
||||
void keyDown(const KeyDef &key, qint64 timeMs);
|
||||
void keyUp(const KeyDef &key, KeyOutput output, const QString &alternate, qint64 timeMs);
|
||||
void keyCancelled(const KeyDef &key);
|
||||
// Finger slid from a layer key onto `target`: type it, then switch back.
|
||||
// Finger slid from a layer key onto `target` and lifted: type it, then switch back to the previous layer.
|
||||
void slideReleased(const KeyDef &target, qint64 timeMs);
|
||||
// Space-bar trackpad: move the text cursor by whole steps.
|
||||
void moveCursor(QPoint steps);
|
||||
|
||||
// A field was focused (context.field) or unfocused: forget what we knew
|
||||
// about the text and start over from what it reported.
|
||||
// Field focused (context.field) or unfocused: discard tracked text and
|
||||
// start from `context`.
|
||||
void resetContext(const TextContext &context);
|
||||
// The focused field reported new text/content type.
|
||||
void setContext(const TextContext &context);
|
||||
@@ -67,22 +67,22 @@ Q_SIGNALS:
|
||||
void shiftChanged();
|
||||
void modifiersChanged();
|
||||
void dismissRequested();
|
||||
void nextLayoutRequested(); // the globe key: switching is the system's job
|
||||
// Hold-keys mode is about to flip: keys still down must be cancelled now,
|
||||
// under the rules they were pressed with.
|
||||
void nextLayoutRequested(); // globe key; SystemLayouts does the switch
|
||||
// Hold-keys mode is about to change: release keys still down now, using
|
||||
// the mode they were pressed in.
|
||||
void holdKeysAboutToChange();
|
||||
|
||||
private:
|
||||
void typeCharacter(const KeyDef &key, KeyOutput output, const QString &alternate);
|
||||
void typeText(const QString &text);
|
||||
// Sends a non-text key or shortcut with the active modifiers, then uses them up.
|
||||
// Sends a non-text key or shortcut with the active modifiers, then clears one-shot modifiers.
|
||||
void sendKey(quint32 keysym);
|
||||
Qt::KeyboardModifiers shortcutModifiers() const;
|
||||
KeyLevel keyLevel() const;
|
||||
// Hold-keys mode: forwards the key to m_hardware; false if it isn't one it presses.
|
||||
// Hold-keys mode: forwards the key to m_hardware. Returns false if m_hardware doesn't handle it.
|
||||
bool holdKey(const KeyDef &key, bool pressed);
|
||||
void useModifiers();
|
||||
// The modifiers changed: refreshes the labels and tells the view.
|
||||
// Call after modifiers change: updates key labels and notifies the view.
|
||||
void modifiersUpdated();
|
||||
void deleteBackward(bool wholeWord);
|
||||
void sendRepeatedKeysym();
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
QString m_layerBeforeSwitch;
|
||||
|
||||
TextContext m_context;
|
||||
// Best guess of the text before the cursor: the field's last report plus
|
||||
// what we typed since. Fields that don't report text rely on the latter.
|
||||
// Estimated text before the cursor: the field's last report plus what we
|
||||
// typed since. For fields that don't report text, only what we typed.
|
||||
QString m_textBefore;
|
||||
};
|
||||
|
||||
@@ -104,13 +104,13 @@ void KeyboardSurface::pointerDown(int id, QPointF pos, qint64 timeMs)
|
||||
m_lastEventMs = timeMs;
|
||||
if (!m_model || !m_controller)
|
||||
return;
|
||||
// A press for an id still down means its release was lost.
|
||||
// A press for an id that is already down means its release was lost.
|
||||
if (const auto stale = m_pointers.constFind(id); stale != m_pointers.cend()) {
|
||||
const Pointer pointer = *stale;
|
||||
m_pointers.erase(stale);
|
||||
cancel(pointer);
|
||||
}
|
||||
// Rollover would let go of keys still held, which hold-keys mode must not.
|
||||
// No rollover in hold-keys mode: it would release keys that are still held.
|
||||
if (!m_controller->holdsKeys())
|
||||
commitPendingTaps(timeMs);
|
||||
|
||||
@@ -179,7 +179,7 @@ void KeyboardSurface::longPressElapsed(int id, quint64 serial)
|
||||
if (it->tracker.gesture() == Gesture::Trackpad)
|
||||
setTrackpadActive(true);
|
||||
else if (it->tracker.gesture() == Gesture::LongPress)
|
||||
openAlternates(Pointer(*it), m_lastEventMs); // a copy: opening may erase from m_pointers
|
||||
openAlternates(Pointer(*it), m_lastEventMs); // copy: openAlternates() may erase from m_pointers
|
||||
}
|
||||
|
||||
// --- Gesture outcomes
|
||||
@@ -198,7 +198,7 @@ void KeyboardSurface::finish(const Pointer &pointer, qint64 timeMs)
|
||||
m_controller->keyUp(key, tracker.flickCommitted() ? KeyOutput::Flick : KeyOutput::Primary, {}, timeMs);
|
||||
break;
|
||||
case Gesture::LongPress: {
|
||||
// Without the popup (it never opened), the key types its primary output.
|
||||
// Popup never opened: type the key's primary output.
|
||||
const QString alternate = ownsAlternates(pointer) ? m_alternates.selectedText() : QString();
|
||||
if (ownsAlternates(pointer))
|
||||
closeAlternates();
|
||||
@@ -230,7 +230,7 @@ void KeyboardSurface::cancel(const Pointer &pointer)
|
||||
m_controller->keyCancelled(pointer.tracker.key());
|
||||
}
|
||||
|
||||
// Clears the visual state a pointer left on the model.
|
||||
// Clears the pointer's pressed/hover state in the model.
|
||||
void KeyboardSurface::release(const Pointer &pointer)
|
||||
{
|
||||
m_model->setPressed(pointer.index, false);
|
||||
@@ -261,12 +261,12 @@ void KeyboardSurface::updateSlideHover(Pointer &pointer)
|
||||
pointer.hoverIndex = hover;
|
||||
}
|
||||
|
||||
// One popup: a second long-press first finishes the one showing it, as a new
|
||||
// press commits pending taps.
|
||||
// Only one popup at a time: a second long-press first commits the open popup,
|
||||
// the same way a new press commits pending taps.
|
||||
void KeyboardSurface::openAlternates(const Pointer &pointer, qint64 timeMs)
|
||||
{
|
||||
if (pointer.index < 0)
|
||||
return; // the layout changed under the finger: no key to place it over
|
||||
return; // layer changed during the press: the key is gone
|
||||
for (auto it = m_pointers.begin(); it != m_pointers.end(); ++it) {
|
||||
if (it->serial != pointer.serial && ownsAlternates(*it)) {
|
||||
const Pointer owner = *it;
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
#include <QQuickItem>
|
||||
#include <limits>
|
||||
|
||||
// Receives all touch points (and the mouse) over the keyboard, runs one
|
||||
// PointerTracker per finger, and turns finished gestures into controller
|
||||
// calls. Several fingers can type at once: a new press commits pending taps
|
||||
// (rollover), as on iOS.
|
||||
// Handles touch points and the mouse over the keyboard. Runs one
|
||||
// PointerTracker per finger and turns finished gestures into controller calls.
|
||||
// Rollover (as on iOS): a new press commits the other fingers' pending taps.
|
||||
class KeyboardSurface : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -22,8 +21,8 @@ class KeyboardSurface : public QQuickItem
|
||||
Q_PROPERTY(KeyboardController *controller READ controller WRITE setController NOTIFY controllerChanged)
|
||||
Q_PROPERTY(AlternatesPicker *alternates READ alternates CONSTANT)
|
||||
Q_PROPERTY(bool trackpadActive READ isTrackpadActive NOTIFY trackpadActiveChanged)
|
||||
// Room above the surface that long-press popups may use; taller keys put
|
||||
// top-row popups lower, over their key, instead of cutting them off.
|
||||
// Space above the surface available to long-press popups. If a popup
|
||||
// doesn't fit, it is moved down over its key instead of being clipped.
|
||||
Q_PROPERTY(qreal popupHeadroom MEMBER m_popupHeadroom NOTIFY popupHeadroomChanged)
|
||||
|
||||
public:
|
||||
@@ -53,10 +52,10 @@ protected:
|
||||
|
||||
private:
|
||||
struct Pointer {
|
||||
PointerTracker tracker; // holds a copy of the key: layers may change mid-press
|
||||
PointerTracker tracker; // has its own copy of the key: the layer may change mid-press
|
||||
int index = -1; // pressed key in the model, -1 once the layer changed
|
||||
int hoverIndex = -1; // key under the finger while sliding
|
||||
quint64 serial = 0; // tells a reused touch id from the original
|
||||
quint64 serial = 0; // distinguishes a reused touch id from the original
|
||||
};
|
||||
|
||||
void pointerDown(int id, QPointF pos, qint64 timeMs);
|
||||
@@ -82,7 +81,7 @@ private:
|
||||
quint64 m_alternatesSerial = 0; // the pointer the popup belongs to
|
||||
QHash<int, Pointer> m_pointers;
|
||||
quint64 m_nextSerial = 1;
|
||||
qint64 m_lastEventMs = 0; // timers have no event time; they use this
|
||||
qint64 m_lastEventMs = 0; // timestamp of the last event; used as "now" in timer callbacks
|
||||
bool m_trackpadActive = false;
|
||||
qreal m_popupHeadroom = std::numeric_limits<qreal>::max();
|
||||
};
|
||||
|
||||
@@ -9,19 +9,19 @@
|
||||
#include <array>
|
||||
|
||||
// Ctrl, Alt, Super and AltGr, each sticky like shift (see ShiftState): tap
|
||||
// for the next key, double-tap to lock, hold while typing with another finger.
|
||||
// The first three turn keys into shortcuts; AltGr picks their AltGr symbols.
|
||||
// for the next key, double-tap to lock, or hold while typing with another finger.
|
||||
// Ctrl/Alt/Super make keys send shortcuts; AltGr selects their AltGr symbols.
|
||||
class Modifiers
|
||||
{
|
||||
public:
|
||||
// Names as used in layouts: "ctrl", "alt", "super", "altgr".
|
||||
void press(const QString &name, qint64 timeMs);
|
||||
void release(const QString &name);
|
||||
// A key was typed: one-shot modifiers are used up.
|
||||
// Call after a key is typed: clears one-shot modifiers.
|
||||
void keyTyped();
|
||||
void clear();
|
||||
|
||||
// Ctrl, Alt and Super: what makes a key a shortcut.
|
||||
// Active Ctrl/Alt/Super, i.e. the ones that make a key a shortcut.
|
||||
Qt::KeyboardModifiers active() const;
|
||||
bool isAltGrActive() const;
|
||||
// name → ShiftState::Mode as int, for the view.
|
||||
|
||||
@@ -29,7 +29,7 @@ void PointerTracker::moveTo(QPointF pos)
|
||||
if (m_key.type == KeyType::LayerSwitch) {
|
||||
m_gesture = Gesture::Slide;
|
||||
} else if (m_config.gestures && m_key.type == KeyType::Space && qAbs(delta.x()) > qAbs(delta.y())) {
|
||||
// From the press point: the slop counts towards the first step.
|
||||
// Measured from the press point, so the slop distance counts towards the first step.
|
||||
m_gesture = Gesture::Trackpad;
|
||||
m_trackpadOrigin = m_start;
|
||||
m_trackpadLines = false;
|
||||
@@ -68,7 +68,7 @@ QPoint PointerTracker::takeTrackpadSteps()
|
||||
return {};
|
||||
|
||||
const QPointF travel = m_position - m_trackpadOrigin;
|
||||
// Truncate towards zero so jitter around the origin doesn't step.
|
||||
// Truncate towards zero so jitter around the origin doesn't produce steps.
|
||||
const int rows = m_trackpadLines ? int(travel.y() / m_config.trackpadRowStep) : 0;
|
||||
const QPoint total(int(travel.x() / m_config.trackpadStep), rows);
|
||||
const QPoint steps = total - m_trackpadStepsTaken;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// Gesture recognition for one finger on one key. Pure logic: the caller feeds
|
||||
// positions and tells it when the long-press delay has elapsed.
|
||||
//
|
||||
// Tap pressed, not (yet) anything else
|
||||
// Tap pressed, no other gesture recognized (yet)
|
||||
// Flick moved down on a key with a flick symbol
|
||||
// LongPress held still on a key with alternates
|
||||
// Trackpad held still on space; movement moves the text cursor. Sliding
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
void ShiftState::press(qint64 timeMs)
|
||||
{
|
||||
// Locks whatever the first tap did: at a sentence start that tap turns
|
||||
// auto-shift off, and the second must still lock.
|
||||
// A double tap always locks, even when the first tap turned auto-shift off
|
||||
// (at a sentence start).
|
||||
const bool doubleTap = timeMs - m_lastPress <= DoubleTapMs && !m_lastPressUnlocked;
|
||||
m_lastPress = timeMs;
|
||||
m_lastPressUnlocked = m_mode == Mode::Locked;
|
||||
@@ -28,7 +28,7 @@ void ShiftState::release()
|
||||
|
||||
void ShiftState::characterTyped()
|
||||
{
|
||||
m_lastPress = NoPress; // shift, type, shift is not a double tap
|
||||
m_lastPress = NoPress; // shift, type, shift must not count as a double tap
|
||||
m_userChoice = false;
|
||||
if (m_held)
|
||||
m_typedWhileHeld = true;
|
||||
|
||||
@@ -18,20 +18,19 @@ public:
|
||||
|
||||
Mode mode() const { return m_mode; }
|
||||
bool isActive() const { return m_mode != Mode::Off; }
|
||||
// Whether shift joins shortcuts and non-text keys (Shift+F5): only when
|
||||
// tapped or held by the user. Neither auto-capitalization nor caps lock
|
||||
// does, as on a PC.
|
||||
// Whether shift is added to shortcuts and non-text keys (Shift+F5): only
|
||||
// if the user tapped or held it, not for auto-capitalization or caps lock (as on a PC).
|
||||
bool appliesToShortcuts() const { return m_held || (m_mode == Mode::Once && !m_automatic); }
|
||||
|
||||
void press(qint64 timeMs);
|
||||
void release();
|
||||
void characterTyped();
|
||||
|
||||
// Auto-capitalization hint; ignored while shift is held or locked. A shift
|
||||
// the user tapped stays until a character is typed or the hint changes,
|
||||
// so a field re-reporting the same context doesn't undo it.
|
||||
// Auto-capitalization hint; ignored while shift is held or locked. A
|
||||
// user-tapped shift is kept until a character is typed or the hint value
|
||||
// changes, so the field re-sending the same context doesn't reset it.
|
||||
void setAutomatic(bool capitalize);
|
||||
// A new field: the user's shift choice no longer applies.
|
||||
// New field focused: discard the user's manual shift.
|
||||
void forgetUserChoice() { m_userChoice = false; }
|
||||
// Caps lock key: locks, or unlocks if locked.
|
||||
void toggleLock();
|
||||
@@ -43,7 +42,7 @@ private:
|
||||
bool m_userChoice = false; // the user set the mode since the last character
|
||||
bool m_held = false;
|
||||
bool m_typedWhileHeld = false;
|
||||
bool m_lastPressUnlocked = false; // a tap leaving caps lock doesn't start a double tap
|
||||
bool m_lastPressUnlocked = false; // the tap that unlocks caps lock must not start a double tap
|
||||
static constexpr qint64 NoPress = std::numeric_limits<qint64>::min() / 2;
|
||||
qint64 m_lastPress = NoPress;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
// Pure text heuristics behind the iOS conveniences.
|
||||
// Text heuristics for the iOS-style typing aids. No state.
|
||||
namespace TextRules {
|
||||
|
||||
// True at the start of a sentence: empty text, after a newline, or after
|
||||
|
||||
@@ -13,7 +13,7 @@ qreal blockUnits(const Block &block)
|
||||
return units;
|
||||
}
|
||||
|
||||
// Key widths in units once stretch keys take up the row's slack.
|
||||
// Key widths in units, after stretch keys are widened to fill the row.
|
||||
QList<qreal> stretchedWidths(const Row &row, qreal targetUnits)
|
||||
{
|
||||
const auto stretchy = std::ranges::count_if(row, &KeyDef::stretch);
|
||||
@@ -44,7 +44,7 @@ qreal totalUnits(const QList<Block> &blocks)
|
||||
|
||||
qsizetype totalRows(const QList<Block> &blocks)
|
||||
{
|
||||
// Filling blocks only count when nothing else is shown.
|
||||
// fillHeight blocks are ignored, unless all blocks are fillHeight.
|
||||
const bool allFill = std::ranges::all_of(blocks, &Block::fillHeight);
|
||||
qsizetype rows = 0;
|
||||
for (const Block &block : blocks) {
|
||||
@@ -78,7 +78,7 @@ QList<QRectF> computeKeyRects(const QList<Block> &blocks, QSizeF size, KeyboardM
|
||||
const auto rows = block.rows.size();
|
||||
const qreal span = block.fillHeight ? innerHeight : spanOf(std::max(rows, block.minRows));
|
||||
const qsizetype fixed = std::min(block.fixedRows, rows);
|
||||
// Rows after the fixed ones share what's left; with none, the block sits at the bottom.
|
||||
// Rows after fixedRows split the remaining height; if there are none, the block is bottom-aligned.
|
||||
const qreal flexHeight = rows > fixed
|
||||
? (span - qreal(fixed) * (sharedRowHeight + metrics.gap) - qreal(rows - fixed - 1) * metrics.gap)
|
||||
/ qreal(rows - fixed)
|
||||
|
||||
@@ -12,8 +12,8 @@ struct KeyboardMetrics {
|
||||
};
|
||||
|
||||
// The keys on screen: blocks of rows, side by side.
|
||||
// A block can reserve room (minUnits, minRows) so that swapping its rows, e.g.
|
||||
// switching layers, moves nothing else; its keys stretch into the room.
|
||||
// A block can reserve space (minUnits, minRows), so swapping its rows (e.g.
|
||||
// switching layers) doesn't move other blocks. Its keys stretch to fill it.
|
||||
struct Block {
|
||||
QList<Row> rows;
|
||||
bool fillHeight = false; // spans the full height instead of max(rows, minRows) shared rows
|
||||
|
||||
@@ -34,5 +34,5 @@ struct KeymapKeys {
|
||||
|
||||
KeymapKeys readKeymapKeys(xkb_keymap *keymap, quint32 layoutIndex);
|
||||
|
||||
// What a dead key shows and types when nothing composes: ´ for dead_acute.
|
||||
// Spacing accent for a dead keysym, used as its label and when nothing composes: ´ for dead_acute.
|
||||
QString deadKeyText(quint32 keysym);
|
||||
|
||||
@@ -19,7 +19,7 @@ const QHash<QString, QString> &baseOverrides()
|
||||
return bases;
|
||||
}
|
||||
|
||||
// "š" → "s": the letter an accented letter is a variant of.
|
||||
// Base letter of an accented letter: "š" → "s".
|
||||
QString baseLetter(const QString &letter)
|
||||
{
|
||||
if (const auto it = baseOverrides().constFind(letter); it != baseOverrides().cend())
|
||||
@@ -41,7 +41,7 @@ KeyDef characterKey(const KeymapKey &source)
|
||||
return key;
|
||||
}
|
||||
|
||||
// The compact letter rows: letters only, and not the keys some keyboards lack.
|
||||
// Compact-mode letter rows: letters only, without keys missing on some keyboards (BKSL, LSGT).
|
||||
QList<KeyDef> letterKeys(const KeymapKeys &keymap, int row, const QStringList &flicks)
|
||||
{
|
||||
QList<KeyDef> keys;
|
||||
@@ -49,7 +49,7 @@ QList<KeyDef> letterKeys(const KeymapKeys &keymap, int row, const QStringList &f
|
||||
if (!source.isLetter() || source.position != KeymapKey::Position::Regular)
|
||||
continue;
|
||||
KeyDef key = characterKey(source);
|
||||
// iPad letters always uppercase; the keymap may shift some to a symbol (sk: ô → ").
|
||||
// Shifted letters are always uppercased (as on iPad), even where the keymap's shift level is a symbol (sk: ô → ").
|
||||
key.shifted = source.lower.toUpper();
|
||||
key.deadShifted = 0;
|
||||
key.flick = flicks.value(keys.size());
|
||||
@@ -58,13 +58,13 @@ QList<KeyDef> letterKeys(const KeymapKeys &keymap, int row, const QStringList &f
|
||||
return keys;
|
||||
}
|
||||
|
||||
// A full row: every typing key, the shifted symbol shown (and flickable) above;
|
||||
// for letters, whose shift only capitalizes, the AltGr symbol (sk: ' on j).
|
||||
// `withLessGreater` tells whether the ISO key left of Z is among them.
|
||||
// Full-mode row: every typing key. The secondary (flick) symbol is the shifted
|
||||
// symbol, or for letters (where shift only capitalizes) the AltGr symbol (sk: ' on j).
|
||||
// `withLessGreater` is set if the row contains the ISO key left of Z.
|
||||
QList<KeyDef> rowKeys(const KeymapKeys &keymap, int row, bool *withLessGreater)
|
||||
{
|
||||
*withLessGreater = false;
|
||||
QSet<QString> elsewhere; // LSGT often repeats a pair that exists elsewhere (US: < >)
|
||||
QSet<QString> elsewhere; // LSGT often duplicates symbols on other keys (US: < >)
|
||||
for (const auto &keys : keymap.rows) {
|
||||
for (const KeymapKey &key : keys) {
|
||||
if (key.position != KeymapKey::Position::LessGreater)
|
||||
@@ -78,7 +78,7 @@ QList<KeyDef> rowKeys(const KeymapKeys &keymap, int row, bool *withLessGreater)
|
||||
continue;
|
||||
*withLessGreater |= source.position == KeymapKey::Position::LessGreater;
|
||||
KeyDef key = characterKey(source);
|
||||
// Show (and flick for) a shifted level that isn't just the capital letter: ! on 1, " on sk ô.
|
||||
// Use the shifted level as the flick symbol unless it's just the capital letter: ! on 1, " on sk ô.
|
||||
if (!source.deadUpper && source.upper != source.lower && source.upper != source.lower.toUpper())
|
||||
key.flick = source.upper;
|
||||
else if (!source.deadAltGr && source.altGr != source.lower)
|
||||
@@ -97,7 +97,7 @@ Row expandPlaceholders(const Row &row, const KeymapKeys &keymap)
|
||||
else if (key.type == KeyType::Keys) {
|
||||
bool withLessGreater = false;
|
||||
const QList<KeyDef> keys = rowKeys(keymap, key.lettersRow, &withLessGreater);
|
||||
// As on ISO keyboards, that key takes its room from the one before it (left shift).
|
||||
// As on ISO keyboards, LSGT's width is taken from the key before it (left shift).
|
||||
if (withLessGreater && !result.isEmpty())
|
||||
result.back().width = std::max(1.0, result.back().width - 1);
|
||||
result += keys;
|
||||
@@ -108,9 +108,9 @@ Row expandPlaceholders(const Row &row, const KeymapKeys &keymap)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Long-press choices per letter: the keymap's letters that have no letter key
|
||||
// first (they're what this language types), then the template's. Letters on the
|
||||
// number row (sk: ť) stay choices in full mode too: they're out of the way there.
|
||||
// Long-press choices per letter: first the keymap's letters that have no key of
|
||||
// their own in the letter rows (the language's own letters), then the template's.
|
||||
// Number-row letters (sk: ť) are kept as choices in full mode too, since the number row is hard to reach.
|
||||
void addAlternates(Layer &layer, const Layout &tmpl, const KeymapKeys &keymap)
|
||||
{
|
||||
QSet<QString> onKeys;
|
||||
|
||||
@@ -8,7 +8,7 @@ enum class KeyboardMode {
|
||||
Full, // iPad Pro / PC: number row, punctuation, tab, modifiers
|
||||
};
|
||||
|
||||
// Turns the template into the layout shown: picks each layer's rows for the
|
||||
// mode, fills the keymap placeholders, and gives letters their long-press
|
||||
// choices (letters of the keymap without a key of their own come first).
|
||||
// Builds the displayed layout from the template: picks each layer's rows for
|
||||
// the mode, fills the keymap placeholders, and assigns long-press choices to
|
||||
// letters (keymap letters without their own key first).
|
||||
Layout buildLayout(const Layout &tmpl, const KeymapKeys &keymap, KeyboardMode mode = KeyboardMode::Compact);
|
||||
|
||||
@@ -49,7 +49,7 @@ QVariant LayoutModel::data(const QModelIndex &index, int role) const
|
||||
const QRectF rect = m_rects.value(index.row());
|
||||
switch (role) {
|
||||
case LabelRole: return key.text(m_keyLevel);
|
||||
// A shifted or AltGr key may already show its flick symbol (! on 1): don't repeat it.
|
||||
// Hide the flick symbol if the main label already shows it (shifted or AltGr: ! on 1).
|
||||
case FlickLabelRole: return key.flick == key.text(m_keyLevel) ? QString() : key.flick;
|
||||
case ModifierRole: return key.modifier;
|
||||
case KeyTypeRole: return keyTypeName(key.type);
|
||||
@@ -185,7 +185,7 @@ QList<Block> LayoutModel::composeBlocks() const
|
||||
main.rows += section.rows;
|
||||
}
|
||||
if (layer && shown(MainSectionId)) {
|
||||
// Room for the biggest layer, so switching layers keeps the other blocks still.
|
||||
// Reserve space for the largest layer, so switching layers doesn't move other blocks.
|
||||
main.fixedRows = main.rows.size();
|
||||
for (const Layer &other : m_layout.layers) {
|
||||
main.minRows = std::max(main.minRows, main.fixedRows + other.rows.size());
|
||||
@@ -202,7 +202,7 @@ QList<Block> LayoutModel::composeBlocks() const
|
||||
if (section.placement == Section::Placement::Right && shown(section.id))
|
||||
blocks.append({section.rows, section.fillHeight});
|
||||
}
|
||||
// Never show nothing.
|
||||
// If no block is shown, fall back to the current layer.
|
||||
if (blocks.isEmpty() && layer)
|
||||
blocks.append({layer->rows});
|
||||
return blocks;
|
||||
|
||||
+12
-12
@@ -46,8 +46,8 @@ std::optional<Layout> loadTemplate()
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// The shown layout is the template filled with the active system layout's
|
||||
// keys. Returns the rebuild, for settings that change the result.
|
||||
// Builds the displayed layout from the template and the active system layout.
|
||||
// Returns the rebuild function, for settings that affect the result.
|
||||
std::function<void()> connectLayouts(const Layout &tmpl, WaylandKeymap &keymap, SystemLayouts &systemLayouts,
|
||||
const Settings &settings, KeyboardController &controller, LayoutModel &model)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ std::function<void()> connectLayouts(const Layout &tmpl, WaylandKeymap &keymap,
|
||||
return rebuild;
|
||||
}
|
||||
|
||||
// Settings that add or remove keys resize the window so keys keep their size.
|
||||
// Settings that add or remove keys resize the window, so key size stays the same.
|
||||
void connectKeySettings(Settings &settings, LayoutModel &model, FloatingWindow &placement, const QQuickWindow &window,
|
||||
const std::function<void()> &rebuild)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ void connectKeySettings(Settings &settings, LayoutModel &model, FloatingWindow &
|
||||
[keepingKeySize, rebuild] { keepingKeySize(rebuild); });
|
||||
}
|
||||
|
||||
// The shrunk button's geometry isn't the keyboard's: not saved.
|
||||
// Geometry isn't saved while collapsed (it's the button's, not the keyboard's).
|
||||
void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settings, const CollapseController &collapse)
|
||||
{
|
||||
if (const auto geometry = settings.load(placement.screenName()))
|
||||
@@ -94,8 +94,8 @@ void restoreAndPersistGeometry(FloatingWindow &placement, WindowSettings &settin
|
||||
});
|
||||
}
|
||||
|
||||
// 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.
|
||||
// On field focus, ask KWin's policy first and show the keyboard only if it
|
||||
// agrees. Asking before showing avoids a brief flash when the answer is no.
|
||||
void connectFieldFocus(VisibilityController &visibility, KWinInputMethod &backend, KWinVirtualKeyboard &policy)
|
||||
{
|
||||
QObject::connect(&backend, &TextBackend::activeChanged, &visibility, [&] {
|
||||
@@ -105,7 +105,7 @@ void connectFieldFocus(VisibilityController &visibility, KWinInputMethod &backen
|
||||
}
|
||||
policy.askShowOnActive(&visibility, [&](bool show) {
|
||||
if (!backend.isActive())
|
||||
return; // left the field before KWin answered
|
||||
return; // field lost focus before KWin answered
|
||||
visibility.setShowOnField(show);
|
||||
visibility.setFieldActive(true);
|
||||
});
|
||||
@@ -118,14 +118,14 @@ void connectFieldFocus(VisibilityController &visibility, KWinInputMethod &backen
|
||||
void connectVisibility(VisibilityController &visibility, KWinInputMethod &backend, const Settings &settings,
|
||||
FloatingWindow &placement)
|
||||
{
|
||||
// First, so the initial state below reaches the window too.
|
||||
// Connect first, so the initial state set below is applied to the window.
|
||||
QObject::connect(&visibility, &VisibilityController::visibleChanged, &placement,
|
||||
[&](bool visible) { visible ? placement.show() : placement.hide(); });
|
||||
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.
|
||||
// Without the input-method global (not launched by KWin, e.g. during UI
|
||||
// development) there are no focus events: stay visible.
|
||||
const auto updateStandalone = [&] { visibility.setStandalone(!backend.isAvailable()); };
|
||||
QObject::connect(&backend, &KWinInputMethod::availableChanged, &visibility, updateStandalone);
|
||||
updateStandalone();
|
||||
@@ -154,10 +154,10 @@ void connectTextContext(KWinInputMethod &backend, KeyboardController &controller
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
blockQuitSignals();
|
||||
// libwayland unsets it once connected.
|
||||
// Read before connecting: libwayland unsets it.
|
||||
const bool launchedByKWin = qEnvironmentVariableIsSet("WAYLAND_SOCKET");
|
||||
QGuiApplication app(argc, argv);
|
||||
// KWin relaunches it only after a crash: a closed window mustn't quit it.
|
||||
// Don't quit when the window closes: KWin only restarts the input method after a crash.
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
quitOnSignal();
|
||||
QGuiApplication::setApplicationName(QStringLiteral("sloposk"));
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <QTimer>
|
||||
#include <QVariantMap>
|
||||
|
||||
// QSettings writes that arrive in bursts (dragging a slider or the window),
|
||||
// saved once the burst ends instead of on every step. Flushes on destruction.
|
||||
// Debounced QSettings writes: values that change rapidly (slider or window
|
||||
// drag) are saved once changes stop. Flushes on destruction.
|
||||
class DebouncedStore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
void setHoldKeys(bool enabled);
|
||||
|
||||
QStringList visibleSections() const { return m_visibleSections; }
|
||||
// Hiding the last visible section is refused: something must stay on screen.
|
||||
// Refuses to hide the last visible section.
|
||||
Q_INVOKABLE void setSectionVisible(const QString &id, bool visible);
|
||||
|
||||
bool fullKeyboard() const { return m_fullKeyboard; }
|
||||
|
||||
@@ -37,7 +37,7 @@ bool CollapseController::collapse()
|
||||
m_expandedSize = m_window.size();
|
||||
m_collapsed = true;
|
||||
Q_EMIT collapsedChanged();
|
||||
// After the next frame, once the compositor knows the lower minimum size.
|
||||
// Wait one frame, so the compositor has the new (lower) minimum size before the resize.
|
||||
connect(&m_window, &QQuickWindow::frameSwapped, this, [this] {
|
||||
if (m_collapsed)
|
||||
m_placement.moveResize(m_keyRect, false);
|
||||
@@ -50,10 +50,10 @@ void CollapseController::expand()
|
||||
if (!m_collapsed || m_expanding.isActive())
|
||||
return;
|
||||
if (roughlyEqual(m_window.size(), m_expandedSize)) {
|
||||
finishExpanding(); // never shrank: nothing to move
|
||||
finishExpanding(); // window never shrank: nothing to resize
|
||||
return;
|
||||
}
|
||||
// Stays collapsed until the window has grown, so the keys don't flash squeezed.
|
||||
// Stay collapsed until the window has grown, so the keys aren't drawn squeezed.
|
||||
m_placement.moveResize(QRectF(-m_keyRect.topLeft(), m_expandedSize), true);
|
||||
m_expanding.start();
|
||||
}
|
||||
@@ -68,7 +68,7 @@ void CollapseController::finishExpanding()
|
||||
{
|
||||
m_expanding.stop();
|
||||
if (!roughlyEqual(m_window.size(), m_expandedSize))
|
||||
m_window.resize(m_expandedSize); // no KWin to do it
|
||||
m_window.resize(m_expandedSize); // KWin didn't resize (timeout): resize directly
|
||||
m_collapsed = false;
|
||||
Q_EMIT collapsedChanged();
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
class FloatingWindow;
|
||||
class QQuickWindow;
|
||||
|
||||
// Shrinks the keyboard window to just its dismiss key, floating on its own,
|
||||
// and grows it back with the key under the button again (kept on screen).
|
||||
// The window itself shrinks, so it can be dragged anywhere and covers
|
||||
// nothing else. QML draws from `collapsed` and `keyRect`.
|
||||
// Collapses the keyboard window to the size of its dismiss key (a floating
|
||||
// button) and expands it back, positioned so the dismiss key is where the
|
||||
// button was (clamped to the screen). The window itself is resized, so the
|
||||
// button can be dragged anywhere and covers nothing else.
|
||||
// QML uses `collapsed` and `keyRect`.
|
||||
class CollapseController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool collapsed READ isCollapsed NOTIFY collapsedChanged)
|
||||
// The key in the full window; the button stays there until the window shrinks.
|
||||
// Dismiss key rect in the full window. The button is drawn there until the window has shrunk.
|
||||
Q_PROPERTY(QRectF keyRect READ keyRect NOTIFY collapsedChanged)
|
||||
|
||||
public:
|
||||
@@ -41,5 +42,5 @@ private:
|
||||
bool m_collapsed = false;
|
||||
QRectF m_keyRect;
|
||||
QSize m_expandedSize;
|
||||
QTimer m_expanding; // waits for KWin; runs out without it
|
||||
QTimer m_expanding; // timeout for KWin's resize; fires if KWin never resizes
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ FloatingWindow::FloatingWindow(QWindow *window, QObject *parent)
|
||||
|
||||
bool FloatingWindow::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
// Exposed means mapped, so KWin knows the window by now.
|
||||
// Exposed implies mapped, so KWin knows the window at this point.
|
||||
if (watched == m_window && event->type() == QEvent::Expose) {
|
||||
if (m_window->isExposed() && !m_exposed)
|
||||
moveResize(QRectF(QPointF(), m_window->size()), true);
|
||||
@@ -107,8 +107,8 @@ void FloatingWindow::startResize(Qt::Edges edges)
|
||||
releaseMouse();
|
||||
}
|
||||
|
||||
// The window manager keeps the button's release: without one, the pressed
|
||||
// item holds on to the mouse and eats the next press.
|
||||
// After a system move/resize the window never gets the mouse release. Send
|
||||
// one, otherwise the pressed item keeps the mouse grab and swallows the next press.
|
||||
void FloatingWindow::releaseMouse()
|
||||
{
|
||||
if (!(QGuiApplication::mouseButtons() & Qt::LeftButton))
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
class QWindow;
|
||||
|
||||
// Makes a keyboard window a frameless, always-on-top window that never takes
|
||||
// focus. The window manager places it and moves it (Super+drag included);
|
||||
// on KWin Wayland that relies on the rule from installKWinRule().
|
||||
// Each time it appears, it's moved back on screen: KWin remembers where it
|
||||
// was, which may be somewhere it was smaller. Geometry is relative to the
|
||||
// window's screen.
|
||||
// Makes the keyboard window frameless, always on top and never focused.
|
||||
// The window manager places and moves it (including Super+drag); on KWin
|
||||
// Wayland this needs the rule from installKWinRule().
|
||||
// On each show, the window is moved back onto the screen: KWin restores the
|
||||
// last position, which may be off-screen at the current size.
|
||||
// Geometry is relative to the window's screen.
|
||||
class FloatingWindow : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -27,9 +27,9 @@ public:
|
||||
|
||||
// Grows from the top-left corner; respects the window's minimum size.
|
||||
Q_INVOKABLE void resizeBy(qreal dw, qreal dh);
|
||||
// Moves the window by `relative`'s offset and resizes it to its size,
|
||||
// optionally clamped to the screen's free area. Done by KWin (Wayland
|
||||
// clients can't place themselves), so it applies later.
|
||||
// Moves the window by `relative`'s offset and resizes it to `relative`'s
|
||||
// size, optionally clamped to the screen's available area. Done by KWin
|
||||
// (Wayland clients can't position themselves), so it's asynchronous.
|
||||
void moveResize(const QRectF &relative, bool keepOnScreen);
|
||||
// Only this part of the window takes input; the rest passes through.
|
||||
Q_INVOKABLE void setInputRegion(const QRectF ®ion);
|
||||
|
||||
@@ -31,7 +31,7 @@ void runKWinScript(const QString &source)
|
||||
{
|
||||
static int count = 0;
|
||||
const QString plugin = QStringLiteral("sloposk-%1").arg(++count);
|
||||
// Unloading from within: `stop` right after `run` could beat the script.
|
||||
// The script unloads itself: calling `stop` right after `run` could unload it before it runs.
|
||||
const QString unload = QStringLiteral("\ncallDBus(\"%1\", \"%2\", \"%3\", \"unloadScript\", \"%4\");\n")
|
||||
.arg(Service, ScriptingPath, ScriptingInterface, plugin);
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
#include <QObject>
|
||||
#include <optional>
|
||||
|
||||
// Decides whether the keyboard is shown:
|
||||
// - 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
|
||||
// from one field straight to another
|
||||
// - never while the system's virtual keyboard toggle is off
|
||||
// Decides whether the keyboard is visible:
|
||||
// - hidden while the system virtual keyboard toggle is off (overrides everything)
|
||||
// - show()/hide()/toggle() override the rules below until the next focus change
|
||||
// (including focus moving directly from one field to another)
|
||||
// - shown if "always visible" is set, or when standalone (no input-method connection)
|
||||
// - shown while a text field is focused, if KWin's policy allows it
|
||||
class VisibilityController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -19,13 +18,14 @@ public:
|
||||
|
||||
bool isVisible() const { return m_visible; }
|
||||
|
||||
// Called on every focus change, even when a field follows a field.
|
||||
// Call on every focus change, including field-to-field.
|
||||
void setFieldActive(bool active);
|
||||
// KWin's policy (off, or touch only) for fields focused from now on.
|
||||
// Whether KWin's policy (disabled / touch only) allows showing for the focused field.
|
||||
void setShowOnField(bool show);
|
||||
void setStandalone(bool standalone);
|
||||
void setAlwaysVisible(bool always);
|
||||
// The system toggle (KWin's tray icon); off hides it whatever else says.
|
||||
// System virtual keyboard toggle (KWin tray icon). When false, the keyboard
|
||||
// is always hidden, regardless of the other settings. Clears show()/hide() overrides.
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
void show();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <QRect>
|
||||
#include <optional>
|
||||
|
||||
// Remembers the keyboard's geometry per screen (~/.config/sloposk.conf).
|
||||
// Saving is debounced so dragging doesn't hammer the disk.
|
||||
// Stores the keyboard's geometry per screen (~/.config/sloposk/sloposk.conf).
|
||||
// Saving is debounced, so dragging doesn't write on every step.
|
||||
class WindowSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -443,7 +443,7 @@ private Q_SLOTS:
|
||||
QCOMPARE(controller->modifierModes().value(u"ctrl"_s).toInt(), 1);
|
||||
controller->setOptions({}); // turned off while held
|
||||
QVERIFY(engine.held.isEmpty());
|
||||
controller->keyUp(ctrl, KeyOutput::Primary, {}, 10); // back to sticky rules: harmless
|
||||
controller->keyUp(ctrl, KeyOutput::Primary, {}, 10); // released under sticky rules: no effect
|
||||
QVERIFY(engine.held.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,11 @@ private Q_SLOTS:
|
||||
QTest::touchEvent(&window, touchscreen).press(0, e);
|
||||
QTest::qWait(LongPressWaitMs);
|
||||
QTest::touchEvent(&window, touchscreen).stationary(0).press(1, u);
|
||||
QTest::qWait(LongPressWaitMs); // u's popup replaces e's, which types its choice
|
||||
QTest::qWait(LongPressWaitMs); // u's popup opens; e's popup closes and types its selection
|
||||
QCOMPARE(engine.output, u"é");
|
||||
QVERIFY(surface->alternates()->isOpen());
|
||||
QTest::touchEvent(&window, touchscreen).release(0, e).stationary(1);
|
||||
QCOMPARE(engine.output, u"é"); // the first finger's lift does nothing more
|
||||
QCOMPARE(engine.output, u"é"); // lifting the first finger types nothing more
|
||||
QVERIFY(surface->alternates()->isOpen());
|
||||
QTest::touchEvent(&window, touchscreen).release(1, u);
|
||||
QCOMPARE(engine.output.size(), 2);
|
||||
|
||||
@@ -79,7 +79,7 @@ private Q_SLOTS:
|
||||
v.show();
|
||||
QVERIFY(!v.isVisible());
|
||||
v.hide();
|
||||
v.setEnabled(true); // turning it back on forgets the dismiss
|
||||
v.setEnabled(true); // re-enabling clears the earlier hide()
|
||||
QVERIFY(v.isVisible());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user