File indexing completed on 2024-07-21 13:06:26

0001 /*
0002    SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-X11.h>
0008 
0009 #include "input.h"
0010 #include "khotkeysglobal.h"
0011 #include "shortcuts_handler.h"
0012 #include "windows_handler.h"
0013 
0014 // #include <X11/Xutil.h>
0015 #include <QX11Info>
0016 
0017 #include <KGlobalAccel>
0018 #include <KLocalizedString>
0019 #include <QDebug>
0020 #include <QKeySequence>
0021 
0022 #include <QAction>
0023 #include <QUuid>
0024 #include <kkeyserver.h>
0025 
0026 namespace KHotKeys
0027 {
0028 ShortcutsHandler::ShortcutsHandler(HandlerType type, QObject *parent)
0029     : QObject(parent)
0030     , _type(type)
0031     , _actions(new KActionCollection(this, QStringLiteral("khotkeys")))
0032 {
0033     _actions->setComponentDisplayName(i18n("Custom Shortcuts Service"));
0034     connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &ShortcutsHandler::shortcutChanged);
0035 }
0036 
0037 ShortcutsHandler::~ShortcutsHandler()
0038 {
0039     _actions->clear();
0040     delete _actions;
0041 }
0042 
0043 QAction *ShortcutsHandler::addAction(const QString &id, const QString &text, const QKeySequence &shortcut)
0044 {
0045 #ifdef KHOTKEYS_TRACE
0046     qDebug() << id << text << shortcut;
0047 #endif
0048     QString realId(id);
0049     // HACK: Do this correctly. Remove uuid on importing / exporting
0050     // On import it can happen that id is already taken. Create it under a
0051     // different name then.
0052     if (_actions->action(id)) {
0053         qDebug() << id << " already present. Using new id!";
0054         realId = QUuid::createUuid().toString();
0055     }
0056 
0057     // Create the action
0058     QAction *newAction = _actions->addAction(realId);
0059     if (!newAction) {
0060         return nullptr;
0061     }
0062     // If our HandlerType is configuration we have to tell kdedglobalaccel
0063     // that this action is only for configuration purposes.
0064     // see KAction::~KAction
0065     if (_type == Configuration) {
0066         newAction->setProperty("isConfigurationAction", QVariant(true));
0067     }
0068     newAction->setText(text);
0069     KGlobalAccel::self()->setShortcut(newAction, QList<QKeySequence>() << shortcut);
0070     // Enable global shortcut. If that fails there is no sense in proceeding
0071     if (!KGlobalAccel::self()->hasShortcut(newAction)) {
0072         qWarning() << "Failed to enable global shortcut for '" << text << "' " << id;
0073         _actions->removeAction(newAction);
0074         return nullptr;
0075     }
0076     Q_ASSERT(newAction->isEnabled());
0077 
0078     return newAction;
0079 }
0080 
0081 QAction *ShortcutsHandler::getAction(const QString &id)
0082 {
0083     return _actions->action(id);
0084 }
0085 
0086 bool ShortcutsHandler::removeAction(const QString &id)
0087 {
0088     QAction *action = getAction(id);
0089     if (!action) {
0090         return false;
0091     } else {
0092         // This will delete the action.
0093         _actions->removeAction(action);
0094 
0095         return true;
0096     }
0097 }
0098 
0099 #ifdef HAVE_XTEST
0100 
0101 } // namespace KHotKeys
0102 #include <X11/extensions/XTest.h>
0103 namespace KHotKeys
0104 {
0105 static bool xtest_available = false;
0106 static bool xtest_inited = false;
0107 static bool xtest()
0108 {
0109     if (xtest_inited)
0110         return xtest_available;
0111     xtest_inited = true;
0112     int dummy1, dummy2, dummy3, dummy4;
0113     xtest_available = (XTestQueryExtension(QX11Info::display(), &dummy1, &dummy2, &dummy3, &dummy4) == True);
0114     return xtest_available;
0115 }
0116 
0117 static void get_modifier_change(int x_mod_needed, QVector<int> &to_press, QVector<int> &to_release)
0118 {
0119     // Get state of all keys
0120     char keymap[32];
0121     XQueryKeymap(QX11Info::display(), keymap);
0122 
0123     // From KKeyServer's initializeMods()
0124     XModifierKeymap *xmk = XGetModifierMapping(QX11Info::display());
0125 
0126     for (int modidx = 0; modidx < 8; ++modidx) {
0127         bool mod_needed = x_mod_needed & (1 << modidx);
0128         for (int kcidx = 0; kcidx < xmk->max_keypermod; ++kcidx) {
0129             int keycode = xmk->modifiermap[modidx * xmk->max_keypermod + kcidx];
0130             if (!keycode)
0131                 continue;
0132 
0133             bool mod_pressed = keymap[keycode / 8] & (1 << (keycode % 8));
0134             if (mod_needed) {
0135                 mod_needed = false;
0136                 if (!mod_pressed)
0137                     to_press.push_back(keycode);
0138             } else if (mod_pressed)
0139                 to_release.push_back(keycode);
0140         }
0141     }
0142 
0143     XFreeModifiermap(xmk);
0144 }
0145 
0146 #endif
0147 
0148 bool ShortcutsHandler::send_macro_key(const QKeySequence &key, Window window_P)
0149 {
0150     if (key.isEmpty())
0151         return false;
0152 
0153     unsigned int keysym = key[0];
0154     int x_keycode;
0155     KKeyServer::keyQtToCodeX(keysym, &x_keycode);
0156 
0157     if (x_keycode == NoSymbol)
0158         return false;
0159 
0160     unsigned int x_mod;
0161     KKeyServer::keyQtToModX(keysym, &x_mod);
0162 #ifdef HAVE_XTEST
0163     if (xtest() && (window_P == None || window_P == InputFocus)) {
0164         QVector<int> keycodes_to_press, keycodes_to_release;
0165         get_modifier_change(x_mod, keycodes_to_press, keycodes_to_release);
0166 
0167         for (int kc : qAsConst(keycodes_to_release))
0168             XTestFakeKeyEvent(QX11Info::display(), kc, False, CurrentTime);
0169 
0170         for (int kc : qAsConst(keycodes_to_press))
0171             XTestFakeKeyEvent(QX11Info::display(), kc, True, CurrentTime);
0172 
0173         bool ret = XTestFakeKeyEvent(QX11Info::display(), x_keycode, True, CurrentTime);
0174         ret = ret && XTestFakeKeyEvent(QX11Info::display(), x_keycode, False, CurrentTime);
0175 
0176         for (int kc : qAsConst(keycodes_to_press))
0177             XTestFakeKeyEvent(QX11Info::display(), kc, False, CurrentTime);
0178 
0179         for (int kc : qAsConst(keycodes_to_release))
0180             XTestFakeKeyEvent(QX11Info::display(), kc, True, CurrentTime);
0181 
0182         return ret;
0183     }
0184 #endif
0185     if (window_P == None || window_P == InputFocus)
0186         window_P = windows_handler->active_window();
0187     if (window_P == None) // CHECKME tohle cele je ponekud ...
0188         window_P = InputFocus;
0189     XEvent ev;
0190     ev.type = KeyPress;
0191     ev.xkey.display = QX11Info::display();
0192     ev.xkey.window = window_P;
0193     ev.xkey.root = QX11Info::appRootWindow(); // I don't know whether these have to be set
0194     ev.xkey.subwindow = None; // to these values, but it seems to work, hmm
0195     ev.xkey.time = CurrentTime;
0196     ev.xkey.x = 0;
0197     ev.xkey.y = 0;
0198     ev.xkey.x_root = 0;
0199     ev.xkey.y_root = 0;
0200     ev.xkey.keycode = x_keycode;
0201     ev.xkey.state = x_mod;
0202     ev.xkey.same_screen = True;
0203     bool ret = XSendEvent(QX11Info::display(), window_P, True, KeyPressMask, &ev);
0204 #if 1
0205     ev.type = KeyRelease; // is this actually really needed ??
0206     ev.xkey.display = QX11Info::display();
0207     ev.xkey.window = window_P;
0208     ev.xkey.root = QX11Info::appRootWindow();
0209     ev.xkey.subwindow = None;
0210     ev.xkey.time = CurrentTime;
0211     ev.xkey.x = 0;
0212     ev.xkey.y = 0;
0213     ev.xkey.x_root = 0;
0214     ev.xkey.y_root = 0;
0215     ev.xkey.state = x_mod;
0216     ev.xkey.keycode = x_keycode;
0217     ev.xkey.same_screen = True;
0218     ret = ret && XSendEvent(QX11Info::display(), window_P, True, KeyReleaseMask, &ev);
0219 #endif
0220     // Qt's autorepeat compression is broken and can create "aab" from "aba"
0221     // XSync() should create delay longer than Qt's max autorepeat interval
0222     XSync(QX11Info::display(), False);
0223     return ret;
0224 }
0225 
0226 bool Mouse::send_mouse_button(int button_P, bool release_P)
0227 {
0228 #ifdef HAVE_XTEST
0229     if (xtest()) {
0230         // CHECKME tohle jeste potrebuje modifikatory
0231         // a asi i spravnou timestamp misto CurrentTime
0232         bool ret = XTestFakeButtonEvent(QX11Info::display(), button_P, True, CurrentTime);
0233         if (release_P)
0234             ret = ret && XTestFakeButtonEvent(QX11Info::display(), button_P, False, CurrentTime);
0235         return ret;
0236     }
0237 #endif
0238     return false;
0239 }
0240 
0241 } // namespace KHotKeys
0242 
0243 #include "moc_shortcuts_handler.cpp"