Full keyboard mode, Ctrl/Alt/Super, dead keys, shift levels
- Full (iPad Pro) mode: every typing key from the keymap, number row, tab, caps lock, ctrl/super/alt; shifted symbol shown above and flickable - Dead keys compose with the locale's XKB compose table - Ctrl/Alt/Super stick like shift and turn keys into shortcuts; a tapped or held shift joins shortcuts and non-text keys, auto-shift and caps lock don't - Auto-capitalization and caps lock only capitalize letters; the user's shift picks the keymap's shifted level - Mode switch in settings keeps key size by resizing the window Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
#include "engine/InputEngine.h"
|
||||
#include "layout/LayoutModel.h"
|
||||
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
// ShiftMode mirrors ShiftState::Mode for QML.
|
||||
static_assert(int(KeyboardController::ShiftMode::Off) == int(ShiftState::Mode::Off));
|
||||
@@ -39,16 +39,27 @@ void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs)
|
||||
case KeyType::Shift:
|
||||
changeShift([&] { m_shift.press(timeMs); });
|
||||
break;
|
||||
case KeyType::Modifier:
|
||||
m_modifiers.press(key.modifier, timeMs);
|
||||
Q_EMIT modifiersChanged();
|
||||
break;
|
||||
case KeyType::Backspace:
|
||||
deleteBackward(false);
|
||||
m_backspaceRepeater.start();
|
||||
if (m_deadKeys.isPending()) {
|
||||
m_deadKeys.cancel(); // backspace takes back the accent
|
||||
} else if (m_modifiers.active()) {
|
||||
sendKey(XKB_KEY_BackSpace);
|
||||
} else {
|
||||
deleteBackward(false);
|
||||
m_backspaceRepeater.start();
|
||||
}
|
||||
break;
|
||||
case KeyType::LayerSwitch:
|
||||
m_layerBeforeSwitch = m_model.layerName();
|
||||
m_model.setLayer(key.target);
|
||||
break;
|
||||
case KeyType::Keysym:
|
||||
m_engine.sendKeysym(key.keysym);
|
||||
m_repeatingModifiers = shortcutModifiers();
|
||||
sendKey(key.keysym);
|
||||
if (key.repeat) {
|
||||
m_repeatingKeysym = key.keysym;
|
||||
m_keysymRepeater.start();
|
||||
@@ -65,6 +76,13 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin
|
||||
case KeyType::Shift:
|
||||
changeShift([&] { m_shift.release(); });
|
||||
break;
|
||||
case KeyType::CapsLock:
|
||||
changeShift([&] { m_shift.toggleLock(); });
|
||||
break;
|
||||
case KeyType::Modifier:
|
||||
m_modifiers.release(key.modifier);
|
||||
Q_EMIT modifiersChanged();
|
||||
break;
|
||||
case KeyType::Backspace:
|
||||
m_backspaceRepeater.stop();
|
||||
break;
|
||||
@@ -72,18 +90,12 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin
|
||||
stopRepeating(key);
|
||||
break;
|
||||
case KeyType::Character:
|
||||
if (output == KeyOutput::Alternate && !alternate.isEmpty())
|
||||
typeText(alternate);
|
||||
else if (output == KeyOutput::Flick && !key.flick.isEmpty())
|
||||
typeText(key.flick);
|
||||
else
|
||||
typeText(key.text(m_shift.isUppercase()));
|
||||
break;
|
||||
case KeyType::Space:
|
||||
typeText(QStringLiteral(" "));
|
||||
typeCharacter(key, output, alternate);
|
||||
break;
|
||||
case KeyType::Enter:
|
||||
m_engine.sendKeysym(XKB_KEY_Return);
|
||||
m_deadKeys.cancel();
|
||||
sendKey(XKB_KEY_Return);
|
||||
m_textBefore += u'\n';
|
||||
autoCapitalize();
|
||||
break;
|
||||
@@ -102,7 +114,10 @@ void KeyboardController::keyCancelled(const KeyDef &key)
|
||||
{
|
||||
if (key.type == KeyType::Shift)
|
||||
changeShift([&] { m_shift.release(); });
|
||||
else if (key.type == KeyType::Backspace)
|
||||
else if (key.type == KeyType::Modifier) {
|
||||
m_modifiers.release(key.modifier);
|
||||
Q_EMIT modifiersChanged();
|
||||
} else if (key.type == KeyType::Backspace)
|
||||
m_backspaceRepeater.stop();
|
||||
else if (key.type == KeyType::Keysym)
|
||||
stopRepeating(key);
|
||||
@@ -128,6 +143,7 @@ void KeyboardController::moveCursor(QPoint steps)
|
||||
|
||||
void KeyboardController::resetContext()
|
||||
{
|
||||
m_deadKeys.cancel();
|
||||
m_context = {};
|
||||
m_textBefore.clear();
|
||||
autoCapitalize();
|
||||
@@ -147,6 +163,52 @@ void KeyboardController::setOptions(const TypingOptions &options)
|
||||
autoCapitalize();
|
||||
}
|
||||
|
||||
void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, const QString &alternate)
|
||||
{
|
||||
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.
|
||||
m_deadKeys.cancel();
|
||||
sendKey(xkb_utf32_to_keysym(base.toLower().toUcs4().value(0)));
|
||||
return;
|
||||
}
|
||||
|
||||
const ShiftLevel level = shiftLevel();
|
||||
if (const quint32 dead = key.deadAt(level); dead && output == KeyOutput::Primary) {
|
||||
m_deadKeys.press(dead);
|
||||
changeShift([&] { m_shift.characterTyped(); });
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = key.type == KeyType::Space ? base : key.text(level);
|
||||
if (output == KeyOutput::Alternate && !alternate.isEmpty())
|
||||
text = alternate;
|
||||
else if (output == KeyOutput::Flick && !key.flick.isEmpty())
|
||||
text = key.flick;
|
||||
if (m_deadKeys.isPending())
|
||||
text = m_deadKeys.compose(text);
|
||||
typeText(text);
|
||||
}
|
||||
|
||||
void KeyboardController::sendKey(quint32 keysym)
|
||||
{
|
||||
m_engine.sendKeysym(keysym, shortcutModifiers());
|
||||
useModifiers();
|
||||
}
|
||||
|
||||
Qt::KeyboardModifiers KeyboardController::shortcutModifiers() const
|
||||
{
|
||||
return m_modifiers.active() | (m_shift.appliesToShortcuts() ? Qt::ShiftModifier : Qt::NoModifier);
|
||||
}
|
||||
|
||||
void KeyboardController::useModifiers()
|
||||
{
|
||||
m_modifiers.keyTyped();
|
||||
Q_EMIT modifiersChanged();
|
||||
if (m_shift.appliesToShortcuts())
|
||||
changeShift([&] { m_shift.characterTyped(); });
|
||||
}
|
||||
|
||||
void KeyboardController::typeText(const QString &text)
|
||||
{
|
||||
if (text == u" " && m_options.doubleSpacePeriod && m_context.isProse()
|
||||
@@ -181,7 +243,7 @@ void KeyboardController::stopRepeating(const KeyDef &key)
|
||||
|
||||
void KeyboardController::sendRepeatedKeysym()
|
||||
{
|
||||
m_engine.sendKeysym(m_repeatingKeysym);
|
||||
m_engine.sendKeysym(m_repeatingKeysym, m_repeatingModifiers);
|
||||
}
|
||||
|
||||
void KeyboardController::autoCapitalize()
|
||||
@@ -191,13 +253,21 @@ void KeyboardController::autoCapitalize()
|
||||
changeShift([&] { m_shift.setAutomatic(capitalize); });
|
||||
}
|
||||
|
||||
ShiftLevel KeyboardController::shiftLevel() const
|
||||
{
|
||||
if (m_shift.appliesToShortcuts())
|
||||
return ShiftLevel::Shifted;
|
||||
return m_shift.isActive() ? ShiftLevel::Capitals : ShiftLevel::None;
|
||||
}
|
||||
|
||||
template<typename Change>
|
||||
void KeyboardController::changeShift(Change change)
|
||||
{
|
||||
const ShiftState::Mode before = m_shift.mode();
|
||||
const ShiftState::Mode mode = m_shift.mode();
|
||||
const ShiftLevel level = shiftLevel();
|
||||
change();
|
||||
if (m_shift.mode() == before)
|
||||
if (m_shift.mode() == mode && shiftLevel() == level)
|
||||
return;
|
||||
m_model.setUppercase(m_shift.isUppercase());
|
||||
m_model.setShiftLevel(shiftLevel());
|
||||
Q_EMIT shiftChanged();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user