/* SPDX-FileCopyrightText: 2000 Matthias Hölzer-Klüpfel SPDX-FileCopyrightText: 2014 Frederik Gladhorn SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include #include "kaccess.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define XK_MISCELLANY #define XK_XKB_KEYS #include #include #include Q_LOGGING_CATEGORY(logKAccess, "kcm_kaccess") struct ModifierKey { const unsigned int mask; const KeySym keysym; const char *name; const char *lockedText; const char *latchedText; const char *unlatchedText; }; static const ModifierKey modifierKeys[] = { {ShiftMask, 0, "Shift", I18N_NOOP("The Shift key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Shift key is now active."), I18N_NOOP("The Shift key is now inactive.")}, {ControlMask, 0, "Control", I18N_NOOP("The Control key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Control key is now active."), I18N_NOOP("The Control key is now inactive.")}, {0, XK_Alt_L, "Alt", I18N_NOOP("The Alt key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Alt key is now active."), I18N_NOOP("The Alt key is now inactive.")}, {0, 0, "Win", I18N_NOOP("The Win key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Win key is now active."), I18N_NOOP("The Win key is now inactive.")}, {0, XK_Meta_L, "Meta", I18N_NOOP("The Meta key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Meta key is now active."), I18N_NOOP("The Meta key is now inactive.")}, {0, XK_Super_L, "Super", I18N_NOOP("The Super key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Super key is now active."), I18N_NOOP("The Super key is now inactive.")}, {0, XK_Hyper_L, "Hyper", I18N_NOOP("The Hyper key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Hyper key is now active."), I18N_NOOP("The Hyper key is now inactive.")}, {0, 0, "Alt Graph", I18N_NOOP("The Alt Graph key has been locked and is now active for all of the following keypresses."), I18N_NOOP("The Alt Graph key is now active."), I18N_NOOP("The Alt Graph key is now inactive.")}, {0, XK_Num_Lock, "Num Lock", I18N_NOOP("The Num Lock key has been activated."), "", I18N_NOOP("The Num Lock key is now inactive.")}, {LockMask, 0, "Caps Lock", I18N_NOOP("The Caps Lock key has been activated."), "", I18N_NOOP("The Caps Lock key is now inactive.")}, {0, XK_Scroll_Lock, "Scroll Lock", I18N_NOOP("The Scroll Lock key has been activated."), "", I18N_NOOP("The Scroll Lock key is now inactive.")}, {0, 0, "", "", "", ""}}; /********************************************************************/ KAccessApp::KAccessApp() : overlay(nullptr) , _player(nullptr) , toggleScreenReaderAction(new QAction(this)) { m_error = false; _activeWindow = KWindowSystem::activeWindow(); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &KAccessApp::activeWindowChanged); features = 0; requestedFeatures = 0; dialog = nullptr; if (!QX11Info::isPlatformX11()) { m_error = true; return; } initMasks(); XkbStateRec state_return; XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return); unsigned char latched = XkbStateMods(&state_return); unsigned char locked = XkbModLocks(&state_return); state = ((int)locked) << 8 | latched; auto service = new KDBusService(KDBusService::Unique, this); connect(service, &KDBusService::activateRequested, this, &KAccessApp::newInstance); QTimer::singleShot(0, this, &KAccessApp::readSettings); } void KAccessApp::newInstance() { KSharedConfig::openConfig()->reparseConfiguration(); readSettings(); } void KAccessApp::readSettings() { KSharedConfig::Ptr _config = KSharedConfig::openConfig(); KConfigGroup cg(_config, "Bell"); // bell _systemBell = cg.readEntry("SystemBell", true); _artsBell = cg.readEntry("ArtsBell", false); _currentPlayerSource = cg.readPathEntry("ArtsBellFile", QString()); _visibleBell = cg.readEntry("VisibleBell", false); _visibleBellInvert = cg.readEntry("VisibleBellInvert", false); _visibleBellColor = cg.readEntry("VisibleBellColor", QColor(Qt::red)); _visibleBellPause = cg.readEntry("VisibleBellPause", 500); // select bell events if we need them int state = (_artsBell || _visibleBell) ? XkbBellNotifyMask : 0; XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbBellNotifyMask, state); // deactivate system bell if not needed if (!_systemBell) XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, 0); else XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, XkbAudibleBellMask); // keyboard KConfigGroup keyboardGroup(_config, "Keyboard"); // get keyboard state XkbDescPtr xkb = XkbGetMap(QX11Info::display(), 0, XkbUseCoreKbd); if (!xkb) return; if (XkbGetControls(QX11Info::display(), XkbAllControlsMask, xkb) != Success) return; // sticky keys if (keyboardGroup.readEntry("StickyKeys", false)) { if (keyboardGroup.readEntry("StickyKeysLatch", true)) xkb->ctrls->ax_options |= XkbAX_LatchToLockMask; else xkb->ctrls->ax_options &= ~XkbAX_LatchToLockMask; if (keyboardGroup.readEntry("StickyKeysAutoOff", false)) xkb->ctrls->ax_options |= XkbAX_TwoKeysMask; else xkb->ctrls->ax_options &= ~XkbAX_TwoKeysMask; if (keyboardGroup.readEntry("StickyKeysBeep", false)) xkb->ctrls->ax_options |= XkbAX_StickyKeysFBMask; else xkb->ctrls->ax_options &= ~XkbAX_StickyKeysFBMask; xkb->ctrls->enabled_ctrls |= XkbStickyKeysMask; } else xkb->ctrls->enabled_ctrls &= ~XkbStickyKeysMask; // toggle keys if (keyboardGroup.readEntry("ToggleKeysBeep", false)) xkb->ctrls->ax_options |= XkbAX_IndicatorFBMask; else xkb->ctrls->ax_options &= ~XkbAX_IndicatorFBMask; // slow keys if (keyboardGroup.readEntry("SlowKeys", false)) { if (keyboardGroup.readEntry("SlowKeysPressBeep", false)) xkb->ctrls->ax_options |= XkbAX_SKPressFBMask; else xkb->ctrls->ax_options &= ~XkbAX_SKPressFBMask; if (keyboardGroup.readEntry("SlowKeysAcceptBeep", false)) xkb->ctrls->ax_options |= XkbAX_SKAcceptFBMask; else xkb->ctrls->ax_options &= ~XkbAX_SKAcceptFBMask; if (keyboardGroup.readEntry("SlowKeysRejectBeep", false)) xkb->ctrls->ax_options |= XkbAX_SKRejectFBMask; else xkb->ctrls->ax_options &= ~XkbAX_SKRejectFBMask; xkb->ctrls->enabled_ctrls |= XkbSlowKeysMask; } else xkb->ctrls->enabled_ctrls &= ~XkbSlowKeysMask; xkb->ctrls->slow_keys_delay = keyboardGroup.readEntry("SlowKeysDelay", 500); // bounce keys if (keyboardGroup.readEntry("BounceKeys", false)) { if (keyboardGroup.readEntry("BounceKeysRejectBeep", false)) xkb->ctrls->ax_options |= XkbAX_BKRejectFBMask; else xkb->ctrls->ax_options &= ~XkbAX_BKRejectFBMask; xkb->ctrls->enabled_ctrls |= XkbBounceKeysMask; } else xkb->ctrls->enabled_ctrls &= ~XkbBounceKeysMask; xkb->ctrls->debounce_delay = keyboardGroup.readEntry("BounceKeysDelay", 500); // gestures for enabling the other features _gestures = keyboardGroup.readEntry("Gestures", false); if (_gestures) xkb->ctrls->enabled_ctrls |= XkbAccessXKeysMask; else xkb->ctrls->enabled_ctrls &= ~XkbAccessXKeysMask; // timeout if (keyboardGroup.readEntry("AccessXTimeout", false)) { xkb->ctrls->ax_timeout = keyboardGroup.readEntry("AccessXTimeoutDelay", 30) * 60; xkb->ctrls->axt_opts_mask = 0; xkb->ctrls->axt_opts_values = 0; xkb->ctrls->axt_ctrls_mask = XkbStickyKeysMask | XkbSlowKeysMask; xkb->ctrls->axt_ctrls_values = 0; xkb->ctrls->enabled_ctrls |= XkbAccessXTimeoutMask; } else xkb->ctrls->enabled_ctrls &= ~XkbAccessXTimeoutMask; // gestures for enabling the other features if (keyboardGroup.readEntry("AccessXBeep", true)) xkb->ctrls->ax_options |= XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask; else xkb->ctrls->ax_options &= ~(XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask); _gestureConfirmation = keyboardGroup.readEntry("GestureConfirmation", false); _kNotifyModifiers = keyboardGroup.readEntry("kNotifyModifiers", false); _kNotifyAccessX = keyboardGroup.readEntry("kNotifyAccessX", false); // mouse-by-keyboard KConfigGroup mouseGroup(_config, "Mouse"); if (mouseGroup.readEntry("MouseKeys", false)) { xkb->ctrls->mk_delay = mouseGroup.readEntry("MKDelay", 160); // Default for initial velocity: 200 pixels/sec int interval = mouseGroup.readEntry("MKInterval", 5); xkb->ctrls->mk_interval = interval; // Default time to reach maximum speed: 5000 msec xkb->ctrls->mk_time_to_max = mouseGroup.readEntry("MKTimeToMax", (5000 + interval / 2) / interval); // Default maximum speed: 1000 pixels/sec // (The old default maximum speed from KDE <= 3.4 // (100000 pixels/sec) was way too fast) xkb->ctrls->mk_max_speed = mouseGroup.readEntry("MKMaxSpeed", interval); xkb->ctrls->mk_curve = mouseGroup.readEntry("MKCurve", 0); xkb->ctrls->mk_dflt_btn = mouseGroup.readEntry("MKDefaultButton", 0); xkb->ctrls->enabled_ctrls |= XkbMouseKeysMask; } else xkb->ctrls->enabled_ctrls &= ~XkbMouseKeysMask; features = xkb->ctrls->enabled_ctrls & (XkbSlowKeysMask | XkbBounceKeysMask | XkbStickyKeysMask | XkbMouseKeysMask); if (dialog == nullptr) requestedFeatures = features; // set state XkbSetControls(QX11Info::display(), XkbControlsEnabledMask | XkbMouseKeysAccelMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbAccessXKeysMask | XkbAccessXTimeoutMask, xkb); // select AccessX events XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask); if (!_artsBell && !_visibleBell && !(_gestures && _gestureConfirmation) && !_kNotifyModifiers && !_kNotifyAccessX) { uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask; uint values = xkb->ctrls->enabled_ctrls & ctrls; XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values); } else { // reset them after program exit uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask; uint values = XkbAudibleBellMask; XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values); } delete overlay; overlay = nullptr; KConfigGroup screenReaderGroup(_config, "ScreenReader"); setScreenReaderEnabled(screenReaderGroup.readEntry("Enabled", false)); QString shortcut = screenReaderGroup.readEntry("Shortcut", QStringLiteral("Meta+Alt+S")); toggleScreenReaderAction->setText(i18n("Toggle Screen Reader On and Off")); toggleScreenReaderAction->setObjectName(QStringLiteral("Toggle Screen Reader On and Off")); toggleScreenReaderAction->setProperty("componentDisplayName", i18nc("Name for kaccess shortcuts category", "Accessibility")); KGlobalAccel::self()->setGlobalShortcut(toggleScreenReaderAction, QKeySequence(shortcut)); connect(toggleScreenReaderAction, &QAction::triggered, this, &KAccessApp::toggleScreenReader); } void KAccessApp::toggleScreenReader() { KSharedConfig::Ptr _config = KSharedConfig::openConfig(); KConfigGroup screenReaderGroup(_config, "ScreenReader"); bool enabled = !screenReaderGroup.readEntry("Enabled", false); screenReaderGroup.writeEntry("Enabled", enabled); setScreenReaderEnabled(enabled); } void KAccessApp::setScreenReaderEnabled(bool enabled) { if (enabled) { QStringList args = {QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("true")}; int ret = QProcess::execute(QStringLiteral("gsettings"), args); if (ret == 0) { qint64 pid = 0; QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--replace")}, QString(), &pid); qCDebug(logKAccess) << "Launching Orca, pid:" << pid; } } else { QProcess::startDetached( QStringLiteral("gsettings"), {QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("false")}); } } static int maskToBit(int mask) { for (int i = 0; i < 8; i++) if (mask & (1 << i)) return i; return -1; } void KAccessApp::initMasks() { for (int i = 0; i < 8; i++) keys[i] = -1; state = 0; for (int i = 0; strcmp(modifierKeys[i].name, "") != 0; i++) { int mask = modifierKeys[i].mask; if (mask == 0) { if (modifierKeys[i].keysym != 0) { mask = XkbKeysymToModifiers(QX11Info::display(), modifierKeys[i].keysym); } else { if (!strcmp(modifierKeys[i].name, "Win")) { mask = KKeyServer::modXMeta(); } else { mask = XkbKeysymToModifiers(QX11Info::display(), XK_Mode_switch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Shift) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Latch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Lock); } } } int bit = maskToBit(mask); if (bit != -1 && keys[bit] == -1) keys[bit] = i; } } struct xkb_any_ { uint8_t response_type; uint8_t xkbType; uint16_t sequence; xcb_timestamp_t time; uint8_t deviceID; }; bool KAccessApp::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *event = static_cast(message); if ((event->response_type & ~0x80) == XkbEventCode + xkb_opcode) { xkb_any_ *ev = reinterpret_cast(event); // Workaround for an XCB bug. xkbType comes from an EventType that is defined with bits, like // 8 // while the generated XCB event type enum is defined as a bitmask, like // XCB_XKB_EVENT_TYPE_BELL_NOTIFY = 256 // This means if xbkType is 8, we need to set the 8th bit to 1, thus raising 2 to power of 8. // See also https://bugs.freedesktop.org/show_bug.cgi?id=51295 const int eventType = pow(2, ev->xkbType); switch (eventType) { case XCB_XKB_EVENT_TYPE_STATE_NOTIFY: xkbStateNotify(); break; case XCB_XKB_EVENT_TYPE_BELL_NOTIFY: xkbBellNotify(reinterpret_cast(event)); break; case XCB_XKB_EVENT_TYPE_CONTROLS_NOTIFY: xkbControlsNotify(reinterpret_cast(event)); break; } return true; } } return false; } void VisualBell::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QTimer::singleShot(_pause, this, &QWidget::hide); } void KAccessApp::activeWindowChanged(WId wid) { _activeWindow = wid; } void KAccessApp::xkbStateNotify() { XkbStateRec state_return; XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return); unsigned char latched = XkbStateMods(&state_return); unsigned char locked = XkbModLocks(&state_return); int mods = ((int)locked) << 8 | latched; if (state != mods) { if (_kNotifyModifiers) for (int i = 0; i < 8; i++) { if (keys[i] != -1) { if (!strcmp(modifierKeys[keys[i]].latchedText, "") && ((((mods >> i) & 0x101) != 0) != (((state >> i) & 0x101) != 0))) { if ((mods >> i) & 1) { KNotification::event(QStringLiteral("lockkey-locked"), i18n(modifierKeys[keys[i]].lockedText)); } else { KNotification::event(QStringLiteral("lockkey-unlocked"), i18n(modifierKeys[keys[i]].unlatchedText)); } } else if (strcmp(modifierKeys[keys[i]].latchedText, "") && (((mods >> i) & 0x101) != ((state >> i) & 0x101))) { if ((mods >> i) & 0x100) { KNotification::event(QStringLiteral("modifierkey-locked"), i18n(modifierKeys[keys[i]].lockedText)); } else if ((mods >> i) & 1) { KNotification::event(QStringLiteral("modifierkey-latched"), i18n(modifierKeys[keys[i]].latchedText)); } else { KNotification::event(QStringLiteral("modifierkey-unlatched"), i18n(modifierKeys[keys[i]].unlatchedText)); } } } } state = mods; } } void KAccessApp::xkbBellNotify(xcb_xkb_bell_notify_event_t *event) { // bail out if we should not really ring if (event->eventOnly) return; // flash the visible bell if (_visibleBell) { // create overlay widget if (!overlay) overlay = new VisualBell(_visibleBellPause); WId id = _activeWindow; NETRect frame, window; NETWinInfo net(QX11Info::connection(), id, qApp->desktop()->winId(), NET::Properties(), NET::Properties2()); net.kdeGeometry(frame, window); overlay->setGeometry(window.pos.x, window.pos.y, window.size.width, window.size.height); if (_visibleBellInvert) { QPixmap screen = QPixmap::grabWindow(id, 0, 0, window.size.width, window.size.height); #ifdef __GNUC__ #warning is this the best way to invert a pixmap? #endif // QPixmap invert(window.size.width, window.size.height); QImage i = screen.toImage(); i.invertPixels(); QPalette pal = overlay->palette(); pal.setBrush(overlay->backgroundRole(), QBrush(QPixmap::fromImage(i))); overlay->setPalette(pal); /* QPainter p(&invert); p.setRasterOp(QPainter::NotCopyROP); p.drawPixmap(0, 0, screen); overlay->setBackgroundPixmap(invert); */ } else { QPalette pal = overlay->palette(); pal.setColor(overlay->backgroundRole(), _visibleBellColor); overlay->setPalette(pal); } // flash the overlay widget overlay->raise(); overlay->show(); qApp->flush(); } // ask Phonon to ring a nice bell if (_artsBell) { if (!_player) { // as creating the player is expensive, delay the creation _player = Phonon::createPlayer(Phonon::AccessibilityCategory); _player->setParent(this); _player->setCurrentSource(_currentPlayerSource); } _player->play(); } } QString mouseKeysShortcut(Display *display) { // Calculate the keycode KeySym sym = XK_MouseKeys_Enable; KeyCode code = XKeysymToKeycode(display, sym); if (code == 0) { sym = XK_Pointer_EnableKeys; code = XKeysymToKeycode(display, sym); if (code == 0) return QString(); // No shortcut available? } // Calculate the modifiers by searching the keysym in the X keyboard mapping XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd); if (!xkbdesc) return QString(); // Failed to obtain the mapping from server bool found = false; unsigned char modifiers = 0; int groups = XkbKeyNumGroups(xkbdesc, code); for (int grp = 0; grp < groups && !found; grp++) { int levels = XkbKeyGroupWidth(xkbdesc, code, grp); for (int level = 0; level < levels && !found; level++) { if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) { // keysym found => determine modifiers int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp]; XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]); for (int i = 0; i < type->map_count && !found; i++) { if (type->map[i].active && (type->map[i].level == level)) { modifiers = type->map[i].mods.mask; found = true; } } } } } XkbFreeClientMap(xkbdesc, 0, true); if (!found) return QString(); // Somehow the keycode -> keysym mapping is flawed XEvent ev; ev.type = KeyPress; ev.xkey.display = display; ev.xkey.keycode = code; ev.xkey.state = 0; int key; KKeyServer::xEventToQt(&ev, &key); QString keyname = QKeySequence(key).toString(); unsigned int AltMask = KKeyServer::modXAlt(); unsigned int WinMask = KKeyServer::modXMeta(); unsigned int NumMask = KKeyServer::modXNumLock(); unsigned int ScrollMask = KKeyServer::modXScrollLock(); unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L); unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L); unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L); unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift) | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock); unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask; AltGrMask &= ~mods; MetaMask &= ~(mods | AltGrMask); SuperMask &= ~(mods | AltGrMask | MetaMask); HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask); if ((modifiers & AltGrMask) != 0) keyname = i18n("AltGraph") + QLatin1Char('+') + keyname; if ((modifiers & HyperMask) != 0) keyname = i18n("Hyper") + QLatin1Char('+') + keyname; if ((modifiers & SuperMask) != 0) keyname = i18n("Super") + QLatin1Char('+') + keyname; if ((modifiers & WinMask) != 0) keyname = i18n("Meta") + QLatin1Char('+') + keyname; if ((modifiers & WinMask) != 0) keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname; if ((modifiers & AltMask) != 0) keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname; if ((modifiers & ControlMask) != 0) keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname; if ((modifiers & ShiftMask) != 0) keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname; return keyname; } void KAccessApp::createDialogContents() { if (dialog == nullptr) { dialog = new QDialog(nullptr); dialog->setWindowTitle(i18n("Warning")); dialog->setObjectName(QStringLiteral("AccessXWarning")); dialog->setModal(true); QVBoxLayout *topLayout = new QVBoxLayout(); QHBoxLayout *lay = new QHBoxLayout(); QLabel *label1 = new QLabel(); QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-warning")); if (icon.isNull()) icon = QMessageBox::standardIcon(QMessageBox::Warning); label1->setPixmap(icon.pixmap(64, 64)); lay->addWidget(label1, 0, Qt::AlignCenter); QVBoxLayout *vlay = new QVBoxLayout(); lay->addItem(vlay); featuresLabel = new QLabel(); featuresLabel->setAlignment(Qt::AlignVCenter); featuresLabel->setWordWrap(true); vlay->addWidget(featuresLabel); vlay->addStretch(); QHBoxLayout *hlay = new QHBoxLayout(); vlay->addItem(hlay); QLabel *showModeLabel = new QLabel(i18n("&When a gesture was used:")); hlay->addWidget(showModeLabel); showModeCombobox = new KComboBox(); hlay->addWidget(showModeCombobox); showModeLabel->setBuddy(showModeCombobox); showModeCombobox->insertItem(0, i18n("Change Settings Without Asking")); showModeCombobox->insertItem(1, i18n("Show This Confirmation Dialog")); showModeCombobox->insertItem(2, i18n("Deactivate All AccessX Features & Gestures")); showModeCombobox->setCurrentIndex(1); topLayout->addLayout(lay); auto buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, dialog); topLayout->addWidget(buttons); dialog->setLayout(topLayout); connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); connect(dialog, &QDialog::accepted, this, &KAccessApp::yesClicked); connect(dialog, &QDialog::rejected, this, &KAccessApp::noClicked); } } void KAccessApp::xkbControlsNotify(xcb_xkb_controls_notify_event_t *event) { unsigned int newFeatures = 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); if (newFeatures != features) { unsigned int enabled = newFeatures & ~features; unsigned int disabled = features & ~newFeatures; if (!_gestureConfirmation) { requestedFeatures = enabled | (requestedFeatures & ~disabled); notifyChanges(); features = newFeatures; } else { // set the AccessX features back to what they were. We will // apply the changes later if the user allows us to do that. readSettings(); requestedFeatures = enabled | (requestedFeatures & ~disabled); enabled = requestedFeatures & ~features; disabled = features & ~requestedFeatures; QStringList enabledFeatures; QStringList disabledFeatures; if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) enabledFeatures << i18n("Slow keys"); else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) disabledFeatures << i18n("Slow keys"); if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) enabledFeatures << i18n("Bounce keys"); else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) disabledFeatures << i18n("Bounce keys"); if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) enabledFeatures << i18n("Sticky keys"); else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) disabledFeatures << i18n("Sticky keys"); if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) enabledFeatures << i18n("Mouse keys"); else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) disabledFeatures << i18n("Mouse keys"); QString question; switch (enabledFeatures.count()) { case 0: switch (disabledFeatures.count()) { case 1: question = i18n("Do you really want to deactivate \"%1\"?", disabledFeatures[0]); break; case 2: question = i18n("Do you really want to deactivate \"%1\" and \"%2\"?", disabledFeatures[0], disabledFeatures[1]); break; case 3: question = i18n("Do you really want to deactivate \"%1\", \"%2\" and \"%3\"?", disabledFeatures[0], disabledFeatures[1], disabledFeatures[2]); break; case 4: question = i18n("Do you really want to deactivate \"%1\", \"%2\", \"%3\" and \"%4\"?", disabledFeatures[0], disabledFeatures[1], disabledFeatures[2], disabledFeatures[3]); break; } break; case 1: switch (disabledFeatures.count()) { case 0: question = i18n("Do you really want to activate \"%1\"?", enabledFeatures[0]); break; case 1: question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\"?", enabledFeatures[0], disabledFeatures[0]); break; case 2: question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\" and \"%3\"?", enabledFeatures[0], disabledFeatures[0], disabledFeatures[1]); break; case 3: question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\", \"%3\" and \"%4\"?", enabledFeatures[0], disabledFeatures[0], disabledFeatures[1], disabledFeatures[2]); break; } break; case 2: switch (disabledFeatures.count()) { case 0: question = i18n("Do you really want to activate \"%1\" and \"%2\"?", enabledFeatures[0], enabledFeatures[1]); break; case 1: question = i18n("Do you really want to activate \"%1\" and \"%2\" and to deactivate \"%3\"?", enabledFeatures[0], enabledFeatures[1], disabledFeatures[0]); break; case 2: question = i18n("Do you really want to activate \"%1\", and \"%2\" and to deactivate \"%3\" and \"%4\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[0], disabledFeatures[1]); break; } break; case 3: switch (disabledFeatures.count()) { case 0: question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[2]); break; case 1: question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\" and to deactivate \"%4\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[2], disabledFeatures[0]); break; } break; case 4: question = i18n("Do you really want to activate \"%1\", \"%2\", \"%3\" and \"%4\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[2], enabledFeatures[3]); break; } QString explanation; if (enabledFeatures.count() + disabledFeatures.count() == 1) { explanation = i18n("An application has requested to change this setting."); if (_gestures) { if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_SLOW_KEYS) explanation = i18n("You held down the Shift key for 8 seconds or an application has requested to change this setting."); else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_STICKY_KEYS) explanation = i18n("You pressed the Shift key 5 consecutive times or an application has requested to change this setting."); else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_MOUSE_KEYS) { QString shortcut = mouseKeysShortcut(QX11Info::display()); if (!shortcut.isEmpty() && !shortcut.isNull()) explanation = i18n("You pressed %1 or an application has requested to change this setting.", shortcut); } } } else { if (_gestures) explanation = i18n("An application has requested to change these settings, or you used a combination of several keyboard gestures."); else explanation = i18n("An application has requested to change these settings."); } createDialogContents(); featuresLabel->setText(question + QStringLiteral("\n\n") + explanation + QStringLiteral(" ") + i18n("These AccessX settings are needed for some users with motion impairments and can be configured in the KDE System " "Settings. You can also turn them on and off with standardized keyboard gestures.\n\nIf you do not need them, you " "can select \"Deactivate all AccessX features and gestures\".")); KWindowSystem::setState(dialog->winId(), NET::KeepAbove); KUserTimestamp::updateUserTimestamp(0); dialog->show(); } } } void KAccessApp::notifyChanges() { if (!_kNotifyAccessX) return; unsigned int enabled = requestedFeatures & ~features; unsigned int disabled = features & ~requestedFeatures; if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) KNotification::event(QStringLiteral("slowkeys"), 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.")); else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) KNotification::event(QStringLiteral("slowkeys"), i18n("Slow keys has been disabled.")); if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) KNotification::event(QStringLiteral("bouncekeys"), i18n("Bounce keys has been enabled. From now on, each key will be blocked for a certain length of time after it was used.")); else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) KNotification::event(QStringLiteral("bouncekeys"), i18n("Bounce keys has been disabled.")); if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) KNotification::event(QStringLiteral("stickykeys"), i18n("Sticky keys has been enabled. From now on, modifier keys will stay latched after you have released them.")); else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) KNotification::event(QStringLiteral("stickykeys"), i18n("Sticky keys has been disabled.")); if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) KNotification::event(QStringLiteral("mousekeys"), i18n("Mouse keys has been enabled. From now on, you can use the number pad of your keyboard in order to control the mouse.")); else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) KNotification::event(QStringLiteral("mousekeys"), i18n("Mouse keys has been disabled.")); } void KAccessApp::applyChanges() { notifyChanges(); unsigned int enabled = requestedFeatures & ~features; unsigned int disabled = features & ~requestedFeatures; KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) config.writeEntry("SlowKeys", true); else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS) config.writeEntry("SlowKeys", false); if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) config.writeEntry("BounceKeys", true); else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS) config.writeEntry("BounceKeys", false); if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) config.writeEntry("StickyKeys", true); else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS) config.writeEntry("StickyKeys", false); KConfigGroup mousegrp(KSharedConfig::openConfig(), "Mouse"); if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) mousegrp.writeEntry("MouseKeys", true); else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS) mousegrp.writeEntry("MouseKeys", false); mousegrp.sync(); config.sync(); } void KAccessApp::yesClicked() { if (dialog) dialog->deleteLater(); dialog = nullptr; KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); switch (showModeCombobox->currentIndex()) { case 0: config.writeEntry("Gestures", true); config.writeEntry("GestureConfirmation", false); break; default: config.writeEntry("Gestures", true); config.writeEntry("GestureConfirmation", true); break; case 2: requestedFeatures = 0; config.writeEntry("Gestures", false); config.writeEntry("GestureConfirmation", true); } config.sync(); if (features != requestedFeatures) { notifyChanges(); applyChanges(); } readSettings(); } void KAccessApp::noClicked() { if (dialog) dialog->deleteLater(); dialog = nullptr; requestedFeatures = features; KConfigGroup config(KSharedConfig::openConfig(), "Keyboard"); switch (showModeCombobox->currentIndex()) { case 0: config.writeEntry("Gestures", true); config.writeEntry("GestureConfirmation", false); break; default: config.writeEntry("Gestures", true); config.writeEntry("GestureConfirmation", true); break; case 2: requestedFeatures = 0; config.writeEntry("Gestures", false); config.writeEntry("GestureConfirmation", true); } config.sync(); if (features != requestedFeatures) applyChanges(); readSettings(); } void KAccessApp::dialogClosed() { if (dialog != nullptr) dialog->deleteLater(); dialog = nullptr; requestedFeatures = features; } void KAccessApp::setXkbOpcode(int opcode) { xkb_opcode = opcode; }