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