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