1 /*
2     SPDX-FileCopyrightText: 2000 Matthias Hölzer-Klüpfel <hoelzer@kde.org>
3     SPDX-FileCopyrightText: 2014 Frederik Gladhorn <gladhorn@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include <cmath>
9 #include <unistd.h>
10 
11 #include "kaccess.h"
12 
13 #include <QDesktopWidget>
14 #include <QMessageBox>
15 #include <QPainter>
16 #include <QProcess>
17 #include <QTimer>
18 
19 #include <QAction>
20 #include <QApplication>
21 #include <QHBoxLayout>
22 #include <QVBoxLayout>
23 
24 #include <KAboutData>
25 #include <KComboBox>
26 #include <KConfig>
27 #include <KConfigGroup>
28 #include <KDBusService>
29 #include <KGlobalAccel>
30 #include <KKeyServer>
31 #include <KLocalizedString>
32 #include <KNotification>
33 #include <KSharedConfig>
34 #include <KUserTimestamp>
35 #include <KWindowSystem>
36 #include <QDialog>
37 #include <QDialogButtonBox>
38 
39 #include <netwm.h>
40 #define XK_MISCELLANY
41 #define XK_XKB_KEYS
42 #include <X11/keysymdef.h>
43 
44 #include <QX11Info>
45 
46 #include <QLoggingCategory>
47 
48 Q_LOGGING_CATEGORY(logKAccess, "kcm_kaccess")
49 
50 struct ModifierKey {
51     const unsigned int mask;
52     const KeySym keysym;
53     const char *name;
54     const char *lockedText;
55     const char *latchedText;
56     const char *unlatchedText;
57 };
58 
59 static const ModifierKey modifierKeys[] = {
60     {ShiftMask,
61      0,
62      "Shift",
63      I18N_NOOP("The Shift key has been locked and is now active for all of the following keypresses."),
64      I18N_NOOP("The Shift key is now active."),
65      I18N_NOOP("The Shift key is now inactive.")},
66     {ControlMask,
67      0,
68      "Control",
69      I18N_NOOP("The Control key has been locked and is now active for all of the following keypresses."),
70      I18N_NOOP("The Control key is now active."),
71      I18N_NOOP("The Control key is now inactive.")},
72     {0,
73      XK_Alt_L,
74      "Alt",
75      I18N_NOOP("The Alt key has been locked and is now active for all of the following keypresses."),
76      I18N_NOOP("The Alt key is now active."),
77      I18N_NOOP("The Alt key is now inactive.")},
78     {0,
79      0,
80      "Win",
81      I18N_NOOP("The Win key has been locked and is now active for all of the following keypresses."),
82      I18N_NOOP("The Win key is now active."),
83      I18N_NOOP("The Win key is now inactive.")},
84     {0,
85      XK_Meta_L,
86      "Meta",
87      I18N_NOOP("The Meta key has been locked and is now active for all of the following keypresses."),
88      I18N_NOOP("The Meta key is now active."),
89      I18N_NOOP("The Meta key is now inactive.")},
90     {0,
91      XK_Super_L,
92      "Super",
93      I18N_NOOP("The Super key has been locked and is now active for all of the following keypresses."),
94      I18N_NOOP("The Super key is now active."),
95      I18N_NOOP("The Super key is now inactive.")},
96     {0,
97      XK_Hyper_L,
98      "Hyper",
99      I18N_NOOP("The Hyper key has been locked and is now active for all of the following keypresses."),
100      I18N_NOOP("The Hyper key is now active."),
101      I18N_NOOP("The Hyper key is now inactive.")},
102     {0,
103      0,
104      "Alt Graph",
105      I18N_NOOP("The Alt Graph key has been locked and is now active for all of the following keypresses."),
106      I18N_NOOP("The Alt Graph key is now active."),
107      I18N_NOOP("The Alt Graph key is now inactive.")},
108     {0, XK_Num_Lock, "Num Lock", I18N_NOOP("The Num Lock key has been activated."), "", I18N_NOOP("The Num Lock key is now inactive.")},
109     {LockMask, 0, "Caps Lock", I18N_NOOP("The Caps Lock key has been activated."), "", I18N_NOOP("The Caps Lock key is now inactive.")},
110     {0, XK_Scroll_Lock, "Scroll Lock", I18N_NOOP("The Scroll Lock key has been activated."), "", I18N_NOOP("The Scroll Lock key is now inactive.")},
111     {0, 0, "", "", "", ""}};
112 
113 /********************************************************************/
114 
KAccessApp()115 KAccessApp::KAccessApp()
116     : overlay(nullptr)
117     , _player(nullptr)
118     , toggleScreenReaderAction(new QAction(this))
119 {
120     m_error = false;
121     _activeWindow = KWindowSystem::activeWindow();
122     connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &KAccessApp::activeWindowChanged);
123 
124     features = 0;
125     requestedFeatures = 0;
126     dialog = nullptr;
127 
128     if (!QX11Info::isPlatformX11()) {
129         m_error = true;
130         return;
131     }
132 
133     initMasks();
134     XkbStateRec state_return;
135     XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return);
136     unsigned char latched = XkbStateMods(&state_return);
137     unsigned char locked = XkbModLocks(&state_return);
138     state = ((int)locked) << 8 | latched;
139 
140     auto service = new KDBusService(KDBusService::Unique, this);
141     connect(service, &KDBusService::activateRequested, this, &KAccessApp::newInstance);
142 
143     QTimer::singleShot(0, this, &KAccessApp::readSettings);
144 }
145 
newInstance()146 void KAccessApp::newInstance()
147 {
148     KSharedConfig::openConfig()->reparseConfiguration();
149     readSettings();
150 }
151 
readSettings()152 void KAccessApp::readSettings()
153 {
154     KSharedConfig::Ptr _config = KSharedConfig::openConfig();
155     KConfigGroup cg(_config, "Bell");
156 
157     // bell
158     _systemBell = cg.readEntry("SystemBell", true);
159     _artsBell = cg.readEntry("ArtsBell", false);
160     _currentPlayerSource = cg.readPathEntry("ArtsBellFile", QString());
161     _visibleBell = cg.readEntry("VisibleBell", false);
162     _visibleBellInvert = cg.readEntry("VisibleBellInvert", false);
163     _visibleBellColor = cg.readEntry("VisibleBellColor", QColor(Qt::red));
164     _visibleBellPause = cg.readEntry("VisibleBellPause", 500);
165 
166     // select bell events if we need them
167     int state = (_artsBell || _visibleBell) ? XkbBellNotifyMask : 0;
168     XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbBellNotifyMask, state);
169 
170     // deactivate system bell if not needed
171     if (!_systemBell)
172         XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, 0);
173     else
174         XkbChangeEnabledControls(QX11Info::display(), XkbUseCoreKbd, XkbAudibleBellMask, XkbAudibleBellMask);
175 
176     // keyboard
177     KConfigGroup keyboardGroup(_config, "Keyboard");
178 
179     // get keyboard state
180     XkbDescPtr xkb = XkbGetMap(QX11Info::display(), 0, XkbUseCoreKbd);
181     if (!xkb)
182         return;
183     if (XkbGetControls(QX11Info::display(), XkbAllControlsMask, xkb) != Success)
184         return;
185 
186     // sticky keys
187     if (keyboardGroup.readEntry("StickyKeys", false)) {
188         if (keyboardGroup.readEntry("StickyKeysLatch", true))
189             xkb->ctrls->ax_options |= XkbAX_LatchToLockMask;
190         else
191             xkb->ctrls->ax_options &= ~XkbAX_LatchToLockMask;
192         if (keyboardGroup.readEntry("StickyKeysAutoOff", false))
193             xkb->ctrls->ax_options |= XkbAX_TwoKeysMask;
194         else
195             xkb->ctrls->ax_options &= ~XkbAX_TwoKeysMask;
196         if (keyboardGroup.readEntry("StickyKeysBeep", false))
197             xkb->ctrls->ax_options |= XkbAX_StickyKeysFBMask;
198         else
199             xkb->ctrls->ax_options &= ~XkbAX_StickyKeysFBMask;
200         xkb->ctrls->enabled_ctrls |= XkbStickyKeysMask;
201     } else
202         xkb->ctrls->enabled_ctrls &= ~XkbStickyKeysMask;
203 
204     // toggle keys
205     if (keyboardGroup.readEntry("ToggleKeysBeep", false))
206         xkb->ctrls->ax_options |= XkbAX_IndicatorFBMask;
207     else
208         xkb->ctrls->ax_options &= ~XkbAX_IndicatorFBMask;
209 
210     // slow keys
211     if (keyboardGroup.readEntry("SlowKeys", false)) {
212         if (keyboardGroup.readEntry("SlowKeysPressBeep", false))
213             xkb->ctrls->ax_options |= XkbAX_SKPressFBMask;
214         else
215             xkb->ctrls->ax_options &= ~XkbAX_SKPressFBMask;
216         if (keyboardGroup.readEntry("SlowKeysAcceptBeep", false))
217             xkb->ctrls->ax_options |= XkbAX_SKAcceptFBMask;
218         else
219             xkb->ctrls->ax_options &= ~XkbAX_SKAcceptFBMask;
220         if (keyboardGroup.readEntry("SlowKeysRejectBeep", false))
221             xkb->ctrls->ax_options |= XkbAX_SKRejectFBMask;
222         else
223             xkb->ctrls->ax_options &= ~XkbAX_SKRejectFBMask;
224         xkb->ctrls->enabled_ctrls |= XkbSlowKeysMask;
225     } else
226         xkb->ctrls->enabled_ctrls &= ~XkbSlowKeysMask;
227     xkb->ctrls->slow_keys_delay = keyboardGroup.readEntry("SlowKeysDelay", 500);
228 
229     // bounce keys
230     if (keyboardGroup.readEntry("BounceKeys", false)) {
231         if (keyboardGroup.readEntry("BounceKeysRejectBeep", false))
232             xkb->ctrls->ax_options |= XkbAX_BKRejectFBMask;
233         else
234             xkb->ctrls->ax_options &= ~XkbAX_BKRejectFBMask;
235         xkb->ctrls->enabled_ctrls |= XkbBounceKeysMask;
236     } else
237         xkb->ctrls->enabled_ctrls &= ~XkbBounceKeysMask;
238     xkb->ctrls->debounce_delay = keyboardGroup.readEntry("BounceKeysDelay", 500);
239 
240     // gestures for enabling the other features
241     _gestures = keyboardGroup.readEntry("Gestures", false);
242     if (_gestures)
243         xkb->ctrls->enabled_ctrls |= XkbAccessXKeysMask;
244     else
245         xkb->ctrls->enabled_ctrls &= ~XkbAccessXKeysMask;
246 
247     // timeout
248     if (keyboardGroup.readEntry("AccessXTimeout", false)) {
249         xkb->ctrls->ax_timeout = keyboardGroup.readEntry("AccessXTimeoutDelay", 30) * 60;
250         xkb->ctrls->axt_opts_mask = 0;
251         xkb->ctrls->axt_opts_values = 0;
252         xkb->ctrls->axt_ctrls_mask = XkbStickyKeysMask | XkbSlowKeysMask;
253         xkb->ctrls->axt_ctrls_values = 0;
254         xkb->ctrls->enabled_ctrls |= XkbAccessXTimeoutMask;
255     } else
256         xkb->ctrls->enabled_ctrls &= ~XkbAccessXTimeoutMask;
257 
258     // gestures for enabling the other features
259     if (keyboardGroup.readEntry("AccessXBeep", true))
260         xkb->ctrls->ax_options |= XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask;
261     else
262         xkb->ctrls->ax_options &= ~(XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask);
263 
264     _gestureConfirmation = keyboardGroup.readEntry("GestureConfirmation", false);
265 
266     _kNotifyModifiers = keyboardGroup.readEntry("kNotifyModifiers", false);
267     _kNotifyAccessX = keyboardGroup.readEntry("kNotifyAccessX", false);
268 
269     // mouse-by-keyboard
270 
271     KConfigGroup mouseGroup(_config, "Mouse");
272 
273     if (mouseGroup.readEntry("MouseKeys", false)) {
274         xkb->ctrls->mk_delay = mouseGroup.readEntry("MKDelay", 160);
275 
276         // Default for initial velocity: 200 pixels/sec
277         int interval = mouseGroup.readEntry("MKInterval", 5);
278         xkb->ctrls->mk_interval = interval;
279 
280         // Default time to reach maximum speed: 5000 msec
281         xkb->ctrls->mk_time_to_max = mouseGroup.readEntry("MKTimeToMax", (5000 + interval / 2) / interval);
282 
283         // Default maximum speed: 1000 pixels/sec
284         //     (The old default maximum speed from KDE <= 3.4
285         //     (100000 pixels/sec) was way too fast)
286         xkb->ctrls->mk_max_speed = mouseGroup.readEntry("MKMaxSpeed", interval);
287 
288         xkb->ctrls->mk_curve = mouseGroup.readEntry("MKCurve", 0);
289         xkb->ctrls->mk_dflt_btn = mouseGroup.readEntry("MKDefaultButton", 0);
290 
291         xkb->ctrls->enabled_ctrls |= XkbMouseKeysMask;
292     } else
293         xkb->ctrls->enabled_ctrls &= ~XkbMouseKeysMask;
294 
295     features = xkb->ctrls->enabled_ctrls & (XkbSlowKeysMask | XkbBounceKeysMask | XkbStickyKeysMask | XkbMouseKeysMask);
296     if (dialog == nullptr)
297         requestedFeatures = features;
298     // set state
299     XkbSetControls(QX11Info::display(),
300                    XkbControlsEnabledMask | XkbMouseKeysAccelMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbAccessXKeysMask
301                        | XkbAccessXTimeoutMask,
302                    xkb);
303 
304     // select AccessX events
305     XkbSelectEvents(QX11Info::display(), XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);
306 
307     if (!_artsBell && !_visibleBell && !(_gestures && _gestureConfirmation) && !_kNotifyModifiers && !_kNotifyAccessX) {
308         uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask;
309         uint values = xkb->ctrls->enabled_ctrls & ctrls;
310         XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values);
311     } else {
312         // reset them after program exit
313         uint ctrls = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask | XkbAudibleBellMask | XkbControlsNotifyMask;
314         uint values = XkbAudibleBellMask;
315         XkbSetAutoResetControls(QX11Info::display(), ctrls, &ctrls, &values);
316     }
317 
318     delete overlay;
319     overlay = nullptr;
320 
321     KConfigGroup screenReaderGroup(_config, "ScreenReader");
322     setScreenReaderEnabled(screenReaderGroup.readEntry("Enabled", false));
323 
324     QString shortcut = screenReaderGroup.readEntry("Shortcut", QStringLiteral("Meta+Alt+S"));
325 
326     toggleScreenReaderAction->setText(i18n("Toggle Screen Reader On and Off"));
327     toggleScreenReaderAction->setObjectName(QStringLiteral("Toggle Screen Reader On and Off"));
328     toggleScreenReaderAction->setProperty("componentDisplayName", i18nc("Name for kaccess shortcuts category", "Accessibility"));
329     KGlobalAccel::self()->setGlobalShortcut(toggleScreenReaderAction, QKeySequence(shortcut));
330     connect(toggleScreenReaderAction, &QAction::triggered, this, &KAccessApp::toggleScreenReader);
331 }
332 
toggleScreenReader()333 void KAccessApp::toggleScreenReader()
334 {
335     KSharedConfig::Ptr _config = KSharedConfig::openConfig();
336     KConfigGroup screenReaderGroup(_config, "ScreenReader");
337     bool enabled = !screenReaderGroup.readEntry("Enabled", false);
338     screenReaderGroup.writeEntry("Enabled", enabled);
339     setScreenReaderEnabled(enabled);
340 }
341 
setScreenReaderEnabled(bool enabled)342 void KAccessApp::setScreenReaderEnabled(bool enabled)
343 {
344     if (enabled) {
345         QStringList args = {QStringLiteral("set"),
346                             QStringLiteral("org.gnome.desktop.a11y.applications"),
347                             QStringLiteral("screen-reader-enabled"),
348                             QStringLiteral("true")};
349         int ret = QProcess::execute(QStringLiteral("gsettings"), args);
350         if (ret == 0) {
351             qint64 pid = 0;
352             QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--replace")}, QString(), &pid);
353             qCDebug(logKAccess) << "Launching Orca, pid:" << pid;
354         }
355     } else {
356         QProcess::startDetached(
357             QStringLiteral("gsettings"),
358             {QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("false")});
359     }
360 }
361 
maskToBit(int mask)362 static int maskToBit(int mask)
363 {
364     for (int i = 0; i < 8; i++)
365         if (mask & (1 << i))
366             return i;
367     return -1;
368 }
369 
initMasks()370 void KAccessApp::initMasks()
371 {
372     for (int i = 0; i < 8; i++)
373         keys[i] = -1;
374     state = 0;
375 
376     for (int i = 0; strcmp(modifierKeys[i].name, "") != 0; i++) {
377         int mask = modifierKeys[i].mask;
378         if (mask == 0) {
379             if (modifierKeys[i].keysym != 0) {
380                 mask = XkbKeysymToModifiers(QX11Info::display(), modifierKeys[i].keysym);
381             } else {
382                 if (!strcmp(modifierKeys[i].name, "Win")) {
383                     mask = KKeyServer::modXMeta();
384                 } else {
385                     mask = XkbKeysymToModifiers(QX11Info::display(), XK_Mode_switch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Shift)
386                         | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Latch) | XkbKeysymToModifiers(QX11Info::display(), XK_ISO_Level3_Lock);
387                 }
388             }
389         }
390 
391         int bit = maskToBit(mask);
392         if (bit != -1 && keys[bit] == -1)
393             keys[bit] = i;
394     }
395 }
396 
397 struct xkb_any_ {
398     uint8_t response_type;
399     uint8_t xkbType;
400     uint16_t sequence;
401     xcb_timestamp_t time;
402     uint8_t deviceID;
403 };
404 
nativeEventFilter(const QByteArray & eventType,void * message,long int * result)405 bool KAccessApp::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
406 {
407     if (eventType == "xcb_generic_event_t") {
408         xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
409         if ((event->response_type & ~0x80) == XkbEventCode + xkb_opcode) {
410             xkb_any_ *ev = reinterpret_cast<xkb_any_ *>(event);
411             // Workaround for an XCB bug. xkbType comes from an EventType that is defined with bits, like
412             // <item name="BellNotify">             <bit>8</bit>
413             // while the generated XCB event type enum is defined as a bitmask, like
414             //     XCB_XKB_EVENT_TYPE_BELL_NOTIFY = 256
415             // This means if xbkType is 8, we need to set the 8th bit to 1, thus raising 2 to power of 8.
416             // See also https://bugs.freedesktop.org/show_bug.cgi?id=51295
417             const int eventType = pow(2, ev->xkbType);
418             switch (eventType) {
419             case XCB_XKB_EVENT_TYPE_STATE_NOTIFY:
420                 xkbStateNotify();
421                 break;
422             case XCB_XKB_EVENT_TYPE_BELL_NOTIFY:
423                 xkbBellNotify(reinterpret_cast<xcb_xkb_bell_notify_event_t *>(event));
424                 break;
425             case XCB_XKB_EVENT_TYPE_CONTROLS_NOTIFY:
426                 xkbControlsNotify(reinterpret_cast<xcb_xkb_controls_notify_event_t *>(event));
427                 break;
428             }
429             return true;
430         }
431     }
432     return false;
433 }
434 
paintEvent(QPaintEvent * event)435 void VisualBell::paintEvent(QPaintEvent *event)
436 {
437     QWidget::paintEvent(event);
438     QTimer::singleShot(_pause, this, &QWidget::hide);
439 }
440 
activeWindowChanged(WId wid)441 void KAccessApp::activeWindowChanged(WId wid)
442 {
443     _activeWindow = wid;
444 }
445 
xkbStateNotify()446 void KAccessApp::xkbStateNotify()
447 {
448     XkbStateRec state_return;
449     XkbGetState(QX11Info::display(), XkbUseCoreKbd, &state_return);
450     unsigned char latched = XkbStateMods(&state_return);
451     unsigned char locked = XkbModLocks(&state_return);
452     int mods = ((int)locked) << 8 | latched;
453 
454     if (state != mods) {
455         if (_kNotifyModifiers)
456             for (int i = 0; i < 8; i++) {
457                 if (keys[i] != -1) {
458                     if (!strcmp(modifierKeys[keys[i]].latchedText, "") && ((((mods >> i) & 0x101) != 0) != (((state >> i) & 0x101) != 0))) {
459                         if ((mods >> i) & 1) {
460                             KNotification::event(QStringLiteral("lockkey-locked"), i18n(modifierKeys[keys[i]].lockedText));
461                         } else {
462                             KNotification::event(QStringLiteral("lockkey-unlocked"), i18n(modifierKeys[keys[i]].unlatchedText));
463                         }
464                     } else if (strcmp(modifierKeys[keys[i]].latchedText, "") && (((mods >> i) & 0x101) != ((state >> i) & 0x101))) {
465                         if ((mods >> i) & 0x100) {
466                             KNotification::event(QStringLiteral("modifierkey-locked"), i18n(modifierKeys[keys[i]].lockedText));
467                         } else if ((mods >> i) & 1) {
468                             KNotification::event(QStringLiteral("modifierkey-latched"), i18n(modifierKeys[keys[i]].latchedText));
469                         } else {
470                             KNotification::event(QStringLiteral("modifierkey-unlatched"), i18n(modifierKeys[keys[i]].unlatchedText));
471                         }
472                     }
473                 }
474             }
475         state = mods;
476     }
477 }
478 
xkbBellNotify(xcb_xkb_bell_notify_event_t * event)479 void KAccessApp::xkbBellNotify(xcb_xkb_bell_notify_event_t *event)
480 {
481     // bail out if we should not really ring
482     if (event->eventOnly)
483         return;
484 
485     // flash the visible bell
486     if (_visibleBell) {
487         // create overlay widget
488         if (!overlay)
489             overlay = new VisualBell(_visibleBellPause);
490 
491         WId id = _activeWindow;
492 
493         NETRect frame, window;
494         NETWinInfo net(QX11Info::connection(), id, qApp->desktop()->winId(), NET::Properties(), NET::Properties2());
495 
496         net.kdeGeometry(frame, window);
497 
498         overlay->setGeometry(window.pos.x, window.pos.y, window.size.width, window.size.height);
499 
500         if (_visibleBellInvert) {
501             QPixmap screen = QPixmap::grabWindow(id, 0, 0, window.size.width, window.size.height);
502 #ifdef __GNUC__
503 #warning is this the best way to invert a pixmap?
504 #endif
505             //    QPixmap invert(window.size.width, window.size.height);
506             QImage i = screen.toImage();
507             i.invertPixels();
508             QPalette pal = overlay->palette();
509             pal.setBrush(overlay->backgroundRole(), QBrush(QPixmap::fromImage(i)));
510             overlay->setPalette(pal);
511             /*
512                   QPainter p(&invert);
513                   p.setRasterOp(QPainter::NotCopyROP);
514                   p.drawPixmap(0, 0, screen);
515                   overlay->setBackgroundPixmap(invert);
516             */
517         } else {
518             QPalette pal = overlay->palette();
519             pal.setColor(overlay->backgroundRole(), _visibleBellColor);
520             overlay->setPalette(pal);
521         }
522 
523         // flash the overlay widget
524         overlay->raise();
525         overlay->show();
526         qApp->flush();
527     }
528 
529     // ask Phonon to ring a nice bell
530     if (_artsBell) {
531         if (!_player) { // as creating the player is expensive, delay the creation
532             _player = Phonon::createPlayer(Phonon::AccessibilityCategory);
533             _player->setParent(this);
534             _player->setCurrentSource(_currentPlayerSource);
535         }
536         _player->play();
537     }
538 }
539 
mouseKeysShortcut(Display * display)540 QString mouseKeysShortcut(Display *display)
541 {
542     // Calculate the keycode
543     KeySym sym = XK_MouseKeys_Enable;
544     KeyCode code = XKeysymToKeycode(display, sym);
545     if (code == 0) {
546         sym = XK_Pointer_EnableKeys;
547         code = XKeysymToKeycode(display, sym);
548         if (code == 0)
549             return QString(); // No shortcut available?
550     }
551 
552     // Calculate the modifiers by searching the keysym in the X keyboard mapping
553     XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd);
554 
555     if (!xkbdesc)
556         return QString(); // Failed to obtain the mapping from server
557 
558     bool found = false;
559     unsigned char modifiers = 0;
560     int groups = XkbKeyNumGroups(xkbdesc, code);
561     for (int grp = 0; grp < groups && !found; grp++) {
562         int levels = XkbKeyGroupWidth(xkbdesc, code, grp);
563         for (int level = 0; level < levels && !found; level++) {
564             if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) {
565                 // keysym found => determine modifiers
566                 int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp];
567                 XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]);
568                 for (int i = 0; i < type->map_count && !found; i++) {
569                     if (type->map[i].active && (type->map[i].level == level)) {
570                         modifiers = type->map[i].mods.mask;
571                         found = true;
572                     }
573                 }
574             }
575         }
576     }
577     XkbFreeClientMap(xkbdesc, 0, true);
578 
579     if (!found)
580         return QString(); // Somehow the keycode -> keysym mapping is flawed
581 
582     XEvent ev;
583     ev.type = KeyPress;
584     ev.xkey.display = display;
585     ev.xkey.keycode = code;
586     ev.xkey.state = 0;
587     int key;
588     KKeyServer::xEventToQt(&ev, &key);
589     QString keyname = QKeySequence(key).toString();
590 
591     unsigned int AltMask = KKeyServer::modXAlt();
592     unsigned int WinMask = KKeyServer::modXMeta();
593     unsigned int NumMask = KKeyServer::modXNumLock();
594     unsigned int ScrollMask = KKeyServer::modXScrollLock();
595 
596     unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L);
597     unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L);
598     unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L);
599     unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift)
600         | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock);
601 
602     unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask;
603 
604     AltGrMask &= ~mods;
605     MetaMask &= ~(mods | AltGrMask);
606     SuperMask &= ~(mods | AltGrMask | MetaMask);
607     HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask);
608 
609     if ((modifiers & AltGrMask) != 0)
610         keyname = i18n("AltGraph") + QLatin1Char('+') + keyname;
611     if ((modifiers & HyperMask) != 0)
612         keyname = i18n("Hyper") + QLatin1Char('+') + keyname;
613     if ((modifiers & SuperMask) != 0)
614         keyname = i18n("Super") + QLatin1Char('+') + keyname;
615     if ((modifiers & WinMask) != 0)
616         keyname = i18n("Meta") + QLatin1Char('+') + keyname;
617     if ((modifiers & WinMask) != 0)
618         keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname;
619     if ((modifiers & AltMask) != 0)
620         keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname;
621     if ((modifiers & ControlMask) != 0)
622         keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname;
623     if ((modifiers & ShiftMask) != 0)
624         keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname;
625 
626     return keyname;
627 }
628 
createDialogContents()629 void KAccessApp::createDialogContents()
630 {
631     if (dialog == nullptr) {
632         dialog = new QDialog(nullptr);
633         dialog->setWindowTitle(i18n("Warning"));
634         dialog->setObjectName(QStringLiteral("AccessXWarning"));
635         dialog->setModal(true);
636 
637         QVBoxLayout *topLayout = new QVBoxLayout();
638 
639         QHBoxLayout *lay = new QHBoxLayout();
640 
641         QLabel *label1 = new QLabel();
642         QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-warning"));
643         if (icon.isNull())
644             icon = QMessageBox::standardIcon(QMessageBox::Warning);
645         label1->setPixmap(icon.pixmap(64, 64));
646 
647         lay->addWidget(label1, 0, Qt::AlignCenter);
648 
649         QVBoxLayout *vlay = new QVBoxLayout();
650         lay->addItem(vlay);
651 
652         featuresLabel = new QLabel();
653         featuresLabel->setAlignment(Qt::AlignVCenter);
654         featuresLabel->setWordWrap(true);
655         vlay->addWidget(featuresLabel);
656         vlay->addStretch();
657 
658         QHBoxLayout *hlay = new QHBoxLayout();
659         vlay->addItem(hlay);
660 
661         QLabel *showModeLabel = new QLabel(i18n("&When a gesture was used:"));
662         hlay->addWidget(showModeLabel);
663 
664         showModeCombobox = new KComboBox();
665         hlay->addWidget(showModeCombobox);
666         showModeLabel->setBuddy(showModeCombobox);
667         showModeCombobox->insertItem(0, i18n("Change Settings Without Asking"));
668         showModeCombobox->insertItem(1, i18n("Show This Confirmation Dialog"));
669         showModeCombobox->insertItem(2, i18n("Deactivate All AccessX Features & Gestures"));
670         showModeCombobox->setCurrentIndex(1);
671         topLayout->addLayout(lay);
672 
673         auto buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, dialog);
674 
675         topLayout->addWidget(buttons);
676         dialog->setLayout(topLayout);
677 
678         connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
679         connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
680         connect(dialog, &QDialog::accepted, this, &KAccessApp::yesClicked);
681         connect(dialog, &QDialog::rejected, this, &KAccessApp::noClicked);
682     }
683 }
684 
xkbControlsNotify(xcb_xkb_controls_notify_event_t * event)685 void KAccessApp::xkbControlsNotify(xcb_xkb_controls_notify_event_t *event)
686 {
687     unsigned int newFeatures =
688         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);
689 
690     if (newFeatures != features) {
691         unsigned int enabled = newFeatures & ~features;
692         unsigned int disabled = features & ~newFeatures;
693 
694         if (!_gestureConfirmation) {
695             requestedFeatures = enabled | (requestedFeatures & ~disabled);
696             notifyChanges();
697             features = newFeatures;
698         } else {
699             // set the AccessX features back to what they were. We will
700             // apply the changes later if the user allows us to do that.
701             readSettings();
702 
703             requestedFeatures = enabled | (requestedFeatures & ~disabled);
704 
705             enabled = requestedFeatures & ~features;
706             disabled = features & ~requestedFeatures;
707 
708             QStringList enabledFeatures;
709             QStringList disabledFeatures;
710 
711             if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
712                 enabledFeatures << i18n("Slow keys");
713             else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
714                 disabledFeatures << i18n("Slow keys");
715 
716             if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
717                 enabledFeatures << i18n("Bounce keys");
718             else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
719                 disabledFeatures << i18n("Bounce keys");
720 
721             if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
722                 enabledFeatures << i18n("Sticky keys");
723             else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
724                 disabledFeatures << i18n("Sticky keys");
725 
726             if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
727                 enabledFeatures << i18n("Mouse keys");
728             else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
729                 disabledFeatures << i18n("Mouse keys");
730 
731             QString question;
732             switch (enabledFeatures.count()) {
733             case 0:
734                 switch (disabledFeatures.count()) {
735                 case 1:
736                     question = i18n("Do you really want to deactivate \"%1\"?", disabledFeatures[0]);
737                     break;
738                 case 2:
739                     question = i18n("Do you really want to deactivate \"%1\" and \"%2\"?", disabledFeatures[0], disabledFeatures[1]);
740                     break;
741                 case 3:
742                     question =
743                         i18n("Do you really want to deactivate \"%1\", \"%2\" and \"%3\"?", disabledFeatures[0], disabledFeatures[1], disabledFeatures[2]);
744                     break;
745                 case 4:
746                     question = i18n("Do you really want to deactivate \"%1\", \"%2\", \"%3\" and \"%4\"?",
747                                     disabledFeatures[0],
748                                     disabledFeatures[1],
749                                     disabledFeatures[2],
750                                     disabledFeatures[3]);
751                     break;
752                 }
753                 break;
754             case 1:
755                 switch (disabledFeatures.count()) {
756                 case 0:
757                     question = i18n("Do you really want to activate \"%1\"?", enabledFeatures[0]);
758                     break;
759                 case 1:
760                     question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\"?", enabledFeatures[0], disabledFeatures[0]);
761                     break;
762                 case 2:
763                     question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\" and \"%3\"?",
764                                     enabledFeatures[0],
765                                     disabledFeatures[0],
766                                     disabledFeatures[1]);
767                     break;
768                 case 3:
769                     question = i18n("Do you really want to activate \"%1\" and to deactivate \"%2\", \"%3\" and \"%4\"?",
770                                     enabledFeatures[0],
771                                     disabledFeatures[0],
772                                     disabledFeatures[1],
773                                     disabledFeatures[2]);
774                     break;
775                 }
776                 break;
777             case 2:
778                 switch (disabledFeatures.count()) {
779                 case 0:
780                     question = i18n("Do you really want to activate \"%1\" and \"%2\"?", enabledFeatures[0], enabledFeatures[1]);
781                     break;
782                 case 1:
783                     question = i18n("Do you really want to activate \"%1\" and \"%2\" and to deactivate \"%3\"?",
784                                     enabledFeatures[0],
785                                     enabledFeatures[1],
786                                     disabledFeatures[0]);
787                     break;
788                 case 2:
789                     question = i18n("Do you really want to activate \"%1\", and \"%2\" and to deactivate \"%3\" and \"%4\"?",
790                                     enabledFeatures[0],
791                                     enabledFeatures[1],
792                                     enabledFeatures[0],
793                                     disabledFeatures[1]);
794                     break;
795                 }
796                 break;
797             case 3:
798                 switch (disabledFeatures.count()) {
799                 case 0:
800                     question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\"?", enabledFeatures[0], enabledFeatures[1], enabledFeatures[2]);
801                     break;
802                 case 1:
803                     question = i18n("Do you really want to activate \"%1\", \"%2\" and \"%3\" and to deactivate \"%4\"?",
804                                     enabledFeatures[0],
805                                     enabledFeatures[1],
806                                     enabledFeatures[2],
807                                     disabledFeatures[0]);
808                     break;
809                 }
810                 break;
811             case 4:
812                 question = i18n("Do you really want to activate \"%1\", \"%2\", \"%3\" and \"%4\"?",
813                                 enabledFeatures[0],
814                                 enabledFeatures[1],
815                                 enabledFeatures[2],
816                                 enabledFeatures[3]);
817                 break;
818             }
819             QString explanation;
820             if (enabledFeatures.count() + disabledFeatures.count() == 1) {
821                 explanation = i18n("An application has requested to change this setting.");
822 
823                 if (_gestures) {
824                     if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_SLOW_KEYS)
825                         explanation = i18n("You held down the Shift key for 8 seconds or an application has requested to change this setting.");
826                     else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_STICKY_KEYS)
827                         explanation = i18n("You pressed the Shift key 5 consecutive times or an application has requested to change this setting.");
828                     else if ((enabled | disabled) == XCB_XKB_BOOL_CTRL_MOUSE_KEYS) {
829                         QString shortcut = mouseKeysShortcut(QX11Info::display());
830                         if (!shortcut.isEmpty() && !shortcut.isNull())
831                             explanation = i18n("You pressed %1 or an application has requested to change this setting.", shortcut);
832                     }
833                 }
834             } else {
835                 if (_gestures)
836                     explanation = i18n("An application has requested to change these settings, or you used a combination of several keyboard gestures.");
837                 else
838                     explanation = i18n("An application has requested to change these settings.");
839             }
840 
841             createDialogContents();
842             featuresLabel->setText(question + QStringLiteral("\n\n") + explanation + QStringLiteral(" ")
843                                    + i18n("These AccessX settings are needed for some users with motion impairments and can be configured in the KDE System "
844                                           "Settings. You can also turn them on and off with standardized keyboard gestures.\n\nIf you do not need them, you "
845                                           "can select \"Deactivate all AccessX features and gestures\"."));
846 
847             KWindowSystem::setState(dialog->winId(), NET::KeepAbove);
848             KUserTimestamp::updateUserTimestamp(0);
849             dialog->show();
850         }
851     }
852 }
853 
notifyChanges()854 void KAccessApp::notifyChanges()
855 {
856     if (!_kNotifyAccessX)
857         return;
858 
859     unsigned int enabled = requestedFeatures & ~features;
860     unsigned int disabled = features & ~requestedFeatures;
861 
862     if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
863         KNotification::event(QStringLiteral("slowkeys"),
864                              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."));
865     else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
866         KNotification::event(QStringLiteral("slowkeys"), i18n("Slow keys has been disabled."));
867 
868     if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
869         KNotification::event(QStringLiteral("bouncekeys"),
870                              i18n("Bounce keys has been enabled. From now on, each key will be blocked for a certain length of time after it was used."));
871     else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
872         KNotification::event(QStringLiteral("bouncekeys"), i18n("Bounce keys has been disabled."));
873 
874     if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
875         KNotification::event(QStringLiteral("stickykeys"),
876                              i18n("Sticky keys has been enabled. From now on, modifier keys will stay latched after you have released them."));
877     else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
878         KNotification::event(QStringLiteral("stickykeys"), i18n("Sticky keys has been disabled."));
879 
880     if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
881         KNotification::event(QStringLiteral("mousekeys"),
882                              i18n("Mouse keys has been enabled. From now on, you can use the number pad of your keyboard in order to control the mouse."));
883     else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
884         KNotification::event(QStringLiteral("mousekeys"), i18n("Mouse keys has been disabled."));
885 }
886 
applyChanges()887 void KAccessApp::applyChanges()
888 {
889     notifyChanges();
890     unsigned int enabled = requestedFeatures & ~features;
891     unsigned int disabled = features & ~requestedFeatures;
892 
893     KConfigGroup config(KSharedConfig::openConfig(), "Keyboard");
894 
895     if (enabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
896         config.writeEntry("SlowKeys", true);
897     else if (disabled & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
898         config.writeEntry("SlowKeys", false);
899 
900     if (enabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
901         config.writeEntry("BounceKeys", true);
902     else if (disabled & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
903         config.writeEntry("BounceKeys", false);
904 
905     if (enabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
906         config.writeEntry("StickyKeys", true);
907     else if (disabled & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
908         config.writeEntry("StickyKeys", false);
909 
910     KConfigGroup mousegrp(KSharedConfig::openConfig(), "Mouse");
911 
912     if (enabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
913         mousegrp.writeEntry("MouseKeys", true);
914     else if (disabled & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
915         mousegrp.writeEntry("MouseKeys", false);
916     mousegrp.sync();
917     config.sync();
918 }
919 
yesClicked()920 void KAccessApp::yesClicked()
921 {
922     if (dialog)
923         dialog->deleteLater();
924     dialog = nullptr;
925 
926     KConfigGroup config(KSharedConfig::openConfig(), "Keyboard");
927     switch (showModeCombobox->currentIndex()) {
928     case 0:
929         config.writeEntry("Gestures", true);
930         config.writeEntry("GestureConfirmation", false);
931         break;
932     default:
933         config.writeEntry("Gestures", true);
934         config.writeEntry("GestureConfirmation", true);
935         break;
936     case 2:
937         requestedFeatures = 0;
938         config.writeEntry("Gestures", false);
939         config.writeEntry("GestureConfirmation", true);
940     }
941     config.sync();
942 
943     if (features != requestedFeatures) {
944         notifyChanges();
945         applyChanges();
946     }
947     readSettings();
948 }
949 
noClicked()950 void KAccessApp::noClicked()
951 {
952     if (dialog)
953         dialog->deleteLater();
954     dialog = nullptr;
955     requestedFeatures = features;
956 
957     KConfigGroup config(KSharedConfig::openConfig(), "Keyboard");
958     switch (showModeCombobox->currentIndex()) {
959     case 0:
960         config.writeEntry("Gestures", true);
961         config.writeEntry("GestureConfirmation", false);
962         break;
963     default:
964         config.writeEntry("Gestures", true);
965         config.writeEntry("GestureConfirmation", true);
966         break;
967     case 2:
968         requestedFeatures = 0;
969         config.writeEntry("Gestures", false);
970         config.writeEntry("GestureConfirmation", true);
971     }
972     config.sync();
973 
974     if (features != requestedFeatures)
975         applyChanges();
976     readSettings();
977 }
978 
dialogClosed()979 void KAccessApp::dialogClosed()
980 {
981     if (dialog != nullptr)
982         dialog->deleteLater();
983     dialog = nullptr;
984 
985     requestedFeatures = features;
986 }
987 
setXkbOpcode(int opcode)988 void KAccessApp::setXkbOpcode(int opcode)
989 {
990     xkb_opcode = opcode;
991 }
992