File indexing completed on 2024-12-01 13:37:31
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016, 2017 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "keyboard_layout.h" 0010 #include "input_event.h" 0011 #include "keyboard_input.h" 0012 #include "keyboard_layout_switching.h" 0013 #include "xkb.h" 0014 0015 #include <KGlobalAccel> 0016 #include <KLocalizedString> 0017 #include <QAction> 0018 #include <QDBusConnection> 0019 #include <QDBusMessage> 0020 #include <QDBusMetaType> 0021 #include <QDBusPendingCall> 0022 0023 namespace KWin 0024 { 0025 0026 KeyboardLayout::KeyboardLayout(Xkb *xkb, const KSharedConfigPtr &config) 0027 : QObject() 0028 , m_xkb(xkb) 0029 , m_configGroup(config->group("Layout")) 0030 { 0031 } 0032 0033 KeyboardLayout::~KeyboardLayout() = default; 0034 0035 static QString translatedLayout(const QString &layout) 0036 { 0037 return i18nd("xkeyboard-config", layout.toUtf8().constData()); 0038 } 0039 0040 void KeyboardLayout::init() 0041 { 0042 QAction *switchKeyboardAction = new QAction(this); 0043 switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout")); 0044 switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); 0045 switchKeyboardAction->setProperty("componentDisplayName", i18n("Keyboard Layout Switcher")); 0046 const QKeySequence sequence = QKeySequence(Qt::META | Qt::ALT | Qt::Key_K); 0047 KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); 0048 KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList<QKeySequence>({sequence})); 0049 0050 connect(switchKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToNextLayout); 0051 0052 QDBusConnection::sessionBus().connect(QString(), 0053 QStringLiteral("/Layouts"), 0054 QStringLiteral("org.kde.keyboard"), 0055 QStringLiteral("reloadConfig"), 0056 this, 0057 SLOT(reconfigure())); 0058 0059 reconfigure(); 0060 } 0061 0062 void KeyboardLayout::initDBusInterface() 0063 { 0064 if (m_xkb->numberOfLayouts() <= 1) { 0065 if (m_dbusInterface) { 0066 m_dbusInterface->deleteLater(); 0067 m_dbusInterface = nullptr; 0068 } 0069 return; 0070 } 0071 if (m_dbusInterface) { 0072 return; 0073 } 0074 m_dbusInterface = new KeyboardLayoutDBusInterface(m_xkb, m_configGroup, this); 0075 connect(this, &KeyboardLayout::layoutChanged, 0076 m_dbusInterface, &KeyboardLayoutDBusInterface::layoutChanged); 0077 // TODO: the signal might be emitted even if the list didn't change 0078 connect(this, &KeyboardLayout::layoutsReconfigured, m_dbusInterface, &KeyboardLayoutDBusInterface::layoutListChanged); 0079 } 0080 0081 void KeyboardLayout::switchToNextLayout() 0082 { 0083 const quint32 previousLayout = m_xkb->currentLayout(); 0084 m_xkb->switchToNextLayout(); 0085 checkLayoutChange(previousLayout); 0086 } 0087 0088 void KeyboardLayout::switchToPreviousLayout() 0089 { 0090 const quint32 previousLayout = m_xkb->currentLayout(); 0091 m_xkb->switchToPreviousLayout(); 0092 checkLayoutChange(previousLayout); 0093 } 0094 0095 void KeyboardLayout::switchToLayout(xkb_layout_index_t index) 0096 { 0097 const quint32 previousLayout = m_xkb->currentLayout(); 0098 m_xkb->switchToLayout(index); 0099 checkLayoutChange(previousLayout); 0100 } 0101 0102 void KeyboardLayout::reconfigure() 0103 { 0104 if (m_configGroup.isValid()) { 0105 m_configGroup.config()->reparseConfiguration(); 0106 const QString policyKey = m_configGroup.readEntry("SwitchMode", QStringLiteral("Global")); 0107 m_xkb->reconfigure(); 0108 if (!m_policy || m_policy->name() != policyKey) { 0109 m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, m_configGroup, policyKey); 0110 } 0111 } else { 0112 m_xkb->reconfigure(); 0113 } 0114 resetLayout(); 0115 } 0116 0117 void KeyboardLayout::resetLayout() 0118 { 0119 m_layout = m_xkb->currentLayout(); 0120 loadShortcuts(); 0121 0122 initDBusInterface(); 0123 Q_EMIT layoutsReconfigured(); 0124 } 0125 0126 void KeyboardLayout::loadShortcuts() 0127 { 0128 qDeleteAll(m_layoutShortcuts); 0129 m_layoutShortcuts.clear(); 0130 const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher"); 0131 const quint32 count = m_xkb->numberOfLayouts(); 0132 for (uint i = 0; i < count; ++i) { 0133 // layout name is translated in the action name in keyboard kcm! 0134 const QString action = QStringLiteral("Switch keyboard layout to %1").arg(translatedLayout(m_xkb->layoutName(i))); 0135 const auto shortcuts = KGlobalAccel::self()->globalShortcut(componentName, action); 0136 if (shortcuts.isEmpty()) { 0137 continue; 0138 } 0139 QAction *a = new QAction(this); 0140 a->setObjectName(action); 0141 a->setProperty("componentName", componentName); 0142 connect(a, &QAction::triggered, this, 0143 std::bind(&KeyboardLayout::switchToLayout, this, i)); 0144 KGlobalAccel::self()->setShortcut(a, shortcuts, KGlobalAccel::Autoloading); 0145 m_layoutShortcuts << a; 0146 } 0147 } 0148 0149 void KeyboardLayout::checkLayoutChange(uint previousLayout) 0150 { 0151 // Get here on key event or DBus call. 0152 // m_layout - layout saved last time OSD occurred 0153 // previousLayout - actual layout just before potential layout change 0154 // We need OSD if current layout deviates from any of these 0155 const uint currentLayout = m_xkb->currentLayout(); 0156 if (m_layout != currentLayout || previousLayout != currentLayout) { 0157 m_layout = currentLayout; 0158 notifyLayoutChange(); 0159 Q_EMIT layoutChanged(currentLayout); 0160 } 0161 } 0162 0163 void KeyboardLayout::notifyLayoutChange() 0164 { 0165 // notify OSD service about the new layout 0166 QDBusMessage msg = QDBusMessage::createMethodCall( 0167 QStringLiteral("org.kde.plasmashell"), 0168 QStringLiteral("/org/kde/osdService"), 0169 QStringLiteral("org.kde.osdService"), 0170 QStringLiteral("kbdLayoutChanged")); 0171 0172 msg << translatedLayout(m_xkb->layoutName()); 0173 0174 QDBusConnection::sessionBus().asyncCall(msg); 0175 } 0176 0177 static const QString s_keyboardService = QStringLiteral("org.kde.keyboard"); 0178 static const QString s_keyboardObject = QStringLiteral("/Layouts"); 0179 0180 KeyboardLayoutDBusInterface::KeyboardLayoutDBusInterface(Xkb *xkb, const KConfigGroup &configGroup, KeyboardLayout *parent) 0181 : QObject(parent) 0182 , m_xkb(xkb) 0183 , m_configGroup(configGroup) 0184 , m_keyboardLayout(parent) 0185 { 0186 qRegisterMetaType<QVector<LayoutNames>>("QVector<LayoutNames>"); 0187 qDBusRegisterMetaType<LayoutNames>(); 0188 qDBusRegisterMetaType<QVector<LayoutNames>>(); 0189 0190 QDBusConnection::sessionBus().registerObject(s_keyboardObject, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals); 0191 QDBusConnection::sessionBus().registerService(s_keyboardService); 0192 } 0193 0194 KeyboardLayoutDBusInterface::~KeyboardLayoutDBusInterface() 0195 { 0196 QDBusConnection::sessionBus().unregisterService(s_keyboardService); 0197 } 0198 0199 void KeyboardLayoutDBusInterface::switchToNextLayout() 0200 { 0201 m_keyboardLayout->switchToNextLayout(); 0202 } 0203 0204 void KeyboardLayoutDBusInterface::switchToPreviousLayout() 0205 { 0206 m_keyboardLayout->switchToPreviousLayout(); 0207 } 0208 0209 bool KeyboardLayoutDBusInterface::setLayout(uint index) 0210 { 0211 const quint32 previousLayout = m_xkb->currentLayout(); 0212 if (!m_xkb->switchToLayout(index)) { 0213 return false; 0214 } 0215 m_keyboardLayout->checkLayoutChange(previousLayout); 0216 return true; 0217 } 0218 0219 uint KeyboardLayoutDBusInterface::getLayout() const 0220 { 0221 return m_xkb->currentLayout(); 0222 } 0223 0224 QVector<KeyboardLayoutDBusInterface::LayoutNames> KeyboardLayoutDBusInterface::getLayoutsList() const 0225 { 0226 // TODO: - should be handled by layout applet itself, it has nothing to do with KWin 0227 const QStringList displayNames = m_configGroup.readEntry("DisplayNames", QStringList()); 0228 0229 QVector<LayoutNames> ret; 0230 const int layoutsSize = m_xkb->numberOfLayouts(); 0231 const int displayNamesSize = displayNames.size(); 0232 for (int i = 0; i < layoutsSize; ++i) { 0233 ret.append({m_xkb->layoutShortName(i), i < displayNamesSize ? displayNames.at(i) : QString(), translatedLayout(m_xkb->layoutName(i))}); 0234 } 0235 return ret; 0236 } 0237 0238 QDBusArgument &operator<<(QDBusArgument &argument, const KeyboardLayoutDBusInterface::LayoutNames &layoutNames) 0239 { 0240 argument.beginStructure(); 0241 argument << layoutNames.shortName << layoutNames.displayName << layoutNames.longName; 0242 argument.endStructure(); 0243 return argument; 0244 } 0245 0246 const QDBusArgument &operator>>(const QDBusArgument &argument, KeyboardLayoutDBusInterface::LayoutNames &layoutNames) 0247 { 0248 argument.beginStructure(); 0249 argument >> layoutNames.shortName >> layoutNames.displayName >> layoutNames.longName; 0250 argument.endStructure(); 0251 return argument; 0252 } 0253 0254 }