File indexing completed on 2024-05-12 17:07:06

0001 /*
0002     SPDX-FileCopyrightText: 2000 Matthias Hölzer-Klüpfel <hoelzer@kde.org>
0003     SPDX-FileCopyrightText: 2014 Frederik Gladhorn <gladhorn@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "kcmaccess.h"
0009 
0010 #include <math.h>
0011 #include <stdlib.h>
0012 
0013 #include <QApplication>
0014 #include <QFileDialog>
0015 #include <QProcess>
0016 #include <QQuickItem>
0017 #include <QStandardPaths>
0018 #include <QWindow>
0019 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0020 #include <QX11Info>
0021 #else
0022 #include <QtGui/private/qtx11extras_p.h>
0023 #endif
0024 
0025 #include <KConfigGroup>
0026 #include <KKeyServer>
0027 #include <KLocalizedString>
0028 #include <KNotifyConfigWidget>
0029 #include <KPluginFactory>
0030 #include <KSharedConfig>
0031 #include <X11/XKBlib.h>
0032 #include <X11/Xlib.h>
0033 
0034 #define XK_MISCELLANY
0035 #define XK_XKB_KEYS
0036 #include <X11/keysymdef.h>
0037 
0038 #include "kcmaccessibilitybell.h"
0039 #include "kcmaccessibilitydata.h"
0040 #include "kcmaccessibilitykeyboard.h"
0041 #include "kcmaccessibilitykeyboardfilters.h"
0042 #include "kcmaccessibilitymouse.h"
0043 #include "kcmaccessibilityscreenreader.h"
0044 
0045 K_PLUGIN_FACTORY_WITH_JSON(KCMAccessFactory, "kcm_access.json", registerPlugin<KAccessConfig>(); registerPlugin<AccessibilityData>();)
0046 
0047 QString mouseKeysShortcut(Display *display)
0048 {
0049     // Calculate the keycode
0050     KeySym sym = XK_MouseKeys_Enable;
0051     KeyCode code = XKeysymToKeycode(display, sym);
0052     if (code == 0) {
0053         sym = XK_Pointer_EnableKeys;
0054         code = XKeysymToKeycode(display, sym);
0055         if (code == 0)
0056             return QString(); // No shortcut available?
0057     }
0058 
0059     // Calculate the modifiers by searching the keysym in the X keyboard mapping
0060     XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd);
0061     if (!xkbdesc)
0062         return QString(); // Failed to obtain the mapping from server
0063 
0064     bool found = false;
0065     unsigned char modifiers = 0;
0066     int groups = XkbKeyNumGroups(xkbdesc, code);
0067     for (int grp = 0; grp < groups && !found; grp++) {
0068         int levels = XkbKeyGroupWidth(xkbdesc, code, grp);
0069         for (int level = 0; level < levels && !found; level++) {
0070             if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) {
0071                 // keysym found => determine modifiers
0072                 int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp];
0073                 XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]);
0074                 for (int i = 0; i < type->map_count && !found; i++) {
0075                     if (type->map[i].active && (type->map[i].level == level)) {
0076                         modifiers = type->map[i].mods.mask;
0077                         found = true;
0078                     }
0079                 }
0080             }
0081         }
0082     }
0083     XkbFreeClientMap(xkbdesc, 0, true);
0084 
0085     if (!found)
0086         return QString(); // Somehow the keycode -> keysym mapping is flawed
0087 
0088     XEvent ev;
0089     ev.type = KeyPress;
0090     ev.xkey.display = display;
0091     ev.xkey.keycode = code;
0092     ev.xkey.state = 0;
0093     int key;
0094     KKeyServer::xEventToQt(&ev, &key);
0095     QString keyname = QKeySequence(key).toString();
0096 
0097     unsigned int AltMask = KKeyServer::modXAlt();
0098     unsigned int WinMask = KKeyServer::modXMeta();
0099     unsigned int NumMask = KKeyServer::modXNumLock();
0100     unsigned int ScrollMask = KKeyServer::modXScrollLock();
0101 
0102     unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L);
0103     unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L);
0104     unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L);
0105     unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift)
0106         | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock);
0107 
0108     unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask;
0109 
0110     AltGrMask &= ~mods;
0111     MetaMask &= ~(mods | AltGrMask);
0112     SuperMask &= ~(mods | AltGrMask | MetaMask);
0113     HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask);
0114 
0115     if ((modifiers & AltGrMask) != 0)
0116         keyname = i18n("AltGraph") + QLatin1Char('+') + keyname;
0117     if ((modifiers & HyperMask) != 0)
0118         keyname = i18n("Hyper") + QLatin1Char('+') + keyname;
0119     if ((modifiers & SuperMask) != 0)
0120         keyname = i18n("Super") + QLatin1Char('+') + keyname;
0121     if ((modifiers & WinMask) != 0)
0122         keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname;
0123     if ((modifiers & AltMask) != 0)
0124         keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname;
0125     if ((modifiers & ControlMask) != 0)
0126         keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname;
0127     if ((modifiers & ShiftMask) != 0)
0128         keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname;
0129 
0130     return modifiers & ScrollMask & LockMask & NumMask ? i18n("Press %1 while NumLock, CapsLock and ScrollLock are active", keyname)
0131         : modifiers & ScrollMask & LockMask            ? i18n("Press %1 while CapsLock and ScrollLock are active", keyname)
0132         : modifiers & ScrollMask & NumMask             ? i18n("Press %1 while NumLock and ScrollLock are active", keyname)
0133         : modifiers & ScrollMask                       ? i18n("Press %1 while ScrollLock is active", keyname)
0134         : modifiers & LockMask & NumMask               ? i18n("Press %1 while NumLock and CapsLock are active", keyname)
0135         : modifiers & LockMask                         ? i18n("Press %1 while CapsLock is active", keyname)
0136         : modifiers & NumMask                          ? i18n("Press %1 while NumLock is active", keyname)
0137                                                        : i18n("Press %1", keyname);
0138 }
0139 
0140 KAccessConfig::KAccessConfig(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
0141     : KQuickAddons::ManagedConfigModule(parent, metaData, args)
0142     , m_data(new AccessibilityData(this))
0143     , m_desktopShortcutInfo(QX11Info::isPlatformX11() ? mouseKeysShortcut(QX11Info::display()) : QString())
0144 {
0145     qmlRegisterAnonymousType<MouseSettings>("org.kde.plasma.access.kcm", 0);
0146     qmlRegisterAnonymousType<BellSettings>("org.kde.plasma.access.kcm", 0);
0147     qmlRegisterAnonymousType<KeyboardSettings>("org.kde.plasma.access.kcm", 0);
0148     qmlRegisterAnonymousType<KeyboardFiltersSettings>("org.kde.plasma.access.kcm", 0);
0149     qmlRegisterAnonymousType<ScreenReaderSettings>("org.kde.plasma.access.kcm", 0);
0150 
0151     int tryOrcaRun = QProcess::execute(QStringLiteral("orca"), {QStringLiteral("--version")});
0152     m_screenReaderInstalled = tryOrcaRun != -2;
0153 
0154     setButtons(ConfigModule::Apply | ConfigModule::Default | ConfigModule::Help);
0155 
0156     connect(m_data->bellSettings(), &BellSettings::configChanged, this, &KAccessConfig::bellIsDefaultsChanged);
0157     connect(m_data->mouseSettings(), &MouseSettings::configChanged, this, &KAccessConfig::mouseIsDefaultsChanged);
0158     connect(m_data->keyboardFiltersSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::keyboardFiltersIsDefaultsChanged);
0159     connect(m_data->keyboardSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::keyboardModifiersIsDefaultsChanged);
0160     connect(m_data->screenReaderSettings(), &ScreenReaderSettings::configChanged, this, &KAccessConfig::screenReaderIsDefaultsChanged);
0161 }
0162 
0163 KAccessConfig::~KAccessConfig()
0164 {
0165 }
0166 
0167 void KAccessConfig::configureKNotify()
0168 {
0169     KNotifyConfigWidget::configure(QApplication::activeWindow(), QStringLiteral("kaccess"));
0170 }
0171 
0172 void KAccessConfig::launchOrcaConfiguration()
0173 {
0174     const QStringList gsettingArgs = {QStringLiteral("set"),
0175                                       QStringLiteral("org.gnome.desktop.a11y.applications"),
0176                                       QStringLiteral("screen-reader-enabled"),
0177                                       QStringLiteral("true")};
0178 
0179     int ret = QProcess::execute(QStringLiteral("gsettings"), gsettingArgs);
0180     if (ret) {
0181         const QString errorStr = QLatin1String("gsettings ") + gsettingArgs.join(QLatin1Char(' '));
0182         setOrcaLaunchFeedback(i18n("Could not set gsettings for Orca: \"%1\" failed", errorStr));
0183         return;
0184     }
0185 
0186     qint64 pid = 0;
0187     bool started = QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--setup")}, QString(), &pid);
0188     if (!started) {
0189         setOrcaLaunchFeedback(i18n("Error: Could not launch \"orca --setup\""));
0190     }
0191 }
0192 
0193 void KAccessConfig::save()
0194 {
0195     ManagedConfigModule::save();
0196 
0197     if (bellSettings()->systemBell() || bellSettings()->customBell() || bellSettings()->visibleBell()) {
0198         KConfig _cfg(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
0199         KConfigGroup cfg(&_cfg, "General");
0200         cfg.writeEntry("UseSystemBell", true);
0201         cfg.sync();
0202     }
0203 
0204     // make kaccess reread the configuration
0205     // turning a11y features off needs to be done by kaccess
0206     // so run it to clear any enabled features and it will exit if it should
0207     QProcess::startDetached(QStringLiteral("kaccess"), {});
0208 }
0209 
0210 QString KAccessConfig::orcaLaunchFeedback() const
0211 {
0212     return m_orcaLaunchFeedback;
0213 }
0214 
0215 void KAccessConfig::setOrcaLaunchFeedback(const QString &value)
0216 {
0217     if (m_orcaLaunchFeedback != value) {
0218         m_orcaLaunchFeedback = value;
0219         Q_EMIT orcaLaunchFeedbackChanged();
0220     }
0221 }
0222 
0223 bool KAccessConfig::orcaInstalled()
0224 {
0225     int tryOrcaRun = QProcess::execute(QStringLiteral("orca"), {QStringLiteral("--version")});
0226     // If the process cannot be started, -2 is returned.
0227     return tryOrcaRun != -2;
0228 }
0229 
0230 MouseSettings *KAccessConfig::mouseSettings() const
0231 {
0232     return m_data->mouseSettings();
0233 }
0234 
0235 BellSettings *KAccessConfig::bellSettings() const
0236 {
0237     return m_data->bellSettings();
0238 }
0239 
0240 KeyboardSettings *KAccessConfig::keyboardSettings() const
0241 {
0242     return m_data->keyboardSettings();
0243 }
0244 
0245 KeyboardFiltersSettings *KAccessConfig::keyboardFiltersSettings() const
0246 {
0247     return m_data->keyboardFiltersSettings();
0248 }
0249 
0250 ScreenReaderSettings *KAccessConfig::screenReaderSettings() const
0251 {
0252     return m_data->screenReaderSettings();
0253 }
0254 
0255 bool KAccessConfig::bellIsDefaults() const
0256 {
0257     return bellSettings()->isDefaults();
0258 }
0259 
0260 bool KAccessConfig::mouseIsDefaults() const
0261 {
0262     return mouseSettings()->isDefaults();
0263 }
0264 
0265 bool KAccessConfig::keyboardFiltersIsDefaults() const
0266 {
0267     return keyboardFiltersSettings()->isDefaults();
0268 }
0269 
0270 bool KAccessConfig::keyboardModifiersIsDefaults() const
0271 {
0272     return keyboardSettings()->isDefaults();
0273 }
0274 
0275 bool KAccessConfig::screenReaderIsDefaults() const
0276 {
0277     return screenReaderSettings()->isDefaults();
0278 }
0279 
0280 #include "kcmaccess.moc"
0281 #include "moc_kcmaccess.cpp"