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