File indexing completed on 2024-05-26 05:37:03

0001 /*
0002     SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include <QCommandLineParser>
0008 #include <QDebug>
0009 #include <QProcess>
0010 #include <QQmlApplicationEngine>
0011 #include <QQmlContext>
0012 #include <QtGui/private/qtx11extras_p.h>
0013 
0014 #include <KAboutData>
0015 #include <KLocalizedString>
0016 
0017 #include <memory>
0018 
0019 #include "application.h"
0020 #include "config-workspace.h"
0021 #include "doodad.h"
0022 #include "geometry.h"
0023 
0024 // kind-of copy from xkb_rules.cpp (less complicated)
0025 static QString getRulesName()
0026 {
0027     XkbRF_VarDefsRec vd;
0028     char *tmp = nullptr;
0029 
0030     if (XkbRF_GetNamesProp(QX11Info::display(), &tmp, &vd) && tmp != nullptr) {
0031         const QString name(tmp);
0032         XFree(tmp);
0033         return name;
0034     }
0035 
0036     return QStringLiteral("evdev"); // default to evdev
0037 }
0038 
0039 static QString findXkbRulesFile()
0040 {
0041     const QString rulesName = getRulesName();
0042     return QStringLiteral("%1/rules/%2").arg(XKBDIR, rulesName);
0043 }
0044 
0045 int main(int argc, char *argv[])
0046 {
0047     setenv("QT_QPA_PLATFORM", "xcb", 1);
0048     Application app(argc, argv);
0049     Q_ASSERT(app.platformName() == QStringLiteral("xcb"));
0050 
0051     KAboutData aboutData(QStringLiteral("tastenbrett"),
0052                          i18nc("app display name", "Keyboard Preview"),
0053                          QStringLiteral("1.0"),
0054                          i18nc("app description", "Keyboard layout visualization"),
0055                          KAboutLicense::GPL);
0056     KAboutData::setApplicationData(aboutData);
0057 
0058     QCommandLineParser parser;
0059     aboutData.setupCommandLine(&parser);
0060 
0061     QCommandLineOption modelOption(QStringList{"m", "model"}, {}, QStringLiteral("MODEL"));
0062     parser.addOption(modelOption);
0063     QCommandLineOption layoutOption(QStringList{"l", "layout"}, {}, QStringLiteral("LAYOUT"));
0064     parser.addOption(layoutOption);
0065     QCommandLineOption variantOption(QStringList{"a", "variant"}, {}, QStringLiteral("VARIANT"));
0066     parser.addOption(variantOption);
0067     QCommandLineOption optionsOption(QStringList{"o", "options"}, {}, QStringLiteral("OPTIONS"));
0068     parser.addOption(optionsOption);
0069     parser.process(app);
0070     aboutData.processCommandLine(&parser);
0071 
0072     XkbRF_VarDefsRec varDefs;
0073     memset(&varDefs, 0, sizeof(XkbRF_VarDefsRec));
0074 
0075     // Models worth testing for obvious mistakes:
0076     // pc104, tm2020 (fancy), kinesis (fancy)
0077     QString model = parser.value(modelOption);
0078     const QString layout = parser.value(layoutOption);
0079     const QString variant = parser.value(variantOption);
0080     const QString options = parser.value(optionsOption);
0081 
0082     // Hold these so so we can pass data into xkb getter.
0083     QByteArray modelArray = model.toUtf8();
0084     QByteArray layoutArray = layout.toUtf8();
0085     QByteArray variantArray = variant.toUtf8();
0086     QByteArray optionsArray = options.toUtf8();
0087 
0088     varDefs.model = modelArray.data();
0089     varDefs.layout = layoutArray.data();
0090     varDefs.variant = variantArray.data();
0091     varDefs.options = optionsArray.data();
0092 
0093     XkbRF_RulesPtr rules = XkbRF_Load(findXkbRulesFile().toUtf8().data(), // needs to be non-const!
0094                                       qgetenv("LOCALE").data(),
0095                                       True,
0096                                       True);
0097 
0098     Q_ASSERT(rules);
0099     std::unique_ptr<XkbRF_RulesRec, std::function<void(XkbRF_RulesPtr)>> rulesCleanup(rules, [](XkbRF_RulesPtr obj) {
0100         XkbRF_Free(obj, True);
0101     });
0102 
0103     XkbComponentNamesRec componentNames;
0104     memset(&componentNames, 0, sizeof(XkbComponentNamesRec));
0105     XkbRF_GetComponents(rules, &varDefs, &componentNames);
0106 
0107     QString errorDescription;
0108     QString errorDetails;
0109     std::unique_ptr<Geometry> geometry;
0110 
0111     XkbDescPtr xkb =
0112         XkbGetKeyboardByName(QX11Info::display(),
0113                              XkbUseCoreKbd,
0114                              &componentNames,
0115                              0,
0116                              XkbGBN_GeometryMask | XkbGBN_KeyNamesMask | XkbGBN_OtherNamesMask | XkbGBN_ClientSymbolsMask | XkbGBN_IndicatorMapMask,
0117                              false);
0118     if (!xkb) {
0119         QProcess setxkbmap;
0120         QProcess xkbcomp;
0121 
0122         setxkbmap.setStandardOutputProcess(&xkbcomp);
0123         xkbcomp.setProcessChannelMode(QProcess::MergedChannels); // combine in single channel
0124         setxkbmap.start(QStringLiteral("setxkbmap"),
0125                         {QStringLiteral("-print"),
0126                          QStringLiteral("-model"),
0127                          model,
0128                          QStringLiteral("-layout"),
0129                          layout,
0130                          QStringLiteral("-variant"),
0131                          variant,
0132                          QStringLiteral("-option"),
0133                          options});
0134         xkbcomp.start(QStringLiteral("xkbcomp"), {QStringLiteral("-")});
0135         setxkbmap.waitForFinished();
0136         xkbcomp.waitForFinished();
0137 
0138         errorDescription = i18nc("@label",
0139                                  "The keyboard geometry failed to load."
0140                                  " This often indicates that the selected model does not support a specific layout"
0141                                  " or layout variant."
0142                                  " This problem will likely also present when you try to use this combination of model, layout and variant.");
0143         errorDetails = xkbcomp.readAllStandardOutput();
0144     } else {
0145         Q_ASSERT(xkb);
0146         geometry.reset(new Geometry(xkb->geom, xkb));
0147     }
0148 
0149     // Register the doodads so we can perform easy type checks with them
0150     // and determine how to render the individual object.
0151     const char uri[] = "org.kde.tastenbrett.private";
0152     qmlRegisterUncreatableType<TextDoodad>(uri, 1, 0, "TextDoodad", QString());
0153     qmlRegisterUncreatableType<LogoDoodad>(uri, 1, 0, "LogoDoodad", QString());
0154     qmlRegisterUncreatableType<ShapeDoodad>(uri, 1, 0, "ShapeDoodad", QString());
0155     qmlRegisterUncreatableType<IndicatorDoodad>(uri, 1, 0, "IndicatorDoodad", QString());
0156 
0157     // The way this is currently written we need the engine after
0158     // we have a geometry, lest geometry is dtor'd before the engine
0159     // causing exhaustive error spam on shutdown.
0160     // Also, the above stuff is blocking, but optimizing it is hardly
0161     // worth the effort. The Xkb calls altogether take ~8ms (I am not
0162     // certain putting xkb into a qfuture is thread-safe or even
0163     // faster). Constructing our QObjects takes 1ms.
0164     QQmlApplicationEngine engine;
0165     const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
0166     QObject::connect(
0167         &engine,
0168         &QQmlApplicationEngine::objectCreated,
0169         &app,
0170         [url](QObject *obj, const QUrl &objUrl) {
0171             if (!obj && url == objUrl)
0172                 QCoreApplication::exit(-1);
0173         },
0174         Qt::QueuedConnection);
0175     engine.rootContext()->setContextProperty("geometry", geometry.get());
0176     engine.rootContext()->setContextProperty("errorDescription", errorDescription);
0177     engine.rootContext()->setContextProperty("errorDetails", errorDetails);
0178     engine.load(url);
0179 
0180     return app.exec();
0181 }
0182 
0183 #include "main.moc"