File indexing completed on 2024-11-10 04:57:49
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(QStringLiteral("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 QAction *switchLastUsedKeyboardAction = new QAction(this); 0053 switchLastUsedKeyboardAction->setObjectName(QStringLiteral("Switch to Last-Used Keyboard Layout")); 0054 switchLastUsedKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); 0055 switchLastUsedKeyboardAction->setProperty("componentDisplayName", i18n("Keyboard Layout Switcher")); 0056 const QKeySequence sequenceLastUsed = QKeySequence(Qt::META | Qt::ALT | Qt::Key_L); 0057 KGlobalAccel::self()->setDefaultShortcut(switchLastUsedKeyboardAction, QList<QKeySequence>({sequenceLastUsed})); 0058 KGlobalAccel::self()->setShortcut(switchLastUsedKeyboardAction, QList<QKeySequence>({sequenceLastUsed})); 0059 0060 connect(switchLastUsedKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToLastUsedLayout); 0061 0062 QDBusConnection::sessionBus().connect(QString(), 0063 QStringLiteral("/Layouts"), 0064 QStringLiteral("org.kde.keyboard"), 0065 QStringLiteral("reloadConfig"), 0066 this, 0067 SLOT(reconfigure())); 0068 0069 reconfigure(); 0070 } 0071 0072 void KeyboardLayout::initDBusInterface() 0073 { 0074 if (m_xkb->numberOfLayouts() <= 1) { 0075 if (m_dbusInterface) { 0076 m_dbusInterface->deleteLater(); 0077 m_dbusInterface = nullptr; 0078 } 0079 return; 0080 } 0081 if (m_dbusInterface) { 0082 return; 0083 } 0084 m_dbusInterface = new KeyboardLayoutDBusInterface(m_xkb, m_configGroup, this); 0085 connect(this, &KeyboardLayout::layoutChanged, 0086 m_dbusInterface, &KeyboardLayoutDBusInterface::layoutChanged); 0087 // TODO: the signal might be emitted even if the list didn't change 0088 connect(this, &KeyboardLayout::layoutsReconfigured, m_dbusInterface, &KeyboardLayoutDBusInterface::layoutListChanged); 0089 } 0090 0091 void KeyboardLayout::switchToNextLayout() 0092 { 0093 const quint32 previousLayout = m_xkb->currentLayout(); 0094 m_xkb->switchToNextLayout(); 0095 checkLayoutChange(previousLayout); 0096 } 0097 0098 void KeyboardLayout::switchToPreviousLayout() 0099 { 0100 const quint32 previousLayout = m_xkb->currentLayout(); 0101 m_xkb->switchToPreviousLayout(); 0102 checkLayoutChange(previousLayout); 0103 } 0104 0105 void KeyboardLayout::switchToLayout(xkb_layout_index_t index) 0106 { 0107 const quint32 previousLayout = m_xkb->currentLayout(); 0108 m_xkb->switchToLayout(index); 0109 checkLayoutChange(previousLayout); 0110 } 0111 0112 void KeyboardLayout::switchToLastUsedLayout() 0113 { 0114 const quint32 count = m_xkb->numberOfLayouts(); 0115 if (!m_lastUsedLayout.has_value() || *m_lastUsedLayout >= count) { 0116 switchToPreviousLayout(); 0117 } else { 0118 switchToLayout(*m_lastUsedLayout); 0119 } 0120 } 0121 0122 void KeyboardLayout::reconfigure() 0123 { 0124 if (m_configGroup.isValid()) { 0125 m_configGroup.config()->reparseConfiguration(); 0126 const QString policyKey = m_configGroup.readEntry("SwitchMode", QStringLiteral("Global")); 0127 m_xkb->reconfigure(); 0128 if (!m_policy || m_policy->name() != policyKey) { 0129 m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, m_configGroup, policyKey); 0130 } 0131 } else { 0132 m_xkb->reconfigure(); 0133 } 0134 resetLayout(); 0135 } 0136 0137 void KeyboardLayout::resetLayout() 0138 { 0139 m_layout = m_xkb->currentLayout(); 0140 loadShortcuts(); 0141 0142 initDBusInterface(); 0143 Q_EMIT layoutsReconfigured(); 0144 } 0145 0146 void KeyboardLayout::loadShortcuts() 0147 { 0148 qDeleteAll(m_layoutShortcuts); 0149 m_layoutShortcuts.clear(); 0150 const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher"); 0151 const quint32 count = m_xkb->numberOfLayouts(); 0152 for (uint i = 0; i < count; ++i) { 0153 // layout name is translated in the action name in keyboard kcm! 0154 const QString action = QStringLiteral("Switch keyboard layout to %1").arg(translatedLayout(m_xkb->layoutName(i))); 0155 const auto shortcuts = KGlobalAccel::self()->globalShortcut(componentName, action); 0156 if (shortcuts.isEmpty()) { 0157 continue; 0158 } 0159 QAction *a = new QAction(this); 0160 a->setObjectName(action); 0161 a->setProperty("componentName", componentName); 0162 connect(a, &QAction::triggered, this, 0163 std::bind(&KeyboardLayout::switchToLayout, this, i)); 0164 KGlobalAccel::self()->setShortcut(a, shortcuts, KGlobalAccel::Autoloading); 0165 m_layoutShortcuts << a; 0166 } 0167 } 0168 0169 void KeyboardLayout::checkLayoutChange(uint previousLayout) 0170 { 0171 // Get here on key event or DBus call. 0172 // m_layout - layout saved last time OSD occurred 0173 // previousLayout - actual layout just before potential layout change 0174 // We need OSD if current layout deviates from any of these 0175 const uint currentLayout = m_xkb->currentLayout(); 0176 if (m_layout != currentLayout || previousLayout != currentLayout) { 0177 m_lastUsedLayout = std::optional<uint>{previousLayout}; 0178 m_layout = currentLayout; 0179 notifyLayoutChange(); 0180 Q_EMIT layoutChanged(currentLayout); 0181 } 0182 } 0183 0184 void KeyboardLayout::notifyLayoutChange() 0185 { 0186 // notify OSD service about the new layout 0187 QDBusMessage msg = QDBusMessage::createMethodCall( 0188 QStringLiteral("org.kde.plasmashell"), 0189 QStringLiteral("/org/kde/osdService"), 0190 QStringLiteral("org.kde.osdService"), 0191 QStringLiteral("kbdLayoutChanged")); 0192 0193 msg << translatedLayout(m_xkb->layoutName()); 0194 0195 QDBusConnection::sessionBus().asyncCall(msg); 0196 } 0197 0198 static const QString s_keyboardService = QStringLiteral("org.kde.keyboard"); 0199 static const QString s_keyboardObject = QStringLiteral("/Layouts"); 0200 0201 KeyboardLayoutDBusInterface::KeyboardLayoutDBusInterface(Xkb *xkb, const KConfigGroup &configGroup, KeyboardLayout *parent) 0202 : QObject(parent) 0203 , m_xkb(xkb) 0204 , m_configGroup(configGroup) 0205 , m_keyboardLayout(parent) 0206 { 0207 qRegisterMetaType<QList<LayoutNames>>("QList<LayoutNames>"); 0208 qDBusRegisterMetaType<LayoutNames>(); 0209 qDBusRegisterMetaType<QList<LayoutNames>>(); 0210 0211 QDBusConnection::sessionBus().registerObject(s_keyboardObject, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals); 0212 QDBusConnection::sessionBus().registerService(s_keyboardService); 0213 } 0214 0215 KeyboardLayoutDBusInterface::~KeyboardLayoutDBusInterface() 0216 { 0217 QDBusConnection::sessionBus().unregisterService(s_keyboardService); 0218 } 0219 0220 void KeyboardLayoutDBusInterface::switchToNextLayout() 0221 { 0222 m_keyboardLayout->switchToNextLayout(); 0223 } 0224 0225 void KeyboardLayoutDBusInterface::switchToPreviousLayout() 0226 { 0227 m_keyboardLayout->switchToPreviousLayout(); 0228 } 0229 0230 bool KeyboardLayoutDBusInterface::setLayout(uint index) 0231 { 0232 const quint32 previousLayout = m_xkb->currentLayout(); 0233 if (!m_xkb->switchToLayout(index)) { 0234 return false; 0235 } 0236 m_keyboardLayout->checkLayoutChange(previousLayout); 0237 return true; 0238 } 0239 0240 uint KeyboardLayoutDBusInterface::getLayout() const 0241 { 0242 return m_xkb->currentLayout(); 0243 } 0244 0245 QList<KeyboardLayoutDBusInterface::LayoutNames> KeyboardLayoutDBusInterface::getLayoutsList() const 0246 { 0247 // TODO: - should be handled by layout applet itself, it has nothing to do with KWin 0248 const QStringList displayNames = m_configGroup.readEntry("DisplayNames", QStringList()); 0249 0250 QList<LayoutNames> ret; 0251 const int layoutsSize = m_xkb->numberOfLayouts(); 0252 const int displayNamesSize = displayNames.size(); 0253 for (int i = 0; i < layoutsSize; ++i) { 0254 ret.append({m_xkb->layoutShortName(i), i < displayNamesSize ? displayNames.at(i) : QString(), translatedLayout(m_xkb->layoutName(i))}); 0255 } 0256 return ret; 0257 } 0258 0259 QDBusArgument &operator<<(QDBusArgument &argument, const KeyboardLayoutDBusInterface::LayoutNames &layoutNames) 0260 { 0261 argument.beginStructure(); 0262 argument << layoutNames.shortName << layoutNames.displayName << layoutNames.longName; 0263 argument.endStructure(); 0264 return argument; 0265 } 0266 0267 const QDBusArgument &operator>>(const QDBusArgument &argument, KeyboardLayoutDBusInterface::LayoutNames &layoutNames) 0268 { 0269 argument.beginStructure(); 0270 argument >> layoutNames.shortName >> layoutNames.displayName >> layoutNames.longName; 0271 argument.endStructure(); 0272 return argument; 0273 } 0274 0275 } 0276 0277 #include "moc_keyboard_layout.cpp"