Files
fedora-tools/plasma-task-group-shortcuts/keyboardlayout.cpp
T

134 lines
4.0 KiB
C++

// SPDX-License-Identifier: MIT
#include "keyboardlayout.h"
#include <QDBusArgument>
#include <QDBusInterface>
#include <QDBusMetaType>
#include <QDBusReply>
#include <QDebug>
#include <memory>
#include <xkbcommon/xkbcommon.h>
namespace KeyboardLayoutDbus
{
struct Layout
{
QString name;
QString variant;
QString displayName;
};
using Layouts = QList<Layout>;
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<KeyboardLayoutDbus::Layout>();
qDBusRegisterMetaType<KeyboardLayoutDbus::Layouts>();
QDBusInterface layouts(QStringLiteral("org.kde.KWin"), QStringLiteral("/Layouts"),
QStringLiteral("org.kde.KeyboardLayouts"));
const QDBusReply<KeyboardLayoutDbus::Layouts> reply = layouts.call(QStringLiteral("getLayoutsList"));
if (!reply.isValid()) {
qWarning() << "Could not read KWin keyboard layouts:" << reply.error().message();
return {};
}
return reply.value();
}
}
std::optional<Qt::Key> numberRowKey(QStringView layout, QStringView variant, int number, bool shifted)
{
if (number < 1 || number > 9) {
return std::nullopt;
}
using Context = std::unique_ptr<xkb_context, decltype(&xkb_context_unref)>;
using Keymap = std::unique_ptr<xkb_keymap, decltype(&xkb_keymap_unref)>;
using State = std::unique_ptr<xkb_state, decltype(&xkb_state_unref)>;
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<Qt::Key>(codePoint);
}
QList<QKeySequence> numberRowShortcuts(int number)
{
QList<QKeySequence> 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;
}