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