aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Gräßlin <[email protected]>2017-01-20 07:15:27 +0100
committerMartin Gräßlin <[email protected]>2017-01-25 14:00:23 +0100
commitfe561c5c7def52320a045f938d0f0722fd7b473b (patch)
tree5cb1a9ef95a088d56dc8c77fe4e23950117a7170
parent1173f190bc6750ff1e9fd4e73c576a6a6561221a (diff)
Add a basic SNI for keyboard layout
Summary: On X11 the SNI for keyboard layout is provided by the keyboard kded. On Wayland that kded has no real access to the layouts and cannot properly implement switching. Given that it's better to integrate the SNI directly in KWin. The implementation of the SNI is largly based on the existing SNI from plasma-desktop/kcms/keyboard. The implementation so far supports: * Switching to next layout on toggle * Presenting all layouts in a context menu * Switching to a specific layout through the context menu * Opening the keyboard layout configuration module * scroll on SNI to switch layout * config option whether to show the SNI Not yet supported are: * flags and/or short text for the layouts The last point needs more explanation. On X11 the layout name is something like "de" or "us". This can be directly mapped to a flag and can be added as a short note. Xkbcommon does not provide this information directly. Instead it provides us the full name of the layout, e.g. "German" or "English (us)". There is no way in the API to go from "German" to "de". Instead we need to parse the evdev.xml file to gather all information about layouts. This is already done in the keyboard kcm to configure layouts. The implementation needs to be split out into a small helper library. Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D4220
-rw-r--r--keyboard_input.cpp48
-rw-r--r--keyboard_input.h6
-rw-r--r--keyboard_layout.cpp127
-rw-r--r--keyboard_layout.h15
4 files changed, 188 insertions, 8 deletions
diff --git a/keyboard_input.cpp b/keyboard_input.cpp
index 36987a3..f3d6574 100644
--- a/keyboard_input.cpp
+++ b/keyboard_input.cpp
@@ -389,7 +389,22 @@ void Xkb::updateModifiers()
QString Xkb::layoutName() const
{
- return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, m_currentLayout));
+ 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)
@@ -483,13 +498,41 @@ void Xkb::switchToNextLayout()
}
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, nextLayout);
+ 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)
@@ -558,6 +601,7 @@ void KeyboardInputRedirection::init()
m_modifiersChangedSpy = new ModifiersChangedSpy(m_input);
m_input->installInputEventSpy(m_modifiersChangedSpy);
m_keyboardLayout = new KeyboardLayout(m_xkb.data());
+ m_keyboardLayout->setConfig(KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals));
m_keyboardLayout->init();
m_input->installInputEventSpy(m_keyboardLayout);
diff --git a/keyboard_input.h b/keyboard_input.h
index e9a31bc..25ed560 100644
--- a/keyboard_input.h
+++ b/keyboard_input.h
@@ -38,6 +38,7 @@ 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
{
@@ -73,6 +74,8 @@ public:
bool shouldKeyRepeat(quint32 key) const;
void switchToNextLayout();
+ void switchToPreviousLayout();
+ void switchToLayout(xkb_layout_index_t layout);
enum class LED {
NumLock = 1 << 0,
@@ -96,6 +99,8 @@ public:
return m_currentLayout;
}
QString layoutName() const;
+ QMap<xkb_layout_index_t, QString> layoutNames() const;
+ quint32 numberOfLayouts() const;
private:
xkb_keymap *loadKeymapFromConfig();
@@ -104,6 +109,7 @@ private:
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;
diff --git a/keyboard_layout.cpp b/keyboard_layout.cpp
index 566efad..51d21bf 100644
--- a/keyboard_layout.cpp
+++ b/keyboard_layout.cpp
@@ -22,13 +22,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "input_event.h"
#include "main.h"
#include "platform.h"
+#include "utils.h"
+#include <KConfigGroup>
#include <KGlobalAccel>
#include <KLocalizedString>
+#include <KNotifications/KStatusNotifierItem>
#include <QAction>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
+#include <QMenu>
namespace KWin
{
@@ -36,6 +40,7 @@ namespace KWin
KeyboardLayout::KeyboardLayout(Xkb *xkb)
: QObject()
, m_xkb(xkb)
+ , m_notifierItem(nullptr)
{
}
@@ -50,12 +55,7 @@ void KeyboardLayout::init()
KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList<QKeySequence>({sequence}));
KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList<QKeySequence>({sequence}));
kwinApp()->platform()->setupActionForGlobalAccel(switchKeyboardAction);
- connect(switchKeyboardAction, &QAction::triggered, this,
- [this] {
- m_xkb->switchToNextLayout();
- checkLayoutChange();
- }
- );
+ connect(switchKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToNextLayout);
QDBusConnection::sessionBus().connect(QString(),
QStringLiteral("/Layouts"),
@@ -67,15 +67,81 @@ void KeyboardLayout::init()
reconfigure();
}
+void KeyboardLayout::initNotifierItem()
+{
+ bool showNotifier = true;
+ bool showSingle = false;
+ if (m_config) {
+ const auto config = m_config->group(QStringLiteral("Layout"));
+ showNotifier = config.readEntry("ShowLayoutIndicator", true);
+ showSingle = config.readEntry("ShowSingle", false);
+ }
+ const bool shouldShow = showNotifier && (showSingle || m_xkb->numberOfLayouts() > 1);
+ if (shouldShow) {
+ if (m_notifierItem) {
+ return;
+ }
+ } else {
+ delete m_notifierItem;
+ m_notifierItem = nullptr;
+ return;
+ }
+
+ m_notifierItem = new KStatusNotifierItem(this);
+ m_notifierItem->setCategory(KStatusNotifierItem::Hardware);
+ m_notifierItem->setStatus(KStatusNotifierItem::Active);
+ m_notifierItem->setToolTipTitle(i18nc("tooltip title", "Keyboard Layout"));
+ m_notifierItem->setTitle(i18nc("tooltip title", "Keyboard Layout"));
+ m_notifierItem->setToolTipIconByName(QStringLiteral("preferences-desktop-keyboard"));
+ m_notifierItem->setStandardActionsEnabled(false);
+
+ // TODO: proper icon
+ m_notifierItem->setIconByName(QStringLiteral("preferences-desktop-keyboard"));
+
+ connect(m_notifierItem, &KStatusNotifierItem::activateRequested, this, &KeyboardLayout::switchToNextLayout);
+ connect(m_notifierItem, &KStatusNotifierItem::scrollRequested, this,
+ [this] (int delta, Qt::Orientation orientation) {
+ if (orientation == Qt::Horizontal) {
+ return;
+ }
+ if (delta > 0) {
+ switchToNextLayout();
+ } else {
+ switchToPreviousLayout();
+ }
+ }
+ );
+
+ m_notifierItem->setStatus(KStatusNotifierItem::Active);
+}
+
+void KeyboardLayout::switchToNextLayout()
+{
+ m_xkb->switchToNextLayout();
+ checkLayoutChange();
+}
+
+void KeyboardLayout::switchToPreviousLayout()
+{
+ m_xkb->switchToPreviousLayout();
+ checkLayoutChange();
+}
+
void KeyboardLayout::reconfigure()
{
m_xkb->reconfigure();
+ if (m_config) {
+ m_config->reparseConfiguration();
+ }
resetLayout();
}
void KeyboardLayout::resetLayout()
{
m_layout = m_xkb->currentLayout();
+ initNotifierItem();
+ updateNotifier();
+ reinitNotifierMenu();
}
void KeyboardLayout::keyEvent(KeyEvent *event)
@@ -93,6 +159,7 @@ void KeyboardLayout::checkLayoutChange()
}
m_layout = layout;
notifyLayoutChange();
+ updateNotifier();
}
void KeyboardLayout::notifyLayoutChange()
@@ -109,4 +176,52 @@ void KeyboardLayout::notifyLayoutChange()
QDBusConnection::sessionBus().asyncCall(msg);
}
+void KeyboardLayout::updateNotifier()
+{
+ if (!m_notifierItem) {
+ return;
+ }
+ m_notifierItem->setToolTipSubTitle(i18nd("xkeyboard-config", m_xkb->layoutName().toUtf8().constData()));
+ // TODO: update icon
+}
+
+void KeyboardLayout::reinitNotifierMenu()
+{
+ if (!m_notifierItem) {
+ return;
+ }
+ const auto layouts = m_xkb->layoutNames();
+
+ QMenu *menu = new QMenu;
+ auto switchLayout = [this] (xkb_layout_index_t index) {
+ m_xkb->switchToLayout(index);
+ checkLayoutChange();
+ };
+ for (auto it = layouts.begin(); it != layouts.end(); it++) {
+ menu->addAction(i18nd("xkeyboard-config", it.value().toUtf8().constData()), std::bind(switchLayout, it.key()));
+ }
+
+ menu->addSeparator();
+ menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Layouts..."), this,
+ [this] {
+ // TODO: introduce helper function to start kcmshell5
+ QProcess *p = new Process(this);
+ p->setArguments(QStringList{QStringLiteral("--args=--tab=layouts"), QStringLiteral("kcm_keyboard")});
+ p->setProcessEnvironment(kwinApp()->processStartupEnvironment());
+ p->setProgram(QStringLiteral("kcmshell5"));
+ connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater);
+ connect(p, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this,
+ [p] (QProcess::ProcessError e) {
+ if (e == QProcess::FailedToStart) {
+ qCDebug(KWIN_CORE) << "Failed to start kcmshell5";
+ }
+ }
+ );
+ p->start();
+ }
+ );
+
+ m_notifierItem->setContextMenu(menu);
+}
+
}
diff --git a/keyboard_layout.h b/keyboard_layout.h
index 7a71e6e..aa39fd7 100644
--- a/keyboard_layout.h
+++ b/keyboard_layout.h
@@ -22,8 +22,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "input_event_spy.h"
#include <QObject>
+
+#include <KSharedConfig>
typedef uint32_t xkb_layout_index_t;
+class KStatusNotifierItem;
+
namespace KWin
{
class Xkb;
@@ -35,6 +39,10 @@ public:
explicit KeyboardLayout(Xkb *xkb);
~KeyboardLayout() override;
+ void setConfig(KSharedConfigPtr config) {
+ m_config = config;
+ }
+
void init();
void checkLayoutChange();
@@ -47,8 +55,15 @@ private Q_SLOTS:
private:
void notifyLayoutChange();
+ void initNotifierItem();
+ void switchToNextLayout();
+ void switchToPreviousLayout();
+ void updateNotifier();
+ void reinitNotifierMenu();
Xkb *m_xkb;
xkb_layout_index_t m_layout = 0;
+ KStatusNotifierItem *m_notifierItem;
+ KSharedConfigPtr m_config;
};
}