1 /*
2    SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
3 
4    SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include <config-X11.h>
8 
9 #include "input.h"
10 #include "khotkeysglobal.h"
11 #include "shortcuts_handler.h"
12 #include "windows_handler.h"
13 
14 // #include <X11/Xutil.h>
15 #include <QX11Info>
16 
17 #include <KGlobalAccel>
18 #include <KLocalizedString>
19 #include <QDebug>
20 #include <QKeySequence>
21 
22 #include <QAction>
23 #include <QUuid>
24 #include <kkeyserver.h>
25 
26 namespace KHotKeys
27 {
ShortcutsHandler(HandlerType type,QObject * parent)28 ShortcutsHandler::ShortcutsHandler(HandlerType type, QObject *parent)
29     : QObject(parent)
30     , _type(type)
31     , _actions(new KActionCollection(this, QStringLiteral("khotkeys")))
32 {
33     _actions->setComponentDisplayName(i18n("Custom Shortcuts Service"));
34     connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &ShortcutsHandler::shortcutChanged);
35 }
36 
~ShortcutsHandler()37 ShortcutsHandler::~ShortcutsHandler()
38 {
39     _actions->clear();
40     delete _actions;
41 }
42 
addAction(const QString & id,const QString & text,const QKeySequence & shortcut)43 QAction *ShortcutsHandler::addAction(const QString &id, const QString &text, const QKeySequence &shortcut)
44 {
45 #ifdef KHOTKEYS_TRACE
46     qDebug() << id << text << shortcut;
47 #endif
48     QString realId(id);
49     // HACK: Do this correctly. Remove uuid on importing / exporting
50     // On import it can happen that id is already taken. Create it under a
51     // different name then.
52     if (_actions->action(id)) {
53         qDebug() << id << " already present. Using new id!";
54         realId = QUuid::createUuid().toString();
55     }
56 
57     // Create the action
58     QAction *newAction = _actions->addAction(realId);
59     if (!newAction) {
60         return nullptr;
61     }
62     // If our HandlerType is configuration we have to tell kdedglobalaccel
63     // that this action is only for configuration purposes.
64     // see KAction::~KAction
65     if (_type == Configuration) {
66         newAction->setProperty("isConfigurationAction", QVariant(true));
67     }
68     newAction->setText(text);
69     KGlobalAccel::self()->setShortcut(newAction, QList<QKeySequence>() << shortcut);
70     // Enable global shortcut. If that fails there is no sense in proceeding
71     if (!KGlobalAccel::self()->hasShortcut(newAction)) {
72         qWarning() << "Failed to enable global shortcut for '" << text << "' " << id;
73         _actions->removeAction(newAction);
74         return nullptr;
75     }
76     Q_ASSERT(newAction->isEnabled());
77 
78     return newAction;
79 }
80 
getAction(const QString & id)81 QAction *ShortcutsHandler::getAction(const QString &id)
82 {
83     return _actions->action(id);
84 }
85 
removeAction(const QString & id)86 bool ShortcutsHandler::removeAction(const QString &id)
87 {
88     QAction *action = getAction(id);
89     if (!action) {
90         return false;
91     } else {
92         // This will delete the action.
93         _actions->removeAction(action);
94 
95         return true;
96     }
97 }
98 
99 #ifdef HAVE_XTEST
100 
101 } // namespace KHotKeys
102 #include <X11/extensions/XTest.h>
103 namespace KHotKeys
104 {
105 static bool xtest_available = false;
106 static bool xtest_inited = false;
xtest()107 static bool xtest()
108 {
109     if (xtest_inited)
110         return xtest_available;
111     xtest_inited = true;
112     int dummy1, dummy2, dummy3, dummy4;
113     xtest_available = (XTestQueryExtension(QX11Info::display(), &dummy1, &dummy2, &dummy3, &dummy4) == True);
114     return xtest_available;
115 }
116 
get_modifier_change(int x_mod_needed,QVector<int> & to_press,QVector<int> & to_release)117 static void get_modifier_change(int x_mod_needed, QVector<int> &to_press, QVector<int> &to_release)
118 {
119     // Get state of all keys
120     char keymap[32];
121     XQueryKeymap(QX11Info::display(), keymap);
122 
123     // From KKeyServer's initializeMods()
124     XModifierKeymap *xmk = XGetModifierMapping(QX11Info::display());
125 
126     for (int modidx = 0; modidx < 8; ++modidx) {
127         bool mod_needed = x_mod_needed & (1 << modidx);
128         for (int kcidx = 0; kcidx < xmk->max_keypermod; ++kcidx) {
129             int keycode = xmk->modifiermap[modidx * xmk->max_keypermod + kcidx];
130             if (!keycode)
131                 continue;
132 
133             bool mod_pressed = keymap[keycode / 8] & (1 << (keycode % 8));
134             if (mod_needed) {
135                 mod_needed = false;
136                 if (!mod_pressed)
137                     to_press.push_back(keycode);
138             } else if (mod_pressed)
139                 to_release.push_back(keycode);
140         }
141     }
142 
143     XFreeModifiermap(xmk);
144 }
145 
146 #endif
147 
send_macro_key(const QKeySequence & key,Window window_P)148 bool ShortcutsHandler::send_macro_key(const QKeySequence &key, Window window_P)
149 {
150     if (key.isEmpty())
151         return false;
152 
153     unsigned int keysym = key[0];
154     int x_keycode;
155     KKeyServer::keyQtToCodeX(keysym, &x_keycode);
156 
157     if (x_keycode == NoSymbol)
158         return false;
159 
160     unsigned int x_mod;
161     KKeyServer::keyQtToModX(keysym, &x_mod);
162 #ifdef HAVE_XTEST
163     if (xtest() && (window_P == None || window_P == InputFocus)) {
164         QVector<int> keycodes_to_press, keycodes_to_release;
165         get_modifier_change(x_mod, keycodes_to_press, keycodes_to_release);
166 
167         for (int kc : qAsConst(keycodes_to_release))
168             XTestFakeKeyEvent(QX11Info::display(), kc, False, CurrentTime);
169 
170         for (int kc : qAsConst(keycodes_to_press))
171             XTestFakeKeyEvent(QX11Info::display(), kc, True, CurrentTime);
172 
173         bool ret = XTestFakeKeyEvent(QX11Info::display(), x_keycode, True, CurrentTime);
174         ret = ret && XTestFakeKeyEvent(QX11Info::display(), x_keycode, False, CurrentTime);
175 
176         for (int kc : qAsConst(keycodes_to_press))
177             XTestFakeKeyEvent(QX11Info::display(), kc, False, CurrentTime);
178 
179         for (int kc : qAsConst(keycodes_to_release))
180             XTestFakeKeyEvent(QX11Info::display(), kc, True, CurrentTime);
181 
182         return ret;
183     }
184 #endif
185     if (window_P == None || window_P == InputFocus)
186         window_P = windows_handler->active_window();
187     if (window_P == None) // CHECKME tohle cele je ponekud ...
188         window_P = InputFocus;
189     XEvent ev;
190     ev.type = KeyPress;
191     ev.xkey.display = QX11Info::display();
192     ev.xkey.window = window_P;
193     ev.xkey.root = QX11Info::appRootWindow(); // I don't know whether these have to be set
194     ev.xkey.subwindow = None; // to these values, but it seems to work, hmm
195     ev.xkey.time = CurrentTime;
196     ev.xkey.x = 0;
197     ev.xkey.y = 0;
198     ev.xkey.x_root = 0;
199     ev.xkey.y_root = 0;
200     ev.xkey.keycode = x_keycode;
201     ev.xkey.state = x_mod;
202     ev.xkey.same_screen = True;
203     bool ret = XSendEvent(QX11Info::display(), window_P, True, KeyPressMask, &ev);
204 #if 1
205     ev.type = KeyRelease; // is this actually really needed ??
206     ev.xkey.display = QX11Info::display();
207     ev.xkey.window = window_P;
208     ev.xkey.root = QX11Info::appRootWindow();
209     ev.xkey.subwindow = None;
210     ev.xkey.time = CurrentTime;
211     ev.xkey.x = 0;
212     ev.xkey.y = 0;
213     ev.xkey.x_root = 0;
214     ev.xkey.y_root = 0;
215     ev.xkey.state = x_mod;
216     ev.xkey.keycode = x_keycode;
217     ev.xkey.same_screen = True;
218     ret = ret && XSendEvent(QX11Info::display(), window_P, True, KeyReleaseMask, &ev);
219 #endif
220     // Qt's autorepeat compression is broken and can create "aab" from "aba"
221     // XSync() should create delay longer than Qt's max autorepeat interval
222     XSync(QX11Info::display(), False);
223     return ret;
224 }
225 
send_mouse_button(int button_P,bool release_P)226 bool Mouse::send_mouse_button(int button_P, bool release_P)
227 {
228 #ifdef HAVE_XTEST
229     if (xtest()) {
230         // CHECKME tohle jeste potrebuje modifikatory
231         // a asi i spravnou timestamp misto CurrentTime
232         bool ret = XTestFakeButtonEvent(QX11Info::display(), button_P, True, CurrentTime);
233         if (release_P)
234             ret = ret && XTestFakeButtonEvent(QX11Info::display(), button_P, False, CurrentTime);
235         return ret;
236     }
237 #endif
238     return false;
239 }
240 
241 } // namespace KHotKeys
242 
243 #include "moc_shortcuts_handler.cpp"
244