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:
2026-10-14 21:29:24 +02:00
co-authored by Claude Opus 5.5
parent 88338fde22
commit ba2f17d665
36 changed files with 948 additions and 202 deletions
+68
View File
@@ -0,0 +1,68 @@
#include "DeadKeys.h"
#include "layout/KeymapKeys.h"
#include <xkbcommon/xkbcommon-compose.h>
#include <xkbcommon/xkbcommon.h>
namespace {
QByteArray systemLocale()
{
for (const char *name : {"LC_ALL", "LC_CTYPE", "LANG"}) {
if (const QByteArray value = qgetenv(name); !value.isEmpty())
return value;
}
return "C";
}
} // namespace
DeadKeys::DeadKeys(const QString &locale)
: m_context(xkb_context_new(XKB_CONTEXT_NO_FLAGS), xkb_context_unref)
, m_table(nullptr, xkb_compose_table_unref)
, m_state(nullptr, xkb_compose_state_unref)
{
// The C locale has no compose table; en_US.UTF-8's is the common base of all.
const QByteArray name = locale.isEmpty() ? systemLocale() : locale.toUtf8();
for (const QByteArray &candidate : {name, QByteArrayLiteral("en_US.UTF-8")}) {
m_table.reset(xkb_compose_table_new_from_locale(m_context.get(), candidate.constData(),
XKB_COMPOSE_COMPILE_NO_FLAGS));
if (m_table)
break;
}
if (m_table)
m_state.reset(xkb_compose_state_new(m_table.get(), XKB_COMPOSE_STATE_NO_FLAGS));
}
DeadKeys::~DeadKeys() = default;
void DeadKeys::press(quint32 deadKeysym)
{
m_pending += deadKeyText(deadKeysym);
if (m_state)
xkb_compose_state_feed(m_state.get(), deadKeysym);
}
QString DeadKeys::compose(const QString &text)
{
QString result = m_pending + text;
if (m_state && !text.isEmpty()) {
const char32_t ucs = text.toUcs4().first();
xkb_compose_state_feed(m_state.get(), xkb_utf32_to_keysym(ucs));
if (xkb_compose_state_get_status(m_state.get()) == XKB_COMPOSE_COMPOSED) {
char utf8[64];
xkb_compose_state_get_utf8(m_state.get(), utf8, sizeof utf8);
result = QString::fromUtf8(utf8) + text.mid(QString::fromUcs4(&ucs, 1).size());
}
}
cancel();
return result;
}
void DeadKeys::cancel()
{
m_pending.clear();
if (m_state)
xkb_compose_state_reset(m_state.get());
}