File indexing completed on 2024-04-28 05:36:54

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