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