File indexing completed on 2024-05-12 05:35:37

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