/* SPDX-FileCopyrightText: 2000 Matthias Hölzer-Klüpfel SPDX-FileCopyrightText: 2014 Frederik Gladhorn SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "kcmaccess.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define XK_MISCELLANY #define XK_XKB_KEYS #include #include "kcmaccessibilitybell.h" #include "kcmaccessibilitydata.h" #include "kcmaccessibilitykeyboard.h" #include "kcmaccessibilitykeyboardfilters.h" #include "kcmaccessibilitymouse.h" #include "kcmaccessibilityscreenreader.h" K_PLUGIN_FACTORY_WITH_JSON(KCMAccessFactory, "kcm_access.json", registerPlugin(); registerPlugin();) 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 = 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 modifiers & ScrollMask & LockMask & NumMask ? i18n("Press %1 while NumLock, CapsLock and ScrollLock are active", keyname) : modifiers & ScrollMask & LockMask ? i18n("Press %1 while CapsLock and ScrollLock are active", keyname) : modifiers & ScrollMask & NumMask ? i18n("Press %1 while NumLock and ScrollLock are active", keyname) : modifiers & ScrollMask ? i18n("Press %1 while ScrollLock is active", keyname) : modifiers & LockMask & NumMask ? i18n("Press %1 while NumLock and CapsLock are active", keyname) : modifiers & LockMask ? i18n("Press %1 while CapsLock is active", keyname) : modifiers & NumMask ? i18n("Press %1 while NumLock is active", keyname) : i18n("Press %1", keyname); } KAccessConfig::KAccessConfig(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) : KQuickAddons::ManagedConfigModule(parent, metaData, args) , m_data(new AccessibilityData(this)) , m_desktopShortcutInfo(QX11Info::isPlatformX11() ? mouseKeysShortcut(QX11Info::display()) : QString()) { qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); int tryOrcaRun = QProcess::execute(QStringLiteral("orca"), {QStringLiteral("--version")}); m_screenReaderInstalled = tryOrcaRun != -2; setButtons(ConfigModule::Apply | ConfigModule::Default | ConfigModule::Help); connect(m_data->bellSettings(), &BellSettings::configChanged, this, &KAccessConfig::bellIsDefaultsChanged); connect(m_data->mouseSettings(), &MouseSettings::configChanged, this, &KAccessConfig::mouseIsDefaultsChanged); connect(m_data->keyboardFiltersSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::keyboardFiltersIsDefaultsChanged); connect(m_data->keyboardSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::keyboardModifiersIsDefaultsChanged); connect(m_data->screenReaderSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::screenReaderIsDefaultsChanged); } KAccessConfig::~KAccessConfig() { } void KAccessConfig::configureKNotify(QQuickItem *parent) { auto dialog = KNotifyConfigWidget::configure(nullptr, QStringLiteral("kaccess")); if (parent && parent->window()) { dialog->winId(); dialog->windowHandle()->setTransientParent(QQuickRenderControl::renderWindowFor(parent->window())); } } void KAccessConfig::launchOrcaConfiguration() { const QStringList gsettingArgs = {QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("true")}; int ret = QProcess::execute(QStringLiteral("gsettings"), gsettingArgs); if (ret) { const QString errorStr = QLatin1String("gsettings ") + gsettingArgs.join(QLatin1Char(' ')); setOrcaLaunchFeedback(i18n("Could not set gsettings for Orca: \"%1\" failed", errorStr)); return; } qint64 pid = 0; bool started = QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--setup")}, QString(), &pid); if (!started) { setOrcaLaunchFeedback(i18n("Error: Could not launch \"orca --setup\"")); } } void KAccessConfig::save() { ManagedConfigModule::save(); if (bellSettings()->systemBell() || bellSettings()->customBell() || bellSettings()->visibleBell()) { KConfig _cfg(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cfg(&_cfg, "General"); cfg.writeEntry("UseSystemBell", true); cfg.sync(); } // make kaccess reread the configuration // turning a11y features off needs to be done by kaccess // so run it to clear any enabled features and it will exit if it should QProcess::startDetached(QStringLiteral("kaccess"), {}); } QString KAccessConfig::orcaLaunchFeedback() const { return m_orcaLaunchFeedback; } void KAccessConfig::setOrcaLaunchFeedback(const QString &value) { if (m_orcaLaunchFeedback != value) { m_orcaLaunchFeedback = value; Q_EMIT orcaLaunchFeedbackChanged(); } } bool KAccessConfig::orcaInstalled() { int tryOrcaRun = QProcess::execute(QStringLiteral("orca"), {QStringLiteral("--version")}); // If the process cannot be started, -2 is returned. return tryOrcaRun != -2; } MouseSettings *KAccessConfig::mouseSettings() const { return m_data->mouseSettings(); } BellSettings *KAccessConfig::bellSettings() const { return m_data->bellSettings(); } KeyboardSettings *KAccessConfig::keyboardSettings() const { return m_data->keyboardSettings(); } KeyboardFiltersSettings *KAccessConfig::keyboardFiltersSettings() const { return m_data->keyboardFiltersSettings(); } ScreenReaderSettings *KAccessConfig::screenReaderSettings() const { return m_data->screenReaderSettings(); } bool KAccessConfig::bellIsDefaults() const { return bellSettings()->isDefaults(); } bool KAccessConfig::mouseIsDefaults() const { return mouseSettings()->isDefaults(); } bool KAccessConfig::keyboardFiltersIsDefaults() const { return keyboardFiltersSettings()->isDefaults(); } bool KAccessConfig::keyboardModifiersIsDefaults() const { return keyboardSettings()->isDefaults(); } bool KAccessConfig::screenReaderIsDefaults() const { return screenReaderSettings()->isDefaults(); } #include "kcmaccess.moc"