File indexing completed on 2024-04-28 16:55:49

0001 /*
0002  * SPDX-FileCopyrightText: 2022 Aleix Pol i Gonzalez <aleixpol@kde.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "xdgshortcut.h"
0008 #include <QDebug>
0009 #include <QRegularExpression>
0010 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0011 #include <QtXkbCommonSupport/private/qxkbcommon_p.h>
0012 #else
0013 #include <QtGui/private/qxkbcommon_p.h>
0014 #endif
0015 
0016 struct Modifier {
0017     const char *xkbModifier;
0018     Qt::KeyboardModifier qtModifier;
0019 };
0020 
0021 std::optional<QKeySequence> XdgShortcut::parse(const QString &shortcutString)
0022 {
0023     static const QHash<QString, Modifier> allowedModifiers = {
0024         {"SHIFT", {XKB_MOD_NAME_SHIFT, Qt::ShiftModifier}},
0025         {"CAPS", {XKB_MOD_NAME_CAPS, Qt::GroupSwitchModifier}},
0026         {"CTRL", {XKB_MOD_NAME_CTRL, Qt::ControlModifier}},
0027         {"ALT", {XKB_MOD_NAME_ALT, Qt::AltModifier}},
0028         {"NUM", {XKB_MOD_NAME_NUM, Qt::KeypadModifier}},
0029         {"LOGO", {XKB_MOD_NAME_LOGO, Qt::MetaModifier}},
0030     };
0031 
0032     xkb_keysym_t identifier = XKB_KEY_NoSymbol;
0033     QStringList modifiers;
0034 
0035     QStringView remaining(shortcutString);
0036     while (!remaining.isEmpty()) {
0037         auto nextPlus = remaining.indexOf(QChar('+'));
0038         if (nextPlus == 0) { // ++ or ending with +
0039             qWarning() << "empty modifier";
0040             return {};
0041         }
0042 
0043         if (nextPlus < 0) { // just the identifier left
0044             // The spec says that the string ends when all the spec'ed characters are over
0045             // Meaning "CTRL+a;Banana" would be an acceptable and parseable string
0046             static QRegularExpression rx(QStringLiteral("^([\\w\\d_]+).*$"));
0047             Q_ASSERT(rx.isValid());
0048             rx.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
0049             QRegularExpressionMatch match = rx.match(remaining);
0050             Q_ASSERT(match.isValid());
0051 
0052             identifier = xkb_keysym_from_name(match.capturedView(1).toUtf8().constData(), XKB_KEYSYM_CASE_INSENSITIVE);
0053             if (identifier == XKB_KEY_NoSymbol) {
0054                 qWarning() << "unknown key" << match.capturedView(1);
0055                 return false;
0056             }
0057             break;
0058         } else { // A modifier
0059             auto modifier = remaining.left(nextPlus);
0060             if (!allowedModifiers.contains(modifier.toString())) {
0061                 qWarning() << "Unknown modifier" << modifier;
0062                 return {};
0063             }
0064 
0065             modifiers << modifier.toString();
0066             remaining = remaining.mid(nextPlus + 1);
0067         }
0068     }
0069 
0070     int keys = 0;
0071     for (const QString &modifier : std::as_const(modifiers)) {
0072         keys |= allowedModifiers[modifier].qtModifier;
0073     }
0074 
0075     keys |= QXkbCommon::keysymToQtKey(identifier, Qt::NoModifier, nullptr, XKB_KEYCODE_INVALID);
0076     return QKeySequence(keys);
0077 }