// SPDX-License-Identifier: MIT #include "keyboardlayout.h" #include #include #include #include #include #include #include namespace KeyboardLayoutDbus { struct Layout { QString name; QString variant; QString displayName; }; using Layouts = QList; QDBusArgument &operator<<(QDBusArgument &argument, const Layout &layout) { argument.beginStructure(); argument << layout.name << layout.variant << layout.displayName; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Layout &layout) { argument.beginStructure(); argument >> layout.name >> layout.variant >> layout.displayName; argument.endStructure(); return argument; } } Q_DECLARE_METATYPE(KeyboardLayoutDbus::Layout) Q_DECLARE_METATYPE(KeyboardLayoutDbus::Layouts) namespace { KeyboardLayoutDbus::Layouts configuredLayouts() { qDBusRegisterMetaType(); qDBusRegisterMetaType(); QDBusInterface layouts(QStringLiteral("org.kde.KWin"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts")); const QDBusReply reply = layouts.call(QStringLiteral("getLayoutsList")); if (!reply.isValid()) { qWarning() << "Could not read KWin keyboard layouts:" << reply.error().message(); return {}; } return reply.value(); } } std::optional numberRowKey(QStringView layout, QStringView variant, int number, bool shifted) { if (number < 1 || number > 9) { return std::nullopt; } using Context = std::unique_ptr; using Keymap = std::unique_ptr; using State = std::unique_ptr; const Context context(xkb_context_new(XKB_CONTEXT_NO_FLAGS), xkb_context_unref); if (!context) { return std::nullopt; } const QByteArray layoutName = layout.toLatin1(); const QByteArray variantName = variant.toLatin1(); const xkb_rule_names names{ .layout = layoutName.constData(), .variant = variantName.isEmpty() ? nullptr : variantName.constData(), }; const Keymap keymap(xkb_keymap_new_from_names(context.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS), xkb_keymap_unref); if (!keymap) { return std::nullopt; } const State state(xkb_state_new(keymap.get()), xkb_state_unref); if (!state) { return std::nullopt; } if (shifted) { const xkb_mod_index_t shift = xkb_keymap_mod_get_index(keymap.get(), XKB_MOD_NAME_SHIFT); if (shift == XKB_MOD_INVALID) { return std::nullopt; } xkb_state_update_mask(state.get(), xkb_mod_mask_t{1} << shift, 0, 0, 0, 0, 0); } const QByteArray keyName = QByteArrayLiteral("AE0") + QByteArray::number(number); const xkb_keycode_t keycode = xkb_keymap_key_by_name(keymap.get(), keyName.constData()); if (keycode == XKB_KEYCODE_INVALID) { return std::nullopt; } const uint32_t codePoint = xkb_keysym_to_utf32(xkb_state_key_get_one_sym(state.get(), keycode)); if (codePoint == 0) { return std::nullopt; } return static_cast(codePoint); } QList numberRowShortcuts(int number) { QList shortcuts; for (const auto &layout : configuredLayouts()) { for (const bool shifted : {false, true}) { const auto key = numberRowKey(layout.name, layout.variant, number, shifted); if (!key) { qWarning() << "Could not map number-row key" << number << "for keyboard layout" << layout.name; continue; } const QKeySequence sequence(Qt::META | *key); if (!shortcuts.contains(sequence)) { shortcuts.append(sequence); } } } return shortcuts; }