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