File indexing completed on 2024-11-24 05:00:24

0001 /*
0002     SPDX-FileCopyrightText: 2014 Weng Xuetian <wengxt@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 #include "xkblayoutmanager.h"
0007 #include <QDir>
0008 #include <QProcess>
0009 #include <QStringList>
0010 
0011 const char *XKB_COMMAND = "setxkbmap";
0012 const char *XKB_QUERY_ARG = "-query";
0013 const char *XKB_LAYOUT_ARG = "-layout";
0014 const char *XMODMAP_COMMAND = "xmodmap";
0015 const char *XMODMAP_KNOWN_FILES[] = {".xmodmap", ".xmodmaprc", ".Xmodmap", ".Xmodmaprc"};
0016 
0017 XkbLayoutManager::XkbLayoutManager()
0018     : m_useXkbModmap(false)
0019 {
0020 }
0021 
0022 void XkbLayoutManager::setLatinLayouts(const gchar **variants, gsize length)
0023 {
0024     m_latinLayouts.clear();
0025     for (gsize i = 0; i < length; i++) {
0026         m_latinLayouts.insert(QString::fromUtf8(variants[i]));
0027     }
0028 }
0029 
0030 void XkbLayoutManager::getLayout()
0031 {
0032     QProcess process;
0033     process.start(QString::fromLatin1(XKB_COMMAND), QStringList(QString::fromLatin1(XKB_QUERY_ARG)));
0034     process.waitForFinished();
0035     const QByteArray output = process.readAllStandardOutput();
0036     const QList<QByteArray> lines = output.split('\n');
0037     for (const QByteArray &line : lines) {
0038         QByteArray element("layout:");
0039         if (line.startsWith(element)) {
0040             m_defaultLayout = QString::fromLatin1(line.mid(element.length(), line.length())).trimmed();
0041         }
0042 
0043         element = "variant:";
0044         if (line.startsWith(element)) {
0045             m_defaultVariant = QString::fromLatin1(line.mid(element.length(), line.length())).trimmed();
0046         }
0047 
0048         element = "options:";
0049         if (line.startsWith(element)) {
0050             m_defaultOption = QString::fromLatin1(line.mid(element.length(), line.length())).trimmed();
0051         }
0052     }
0053 }
0054 
0055 void XkbLayoutManager::setLayout(IBusEngineDesc *desc)
0056 {
0057     const gchar *clayout = ibus_engine_desc_get_layout(desc);
0058     const gchar *cvariant = ibus_engine_desc_get_layout_variant(desc);
0059     const gchar *coption = ibus_engine_desc_get_layout_option(desc);
0060 
0061     QString layout = QString::fromUtf8(clayout ? clayout : "");
0062     QString variant = QString::fromUtf8(cvariant ? cvariant : "");
0063     QString option = QString::fromUtf8(coption ? coption : "");
0064 
0065     if (layout == QLatin1String{"default"} && (variant.isEmpty() || variant == QLatin1String{"default"})
0066         && (option.isEmpty() && option == QLatin1String{"default"})) {
0067         return;
0068     }
0069 
0070     bool need_us_layout = false;
0071     if (variant != QLatin1String{"eng"}) {
0072         need_us_layout = m_latinLayouts.contains(layout);
0073     }
0074     if (!variant.isEmpty() && !need_us_layout) {
0075         need_us_layout = m_latinLayouts.contains(QLatin1String("%1(%2)").arg(layout, variant));
0076     }
0077 
0078     if (m_defaultLayout.isEmpty()) {
0079         getLayout();
0080     }
0081 
0082     if (layout.isEmpty() || layout == QLatin1String{"default"}) {
0083         layout = m_defaultLayout;
0084         variant = m_defaultVariant;
0085     }
0086 
0087     if (layout.isEmpty()) {
0088         g_warning("Could not get the correct layout");
0089         return;
0090     }
0091 
0092     if (option.isEmpty() || option == QLatin1String{"default"}) {
0093         option = m_defaultOption;
0094     } else {
0095         if (!(m_defaultOption.split(QLatin1Char{','}).contains(option))) {
0096             option = QLatin1String("%1,%2").arg(m_defaultOption, option);
0097         } else {
0098             option = m_defaultOption;
0099         }
0100     }
0101 
0102     if (need_us_layout) {
0103         layout += QLatin1String{",us"};
0104         if (!variant.isEmpty()) {
0105             variant += QLatin1Char{','};
0106         }
0107     }
0108 
0109     QStringList args;
0110     args << QString::fromLatin1(XKB_LAYOUT_ARG);
0111     args << layout;
0112     if (!variant.isEmpty() && variant != QLatin1String("default")) {
0113         args << QStringLiteral("-variant");
0114         args << variant;
0115     }
0116     if (!option.isEmpty() && option != QLatin1String("default")) {
0117         /*TODO: Need to get the session XKB options */
0118         args << QStringLiteral("-option");
0119         args << option;
0120     }
0121 
0122     int exit_status = QProcess::execute(QString::fromLatin1(XKB_COMMAND), args);
0123 
0124     if (exit_status != 0) {
0125         g_warning("Execute setxkbmap failed.");
0126     }
0127 
0128     runXmodmap();
0129 }
0130 
0131 void XkbLayoutManager::runXmodmap()
0132 {
0133     if (!m_useXkbModmap) {
0134         return;
0135     }
0136 
0137     QDir home = QDir::home();
0138     for (size_t i = 0; i < sizeof(XMODMAP_KNOWN_FILES) / sizeof(XMODMAP_KNOWN_FILES[0]); i++) {
0139         if (home.exists(QString::fromLatin1(XMODMAP_KNOWN_FILES[i]))) {
0140             QProcess::startDetached(QString::fromLatin1(XMODMAP_COMMAND), QStringList{home.filePath(QString::fromLatin1(XMODMAP_KNOWN_FILES[i]))});
0141             break;
0142         }
0143     }
0144 }
0145 
0146 void XkbLayoutManager::setUseXkbModmap(bool use)
0147 {
0148     m_useXkbModmap = use;
0149 }