File indexing completed on 2024-05-12 17:07:16
0001 /* 0002 SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "x11_helper.h" 0008 #include "debug.h" 0009 0010 #include <QCoreApplication> 0011 #include <QDebug> 0012 0013 #include <X11/X.h> 0014 #include <X11/XKBlib.h> 0015 #include <X11/Xatom.h> 0016 #include <X11/Xlib.h> 0017 #include <X11/extensions/XKBrules.h> 0018 0019 #include <fixx11h.h> 0020 0021 // more information about the limit https://bugs.freedesktop.org/show_bug.cgi?id=19501 0022 const int X11Helper::MAX_GROUP_COUNT = 4; 0023 0024 const char X11Helper::LEFT_VARIANT_STR[] = "("; 0025 const char X11Helper::RIGHT_VARIANT_STR[] = ")"; 0026 0027 bool X11Helper::xkbSupported(int *xkbOpcode) 0028 { 0029 if (!QX11Info::isPlatformX11()) { 0030 return false; 0031 } 0032 // Verify the Xlib has matching XKB extension. 0033 0034 int major = XkbMajorVersion; 0035 int minor = XkbMinorVersion; 0036 0037 if (!XkbLibraryVersion(&major, &minor)) { 0038 qCWarning(KCM_KEYBOARD) << "Xlib XKB extension " << major << '.' << minor << " != " << XkbMajorVersion << '.' << XkbMinorVersion; 0039 return false; 0040 } 0041 0042 // Verify the X server has matching XKB extension. 0043 0044 int opcode_rtrn; 0045 int error_rtrn; 0046 int xkb_opcode; 0047 if (!XkbQueryExtension(QX11Info::display(), &opcode_rtrn, &xkb_opcode, &error_rtrn, &major, &minor)) { 0048 qCWarning(KCM_KEYBOARD) << "X server XKB extension " << major << '.' << minor << " != " << XkbMajorVersion << '.' << XkbMinorVersion; 0049 return false; 0050 } 0051 0052 if (xkbOpcode != nullptr) { 0053 *xkbOpcode = xkb_opcode; 0054 } 0055 0056 return true; 0057 } 0058 0059 void X11Helper::switchToNextLayout() 0060 { 0061 int size = getLayoutsList().size(); // TODO: could optimize a bit as we don't need the layouts - just count 0062 int group = (X11Helper::getGroup() + 1) % size; 0063 X11Helper::setGroup(group); 0064 } 0065 0066 void X11Helper::scrollLayouts(int delta) 0067 { 0068 int size = getLayoutsList().size(); // TODO: could optimize a bit as we don't need the layouts - just count 0069 int group = X11Helper::getGroup() + delta; 0070 group = group < 0 ? size - ((-group) % size) : group % size; 0071 0072 X11Helper::setGroup(group); 0073 } 0074 0075 QStringList X11Helper::getLayoutsListAsString(const QList<LayoutUnit> &layoutsList) 0076 { 0077 QStringList stringList; 0078 for (const auto &layoutUnit : layoutsList) { 0079 stringList << layoutUnit.toString(); 0080 } 0081 return stringList; 0082 } 0083 0084 bool X11Helper::setLayout(const LayoutUnit &layout) 0085 { 0086 QList<LayoutUnit> currentLayouts = getLayoutsList(); 0087 int idx = currentLayouts.indexOf(layout); 0088 if (idx == -1 || idx >= X11Helper::MAX_GROUP_COUNT) { 0089 qCWarning(KCM_KEYBOARD) << "Layout" << layout.toString() << "is not found in current layout list" << getLayoutsListAsString(currentLayouts); 0090 return false; 0091 } 0092 0093 return X11Helper::setGroup((unsigned int)idx); 0094 } 0095 0096 bool X11Helper::setDefaultLayout() 0097 { 0098 return X11Helper::setGroup(0); 0099 } 0100 0101 bool X11Helper::isDefaultLayout() 0102 { 0103 return X11Helper::getGroup() == 0; 0104 } 0105 0106 LayoutUnit X11Helper::getCurrentLayout() 0107 { 0108 if (!QX11Info::isPlatformX11()) { 0109 return LayoutUnit(); 0110 } 0111 QList<LayoutUnit> currentLayouts = getLayoutsList(); 0112 unsigned int group = X11Helper::getGroup(); 0113 if (group < static_cast<unsigned int>(currentLayouts.size())) 0114 return currentLayouts.at(static_cast<int>(group)); 0115 0116 qCWarning(KCM_KEYBOARD) << "Current group number" << group << "is outside of current layout list" << getLayoutsListAsString(currentLayouts); 0117 return LayoutUnit(); 0118 } 0119 0120 LayoutSet X11Helper::getCurrentLayouts() 0121 { 0122 LayoutSet layoutSet; 0123 0124 QList<LayoutUnit> currentLayouts = getLayoutsList(); 0125 layoutSet.layouts = currentLayouts; 0126 0127 unsigned int group = X11Helper::getGroup(); 0128 if (group < (unsigned int)currentLayouts.size()) { 0129 layoutSet.currentLayout = currentLayouts[group]; 0130 } else { 0131 qCWarning(KCM_KEYBOARD) << "Current group number" << group << "is outside of current layout list" << getLayoutsListAsString(currentLayouts); 0132 layoutSet.currentLayout = LayoutUnit(); 0133 } 0134 0135 return layoutSet; 0136 } 0137 0138 QList<LayoutUnit> X11Helper::getLayoutsList() 0139 { 0140 if (!QX11Info::isPlatformX11()) { 0141 return QList<LayoutUnit>(); 0142 } 0143 XkbConfig xkbConfig; 0144 QList<LayoutUnit> layouts; 0145 if (X11Helper::getGroupNames(QX11Info::display(), &xkbConfig, X11Helper::LAYOUTS_ONLY)) { 0146 for (int i = 0; i < xkbConfig.layouts.size(); i++) { 0147 QString layout(xkbConfig.layouts[i]); 0148 QString variant; 0149 if (i < xkbConfig.variants.size() && !xkbConfig.variants[i].isEmpty()) { 0150 variant = xkbConfig.variants[i]; 0151 } 0152 layouts << LayoutUnit(layout, variant); 0153 } 0154 } else { 0155 qCWarning(KCM_KEYBOARD) << "Failed to get layout groups from X server"; 0156 } 0157 return layouts; 0158 } 0159 0160 bool X11Helper::setGroup(unsigned int group) 0161 { 0162 qCDebug(KCM_KEYBOARD) << group; 0163 xcb_void_cookie_t cookie; 0164 cookie = xcb_xkb_latch_lock_state(QX11Info::connection(), XCB_XKB_ID_USE_CORE_KBD, 0, 0, 1, group, 0, 0, 0); 0165 xcb_generic_error_t *error = nullptr; 0166 error = xcb_request_check(QX11Info::connection(), cookie); 0167 if (error) { 0168 qCDebug(KCM_KEYBOARD) << "Couldn't change the group" << error->error_code; 0169 return false; 0170 } 0171 0172 return true; 0173 } 0174 0175 unsigned int X11Helper::getGroup() 0176 { 0177 XkbStateRec xkbState; 0178 XkbGetState(QX11Info::display(), XkbUseCoreKbd, &xkbState); 0179 return xkbState.group; 0180 } 0181 0182 bool X11Helper::getGroupNames(Display *display, XkbConfig *xkbConfig, FetchType fetchType) 0183 { 0184 static const char OPTIONS_SEPARATOR[] = ","; 0185 0186 Atom real_prop_type; 0187 int fmt; 0188 unsigned long nitems, extra_bytes; 0189 char *prop_data = nullptr; 0190 Status ret; 0191 0192 Atom rules_atom = XInternAtom(display, _XKB_RF_NAMES_PROP_ATOM, False); 0193 0194 /* no such atom! */ 0195 if (rules_atom == None) { /* property cannot exist */ 0196 qCWarning(KCM_KEYBOARD) << "Failed to fetch layouts from server:" 0197 << "could not find the atom" << _XKB_RF_NAMES_PROP_ATOM; 0198 return false; 0199 } 0200 0201 ret = XGetWindowProperty(display, 0202 DefaultRootWindow(display), 0203 rules_atom, 0204 0L, 0205 _XKB_RF_NAMES_PROP_MAXLEN, 0206 False, 0207 XA_STRING, 0208 &real_prop_type, 0209 &fmt, 0210 &nitems, 0211 &extra_bytes, 0212 (unsigned char **)(void *)&prop_data); 0213 0214 /* property not found! */ 0215 if (ret != Success) { 0216 qCWarning(KCM_KEYBOARD) << "Failed to fetch layouts from server:" 0217 << "Could not get the property"; 0218 return false; 0219 } 0220 0221 /* has to be array of strings */ 0222 if ((extra_bytes > 0) || (real_prop_type != XA_STRING) || (fmt != 8)) { 0223 if (prop_data) 0224 XFree(prop_data); 0225 qCWarning(KCM_KEYBOARD) << "Failed to fetch layouts from server:" 0226 << "Wrong property format"; 0227 return false; 0228 } 0229 0230 // qCDebug(KCM_KEYBOARD) << "prop_data:" << nitems << prop_data; 0231 QStringList names; 0232 for (char *p = prop_data; p - prop_data < (long)nitems && p != nullptr; p += strlen(p) + 1) { 0233 names.append(p); 0234 // qDebug() << " " << p; 0235 } 0236 0237 if (names.count() < 4) { //{ rules, model, layouts, variants, options } 0238 XFree(prop_data); 0239 return false; 0240 } 0241 0242 if (fetchType == ALL || fetchType == LAYOUTS_ONLY) { 0243 QStringList layouts = names[2].split(OPTIONS_SEPARATOR); 0244 QStringList variants = names[3].split(OPTIONS_SEPARATOR); 0245 0246 for (int ii = 0; ii < layouts.count(); ii++) { 0247 xkbConfig->layouts << layouts[ii]; 0248 xkbConfig->variants << variants.value(ii); // Empty if not specified 0249 } 0250 qCDebug(KCM_KEYBOARD) << "Fetched layout groups from X server:" 0251 << "\tlayouts:" << xkbConfig->layouts << "\tvariants:" << xkbConfig->variants; 0252 } 0253 0254 if (fetchType == ALL || fetchType == MODEL_ONLY) { 0255 xkbConfig->keyboardModel = names[1]; 0256 qCDebug(KCM_KEYBOARD) << "Fetched keyboard model from X server:" << xkbConfig->keyboardModel; 0257 } 0258 0259 if (fetchType == ALL) { 0260 if (names.count() >= 5) { 0261 if (!names[4].isEmpty()) 0262 xkbConfig->options = names[4].split(OPTIONS_SEPARATOR); 0263 else 0264 xkbConfig->options.clear(); 0265 0266 qCDebug(KCM_KEYBOARD) << "Fetched xkbOptions from X server:" << xkbConfig->options; 0267 } 0268 } 0269 0270 XFree(prop_data); 0271 return true; 0272 } 0273 0274 XEventNotifier::XEventNotifier() 0275 : xkbOpcode(-1) 0276 { 0277 if (QCoreApplication::instance() == nullptr) { 0278 qCWarning(KCM_KEYBOARD) << "Layout Widget won't work properly without QCoreApplication instance"; 0279 } 0280 } 0281 0282 void XEventNotifier::start() 0283 { 0284 qCDebug(KCM_KEYBOARD) << "qCoreApp" << QCoreApplication::instance(); 0285 if (QCoreApplication::instance() != nullptr && X11Helper::xkbSupported(&xkbOpcode)) { 0286 registerForXkbEvents(QX11Info::display()); 0287 0288 // start the event loop 0289 QCoreApplication::instance()->installNativeEventFilter(this); 0290 } 0291 } 0292 0293 void XEventNotifier::stop() 0294 { 0295 if (QCoreApplication::instance() != nullptr) { 0296 // TODO: unregister 0297 // XEventNotifier::unregisterForXkbEvents(QX11Info::display()); 0298 0299 // stop the event loop 0300 QCoreApplication::instance()->removeNativeEventFilter(this); 0301 } 0302 } 0303 0304 bool XEventNotifier::isXkbEvent(xcb_generic_event_t *event) 0305 { 0306 // qDebug() << "event response type:" << (event->response_type & ~0x80) << xkbOpcode << ((event->response_type & ~0x80) == xkbOpcode + XkbEventCode); 0307 return (event->response_type & ~0x80) == xkbOpcode + XkbEventCode; 0308 } 0309 0310 bool XEventNotifier::processOtherEvents(xcb_generic_event_t * /*event*/) 0311 { 0312 return true; 0313 } 0314 0315 bool XEventNotifier::processXkbEvents(xcb_generic_event_t *event) 0316 { 0317 _xkb_event *xkbevt = reinterpret_cast<_xkb_event *>(event); 0318 if (XEventNotifier::isGroupSwitchEvent(xkbevt)) { 0319 // qDebug() << "group switch event"; 0320 Q_EMIT layoutChanged(); 0321 } else if (XEventNotifier::isLayoutSwitchEvent(xkbevt)) { 0322 // qDebug() << "layout switch event"; 0323 Q_EMIT layoutMapChanged(); 0324 } 0325 return true; 0326 } 0327 0328 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0329 bool XEventNotifier::nativeEventFilter(const QByteArray &eventType, void *message, long *) 0330 #else 0331 bool XEventNotifier::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) 0332 #endif 0333 { 0334 // qDebug() << "event type:" << eventType; 0335 if (eventType == "xcb_generic_event_t") { 0336 xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); 0337 if (isXkbEvent(ev)) { 0338 processXkbEvents(ev); 0339 } else { 0340 processOtherEvents(ev); 0341 } 0342 } 0343 return false; 0344 } 0345 0346 bool XEventNotifier::isGroupSwitchEvent(_xkb_event *xkbEvent) 0347 { 0348 // XkbEvent *xkbEvent = (XkbEvent*) event; 0349 #define GROUP_CHANGE_MASK (XkbGroupStateMask | XkbGroupBaseMask | XkbGroupLatchMask | XkbGroupLockMask) 0350 0351 return xkbEvent->any.xkbType == XkbStateNotify && (xkbEvent->state_notify.changed & GROUP_CHANGE_MASK); 0352 } 0353 0354 bool XEventNotifier::isLayoutSwitchEvent(_xkb_event *xkbEvent) 0355 { 0356 // XkbEvent *xkbEvent = (XkbEvent*) event; 0357 0358 return //( (xkbEvent->any.xkb_type == XkbMapNotify) && (xkbEvent->map.changed & XkbKeySymsMask) ) || 0359 /* || ( (xkbEvent->any.xkb_type == XkbNamesNotify) && (xkbEvent->names.changed & XkbGroupNamesMask) || )*/ 0360 (xkbEvent->any.xkbType == XkbNewKeyboardNotify); 0361 } 0362 0363 int XEventNotifier::registerForXkbEvents(Display *display) 0364 { 0365 int eventMask = XkbNewKeyboardNotifyMask | XkbStateNotifyMask; 0366 if (!XkbSelectEvents(display, XkbUseCoreKbd, eventMask, eventMask)) { 0367 qCWarning(KCM_KEYBOARD) << "Couldn't select desired XKB events"; 0368 return false; 0369 } 0370 return true; 0371 } 0372 0373 static const char LAYOUT_VARIANT_SEPARATOR_PREFIX[] = "("; 0374 static const char LAYOUT_VARIANT_SEPARATOR_SUFFIX[] = ")"; 0375 0376 static QString &stripVariantName(QString &variant) 0377 { 0378 if (variant.endsWith(LAYOUT_VARIANT_SEPARATOR_SUFFIX)) { 0379 int suffixLen = strlen(LAYOUT_VARIANT_SEPARATOR_SUFFIX); 0380 return variant.remove(variant.length() - suffixLen, suffixLen); 0381 } 0382 return variant; 0383 } 0384 0385 LayoutUnit::LayoutUnit(const QString &fullLayoutName) 0386 { 0387 QStringList lv = fullLayoutName.split(LAYOUT_VARIANT_SEPARATOR_PREFIX); 0388 m_layout = lv[0]; 0389 m_variant = lv.size() > 1 ? stripVariantName(lv[1]) : QString(); 0390 } 0391 0392 QString LayoutUnit::toString() const 0393 { 0394 if (m_variant.isEmpty()) 0395 return m_layout; 0396 0397 return m_layout + LAYOUT_VARIANT_SEPARATOR_PREFIX + m_variant + LAYOUT_VARIANT_SEPARATOR_SUFFIX; 0398 } 0399 0400 const int LayoutUnit::MAX_LABEL_LENGTH = 3;