File indexing completed on 2024-05-05 17:41:52
0001 /* 0002 SPDX-FileCopyrightText: 2000 Matthias Hölzer-Klüpfel <hoelzer@kde.org> 0003 SPDX-FileCopyrightText: 2014 Frederik Gladhorn <gladhorn@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include <cmath> 0009 #include <unistd.h> 0010 0011 #include "kaccess.h" 0012 0013 #include <QMessageBox> 0014 #include <QPainter> 0015 #include <QProcess> 0016 #include <QScreen> 0017 #include <QTimer> 0018 0019 #include <QAction> 0020 #include <QApplication> 0021 #include <QHBoxLayout> 0022 #include <QVBoxLayout> 0023 0024 #include <KAboutData> 0025 #include <KComboBox> 0026 #include <KConfig> 0027 #include <KConfigGroup> 0028 #include <KDBusService> 0029 #include <KGlobalAccel> 0030 #include <KKeyServer> 0031 #include <KLazyLocalizedString> 0032 #include <KLocalizedString> 0033 #include <KNotification> 0034 #include <KSharedConfig> 0035 #include <KUserTimestamp> 0036 #include <KWindowSystem> 0037 #include <KX11Extras> 0038 #include <QDialog> 0039 #include <QDialogButtonBox> 0040 0041 #include <netwm.h> 0042 #define XK_MISCELLANY 0043 #define XK_XKB_KEYS 0044 #include <X11/keysymdef.h> 0045 0046 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0047 #include <QX11Info> 0048 #else 0049 #include <QtGui/private/qtx11extras_p.h> 0050 #endif 0051 0052 #include <QLoggingCategory> 0053 0054 Q_LOGGING_CATEGORY(logKAccess, "kcm_kaccess") 0055 0056 struct ModifierKey { 0057 const unsigned int mask; 0058 const KeySym keysym; 0059 const char *name; 0060 const KLazyLocalizedString lockedText; 0061 const KLazyLocalizedString latchedText; 0062 const KLazyLocalizedString unlatchedText; 0063 }; 0064 0065 static const ModifierKey modifierKeys[] = { 0066 {ShiftMask, 0067 0, 0068 "Shift", 0069 kli18n("The Shift key has been locked and is now active for all of the following keypresses."), 0070 kli18n("The Shift key is now active."), 0071 kli18n("The Shift key is now inactive.")}, 0072 {ControlMask, 0073 0, 0074 "Control", 0075 kli18n("The Control key has been locked and is now active for all of the following keypresses."), 0076 kli18n("The Control key is now active."), 0077 kli18n("The Control key is now inactive.")}, 0078 {0, 0079 XK_Alt_L, 0080 "Alt", 0081 kli18n("The Alt key has been locked and is now active for all of the following keypresses."), 0082 kli18n("The Alt key is now active."), 0083 kli18n("The Alt key is now inactive.")}, 0084 {0, 0085 0, 0086 "Win", 0087 kli18n("The Win key has been locked and is now active for all of the following keypresses."), 0088 kli18n("The Win key is now active."), 0089 kli18n("The Win key is now inactive.")}, 0090 {0, 0091 XK_Meta_L, 0092 "Meta", 0093 kli18n("The Meta key has been locked and is now active for all of the following keypresses."), 0094 kli18n("The Meta key is now active."), 0095 kli18n("The Meta key is now inactive.")}, 0096 {0, 0097 XK_Super_L, 0098 "Super", 0099 kli18n("The Super key has been locked and is now active for all of the following keypresses."), 0100 kli18n("The Super key is now active."), 0101 kli18n("The Super key is now inactive.")}, 0102 {0, 0103 XK_Hyper_L, 0104 "Hyper", 0105 kli18n("The Hyper key has been locked and is now active for all of the following keypresses."), 0106 kli18n("The Hyper key is now active."), 0107 kli18n("The Hyper key is now inactive.")}, 0108 {0, 0109 0, 0110 "Alt Graph", 0111 kli18n("The Alt Graph key has been locked and is now active for all of the following keypresses."), 0112 kli18n("The Alt Graph key is now active."), 0113 kli18n("The Alt Graph key is now inactive.")}, 0114 {0, XK_Num_Lock, "Num Lock", kli18n("The Num Lock key has been activated."), {}, kli18n("The Num Lock key is now inactive.")}, 0115 {LockMask, 0, "Caps Lock", kli18n("The Caps Lock key has been activated."), {}, kli18n("The Caps Lock key is now inactive.")}, 0116 {0, XK_Scroll_Lock, "Scroll Lock", kli18n("The Scroll Lock key has been activated."), {}, kli18n("The Scroll Lock key is now inactive.")}, 0117 {0, 0, "", {}, {}, {}}}; 0118 0119 /********************************************************************/ 0120 0121 KAccessApp::KAccessApp() 0122 : overlay(nullptr) 0123 , _player(nullptr) 0124 , _activeWindow(KX11Extras::activeWindow()) 0125 , toggleScreenReaderAction(new QAction(this)) 0126 { 0127 m_error = false; 0128 connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &KAccessApp::activeWindowChanged); 0129 0130 features = 0; 0131 requestedFeatures = 0; 0132 dialog = nullptr; 0133 0134 if (!QX11Info::isPlatformX11()) { 0135 m_error = true; 0136 return; 0137 } 0138 0139 initMasks(); 0140 XkbStateRec state_return; 0141 XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return); 0142 unsigned char latched = XkbStateMods(&state_return); 0143 unsigned char locked = XkbModLocks(&state_return); 0144 state = ((int)locked) << 8 | latched; 0145 0146 auto service = new KDBusService(KDBusService::Unique, this); 0147 connect(service, &KDBusService::activateRequested, this, &KAccessApp::newInstance); 0148 0149 QTimer::singleShot(0, this, &KAccessApp::readSettings); 0150 } 0151 0152 void KAccessApp::newInstance() 0153 { 0154 KSharedConfig::openConfig()->reparseConfiguration(); 0155 readSettings(); 0156 } 0157 0158 void KAccessApp::readSettings() 0159 { 0160 // select bell events if we need them 0161 int state = (m_bellSettings.customBell() || m_bellSettings.visibleBell()) ? XkbBellNotifyMask : 0; 0162 XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbBellNotifyMask, state); 0163 0164 // deactivate system bell if not needed 0165 if (!m_bellSettings.systemBell()) 0166 XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, 0); 0167 else 0168 XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, XkbAudibleBellMask); 0169 0170 // get keyboard state 0171 XkbDescPtr xkb = XkbGetMap(QX11Info::display(), 0, XkbUseCoreKbd); 0172 if (!xkb) 0173 return; 0174 if (XkbGetControls(QX11Info::display(), XkbAllControlsMask, xkb) != Success) 0175 return; 0176 0177 // sticky keys 0178 if (m_keyboardSettings.stickyKeys()) { 0179 if (m_keyboardSettings.stickyKeysLatch()) 0180 xkb->ctrls->ax_options |= XkbAX_LatchToLockMask; 0181 else 0182 xkb->ctrls->ax_options &= ~XkbAX_LatchToLockMask; 0183 if (m_keyboardSettings.stickyKeysAutoOff()) 0184 xkb->ctrls->ax_options |= XkbAX_TwoKeysMask; 0185 else 0186 xkb->ctrls->ax_options &= ~XkbAX_TwoKeysMask; 0187 if (m_keyboardSettings.stickyKeysBeep()) 0188 xkb->ctrls->ax_options |= XkbAX_StickyKeysFBMask; 0189 else 0190 xkb->ctrls->ax_options &= ~XkbAX_StickyKeysFBMask; 0191 xkb->ctrls->enabled_ctrls |= XkbStickyKeysMask; 0192 } else 0193 xkb->ctrls->enabled_ctrls &= ~XkbStickyKeysMask; 0194 0195 // toggle keys 0196 if (m_keyboardSettings.toggleKeysBeep()) 0197 xkb->ctrls->ax_options |= XkbAX_IndicatorFBMask; 0198 else 0199 xkb->ctrls->ax_options &= ~XkbAX_IndicatorFBMask; 0200 0201 // slow keys 0202 if (m_keyboardFiltersSettings.slowKeys()) { 0203 if (m_keyboardFiltersSettings.slowKeysPressBeep()) 0204 xkb->ctrls->ax_options |= XkbAX_SKPressFBMask; 0205 else 0206 xkb->ctrls->ax_options &= ~XkbAX_SKPressFBMask; 0207 if (m_keyboardFiltersSettings.slowKeysAcceptBeep()) 0208 xkb->ctrls->ax_options |= XkbAX_SKAcceptFBMask; 0209 else 0210 xkb->ctrls->ax_options &= ~XkbAX_SKAcceptFBMask; 0211 if (m_keyboardFiltersSettings.slowKeysRejectBeep()) 0212 xkb->ctrls->ax_options |= XkbAX_SKRejectFBMask; 0213 else 0214 xkb->ctrls->ax_options &= ~XkbAX_SKRejectFBMask; 0215 xkb->ctrls->enabled_ctrls |= XkbSlowKeysMask; 0216 } else 0217 xkb->ctrls->enabled_ctrls &= ~XkbSlowKeysMask; 0218 xkb->ctrls->slow_keys_delay = m_keyboardFiltersSettings.slowKeysDelay(); 0219 0220 // bounce keys 0221 if (m_keyboardFiltersSettings.bounceKeys()) { 0222 if (m_keyboardFiltersSettings.bounceKeysRejectBeep()) 0223 xkb->ctrls->ax_options |= XkbAX_BKRejectFBMask; 0224 else 0225 xkb->ctrls->ax_options &= ~XkbAX_BKRejectFBMask; 0226 xkb->ctrls->enabled_ctrls |= XkbBounceKeysMask; 0227 } else 0228 xkb->ctrls->enabled_ctrls &= ~XkbBounceKeysMask; 0229 xkb->ctrls->debounce_delay = m_keyboardFiltersSettings.bounceKeysDelay(); 0230 0231 // gestures for enabling the other features 0232 if (m_mouseSettings.gestures()) 0233 xkb->ctrls->enabled_ctrls |= XkbAccessXKeysMask; 0234 else 0235 xkb->ctrls->enabled_ctrls &= ~XkbAccessXKeysMask; 0236 0237 // timeout 0238 if (m_keyboardSettings.accessXTimeout()) { 0239 xkb->ctrls->ax_timeout = m_keyboardSettings.accessXTimeoutDelay() * 60; 0240 xkb->ctrls->axt_opts_mask = 0; 0241 xkb->ctrls->axt_opts_values = 0; 0242 xkb->ctrls->axt_ctrls_mask = XkbStickyKeysMask | XkbSlowKeysMask; 0243 xkb->ctrls->axt_ctrls_values = 0; 0244 xkb->ctrls->enabled_ctrls |= XkbAccessXTimeoutMask; 0245 } else 0246 xkb->ctrls->enabled_ctrls &= ~XkbAccessXTimeoutMask; 0247 0248 // gestures for enabling the other features 0249 if (m_keyboardSettings.accessXBeep()) 0250 xkb->ctrls->ax_options |= XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask; 0251 else 0252 xkb->ctrls->ax_options &= ~(XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask); 0253 0254 // mouse-by-keyboard 0255 0256 if (m_mouseSettings.mouseKeys()) { 0257 xkb->ctrls->mk_delay = m_mouseSettings.accelerationDelay(); 0258 0259 const int interval = m_mouseSettings.repetitionInterval(); 0260 xkb->ctrls->mk_interval = interval; 0261 0262 xkb->ctrls->mk_time_to_max = m_mouseSettings.accelerationTime(); 0263 0264 xkb->ctrls->mk_max_speed = m_mouseSettings.maxSpeed(); 0265 0266 xkb->ctrls->mk_curve = m_mouseSettings.profileCurve(); 0267 xkb->ctrls->mk_dflt_btn = 0; 0268 0269 xkb->ctrls->enabled_ctrls |= XkbMouseKeysMask; 0270 } else 0271 xkb->ctrls->enabled_ctrls &= ~XkbMouseKeysMask; 0272 0273 features = xkb->ctrls->enabled_ctrls & (XkbSlowKeysMask | XkbBounceKeysMask | XkbStickyKeysMask | XkbMouseKeysMask); 0274 if (dialog == nullptr) 0275 requestedFeatures = features; 0276 // set state 0277 XkbSetControls(QX11Info::display(), 0278 XkbControlsEnabledMask | XkbMouseKeysAccelMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbAccessXKeysMask 0279 | XkbAccessXTimeoutMask, 0280 xkb); 0281 0282 // select AccessX events 0283 XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask); 0284 0285 if (!m_bellSettings.customBell() && !m_bellSettings.visibleBell() && !(m_mouseSettings.gestures() && m_mouseSettings.gestureConfirmation()) 0286 && !m_keyboardSettings.keyboardNotifyModifiers() && !m_mouseSettings.keyboardNotifyAccess()) { 0287 uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask; 0288 uint values = xkb->ctrls->enabled_ctrls & ctrls; 0289 XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values); 0290 } else { 0291 // reset them after program exit 0292 uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask; 0293 uint values = XkbAudibleBellMask; 0294 XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values); 0295 } 0296 0297 delete overlay; 0298 overlay = nullptr; 0299 0300 setScreenReaderEnabled(m_screenReaderSettings.enabled()); 0301 0302 toggleScreenReaderAction->setText(i18n("Toggle Screen Reader On and Off")); 0303 toggleScreenReaderAction->setObjectName(QStringLiteral("Toggle Screen Reader On and Off")); 0304 toggleScreenReaderAction->setProperty("componentDisplayName", i18nc("Name for kaccess shortcuts category", "Accessibility")); 0305 KGlobalAccel::self()->setGlobalShortcut(toggleScreenReaderAction, Qt::META | Qt::ALT | Qt::Key_S); 0306 connect(toggleScreenReaderAction, &QAction::triggered, this, &KAccessApp::toggleScreenReader); 0307 } 0308 0309 void KAccessApp::toggleScreenReader() 0310 { 0311 KSharedConfig::Ptr _config = KSharedConfig::openConfig(); 0312 KConfigGroup screenReaderGroup(_config, "ScreenReader"); 0313 bool enabled = !screenReaderGroup.readEntry("Enabled", false); 0314 screenReaderGroup.writeEntry("Enabled", enabled); 0315 setScreenReaderEnabled(enabled); 0316 } 0317 0318 void KAccessApp::setScreenReaderEnabled(bool enabled) 0319 { 0320 if (enabled) { 0321 QStringList args = {QStringLiteral("set"), 0322 QStringLiteral("org.gnome.desktop.a11y.applications"), 0323 QStringLiteral("screen-reader-enabled"), 0324 QStringLiteral("true")}; 0325 int ret = QProcess::execute(QStringLiteral("gsettings"), args); 0326 if (ret == 0) { 0327 qint64 pid = 0; 0328 QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--replace")}, QString(), &pid); 0329 qCDebug(logKAccess) << "Launching Orca, pid:" << pid; 0330 } 0331 } else { 0332 QProcess::startDetached( 0333 QStringLiteral("gsettings"), 0334 {QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("false")}); 0335 } 0336 } 0337 0338 static int maskToBit(int mask) 0339 { 0340 for (int i = 0; i < 8; i++) 0341 if (mask & (1 << i)) 0342 return i; 0343 return -1; 0344 } 0345 0346 void KAccessApp::initMasks() 0347 { 0348 for (int i = 0; i < 8; i++) 0349 keys[i] = -1; 0350 state = 0; 0351 0352 for (int i = 0; strcmp(modifierKeys[i].name, "") != 0; i++) { 0353 int mask = modifierKeys[i].mask; 0354 if (mask == 0) { 0355 if (modifierKeys[i].keysym != 0) { 0356 mask = XkbKeysymToModifiers(QX11Info::display(), modifierKeys[i].keysym); 0357 } else { 0358 if (!strcmp(modifierKeys[i].name, "Win")) { 0359 mask = KKeyServer::modXMeta(); 0360 } else { 0361 mask = XkbKeysymToModifiers(QX11Info::display(), XK_Mode_switch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Shift) 0362 | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Latch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Lock); 0363 } 0364 } 0365 } 0366 0367 int bit = maskToBit(mask); 0368 if (bit != -1 && keys[bit] == -1) 0369 keys[bit] = i; 0370 } 0371 } 0372 0373 struct xkb_any_ { 0374 uint8_t response_type; 0375 uint8_t xkbType; 0376 uint16_t sequence; 0377 xcb_timestamp_t time; 0378 uint8_t deviceID; 0379 }; 0380 0381 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0382 bool KAccessApp::nativeEventFilter(const QByteArray &eventType, void *message, long *result) 0383 #else 0384 bool KAccessApp::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) 0385 #endif 0386 { 0387 if (eventType == "xcb_generic_event_t") { 0388 xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message); 0389 if ((event->response_type & ~0x80) == XkbEventCode + xkb_opcode) { 0390 xkb_any_ *ev = reinterpret_cast<xkb_any_ *>(event); 0391 // Workaround for an XCB bug. xkbType comes from an EventType that is defined with bits, like 0392 // <item name="BellNotify"> <bit>8</bit> 0393 // while the generated XCB event type enum is defined as a bitmask, like 0394 // XCB_XKB_EVENT_TYPE_BELL_NOTIFY = 256 0395 // This means if xbkType is 8, we need to set the 8th bit to 1, thus raising 2 to power of 8. 0396 // See also https://bugs.freedesktop.org/show_bug.cgi?id=51295 0397 const int eventType = pow(2, ev->xkbType); 0398 switch (eventType) { 0399 case XCB_XKB_EVENT_TYPE_STATE_NOTIFY: 0400 xkbStateNotify(); 0401 break; 0402 case XCB_XKB_EVENT_TYPE_BELL_NOTIFY: 0403 xkbBellNotify(reinterpret_cast<xcb_xkb_bell_notify_event_t *>(event)); 0404 break; 0405 case XCB_XKB_EVENT_TYPE_CONTROLS_NOTIFY: 0406 xkbControlsNotify(reinterpret_cast<xcb_xkb_controls_notify_event_t *>(event)); 0407 break; 0408 } 0409 return true; 0410 } 0411 } 0412 return false; 0413 } 0414 0415 void VisualBell::paintEvent(QPaintEvent *event) 0416 { 0417 QWidget::paintEvent(event); 0418 QTimer::singleShot(_pause, this, &QWidget::hide); 0419 } 0420 0421 void KAccessApp::activeWindowChanged(WId wid) 0422 { 0423 _activeWindow = wid; 0424 } 0425 0426 void KAccessApp::xkbStateNotify() 0427 { 0428 XkbStateRec state_return; 0429 XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return); 0430 unsigned char latched = XkbStateMods(&state_return); 0431 unsigned char locked = XkbModLocks(&state_return); 0432 int mods = ((int)locked) << 8 | latched; 0433 0434 if (state != mods) { 0435 if (m_keyboardSettings.keyboardNotifyModifiers()) 0436 for (int i = 0; i < 8; i++) { 0437 if (keys[i] != -1) { 0438 if (modifierKeys[keys[i]].latchedText.isEmpty() && ((((mods >> i) & 0x101) != 0) != (((state >> i) & 0x101) != 0))) { 0439 if ((mods >> i) & 1) { 0440 KNotification::event(QStringLiteral("lockkey-locked"), modifierKeys[keys[i]].lockedText.toString()); 0441 } else { 0442 KNotification::event(QStringLiteral("lockkey-unlocked"), modifierKeys[keys[i]].unlatchedText.toString()); 0443 } 0444 } else if (!modifierKeys[keys[i]].latchedText.isEmpty() && (((mods >> i) & 0x101) != ((state >> i) & 0x101))) { 0445 if ((mods >> i) & 0x100) { 0446 KNotification::event(QStringLiteral("modifierkey-locked"), modifierKeys[keys[i]].lockedText.toString()); 0447 } else if ((mods >> i) & 1) { 0448 KNotification::event(QStringLiteral("modifierkey-latched"), modifierKeys[keys[i]].latchedText.toString()); 0449 } else { 0450 KNotification::event(QStringLiteral("modifierkey-unlatched"), modifierKeys[keys[i]].unlatchedText.toString()); 0451 } 0452 } 0453 } 0454 } 0455 state = mods; 0456 } 0457 } 0458 0459 void KAccessApp::xkbBellNotify(xcb_xkb_bell_notify_event_t *event) 0460 { 0461 // bail out if we should not really ring 0462 if (event->eventOnly) 0463 return; 0464 0465 // flash the visible bell 0466 if (m_bellSettings.visibleBell()) { 0467 // create overlay widget 0468 if (!overlay) 0469 overlay = new VisualBell(m_bellSettings.visibleBellPause()); 0470 0471 WId id = _activeWindow; 0472 0473 NETRect frame, window; 0474 NETWinInfo net(QX11Info::connection(), id, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); 0475 0476 net.kdeGeometry(frame, window); 0477 0478 overlay->setGeometry(window.pos.x, window.pos.y, window.size.width, window.size.height); 0479 0480 if (m_bellSettings.invertScreen()) { 0481 QPixmap screen = QGuiApplication::primaryScreen()->grabWindow(id, 0, 0, window.size.width, window.size.height); 0482 0483 #ifdef __GNUC__ 0484 #warning is this the best way to invert a pixmap? 0485 #endif 0486 // QPixmap invert(window.size.width, window.size.height); 0487 QImage i = screen.toImage(); 0488 i.invertPixels(); 0489 QPalette pal = overlay->palette(); 0490 pal.setBrush(overlay->backgroundRole(), QBrush(QPixmap::fromImage(i))); 0491 overlay->setPalette(pal); 0492 /* 0493 QPainter p(&invert); 0494 p.setRasterOp(QPainter::NotCopyROP); 0495 p.drawPixmap(0, 0, screen); 0496 overlay->setBackgroundPixmap(invert); 0497 */ 0498 } else { 0499 QPalette pal = overlay->palette(); 0500 pal.setColor(overlay->backgroundRole(), m_bellSettings.visibleBellColor()); 0501 overlay->setPalette(pal); 0502 } 0503 0504 // flash the overlay widget 0505 overlay->raise(); 0506 overlay->show(); 0507 QCoreApplication::sendPostedEvents(); 0508 } 0509 0510 // ask Phonon to ring a nice bell 0511 if (m_bellSettings.customBell()) { 0512 if (!_player) { // as creating the player is expensive, delay the creation 0513 _player = Phonon::createPlayer(Phonon::AccessibilityCategory); 0514 _player->setParent(this); 0515 _player->setCurrentSource(_currentPlayerSource); 0516 } 0517 _player->play(); 0518 } 0519 } 0520 0521 QString mouseKeysShortcut(Display *display) 0522 { 0523 // Calculate the keycode 0524 KeySym sym = XK_MouseKeys_Enable; 0525 KeyCode code = XKeysymToKeycode(display, sym); 0526 if (code == 0) { 0527 sym = XK_Pointer_EnableKeys; 0528 code = XKeysymToKeycode(display, sym); 0529 if (code == 0) 0530 return QString(); // No shortcut available? 0531 } 0532 0533 // Calculate the modifiers by searching the keysym in the X keyboard mapping 0534 XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd); 0535 0536 if (!xkbdesc) 0537 return QString(); // Failed to obtain the mapping from server 0538 0539 bool found = false; 0540 unsigned char modifiers = 0; 0541 int groups = XkbKeyNumGroups(xkbdesc, code); 0542 for (int grp = 0; grp < groups && !found; grp++) { 0543 int levels = XkbKeyGroupWidth(xkbdesc, code, grp); 0544 for (int level = 0; level < levels && !found; level++) { 0545 if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) { 0546 // keysym found => determine modifiers 0547 int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp]; 0548 XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]); 0549 for (int i = 0; i < type->map_count && !found; i++) { 0550 if (type->map[i].active && (type->map[i].level == level)) { 0551 modifiers = type->map[i].mods.mask; 0552 found = true; 0553 } 0554 } 0555 } 0556 } 0557 } 0558 XkbFreeClientMap(xkbdesc, 0, true); 0559 0560 if (!found) 0561 return QString(); // Somehow the keycode -> keysym mapping is flawed 0562 0563 XEvent ev; 0564 ev.type = KeyPress; 0565 ev.xkey.display = display; 0566 ev.xkey.keycode = code; 0567 ev.xkey.state = 0; 0568 int key; 0569 KKeyServer::xEventToQt(&ev, &key); 0570 QString keyname = QKeySequence(key).toString(); 0571 0572 unsigned int AltMask = KKeyServer::modXAlt(); 0573 unsigned int WinMask = KKeyServer::modXMeta(); 0574 unsigned int NumMask = KKeyServer::modXNumLock(); 0575 unsigned int ScrollMask = KKeyServer::modXScrollLock(); 0576 0577 unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L); 0578 unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L); 0579 unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L); 0580 unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift) 0581 | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock); 0582 0583 unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask; 0584 0585 AltGrMask &= ~mods; 0586 MetaMask &= ~(mods | AltGrMask); 0587 SuperMask &= ~(mods | AltGrMask | MetaMask); 0588 HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask); 0589 0590 if ((modifiers & AltGrMask) != 0) 0591 keyname = i18n("AltGraph") + QLatin1Char('+') + keyname; 0592 if ((modifiers & HyperMask) != 0) 0593 keyname = i18n("Hyper") + QLatin1Char('+') + keyname; 0594 if ((modifiers & SuperMask) != 0) 0595 keyname = i18n("Super") + QLatin1Char('+') + keyname; 0596 if ((modifiers & WinMask) != 0) 0597 keyname = i18n("Meta") + QLatin1Char('+') + keyname; 0598 if ((modifiers & WinMask) != 0) 0599 keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname; 0600 if ((modifiers & AltMask) != 0) 0601 keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname; 0602 if ((modifiers & ControlMask) != 0) 0603 keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname; 0604 if ((modifiers & ShiftMask) != 0) 0605 keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname; 0606 0607 return keyname; 0608 } 0609 0610 void KAccessApp::createDialogContents() 0611 { 0612 if (dialog == nullptr) { 0613 dialog = new QDialog(nullptr); 0614 dialog->setWindowTitle(i18n("Warning")); 0615 dialog->setObjectName(QStringLiteral("AccessXWarning")); 0616 dialog->setModal(true); 0617 0618 QVBoxLayout *topLayout = new QVBoxLayout(); 0619 0620 QHBoxLayout *lay = new QHBoxLayout(); 0621 0622 QLabel *label1 = new QLabel(); 0623 QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-warning")); 0624 if (icon.isNull()) 0625 icon = QMessageBox::standardIcon(QMessageBox::Warning); 0626 label1->setPixmap(icon.pixmap(64, 64)); 0627 0628 lay->addWidget(label1, 0, Qt::AlignCenter); 0629 0630 QVBoxLayout *vlay = new QVBoxLayout(); 0631 lay->addItem(vlay); 0632 0633 featuresLabel = new QLabel(); 0634 featuresLabel->setAlignment(Qt::AlignVCenter); 0635 featuresLabel->setWordWrap(true); 0636 vlay->addWidget(featuresLabel); 0637 vlay->addStretch(); 0638 0639 QHBoxLayout *hlay = new QHBoxLayout(); 0640 vlay->addItem(hlay); 0641 0642 QLabel *showModeLabel = new QLabel(i18n("&When a gesture was used:")); 0643 hlay->addWidget(showModeLabel); 0644 0645 showModeCombobox = new KComboBox(); 0646 hlay->addWidget(showModeCombobox); 0647 showModeLabel->setBuddy(showModeCombobox); 0648 showModeCombobox->insertItem(0, i18n("Change Settings Without Asking")); 0649 showModeCombobox->insertItem(1, i18n("Show This Confirmation Dialog")); 0650 showModeCombobox->insertItem(2, i18n("Deactivate All AccessX Features & Gestures")); 0651 showModeCombobox->setCurrentIndex(1); 0652 topLayout->addLayout(lay); 0653 0654 auto buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, dialog); 0655 0656 topLayout->addWidget(buttons); 0657 dialog->setLayout(topLayout); 0658 0659 connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); 0660 connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); 0661 connect(dialog, &QDialog::accepted, this, &KAccessApp::yesClicked); 0662 connect(dialog, &QDialog::rejected, this, &KAccessApp::noClicked); 0663 } 0664 } 0665 0666 void KAccessApp::xkbControlsNotify(xcb_xkb_controls_notify_event_t *event) 0667 { 0668 unsigned int newFeatures = 0669 event->enabledControls & (XCB_XKB_BOOL_CTRL_SLOW_KEYS | XCB_XKB_BOOL_CTRL_BOUNCE_KEYS | XCB_XKB_BOOL_CTRL_STICKY_KEYS | XCB_XKB_BOOL_CTRL_MOUSE_KEYS); 0670 0671 if (newFeatures != features) { 0672 unsigned int enabled = newFeatures & ~features; 0673 unsigned int disabled = features & ~newFeatures; 0674 0675 if (!m_mouseSettings.gestureConfirmation()) { 0676 requestedFeatures = enabled | (requestedFeatures & ~disabled); 0677 notifyChanges(); 0678 features = newFeatures; 0679 } else { 0680 // set the AccessX features back to what they were. We will 0681 // apply the changes later if the user allows us to do that. 0682 readSettings(); 0683 0684 requestedFeatures = enabled | (requestedFeatures & ~disabled); 0685 0686 enabled = requestedFeatures & ~features; 0687 disabled = features & ~requestedFeatures; 0688 0689 QStringList enabledFeatures; 0690 QStringList disabledFeatures; 0691 0692 if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0693 enabledFeatures << i18n("Slow keys"); 0694 else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0695 disabledFeatures << i18n("Slow keys"); 0696 0697 if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0698 enabledFeatures << i18n("Bounce keys"); 0699 else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0700 disabledFeatures << i18n("Bounce keys"); 0701 0702 if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0703 enabledFeatures << i18n("Sticky keys"); 0704 else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0705 disabledFeatures << i18n("Sticky keys"); 0706 0707 if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0708 enabledFeatures << i18n("Mouse keys"); 0709 else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0710 disabledFeatures << i18n("Mouse keys"); 0711 0712 QString question; 0713 switch (enabledFeatures.count()) { 0714 case 0: 0715 switch (disabledFeatures.count()) { 0716 case 1: 0717 question = i18n("Do you really want to deactivate \"%1\"?", disabledFeatures[0]); 0718 break; 0719 case 2: 0720 question = i18n("Do you really want to deactivate \"%1\" and \"%2\"?", disabledFeatures[0], disabledFeatures[1]); 0721 break; 0722 case 3: 0723 question = 0724 i18n("Do you really want to deactivate \"%1\", \"%2\" and \"%3\"?", disabledFeatures[0], disabledFeatures[1], disabledFeatures[2]); 0725 break; 0726 case 4: 0727 question = i18n("Do you really want to deactivate \"%1\", \"%2\", \"%3\" and \"%4\"?", 0728 disabledFeatures[0], 0729 disabledFeatures[1], 0730 disabledFeatures[2], 0731 disabledFeatures[3]); 0732 break; 0733 } 0734 break; 0735 case 1: 0736 switch (disabledFeatures.count()) { 0737 case 0: 0738 question = i18n("Do you really want to activate \"%1\"?", enabledFeatures[0]); 0739 break; 0740 case 1: 0741 question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\"?", enabledFeatures[0], disabledFeatures[0]); 0742 break; 0743 case 2: 0744 question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\" and \"%3\"?", 0745 enabledFeatures[0], 0746 disabledFeatures[0], 0747 disabledFeatures[1]); 0748 break; 0749 case 3: 0750 question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\", \"%3\" and \"%4\"?", 0751 enabledFeatures[0], 0752 disabledFeatures[0], 0753 disabledFeatures[1], 0754 disabledFeatures[2]); 0755 break; 0756 } 0757 break; 0758 case 2: 0759 switch (disabledFeatures.count()) { 0760 case 0: 0761 question = i18n("Do you really want to activate \"%1\" and \"%2\"?", enabledFeatures[0], enabledFeatures[1]); 0762 break; 0763 case 1: 0764 question = i18n("Do you really want to activate \"%1\" and \"%2\" and to deactivate \"%3\"?", 0765 enabledFeatures[0], 0766 enabledFeatures[1], 0767 disabledFeatures[0]); 0768 break; 0769 case 2: 0770 question = i18n("Do you really want to activate \"%1\", and \"%2\" and to deactivate \"%3\" and \"%4\"?", 0771 enabledFeatures[0], 0772 enabledFeatures[1], 0773 enabledFeatures[0], 0774 disabledFeatures[1]); 0775 break; 0776 } 0777 break; 0778 case 3: 0779 switch (disabledFeatures.count()) { 0780 case 0: 0781 question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[2]); 0782 break; 0783 case 1: 0784 question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\" and to deactivate \"%4\"?", 0785 enabledFeatures[0], 0786 enabledFeatures[1], 0787 enabledFeatures[2], 0788 disabledFeatures[0]); 0789 break; 0790 } 0791 break; 0792 case 4: 0793 question = i18n("Do you really want to activate \"%1\", \"%2\", \"%3\" and \"%4\"?", 0794 enabledFeatures[0], 0795 enabledFeatures[1], 0796 enabledFeatures[2], 0797 enabledFeatures[3]); 0798 break; 0799 } 0800 QString explanation; 0801 if (enabledFeatures.count() + disabledFeatures.count() == 1) { 0802 explanation = i18n("An application has requested to change this setting."); 0803 0804 if (m_mouseSettings.gestures()) { 0805 if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0806 explanation = i18n("You held down the Shift key for 8 seconds or an application has requested to change this setting."); 0807 else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0808 explanation = i18n("You pressed the Shift key 5 consecutive times or an application has requested to change this setting."); 0809 else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_MOUSE_KEYS) { 0810 QString shortcut = mouseKeysShortcut(QX11Info::display()); 0811 if (!shortcut.isEmpty() && !shortcut.isNull()) 0812 explanation = i18n("You pressed %1 or an application has requested to change this setting.", shortcut); 0813 } 0814 } 0815 } else { 0816 if (m_mouseSettings.gestures()) 0817 explanation = i18n("An application has requested to change these settings, or you used a combination of several keyboard gestures."); 0818 else 0819 explanation = i18n("An application has requested to change these settings."); 0820 } 0821 0822 createDialogContents(); 0823 featuresLabel->setText(question + QStringLiteral("\n\n") + explanation + QStringLiteral(" ") 0824 + i18n("These AccessX settings are needed for some users with motion impairments and can be configured in the KDE System " 0825 "Settings. You can also turn them on and off with standardized keyboard gestures.\n\nIf you do not need them, you " 0826 "can select \"Deactivate all AccessX features and gestures\".")); 0827 0828 dialog->setWindowFlag(Qt::WindowStaysOnTopHint); 0829 KUserTimestamp::updateUserTimestamp(0); 0830 dialog->show(); 0831 } 0832 } 0833 } 0834 0835 void KAccessApp::notifyChanges() 0836 { 0837 if (!m_mouseSettings.keyboardNotifyAccess()) 0838 return; 0839 0840 unsigned int enabled = requestedFeatures & ~features; 0841 unsigned int disabled = features & ~requestedFeatures; 0842 0843 if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0844 KNotification::event(QStringLiteral("slowkeys"), 0845 i18n("Slow keys has been enabled. From now on, you need to press each key for a certain length of time before it gets accepted.")); 0846 else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0847 KNotification::event(QStringLiteral("slowkeys"), i18n("Slow keys has been disabled.")); 0848 0849 if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0850 KNotification::event(QStringLiteral("bouncekeys"), 0851 i18n("Bounce keys has been enabled. From now on, each key will be blocked for a certain length of time after it was used.")); 0852 else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0853 KNotification::event(QStringLiteral("bouncekeys"), i18n("Bounce keys has been disabled.")); 0854 0855 if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0856 KNotification::event(QStringLiteral("stickykeys"), 0857 i18n("Sticky keys has been enabled. From now on, modifier keys will stay latched after you have released them.")); 0858 else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0859 KNotification::event(QStringLiteral("stickykeys"), i18n("Sticky keys has been disabled.")); 0860 0861 if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0862 KNotification::event(QStringLiteral("mousekeys"), 0863 i18n("Mouse keys has been enabled. From now on, you can use the number pad of your keyboard in order to control the mouse.")); 0864 else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0865 KNotification::event(QStringLiteral("mousekeys"), i18n("Mouse keys has been disabled.")); 0866 } 0867 0868 void KAccessApp::applyChanges() 0869 { 0870 notifyChanges(); 0871 unsigned int enabled = requestedFeatures & ~features; 0872 unsigned int disabled = features & ~requestedFeatures; 0873 0874 KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); 0875 0876 if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0877 config.writeEntry("SlowKeys", true); 0878 else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) 0879 config.writeEntry("SlowKeys", false); 0880 0881 if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0882 config.writeEntry("BounceKeys", true); 0883 else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) 0884 config.writeEntry("BounceKeys", false); 0885 0886 if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0887 config.writeEntry("StickyKeys", true); 0888 else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) 0889 config.writeEntry("StickyKeys", false); 0890 0891 KConfigGroup mousegrp(KSharedConfig::openConfig(), "Mouse"); 0892 0893 if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0894 mousegrp.writeEntry("MouseKeys", true); 0895 else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) 0896 mousegrp.writeEntry("MouseKeys", false); 0897 mousegrp.sync(); 0898 config.sync(); 0899 } 0900 0901 void KAccessApp::yesClicked() 0902 { 0903 if (dialog) 0904 dialog->deleteLater(); 0905 dialog = nullptr; 0906 0907 KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); 0908 switch (showModeCombobox->currentIndex()) { 0909 case 0: 0910 config.writeEntry("Gestures", true); 0911 config.writeEntry("GestureConfirmation", false); 0912 break; 0913 default: 0914 config.writeEntry("Gestures", true); 0915 config.writeEntry("GestureConfirmation", true); 0916 break; 0917 case 2: 0918 requestedFeatures = 0; 0919 config.writeEntry("Gestures", false); 0920 config.writeEntry("GestureConfirmation", true); 0921 } 0922 config.sync(); 0923 0924 if (features != requestedFeatures) { 0925 notifyChanges(); 0926 applyChanges(); 0927 } 0928 readSettings(); 0929 } 0930 0931 void KAccessApp::noClicked() 0932 { 0933 if (dialog) 0934 dialog->deleteLater(); 0935 dialog = nullptr; 0936 requestedFeatures = features; 0937 0938 KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); 0939 switch (showModeCombobox->currentIndex()) { 0940 case 0: 0941 config.writeEntry("Gestures", true); 0942 config.writeEntry("GestureConfirmation", false); 0943 break; 0944 default: 0945 config.writeEntry("Gestures", true); 0946 config.writeEntry("GestureConfirmation", true); 0947 break; 0948 case 2: 0949 requestedFeatures = 0; 0950 config.writeEntry("Gestures", false); 0951 config.writeEntry("GestureConfirmation", true); 0952 } 0953 config.sync(); 0954 0955 if (features != requestedFeatures) 0956 applyChanges(); 0957 readSettings(); 0958 } 0959 0960 void KAccessApp::dialogClosed() 0961 { 0962 if (dialog != nullptr) 0963 dialog->deleteLater(); 0964 dialog = nullptr; 0965 0966 requestedFeatures = features; 0967 } 0968 0969 void KAccessApp::setXkbOpcode(int opcode) 0970 { 0971 xkb_opcode = opcode; 0972 }