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

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "xkb_helper.h"
0008 #include "debug.h"
0009 
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QElapsedTimer>
0013 #include <QFile>
0014 #include <QProcess>
0015 #include <QStandardPaths>
0016 #include <QString>
0017 #include <QTime>
0018 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0019 #include <QX11Info>
0020 #else
0021 #include <QtGui/private/qtx11extras_p.h>
0022 #endif
0023 
0024 #include "keyboard_config.h"
0025 #include "keyboardsettings.h"
0026 
0027 static const char SETXKBMAP_EXEC[] = "setxkbmap";
0028 static const char XMODMAP_EXEC[] = "xmodmap";
0029 
0030 static bool setxkbmapNotFound = false;
0031 static QString setxkbmapExe;
0032 
0033 static bool xmodmapNotFound = false;
0034 static QString xmodmapExe;
0035 
0036 static const QString COMMAND_OPTIONS_SEPARATOR(QStringLiteral(","));
0037 
0038 static QString getSetxkbmapExe()
0039 {
0040     if (setxkbmapNotFound)
0041         return QString();
0042 
0043     if (setxkbmapExe.isEmpty()) {
0044         setxkbmapExe = QStandardPaths::findExecutable(SETXKBMAP_EXEC);
0045         if (setxkbmapExe.isEmpty()) {
0046             setxkbmapNotFound = true;
0047             qCCritical(KCM_KEYBOARD) << "Can't find" << SETXKBMAP_EXEC << "- keyboard layouts won't be configured";
0048             return QString();
0049         }
0050     }
0051     return setxkbmapExe;
0052 }
0053 
0054 static void executeXmodmap(const QString &configFileName)
0055 {
0056     if (xmodmapNotFound)
0057         return;
0058 
0059     if (QFile(configFileName).exists()) {
0060         if (xmodmapExe.isEmpty()) {
0061             xmodmapExe = QStandardPaths::findExecutable(XMODMAP_EXEC);
0062             if (xmodmapExe.isEmpty()) {
0063                 xmodmapNotFound = true;
0064                 qCCritical(KCM_KEYBOARD) << "Can't find" << XMODMAP_EXEC << "- xmodmap files won't be run";
0065                 return;
0066             }
0067         }
0068 
0069         qCDebug(KCM_KEYBOARD) << "Executing" << xmodmapExe << configFileName;
0070         const int res = QProcess::execute(xmodmapExe, QStringList{configFileName});
0071         if (res != 0) {
0072             qCCritical(KCM_KEYBOARD) << "Failed with return code:" << res;
0073         }
0074     }
0075 }
0076 
0077 static void restoreXmodmap()
0078 {
0079     // TODO: is just home .Xmodmap enough or should system be involved too?
0080     //    QString configFileName = QDir("/etc/X11/xinit").filePath(".Xmodmap");
0081     //    executeXmodmap(configFileName);
0082     QString configFileName = QDir::home().filePath(QStringLiteral(".Xmodmap"));
0083     executeXmodmap(configFileName);
0084 }
0085 
0086 // TODO: make private
0087 bool XkbHelper::runConfigLayoutCommand(const QStringList &setxkbmapCommandArguments)
0088 {
0089     QElapsedTimer timer;
0090     timer.start();
0091 
0092     const auto setxkbmapExe = getSetxkbmapExe();
0093     if (setxkbmapExe.isEmpty()) {
0094         return false;
0095     }
0096 
0097     qCDebug(KCM_KEYBOARD) << "Running" << setxkbmapExe << setxkbmapCommandArguments.join(QLatin1Char(' '));
0098 
0099     const int res = QProcess::execute(setxkbmapExe, setxkbmapCommandArguments);
0100     if (res != 0) {
0101         qCCritical(KCM_KEYBOARD) << "Failed with return code:" << res;
0102         return false;
0103     }
0104 
0105     // restore Xmodmap mapping reset by setxkbmap
0106     qCDebug(KCM_KEYBOARD) << "Executed successfully in " << timer.elapsed() << "ms";
0107     restoreXmodmap();
0108     qCDebug(KCM_KEYBOARD) << "\t and with xmodmap" << timer.elapsed() << "ms";
0109     return true;
0110 }
0111 
0112 bool XkbHelper::initializeKeyboardLayouts(const QList<LayoutUnit> &layoutUnits)
0113 {
0114     QStringList layouts;
0115     QStringList variants;
0116     for (const auto &layoutUnit : layoutUnits) {
0117         layouts.append(layoutUnit.layout());
0118         variants.append(layoutUnit.variant());
0119     }
0120 
0121     QStringList setxkbmapCommandArguments;
0122     setxkbmapCommandArguments.append(QStringLiteral("-layout"));
0123     setxkbmapCommandArguments.append(layouts.join(COMMAND_OPTIONS_SEPARATOR));
0124     if (!variants.join(QString()).isEmpty()) {
0125         setxkbmapCommandArguments.append(QStringLiteral("-variant"));
0126         setxkbmapCommandArguments.append(variants.join(COMMAND_OPTIONS_SEPARATOR));
0127     }
0128 
0129     return runConfigLayoutCommand(setxkbmapCommandArguments);
0130 }
0131 
0132 bool XkbHelper::initializeKeyboardLayouts(KeyboardConfig &config)
0133 {
0134     QStringList setxkbmapCommandArguments;
0135     if (!config.keyboardModel().isEmpty()) {
0136         XkbConfig xkbConfig;
0137         X11Helper::getGroupNames(QX11Info::display(), &xkbConfig, X11Helper::MODEL_ONLY);
0138         if (xkbConfig.keyboardModel != config.keyboardModel()) {
0139             setxkbmapCommandArguments.append(QStringLiteral("-model"));
0140             setxkbmapCommandArguments.append(config.keyboardModel());
0141         }
0142     }
0143     if (config.configureLayouts()) {
0144         QStringList layouts;
0145         QStringList variants;
0146         const QList<LayoutUnit> defaultLayouts = config.getDefaultLayouts();
0147         for (const auto &layoutUnit : defaultLayouts) {
0148             layouts.append(layoutUnit.layout());
0149             variants.append(layoutUnit.variant());
0150         }
0151 
0152         setxkbmapCommandArguments.append(QStringLiteral("-layout"));
0153         setxkbmapCommandArguments.append(layouts.join(COMMAND_OPTIONS_SEPARATOR));
0154         if (!variants.join(QString()).isEmpty()) {
0155             setxkbmapCommandArguments.append(QStringLiteral("-variant"));
0156             setxkbmapCommandArguments.append(variants.join(COMMAND_OPTIONS_SEPARATOR));
0157         }
0158     }
0159     if (config.resetOldXkbOptions()) {
0160         // Pass -option "" to clear previously set options
0161         setxkbmapCommandArguments.append(QStringLiteral("-option"));
0162         setxkbmapCommandArguments.append(QStringLiteral(""));
0163     }
0164     const QStringList xkbOpts = config.xkbOptions();
0165     for (const auto &option : xkbOpts) {
0166         setxkbmapCommandArguments.append(QStringLiteral("-option"));
0167         setxkbmapCommandArguments.append(option);
0168     }
0169 
0170     if (!setxkbmapCommandArguments.isEmpty()) {
0171         return runConfigLayoutCommand(setxkbmapCommandArguments);
0172         if (config.configureLayouts()) {
0173             X11Helper::setDefaultLayout();
0174         }
0175     }
0176     return false;
0177 }