1 /* Copyright 2008 Edwin Stang (edwinstang@gmail.com),
2 *
3 * This file is part of JXGrabKey.
4 *
5 * JXGrabKey is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * JXGrabKey is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with JXGrabKey. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define SLEEP_TIME_MS 100
20 #define MICROSECOND_TO_MILLISECOND_MULTIPLICATOR 1000
21
22 #include "JXGrabKey.h"
23
24 #include <X11/Xlib.h>
25 #include <X11/keysym.h>
26 #include <X11/XKBlib.h>
27
28 #include <unistd.h>
29 #include <pthread.h>
30 #include <streambuf>
31 #include <iostream>
32 #include <sstream>
33 #include <vector>
34
35 using namespace std;
36
37 Display *dpy = NULL;
38 vector<KeyStruct> keys;
39 bool debug = false;
40 bool isListening = false;
41 bool errorInListen = false;
42 bool doListen = true;
43 bool registerHotkeyIsWaitingForException = false;
44 bool registerHotkeyHasException = false;
45
46 pthread_spinlock_t x_lock;
47
48 unsigned int numlock_mask = 0;
49 unsigned int scrolllock_mask = 0;
50 unsigned int capslock_mask = 0;
51
Java_jxgrabkey_JXGrabKey_clean(JNIEnv * _env,jobject _obj)52 JNIEXPORT void JNICALL Java_jxgrabkey_JXGrabKey_clean(JNIEnv *_env,
53 jobject _obj) {
54
55 while (!isListening && !errorInListen) {
56 if (debug) {
57 ostringstream sout;
58 sout << "clean() - sleeping " << std::dec << SLEEP_TIME_MS
59 << " ms for listen() to be ready";
60 printToDebugCallback(_env, sout.str().c_str());
61 }
62 usleep(SLEEP_TIME_MS * MICROSECOND_TO_MILLISECOND_MULTIPLICATOR);
63 }
64 if (errorInListen) {
65 if (debug) {
66 ostringstream sout;
67 sout << "clean() - WARNING: aborting because of error in listen()";
68 printToDebugCallback(_env, sout.str().c_str());
69 }
70 return;
71 }
72
73 pthread_spin_lock(&x_lock);
74 for (int i = 0; i < keys.size(); i++) {
75 Java_jxgrabkey_JXGrabKey_unregisterHotKey(_env, _obj, keys.at(i).id);
76 }
77 pthread_spin_unlock(&x_lock);
78
79 doListen = false;
80 }
81
Java_jxgrabkey_JXGrabKey_registerHotkey__III(JNIEnv * _env,jobject _obj,jint _id,jint _mask,jint _key)82 JNIEXPORT void JNICALL Java_jxgrabkey_JXGrabKey_registerHotkey__III(
83 JNIEnv *_env, jobject _obj, jint _id, jint _mask, jint _key) {
84
85 if (debug) {
86 ostringstream sout;
87 sout << "++ registerHotkey(" << std::dec << _id << ", 0x" << std::hex
88 << _mask << ", 0x" << std::hex << _key << ")";
89 printToDebugCallback(_env, sout.str().c_str());
90 }
91
92 while (!isListening && !errorInListen) {
93 if (debug) {
94 ostringstream sout;
95 sout << "registerHotkey() - sleeping " << std::dec << SLEEP_TIME_MS
96 << " ms for listen() to be ready";
97 printToDebugCallback(_env, sout.str().c_str());
98 }
99 usleep(SLEEP_TIME_MS * MICROSECOND_TO_MILLISECOND_MULTIPLICATOR);
100 }
101
102 if (errorInListen) {
103 if (debug) {
104 ostringstream sout;
105 sout
106 << "registerHotkey() - WARNING: aborting because of error in listen(): errorInListen = "
107 << std::dec << errorInListen;
108 printToDebugCallback(_env, sout.str().c_str());
109 }
110 return;
111 }
112
113 pthread_spin_lock(&x_lock);
114 for (int i = 0; i < keys.size(); i++) {
115 if (keys[i].id == _id) {
116 Java_jxgrabkey_JXGrabKey_unregisterHotKey(_env, _obj, _id);
117 break;
118 }
119 }
120
121 struct KeyStruct key;
122 key.id = _id;
123 key.key = XKeysymToKeycode(dpy, _key);
124 key.mask = _mask;
125 keys.push_back(key);
126 pthread_spin_unlock(&x_lock);
127
128 if (debug) {
129 ostringstream sout;
130
131 sout << "registerHotkey() - found in X11 mask (0x" << std::hex
132 << key.mask << "): ";
133
134 if (key.mask & ShiftMask) {
135 sout << "'Shift' ";
136 }
137 if (key.mask & LockMask) {
138 sout << "'Lock' ";
139 }
140 if (key.mask & ControlMask) {
141 sout << "'Control' ";
142 }
143 if (key.mask & Mod1Mask) {
144 sout << "'Mod1' ";
145 }
146 if (key.mask & Mod2Mask) {
147 sout << "'Mod2' ";
148 }
149 if (key.mask & Mod3Mask) {
150 sout << "'Mod3' ";
151 }
152 if (key.mask & Mod4Mask) {
153 sout << "'Mod4' ";
154 }
155 if (key.mask & Mod5Mask) {
156 sout << "'Mod5' ";
157 }
158
159 sout << endl;
160 sout << "registerHotkey() - converted X11 keysym '" << XKeysymToString(
161 _key) << "' (0x" << std::hex << _key << ") to X11 key (0x"
162 << std::hex << (int) key.key << ")";
163
164 printToDebugCallback(_env, sout.str().c_str());
165 }
166
167 grabKey(_env, key);
168
169 //Flush X to receive errors
170 registerHotkeyIsWaitingForException = true;
171 XSync(dpy, false);
172 if (registerHotkeyHasException) {
173 ostringstream message;
174 message << "Unable to register hotkey with id = " << _id
175 << ". Each hotkey combination can only be grabbed by one application at a time!";
176
177 jclass cls = _env->FindClass("jxgrabkey/HotkeyConflictException");
178 if (cls != NULL) {
179 _env->ThrowNew(cls, message.str().c_str());
180 //Don't print anything to console until function return,
181 //or else java will not register the exception!
182 } else {
183 if (debug) {
184 ostringstream sout;
185 sout
186 << "registerHotkey() - WARNING: Unable to throw HotkeyConflictException, class not found!";
187 sout << " - " << message.str().c_str();
188 printToDebugCallback(NULL, sout.str().c_str());
189 }
190 }
191 }
192 registerHotkeyIsWaitingForException = false;
193 if (registerHotkeyHasException) {
194 registerHotkeyHasException = false;
195 return;
196 }
197
198 if (debug) {
199 ostringstream sout;
200 sout << "-- registerHotkey()";
201 printToDebugCallback(_env, sout.str().c_str());
202 }
203 }
204
Java_jxgrabkey_JXGrabKey_unregisterHotKey(JNIEnv * _env,jobject _obj,jint _id)205 JNIEXPORT void JNICALL Java_jxgrabkey_JXGrabKey_unregisterHotKey(JNIEnv *_env,
206 jobject _obj, jint _id) {
207
208 if (debug) {
209 ostringstream sout;
210 sout << "++ unregisterHotkey(" << std::dec << _id << ")";
211 printToDebugCallback(_env, sout.str().c_str());
212 }
213
214 while (!isListening && !errorInListen) {
215 if (debug) {
216 ostringstream sout;
217 sout << "unregisterHotkey() - sleeping " << std::dec
218 << SLEEP_TIME_MS << " ms for listen() to be ready";
219 printToDebugCallback(_env, sout.str().c_str());
220 }
221 usleep(SLEEP_TIME_MS * MICROSECOND_TO_MILLISECOND_MULTIPLICATOR);
222 }
223 if (errorInListen) {
224 if (debug) {
225 ostringstream sout;
226 sout
227 << "unregisterHotkey() - WARNING: aborting because of error in listen()";
228 printToDebugCallback(_env, sout.str().c_str());
229 }
230 return;
231 }
232
233 pthread_spin_lock(&x_lock);
234 for (int i = 0; i < keys.size(); i++) {
235 if (keys.at(i).id == _id) {
236 ungrabKey(_env, keys.at(i));
237 keys.erase(keys.begin() + i);
238 break;
239 }
240 }
241 pthread_spin_unlock(&x_lock);
242
243 if (debug) {
244 ostringstream sout;
245 sout << "-- unregisterHotkey()";
246 printToDebugCallback(_env, sout.str().c_str());
247 }
248 }
249
Java_jxgrabkey_JXGrabKey_listen(JNIEnv * _env,jobject _obj)250 JNIEXPORT void JNICALL Java_jxgrabkey_JXGrabKey_listen(JNIEnv *_env,
251 jobject _obj) {
252
253 if (debug) {
254 ostringstream sout;
255 sout << "++ listen()";
256 printToDebugCallback(_env, sout.str().c_str());
257 }
258
259 if (isListening) {
260 if (debug) {
261 ostringstream sout;
262 sout << "listen() - WARNING: already listening, aborting";
263 printToDebugCallback(_env, sout.str().c_str());
264 }
265 return;
266 }
267
268 jclass cls = _env->FindClass("jxgrabkey/JXGrabKey");
269 if (cls == NULL) {
270 if (debug) {
271 ostringstream sout;
272 sout << "listen() - ERROR: cannot find class jxgrabkey.JXGrabKey";
273 printToDebugCallback(_env, sout.str().c_str());
274 }
275 errorInListen = true;
276 return;
277 }
278
279 jmethodID mid = _env->GetStaticMethodID(cls, "fireKeyEvent", "(I)V");
280 if (mid == NULL) {
281 if (debug) {
282 ostringstream sout;
283 sout << "listen() - ERROR: cannot find method fireKeyEvent(int)";
284 printToDebugCallback(_env, sout.str().c_str());
285 }
286 errorInListen = true;
287 return;
288 }
289
290 dpy = XOpenDisplay(NULL);
291
292 if (!dpy) {
293 if (debug) {
294 ostringstream sout;
295 sout << "listen() - ERROR: cannot open display " << XDisplayName(
296 NULL);
297 printToDebugCallback(_env, sout.str().c_str());
298 }
299 errorInListen = true;
300 return;
301 }
302
303 getOffendingModifiers(dpy);
304
305 int ret = XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
306 if (debug && !ret) {
307 ostringstream sout;
308 sout << "listen() - WARNING: XAllowEvents() returned false";
309 printToDebugCallback(_env, sout.str().c_str());
310 }
311
312 for (int screen = 0; screen < ScreenCount(dpy); screen++) {
313 ret = XSelectInput(dpy, RootWindow(dpy, screen), KeyPressMask);
314 if (debug && !ret) {
315 ostringstream sout;
316 sout << "listen() - WARNING: XSelectInput() on screen " << std::dec
317 << screen << " returned false";
318 printToDebugCallback(_env, sout.str().c_str());
319 }
320 }
321
322 XSetErrorHandler((XErrorHandler) xErrorHandler);
323 pthread_spin_init(&x_lock, PTHREAD_PROCESS_SHARED); // init here bcoz of the returns
324
325 doListen = true;
326 isListening = true;
327
328 XEvent ev;
329
330 while (doListen) {
331
332 while (!XPending(dpy) && doListen) { //Don't block on XNextEvent(), this breaks XGrabKey()!
333 usleep(SLEEP_TIME_MS * MICROSECOND_TO_MILLISECOND_MULTIPLICATOR);
334 }
335
336 if (doListen) {
337 XNextEvent(dpy, &ev);
338
339 if (ev.type == KeyPress) {
340 pthread_spin_lock(&x_lock);
341 for (int i = 0; i < keys.size(); i++) {
342 ev.xkey.state &= ~(numlock_mask | capslock_mask
343 | scrolllock_mask); //Filter offending modifiers
344 if (ev.xkey.keycode == keys.at(i).key && ev.xkey.state
345 == keys.at(i).mask) {
346 if (debug) {
347 ostringstream sout;
348 sout << "listen() - received: id = " << std::dec
349 << keys.at(i).id
350 << "; type = KeyPress; x11Keycode = '"
351 << XKeysymToString(XkbKeycodeToKeysym(dpy,
352 ev.xkey.keycode, 0, 0)) << "' (0x"
353 << std::hex << ev.xkey.keycode
354 << "); x11Mask = 0x" << std::hex
355 << ev.xkey.state << endl;
356 printToDebugCallback(_env, sout.str().c_str());
357 }
358 _env->CallStaticVoidMethod(cls, mid, keys.at(i).id);
359 break;
360 }
361 }
362 pthread_spin_unlock(&x_lock);
363 }
364 }
365 }
366
367 isListening = false;
368
369 pthread_spin_destroy(&x_lock);
370 XCloseDisplay(dpy);
371 }
372
Java_jxgrabkey_JXGrabKey_setDebug(JNIEnv * _env,jobject _obj,jboolean _debug)373 JNIEXPORT void JNICALL Java_jxgrabkey_JXGrabKey_setDebug(JNIEnv *_env,
374 jobject _obj, jboolean _debug) {
375 debug = _debug;
376 }
377
getOffendingModifiers(Display * _dpy)378 void getOffendingModifiers(Display* _dpy) {
379
380 //Took this code from project xbindkeys
381 int i;
382 XModifierKeymap *modmap;
383 KeyCode nlock, slock;
384 static int mask_table[8] = { ShiftMask, LockMask, ControlMask, Mod1Mask,
385 Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask };
386
387 nlock = XKeysymToKeycode(_dpy, XK_Num_Lock);
388 slock = XKeysymToKeycode(_dpy, XK_Scroll_Lock);
389
390 modmap = XGetModifierMapping(_dpy);
391
392 if (modmap != NULL && modmap->max_keypermod > 0) {
393 for (i = 0; i < 8 * modmap->max_keypermod; i++) {
394 if (modmap->modifiermap[i] == nlock && nlock != 0)
395 numlock_mask = mask_table[i / modmap->max_keypermod];
396 else if (modmap->modifiermap[i] == slock && slock != 0)
397 scrolllock_mask = mask_table[i / modmap->max_keypermod];
398 }
399 }
400
401 capslock_mask = LockMask;
402
403 if (modmap)
404 XFreeModifiermap(modmap);
405 }
406
printToDebugCallback(JNIEnv * _env,const char * message)407 void printToDebugCallback(JNIEnv *_env, const char* message) {
408 if (debug) {
409 static JNIEnv *env = _env;
410 if (env != NULL) {
411 static jclass cls = env->FindClass("jxgrabkey/JXGrabKey");
412 static jmethodID mid = env->GetStaticMethodID(cls, "debugCallback",
413 "(Ljava/lang/String;)V");
414
415 if (mid != NULL) {
416 env->CallStaticVoidMethod(cls, mid, env->NewStringUTF(message));
417 } else {
418 cout << "JAVA DEBUG CALLBACK NOT FOUND - " << message << endl;
419 fflush(stdout);
420 }
421 } else {
422 cout << "JAVA DEBUG CALLBACK NOT INITIALIZED - " << message << endl;
423 fflush(stdout);
424 }
425 }
426 }
427
xErrorHandler(Display * _dpy,XErrorEvent * _event)428 static int *xErrorHandler(Display *_dpy, XErrorEvent *_event) {
429 if (registerHotkeyIsWaitingForException) {
430 //The exception has to be thrown in registerHotkey,
431 //coz this functions runs in a different thread!
432 registerHotkeyHasException = true;
433 }
434
435 if (debug) {
436 ostringstream sout;
437 sout << "xErrorHandler() - Caught error: serial = " << std::dec
438 << _event->serial;
439 sout << "; resourceid = " << std::dec << _event->resourceid;
440 sout << "; type = " << std::dec << _event->type;
441 sout << "; error_code = " << std::dec << (int) _event->error_code;
442 sout << "; request_code = " << std::dec << (int) _event->request_code;
443 sout << "; minor_code = " << std::dec << (int) _event->minor_code;
444 printToDebugCallback(NULL, sout.str().c_str());
445 }
446
447 return NULL;
448 }
449
grabKey(JNIEnv * _env,KeyStruct key)450 void grabKey(JNIEnv *_env, KeyStruct key) {
451 Mask modifierCombinations[] = { key.mask, key.mask | numlock_mask, key.mask
452 | scrolllock_mask, key.mask | capslock_mask, key.mask
453 | numlock_mask | scrolllock_mask, key.mask | numlock_mask
454 | capslock_mask, key.mask | scrolllock_mask | capslock_mask,
455 key.mask | numlock_mask | scrolllock_mask | capslock_mask };
456
457 for (int screen = 0; screen < ScreenCount(dpy); screen++) {
458 for (int m = 0; m < 8; m++) {
459 int ret =
460 XGrabKey(dpy, key.key, modifierCombinations[m],
461 RootWindow(dpy, screen), True, GrabModeAsync,
462 GrabModeAsync);
463 if (debug && !ret) {
464 ostringstream sout;
465 sout << "grabKey() - WARNING: XGrabKey() on screen "
466 << std::dec << screen << " for mask combination "
467 << std::dec << m << " returned false";
468 printToDebugCallback(_env, sout.str().c_str());
469 }
470 }
471 }
472 }
473
ungrabKey(JNIEnv * _env,KeyStruct key)474 void ungrabKey(JNIEnv *_env, KeyStruct key) {
475 Mask modifierCombinations[] = { key.mask, key.mask | numlock_mask, key.mask
476 | scrolllock_mask, key.mask | capslock_mask, key.mask
477 | numlock_mask | scrolllock_mask, key.mask | numlock_mask
478 | capslock_mask, key.mask | scrolllock_mask | capslock_mask,
479 key.mask | numlock_mask | scrolllock_mask | capslock_mask };
480
481 for (int screen = 0; screen < ScreenCount(dpy); screen++) {
482 for (int m = 0; m < 8; m++) {
483 int ret = XUngrabKey(dpy, key.key, modifierCombinations[m],
484 RootWindow(dpy, screen));
485 if (debug && !ret) {
486 ostringstream sout;
487 sout << "ungrabKey() - WARNING: XUngrabKey() on screen "
488 << std::dec << screen << " for mask combination "
489 << std::dec << m << " returned false";
490 printToDebugCallback(_env, sout.str().c_str());
491 }
492 }
493 }
494 }
495