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 }