File indexing completed on 2024-05-19 05:36:13

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