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 }