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
This commit is contained in:
parent
1173f190bc
commit
fe561c5c7d
4 changed files with 188 additions and 8 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue