aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Gräßlin <[email protected]>2017-02-15 17:47:38 +0100
committerMartin Gräßlin <[email protected]>2017-02-15 17:48:55 +0100
commit58f26b8f5563984e3d8945d63f190a6a5d303e53 (patch)
tree68f71561c9fa7a1c8d9a7eeee408636a2e84436d
parent65ddd32d1a783281610041d24daff7ee92011ded (diff)
Split KWin::Xkb into a dedicated .h and .cpp
Summary: Closes T5221 Test Plan: Compiles Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Maniphest Tasks: T5221 Differential Revision: https://phabricator.kde.org/D4623
-rw-r--r--CMakeLists.txt1
-rw-r--r--keyboard_input.cpp452
-rw-r--r--keyboard_input.h98
-rw-r--r--xkb.cpp485
-rw-r--r--xkb.h140
5 files changed, 627 insertions, 549 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0e7c9e8..f24700c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -435,6 +435,7 @@ set(kwin_KDEINIT_SRCS
virtualkeyboard.cpp
appmenu.cpp
modifier_only_shortcuts.cpp
+ xkb.cpp
)
if(KWIN_BUILD_TABBOX)
diff --git a/keyboard_input.cpp b/keyboard_input.cpp
index 3bf0232..97107b7 100644
--- a/keyboard_input.cpp
+++ b/keyboard_input.cpp
@@ -35,465 +35,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
//screenlocker
#include <KScreenLocker/KsldApp>
// Frameworks
-#include <KKeyServer>
#include <KGlobalAccel>
// Qt
#include <QKeyEvent>
-#include <QTemporaryFile>
-// xkbcommon
-#include <xkbcommon/xkbcommon.h>
-#include <xkbcommon/xkbcommon-compose.h>
-#include <xkbcommon/xkbcommon-keysyms.h>
-// system
-#include <sys/mman.h>
-#include <unistd.h>
-
-Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtCriticalMsg)
namespace KWin
{
-static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args)
-{
- Q_UNUSED(context)
- char buf[1024];
- if (std::vsnprintf(buf, 1023, format, args) <= 0) {
- return;
- }
- switch (priority) {
- case XKB_LOG_LEVEL_DEBUG:
- qCDebug(KWIN_XKB) << "XKB:" << buf;
- break;
- case XKB_LOG_LEVEL_INFO:
- qCInfo(KWIN_XKB) << "XKB:" << buf;
- break;
- case XKB_LOG_LEVEL_WARNING:
- qCWarning(KWIN_XKB) << "XKB:" << buf;
- break;
- case XKB_LOG_LEVEL_ERROR:
- case XKB_LOG_LEVEL_CRITICAL:
- default:
- qCCritical(KWIN_XKB) << "XKB:" << buf;
- break;
- }
-}
-
-Xkb::Xkb(InputRedirection *input)
- : m_input(input)
- , m_context(xkb_context_new(static_cast<xkb_context_flags>(0)))
- , m_keymap(NULL)
- , m_state(NULL)
- , m_shiftModifier(0)
- , m_capsModifier(0)
- , m_controlModifier(0)
- , m_altModifier(0)
- , m_metaModifier(0)
- , m_numLock(0)
- , m_capsLock(0)
- , m_scrollLock(0)
- , m_modifiers(Qt::NoModifier)
- , m_consumedModifiers(Qt::NoModifier)
- , m_keysym(XKB_KEY_NoSymbol)
- , m_leds()
-{
- qRegisterMetaType<KWin::Xkb::LEDs>();
- if (!m_context) {
- qCDebug(KWIN_XKB) << "Could not create xkb context";
- } else {
- xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG);
- xkb_context_set_log_fn(m_context, &xkbLogHandler);
-
- // get locale as described in xkbcommon doc
- // cannot use QLocale as it drops the modifier part
- QByteArray locale = qgetenv("LC_ALL");
- if (locale.isEmpty()) {
- locale = qgetenv("LC_CTYPE");
- }
- if (locale.isEmpty()) {
- locale = qgetenv("LANG");
- }
- if (locale.isEmpty()) {
- locale = QByteArrayLiteral("C");
- }
-
- m_compose.table = xkb_compose_table_new_from_locale(m_context, locale.constData(), XKB_COMPOSE_COMPILE_NO_FLAGS);
- if (m_compose.table) {
- m_compose.state = xkb_compose_state_new(m_compose.table, XKB_COMPOSE_STATE_NO_FLAGS);
- }
- }
-}
-
-Xkb::~Xkb()
-{
- xkb_compose_state_unref(m_compose.state);
- xkb_compose_table_unref(m_compose.table);
- xkb_state_unref(m_state);
- xkb_keymap_unref(m_keymap);
- xkb_context_unref(m_context);
-}
-
-void Xkb::reconfigure()
-{
- if (!m_context) {
- return;
- }
-
- xkb_keymap *keymap = nullptr;
- if (!qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) {
- keymap = loadKeymapFromConfig();
- }
- if (!keymap) {
- qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration";
- keymap = loadDefaultKeymap();
- }
- if (keymap) {
- updateKeymap(keymap);
- } else {
- qCDebug(KWIN_XKB) << "Could not create default xkb keymap";
- }
-}
-
-xkb_keymap *Xkb::loadKeymapFromConfig()
-{
- // load config
- if (!m_config) {
- return nullptr;
- }
- const KConfigGroup config = m_config->group("Layout");
- const QByteArray model = config.readEntry("Model", "pc104").toLocal8Bit();
- const QByteArray layout = config.readEntry("LayoutList", "").toLocal8Bit();
- const QByteArray options = config.readEntry("Options", "").toLocal8Bit();
-
- xkb_rule_names ruleNames = {
- .rules = nullptr,
- .model = model.constData(),
- .layout = layout.constData(),
- .variant = nullptr,
- .options = options.constData()
- };
- return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS);
-}
-
-xkb_keymap *Xkb::loadDefaultKeymap()
-{
- return xkb_keymap_new_from_names(m_context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
-}
-
-void Xkb::installKeymap(int fd, uint32_t size)
-{
- if (!m_context) {
- return;
- }
- char *map = reinterpret_cast<char*>(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
- if (map == MAP_FAILED) {
- return;
- }
- xkb_keymap *keymap = xkb_keymap_new_from_string(m_context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_MAP_COMPILE_PLACEHOLDER);
- munmap(map, size);
- if (!keymap) {
- qCDebug(KWIN_XKB) << "Could not map keymap from file";
- return;
- }
- updateKeymap(keymap);
-}
-
-void Xkb::updateKeymap(xkb_keymap *keymap)
-{
- Q_ASSERT(keymap);
- xkb_state *state = xkb_state_new(keymap);
- if (!state) {
- qCDebug(KWIN_XKB) << "Could not create XKB state";
- xkb_keymap_unref(keymap);
- return;
- }
- // now release the old ones
- xkb_state_unref(m_state);
- xkb_keymap_unref(m_keymap);
-
- m_keymap = keymap;
- m_state = state;
-
- m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT);
- m_capsModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CAPS);
- m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL);
- m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT);
- m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO);
-
- m_numLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM);
- m_capsLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS);
- m_scrollLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL);
-
- m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
-
- createKeymapFile();
-}
-
-void Xkb::createKeymapFile()
-{
- if (!waylandServer()) {
- return;
- }
- // TODO: uninstall keymap on server?
- if (!m_keymap) {
- return;
- }
-
- ScopedCPointer<char> keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1));
- if (keymapString.isNull()) {
- return;
- }
- const uint size = qstrlen(keymapString.data()) + 1;
-
- QTemporaryFile *tmp = new QTemporaryFile(m_input);
- if (!tmp->open()) {
- delete tmp;
- return;
- }
- unlink(tmp->fileName().toUtf8().constData());
- if (!tmp->resize(size)) {
- delete tmp;
- return;
- }
- uchar *address = tmp->map(0, size);
- if (!address) {
- return;
- }
- if (qstrncpy(reinterpret_cast<char*>(address), keymapString.data(), size) == nullptr) {
- delete tmp;
- return;
- }
- waylandServer()->seat()->setKeymap(tmp->handle(), size);
-}
-
-void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
-{
- if (!m_keymap || !m_state) {
- return;
- }
- xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group);
- updateModifiers();
-}
-
-void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state)
-{
- if (!m_keymap || !m_state) {
- return;
- }
- xkb_state_update_key(m_state, key + 8, static_cast<xkb_key_direction>(state));
- if (state == InputRedirection::KeyboardKeyPressed) {
- const auto sym = toKeysym(key);
- if (m_compose.state && xkb_compose_state_feed(m_compose.state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
- switch (xkb_compose_state_get_status(m_compose.state)) {
- case XKB_COMPOSE_NOTHING:
- m_keysym = sym;
- break;
- case XKB_COMPOSE_COMPOSED:
- m_keysym = xkb_compose_state_get_one_sym(m_compose.state);
- break;
- default:
- m_keysym = XKB_KEY_NoSymbol;
- break;
- }
- } else {
- m_keysym = sym;
- }
- }
- updateModifiers();
- updateConsumedModifiers(key);
-}
-
-void Xkb::updateModifiers()
-{
- Qt::KeyboardModifiers mods = Qt::NoModifier;
- if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1 ||
- xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::ShiftModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::AltModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::ControlModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::MetaModifier;
- }
- m_modifiers = mods;
-
- // update LEDs
- LEDs leds;
- if (xkb_state_led_index_is_active(m_state, m_numLock) == 1) {
- leds = leds | LED::NumLock;
- }
- if (xkb_state_led_index_is_active(m_state, m_capsLock) == 1) {
- leds = leds | LED::CapsLock;
- }
- if (xkb_state_led_index_is_active(m_state, m_scrollLock) == 1) {
- leds = leds | LED::ScrollLock;
- }
- if (m_leds != leds) {
- m_leds = leds;
- emit m_input->keyboard()->ledsChanged(m_leds);
- }
-
- const xkb_layout_index_t layout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
- if (layout != m_currentLayout) {
- m_currentLayout = layout;
- }
- if (waylandServer()) {
- waylandServer()->seat()->updateKeyboardModifiers(xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)),
- xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)),
- xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)),
- layout);
- }
-}
-
-QString Xkb::layoutName() const
-{
- return layoutName(m_currentLayout);
-}
-
-QString Xkb::layoutName(xkb_layout_index_t layout) const
-{
- return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, layout));
-}
-
-QMap<xkb_layout_index_t, QString> Xkb::layoutNames() const
-{
- QMap<xkb_layout_index_t, QString> layouts;
- const auto size = xkb_keymap_num_layouts(m_keymap);
- for (xkb_layout_index_t i = 0; i < size; i++) {
- layouts.insert(i, layoutName(i));
- }
- return layouts;
-}
-
-void Xkb::updateConsumedModifiers(uint32_t key)
-{
- Qt::KeyboardModifiers mods = Qt::NoModifier;
- if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_shiftModifier, XKB_CONSUMED_MODE_GTK) == 1) {
- mods |= Qt::ShiftModifier;
- }
- if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_altModifier, XKB_CONSUMED_MODE_GTK) == 1) {
- mods |= Qt::AltModifier;
- }
- if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_controlModifier, XKB_CONSUMED_MODE_GTK) == 1) {
- mods |= Qt::ControlModifier;
- }
- if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_metaModifier, XKB_CONSUMED_MODE_GTK) == 1) {
- mods |= Qt::MetaModifier;
- }
- m_consumedModifiers = mods;
-}
-
-Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts() const
-{
- Qt::KeyboardModifiers mods = Qt::NoModifier;
- if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::ShiftModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::AltModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::ControlModifier;
- }
- if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
- mods |= Qt::MetaModifier;
- }
-
- Qt::KeyboardModifiers consumedMods = m_consumedModifiers;
- if ((mods & Qt::ShiftModifier) && (consumedMods == Qt::ShiftModifier)) {
- // test whether current keysym is a letter
- // in that case the shift should be removed from the consumed modifiers again
- // otherwise it would not be possible to trigger e.g. Shift+W as a shortcut
- // see BUG: 370341
- if (QChar(toQtKey(m_keysym)).isLetter()) {
- consumedMods = Qt::KeyboardModifiers();
- }
- }
-
- return mods & ~consumedMods;
-}
-
-xkb_keysym_t Xkb::toKeysym(uint32_t key)
-{
- if (!m_state) {
- return XKB_KEY_NoSymbol;
- }
- return xkb_state_key_get_one_sym(m_state, key + 8);
-}
-
-QString Xkb::toString(xkb_keysym_t keysym)
-{
- if (!m_state || keysym == XKB_KEY_NoSymbol) {
- return QString();
- }
- QByteArray byteArray(7, 0);
- int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size());
- if (ok == -1 || ok == 0) {
- return QString();
- }
- return QString::fromUtf8(byteArray.constData());
-}
-
-Qt::Key Xkb::toQtKey(xkb_keysym_t keysym) const
-{
- int key = Qt::Key_unknown;
- KKeyServer::symXToKeyQt(keysym, &key);
- return static_cast<Qt::Key>(key);
-}
-
-bool Xkb::shouldKeyRepeat(quint32 key) const
-{
- if (!m_keymap) {
- return false;
- }
- return xkb_keymap_key_repeats(m_keymap, key + 8) != 0;
-}
-
-void Xkb::switchToNextLayout()
-{
- if (!m_keymap || !m_state) {
- return;
- }
- const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap);
- const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts;
- switchToLayout(nextLayout);
-}
-
-void Xkb::switchToPreviousLayout()
-{
- if (!m_keymap || !m_state) {
- return;
- }
- const xkb_layout_index_t previousLayout = m_currentLayout == 0 ? numberOfLayouts() - 1 : m_currentLayout -1;
- switchToLayout(previousLayout);
-}
-
-void Xkb::switchToLayout(xkb_layout_index_t layout)
-{
- if (!m_keymap || !m_state) {
- return;
- }
- if (layout >= numberOfLayouts()) {
- return;
- }
- const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED));
- const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED));
- const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED));
- xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, layout);
- updateModifiers();
-}
-
-quint32 Xkb::numberOfLayouts() const
-{
- if (!m_keymap) {
- return 0;
- }
- return xkb_keymap_num_layouts(m_keymap);
-}
-
KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
: QObject(parent)
, m_input(parent)
diff --git a/keyboard_input.h b/keyboard_input.h
index e0dffa3..83a60c0 100644
--- a/keyboard_input.h
+++ b/keyboard_input.h
@@ -21,14 +21,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define KWIN_KEYBOARD_INPUT_H
#include "input.h"
+#include "xkb.h"
#include <QObject>
#include <QPointer>
#include <QPointF>
-#include <QLoggingCategory>
-Q_DECLARE_LOGGING_CATEGORY(KWIN_XKB)
-
#include <KSharedConfig>
class QWindow;
@@ -55,91 +53,6 @@ namespace LibInput
class Device;
}
-class KWIN_EXPORT Xkb
-{
-public:
- Xkb(InputRedirection *input);
- ~Xkb();
- void setConfig(KSharedConfigPtr config) {
- m_config = config;
- }
- void reconfigure();
-
- void installKeymap(int fd, uint32_t size);
- void updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group);
- void updateKey(uint32_t key, InputRedirection::KeyboardKeyState state);
- xkb_keysym_t toKeysym(uint32_t key);
- xkb_keysym_t currentKeysym() const {
- return m_keysym;
- }
- QString toString(xkb_keysym_t keysym);
- Qt::Key toQtKey(xkb_keysym_t keysym) const;
- Qt::KeyboardModifiers modifiers() const;
- Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const;
- bool shouldKeyRepeat(quint32 key) const;
-
- void switchToNextLayout();
- void switchToPreviousLayout();
- void switchToLayout(xkb_layout_index_t layout);
-
- enum class LED {
- NumLock = 1 << 0,
- CapsLock = 1 << 1,
- ScrollLock = 1 << 2
- };
- Q_DECLARE_FLAGS(LEDs, LED)
- LEDs leds() const {
- return m_leds;
- }
-
- xkb_keymap *keymap() const {
- return m_keymap;
- }
-
- xkb_state *state() const {
- return m_state;
- }
-
- quint32 currentLayout() const {
- return m_currentLayout;
- }
- QString layoutName() const;
- QMap<xkb_layout_index_t, QString> layoutNames() const;
- quint32 numberOfLayouts() const;
-
-private:
- xkb_keymap *loadKeymapFromConfig();
- xkb_keymap *loadDefaultKeymap();
- void updateKeymap(xkb_keymap *keymap);
- void createKeymapFile();
- void updateModifiers();
- void updateConsumedModifiers(uint32_t key);
- QString layoutName(xkb_layout_index_t layout) const;
- InputRedirection *m_input;
- xkb_context *m_context;
- xkb_keymap *m_keymap;
- xkb_state *m_state;
- xkb_mod_index_t m_shiftModifier;
- xkb_mod_index_t m_capsModifier;
- xkb_mod_index_t m_controlModifier;
- xkb_mod_index_t m_altModifier;
- xkb_mod_index_t m_metaModifier;
- xkb_led_index_t m_numLock;
- xkb_led_index_t m_capsLock;
- xkb_led_index_t m_scrollLock;
- Qt::KeyboardModifiers m_modifiers;
- Qt::KeyboardModifiers m_consumedModifiers;
- xkb_keysym_t m_keysym;
- quint32 m_currentLayout = 0;
-
- struct {
- xkb_compose_table *table = nullptr;
- xkb_compose_state *state = nullptr;
- } m_compose;
- LEDs m_leds;
- KSharedConfigPtr m_config;
-};
-
class KWIN_EXPORT KeyboardInputRedirection : public QObject
{
Q_OBJECT
@@ -186,15 +99,6 @@ private:
KeyboardLayout *m_keyboardLayout = nullptr;
};
-inline
-Qt::KeyboardModifiers Xkb::modifiers() const
-{
- return m_modifiers;
}
-}
-
-Q_DECLARE_METATYPE(KWin::Xkb::LED)
-Q_DECLARE_METATYPE(KWin::Xkb::LEDs)
-
#endif
diff --git a/xkb.cpp b/xkb.cpp
new file mode 100644
index 0000000..1171118
--- /dev/null
+++ b/xkb.cpp
@@ -0,0 +1,485 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2013, 2016, 2017 Martin Gräßlin <[email protected]>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*********************************************************************/
+#include "xkb.h"
+#include "keyboard_input.h"
+#include "utils.h"
+#include "wayland_server.h"
+// frameworks
+#include <KConfigGroup>
+#include <KKeyServer>
+// KWayland
+#include <KWayland/Server/seat_interface.h>
+// Qt
+#include <QTemporaryFile>
+// xkbcommon
+#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
+#include <xkbcommon/xkbcommon-keysyms.h>
+// system
+#include <sys/mman.h>
+#include <unistd.h>
+
+Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtCriticalMsg)
+
+namespace KWin
+{
+
+static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args)
+{
+ Q_UNUSED(context)
+ char buf[1024];
+ if (std::vsnprintf(buf, 1023, format, args) <= 0) {
+ return;
+ }
+ switch (priority) {
+ case XKB_LOG_LEVEL_DEBUG:
+ qCDebug(KWIN_XKB) << "XKB:" << buf;
+ break;
+ case XKB_LOG_LEVEL_INFO:
+ qCInfo(KWIN_XKB) << "XKB:" << buf;
+ break;
+ case XKB_LOG_LEVEL_WARNING:
+ qCWarning(KWIN_XKB) << "XKB:" << buf;
+ break;
+ case XKB_LOG_LEVEL_ERROR:
+ case XKB_LOG_LEVEL_CRITICAL:
+ default:
+ qCCritical(KWIN_XKB) << "XKB:" << buf;
+ break;
+ }
+}
+
+Xkb::Xkb(InputRedirection *input)
+ : m_input(input)
+ , m_context(xkb_context_new(static_cast<xkb_context_flags>(0)))
+ , m_keymap(NULL)
+ , m_state(NULL)
+ , m_shiftModifier(0)
+ , m_capsModifier(0)
+ , m_controlModifier(0)
+ , m_altModifier(0)
+ , m_metaModifier(0)
+ , m_numLock(0)
+ , m_capsLock(0)
+ , m_scrollLock(0)
+ , m_modifiers(Qt::NoModifier)
+ , m_consumedModifiers(Qt::NoModifier)
+ , m_keysym(XKB_KEY_NoSymbol)
+ , m_leds()
+{
+ qRegisterMetaType<KWin::Xkb::LEDs>();
+ if (!m_context) {
+ qCDebug(KWIN_XKB) << "Could not create xkb context";
+ } else {
+ xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG);
+ xkb_context_set_log_fn(m_context, &xkbLogHandler);
+
+ // get locale as described in xkbcommon doc
+ // cannot use QLocale as it drops the modifier part
+ QByteArray locale = qgetenv("LC_ALL");
+ if (locale.isEmpty()) {
+ locale = qgetenv("LC_CTYPE");
+ }
+ if (locale.isEmpty()) {
+ locale = qgetenv("LANG");
+ }
+ if (locale.isEmpty()) {
+ locale = QByteArrayLiteral("C");
+ }
+
+ m_compose.table = xkb_compose_table_new_from_locale(m_context, locale.constData(), XKB_COMPOSE_COMPILE_NO_FLAGS);
+ if (m_compose.table) {
+ m_compose.state = xkb_compose_state_new(m_compose.table, XKB_COMPOSE_STATE_NO_FLAGS);
+ }
+ }
+}
+
+Xkb::~Xkb()
+{
+ xkb_compose_state_unref(m_compose.state);
+ xkb_compose_table_unref(m_compose.table);
+ xkb_state_unref(m_state);
+ xkb_keymap_unref(m_keymap);
+ xkb_context_unref(m_context);
+}
+
+void Xkb::reconfigure()
+{
+ if (!m_context) {
+ return;
+ }
+
+ xkb_keymap *keymap = nullptr;
+ if (!qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) {
+ keymap = loadKeymapFromConfig();
+ }
+ if (!keymap) {
+ qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration";
+ keymap = loadDefaultKeymap();
+ }
+ if (keymap) {
+ updateKeymap(keymap);
+ } else {
+ qCDebug(KWIN_XKB) << "Could not create default xkb keymap";
+ }
+}
+
+xkb_keymap *Xkb::loadKeymapFromConfig()
+{
+ // load config
+ if (!m_config) {
+ return nullptr;
+ }
+ const KConfigGroup config = m_config->group("Layout");
+ const QByteArray model = config.readEntry("Model", "pc104").toLocal8Bit();
+ const QByteArray layout = config.readEntry("LayoutList", "").toLocal8Bit();
+ const QByteArray options = config.readEntry("Options", "").toLocal8Bit();
+
+ xkb_rule_names ruleNames = {
+ .rules = nullptr,
+ .model = model.constData(),
+ .layout = layout.constData(),
+ .variant = nullptr,
+ .options = options.constData()
+ };
+ return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS);
+}
+
+xkb_keymap *Xkb::loadDefaultKeymap()
+{
+ return xkb_keymap_new_from_names(m_context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
+}
+
+void Xkb::installKeymap(int fd, uint32_t size)
+{
+ if (!m_context) {
+ return;
+ }
+ char *map = reinterpret_cast<char*>(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
+ if (map == MAP_FAILED) {
+ return;
+ }
+ xkb_keymap *keymap = xkb_keymap_new_from_string(m_context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_MAP_COMPILE_PLACEHOLDER);
+ munmap(map, size);
+ if (!keymap) {
+ qCDebug(KWIN_XKB) << "Could not map keymap from file";
+ return;
+ }
+ updateKeymap(keymap);
+}
+
+void Xkb::updateKeymap(xkb_keymap *keymap)
+{
+ Q_ASSERT(keymap);
+ xkb_state *state = xkb_state_new(keymap);
+ if (!state) {
+ qCDebug(KWIN_XKB) << "Could not create XKB state";
+ xkb_keymap_unref(keymap);
+ return;
+ }
+ // now release the old ones
+ xkb_state_unref(m_state);
+ xkb_keymap_unref(m_keymap);
+
+ m_keymap = keymap;
+ m_state = state;
+
+ m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT);
+ m_capsModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CAPS);
+ m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL);
+ m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT);
+ m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO);
+
+ m_numLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM);
+ m_capsLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS);
+ m_scrollLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL);
+
+ m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
+
+ createKeymapFile();
+}
+
+void Xkb::createKeymapFile()
+{
+ if (!waylandServer()) {
+ return;
+ }
+ // TODO: uninstall keymap on server?
+ if (!m_keymap) {
+ return;
+ }
+
+ ScopedCPointer<char> keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1));
+ if (keymapString.isNull()) {
+ return;
+ }
+ const uint size = qstrlen(keymapString.data()) + 1;
+
+ QTemporaryFile *tmp = new QTemporaryFile(m_input);
+ if (!tmp->open()) {
+ delete tmp;
+ return;
+ }
+ unlink(tmp->fileName().toUtf8().constData());
+ if (!tmp->resize(size)) {
+ delete tmp;
+ return;
+ }
+ uchar *address = tmp->map(0, size);
+ if (!address) {
+ return;
+ }
+ if (qstrncpy(reinterpret_cast<char*>(address), keymapString.data(), size) == nullptr) {
+ delete tmp;
+ return;
+ }
+ waylandServer()->seat()->setKeymap(tmp->handle(), size);
+}
+
+void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
+{
+ if (!m_keymap || !m_state) {
+ return;
+ }
+ xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group);
+ updateModifiers();
+}
+
+void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state)
+{
+ if (!m_keymap || !m_state) {
+ return;
+ }
+ xkb_state_update_key(m_state, key + 8, static_cast<xkb_key_direction>(state));
+ if (state == InputRedirection::KeyboardKeyPressed) {
+ const auto sym = toKeysym(key);
+ if (m_compose.state && xkb_compose_state_feed(m_compose.state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
+ switch (xkb_compose_state_get_status(m_compose.state)) {
+ case XKB_COMPOSE_NOTHING:
+ m_keysym = sym;
+ break;
+ case XKB_COMPOSE_COMPOSED:
+ m_keysym = xkb_compose_state_get_one_sym(m_compose.state);
+ break;
+ default:
+ m_keysym = XKB_KEY_NoSymbol;
+ break;
+ }
+ } else {
+ m_keysym = sym;
+ }
+ }
+ updateModifiers();
+ updateConsumedModifiers(key);
+}
+
+void Xkb::updateModifiers()
+{
+ Qt::KeyboardModifiers mods = Qt::NoModifier;
+ if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1 ||
+ xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::ShiftModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::AltModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::ControlModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::MetaModifier;
+ }
+ m_modifiers = mods;
+
+ // update LEDs
+ LEDs leds;
+ if (xkb_state_led_index_is_active(m_state, m_numLock) == 1) {
+ leds = leds | LED::NumLock;
+ }
+ if (xkb_state_led_index_is_active(m_state, m_capsLock) == 1) {
+ leds = leds | LED::CapsLock;
+ }
+ if (xkb_state_led_index_is_active(m_state, m_scrollLock) == 1) {
+ leds = leds | LED::ScrollLock;
+ }
+ if (m_leds != leds) {
+ m_leds = leds;
+ emit m_input->keyboard()->ledsChanged(m_leds);
+ }
+
+ const xkb_layout_index_t layout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
+ if (layout != m_currentLayout) {
+ m_currentLayout = layout;
+ }
+ if (waylandServer()) {
+ waylandServer()->seat()->updateKeyboardModifiers(xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)),
+ xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)),
+ xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)),
+ layout);
+ }
+}
+
+QString Xkb::layoutName() const
+{
+ return layoutName(m_currentLayout);
+}
+
+QString Xkb::layoutName(xkb_layout_index_t layout) const
+{
+ return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, layout));
+}
+
+QMap<xkb_layout_index_t, QString> Xkb::layoutNames() const
+{
+ QMap<xkb_layout_index_t, QString> layouts;
+ const auto size = xkb_keymap_num_layouts(m_keymap);
+ for (xkb_layout_index_t i = 0; i < size; i++) {
+ layouts.insert(i, layoutName(i));
+ }
+ return layouts;
+}
+
+void Xkb::updateConsumedModifiers(uint32_t key)
+{
+ Qt::KeyboardModifiers mods = Qt::NoModifier;
+ if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_shiftModifier, XKB_CONSUMED_MODE_GTK) == 1) {
+ mods |= Qt::ShiftModifier;
+ }
+ if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_altModifier, XKB_CONSUMED_MODE_GTK) == 1) {
+ mods |= Qt::AltModifier;
+ }
+ if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_controlModifier, XKB_CONSUMED_MODE_GTK) == 1) {
+ mods |= Qt::ControlModifier;
+ }
+ if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_metaModifier, XKB_CONSUMED_MODE_GTK) == 1) {
+ mods |= Qt::MetaModifier;
+ }
+ m_consumedModifiers = mods;
+}
+
+Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts() const
+{
+ Qt::KeyboardModifiers mods = Qt::NoModifier;
+ if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::ShiftModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::AltModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::ControlModifier;
+ }
+ if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
+ mods |= Qt::MetaModifier;
+ }
+
+ Qt::KeyboardModifiers consumedMods = m_consumedModifiers;
+ if ((mods & Qt::ShiftModifier) && (consumedMods == Qt::ShiftModifier)) {
+ // test whether current keysym is a letter
+ // in that case the shift should be removed from the consumed modifiers again
+ // otherwise it would not be possible to trigger e.g. Shift+W as a shortcut
+ // see BUG: 370341
+ if (QChar(toQtKey(m_keysym)).isLetter()) {
+ consumedMods = Qt::KeyboardModifiers();
+ }
+ }
+
+ return mods & ~consumedMods;
+}
+
+xkb_keysym_t Xkb::toKeysym(uint32_t key)
+{
+ if (!m_state) {
+ return XKB_KEY_NoSymbol;
+ }
+ return xkb_state_key_get_one_sym(m_state, key + 8);
+}
+
+QString Xkb::toString(xkb_keysym_t keysym)
+{
+ if (!m_state || keysym == XKB_KEY_NoSymbol) {
+ return QString();
+ }
+ QByteArray byteArray(7, 0);
+ int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size());
+ if (ok == -1 || ok == 0) {
+ return QString();
+ }
+ return QString::fromUtf8(byteArray.constData());
+}
+
+Qt::Key Xkb::toQtKey(xkb_keysym_t keysym) const
+{
+ int key = Qt::Key_unknown;
+ KKeyServer::symXToKeyQt(keysym, &key);
+ return static_cast<Qt::Key>(key);
+}
+
+bool Xkb::shouldKeyRepeat(quint32 key) const
+{
+ if (!m_keymap) {
+ return false;
+ }
+ return xkb_keymap_key_repeats(m_keymap, key + 8) != 0;
+}
+
+void Xkb::switchToNextLayout()
+{
+ if (!m_keymap || !m_state) {
+ return;
+ }
+ const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap);
+ const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts;
+ switchToLayout(nextLayout);
+}
+
+void Xkb::switchToPreviousLayout()
+{
+ if (!m_keymap || !m_state) {
+ return;
+ }
+ const xkb_layout_index_t previousLayout = m_currentLayout == 0 ? numberOfLayouts() - 1 : m_currentLayout -1;
+ switchToLayout(previousLayout);
+}
+
+void Xkb::switchToLayout(xkb_layout_index_t layout)
+{
+ if (!m_keymap || !m_state) {
+ return;
+ }
+ if (layout >= numberOfLayouts()) {
+ return;
+ }
+ const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED));
+ const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED));
+ const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED));
+ xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, layout);
+ updateModifiers();
+}
+
+quint32 Xkb::numberOfLayouts() const
+{
+ if (!m_keymap) {
+ return 0;
+ }
+ return xkb_keymap_num_layouts(m_keymap);
+}
+
+}
diff --git a/xkb.h b/xkb.h
new file mode 100644
index 0000000..940ebf4
--- /dev/null
+++ b/xkb.h
@@ -0,0 +1,140 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2013, 2016, 2017 Martin Gräßlin <[email protected]>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*********************************************************************/
+#ifndef KWIN_XKB_H
+#define KWIN_XKB_H
+#include "input.h"
+
+#include <kwin_export.h>
+
+#include <KSharedConfig>
+
+#include <QLoggingCategory>
+Q_DECLARE_LOGGING_CATEGORY(KWIN_XKB)
+
+struct xkb_context;
+struct xkb_keymap;
+struct xkb_state;
+struct xkb_compose_table;
+struct xkb_compose_state;
+typedef uint32_t xkb_mod_index_t;
+typedef uint32_t xkb_led_index_t;
+typedef uint32_t xkb_keysym_t;
+typedef uint32_t xkb_layout_index_t;
+
+namespace KWin
+{
+
+class KWIN_EXPORT Xkb
+{
+public:
+ Xkb(InputRedirection *input);
+ ~Xkb();
+ void setConfig(KSharedConfigPtr config) {
+ m_config = config;
+ }
+ void reconfigure();
+
+ void installKeymap(int fd, uint32_t size);
+ void updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group);
+ void updateKey(uint32_t key, InputRedirection::KeyboardKeyState state);
+ xkb_keysym_t toKeysym(uint32_t key);
+ xkb_keysym_t currentKeysym() const {
+ return m_keysym;
+ }
+ QString toString(xkb_keysym_t keysym);
+ Qt::Key toQtKey(xkb_keysym_t keysym) const;
+ Qt::KeyboardModifiers modifiers() const;
+ Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const;
+ bool shouldKeyRepeat(quint32 key) const;
+
+ void switchToNextLayout();
+ void switchToPreviousLayout();
+ void switchToLayout(xkb_layout_index_t layout);
+
+ enum class LED {
+ NumLock = 1 << 0,
+ CapsLock = 1 << 1,
+ ScrollLock = 1 << 2
+ };
+ Q_DECLARE_FLAGS(LEDs, LED)
+ LEDs leds() const {
+ return m_leds;
+ }
+
+ xkb_keymap *keymap() const {
+ return m_keymap;
+ }
+
+ xkb_state *state() const {
+ return m_state;
+ }
+
+ quint32 currentLayout() const {
+ return m_currentLayout;
+ }
+ QString layoutName() const;
+ QMap<xkb_layout_index_t, QString> layoutNames() const;
+ quint32 numberOfLayouts() const;
+
+private:
+ xkb_keymap *loadKeymapFromConfig();
+ xkb_keymap *loadDefaultKeymap();
+ void updateKeymap(xkb_keymap *keymap);
+ void createKeymapFile();
+ void updateModifiers();
+ void updateConsumedModifiers(uint32_t key);
+ QString layoutName(xkb_layout_index_t layout) const;
+ InputRedirection *m_input;
+ xkb_context *m_context;
+ xkb_keymap *m_keymap;
+ xkb_state *m_state;
+ xkb_mod_index_t m_shiftModifier;
+ xkb_mod_index_t m_capsModifier;
+ xkb_mod_index_t m_controlModifier;
+ xkb_mod_index_t m_altModifier;
+ xkb_mod_index_t m_metaModifier;
+ xkb_led_index_t m_numLock;
+ xkb_led_index_t m_capsLock;
+ xkb_led_index_t m_scrollLock;
+ Qt::KeyboardModifiers m_modifiers;
+ Qt::KeyboardModifiers m_consumedModifiers;
+ xkb_keysym_t m_keysym;
+ quint32 m_currentLayout = 0;
+
+ struct {
+ xkb_compose_table *table = nullptr;
+ xkb_compose_state *state = nullptr;
+ } m_compose;
+ LEDs m_leds;
+ KSharedConfigPtr m_config;
+};
+
+inline
+Qt::KeyboardModifiers Xkb::modifiers() const
+{
+ return m_modifiers;
+}
+
+}
+
+Q_DECLARE_METATYPE(KWin::Xkb::LED)
+Q_DECLARE_METATYPE(KWin::Xkb::LEDs)
+
+#endif