All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.libuiohook.src.x11.post_event.c Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
/* libUIOHook: Cross-platfrom userland keyboard and mouse hooking.
 * Copyright (C) 2006-2015 Alexander Barker.  All Rights Received.
 * https://github.com/kwhat/libuiohook/
 *
 * libUIOHook is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libUIOHook is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

#ifdef HAVE_CONFIG_H
#include 
#endif

#include 
#include 
#include 
#include 
#include 
#include 
#ifdef USE_XTEST
#include 
#endif

#include "input_helper.h"
#include "logger.h"

extern Display *disp;

// This lookup table must be in the same order the masks are defined.
#ifdef USE_XTEST
static KeySym keymask_lookup[8] = {
	XK_Shift_L,
	XK_Control_L,
	XK_Meta_L,
	XK_Alt_L,

	XK_Shift_R,
	XK_Control_R,
	XK_Meta_R,
	XK_Alt_R
};

static unsigned int btnmask_lookup[5] = {
	MASK_BUTTON1,
	MASK_BUTTON2,
	MASK_BUTTON3,
	MASK_BUTTON4,
	MASK_BUTTON5
};
#else
// TODO Possibly relocate to input helper.
static unsigned int convert_to_native_mask(unsigned int mask) {
	unsigned int native_mask = 0x00;

	if (mask & (MASK_SHIFT))	{ native_mask |= ShiftMask;		}
	if (mask & (MASK_CTRL))		{ native_mask |= ControlMask;	}
	if (mask & (MASK_META))		{ native_mask |= Mod4Mask;		}
	if (mask & (MASK_ALT))		{ native_mask |= Mod1Mask;		}

	if (mask & MASK_BUTTON1)	{ native_mask |= Button1Mask;	}
	if (mask & MASK_BUTTON2)	{ native_mask |= Button2Mask;	}
	if (mask & MASK_BUTTON3)	{ native_mask |= Button3Mask;	}
	if (mask & MASK_BUTTON4)	{ native_mask |= Button4Mask;	}
	if (mask & MASK_BUTTON5)	{ native_mask |= Button5Mask;	}

	return native_mask;
}

static inline XKeyEvent * create_key_event() {
	XKeyEvent *event = malloc(sizeof(XKeyEvent));

	event->serial = 0x00;
	event->send_event = False;
	event->display = disp;
	event->time = CurrentTime;
	event->same_screen = True;

	unsigned int mask;
	if (!XQueryPointer(disp, DefaultRootWindow(disp), &(event->root), &(event->subwindow), &(event->x_root), &(event->y_root), &(event->x), &(event->y), &mask)) {
		event->root = DefaultRootWindow(disp);
		event->window = event->root;
		event->subwindow = None;

		event->x_root = 0;
		event->y_root = 0;
		event->x = 0;
		event->y = 0;
	}

	event->type = 0x00;
	event->state = 0x00;
	event->keycode = 0x00;

	return event;
}

static inline XButtonEvent * create_button_event() {
	XButtonEvent *event = malloc(sizeof(XButtonEvent));

	event->serial = 0x00;
	event->send_event = False;
	event->display = disp;
	event->time = CurrentTime;
	event->same_screen = True;

	event->root = DefaultRootWindow(disp);
	event->window = event->root;
	event->subwindow = None;

	event->type = 0x00;
	event->state = 0x00;
	event->x_root = 0;
	event->y_root = 0;
	event->x = 0;
	event->y = 0;
	event->button = 0x00;

	return event;
}

static inline XMotionEvent * create_motion_event() {
	XMotionEvent *event = malloc(sizeof(XMotionEvent));

	event->serial = MotionNotify;
	event->send_event = False;
	event->display = disp;
	event->time = CurrentTime;
	event->same_screen = True;
	event->is_hint = NotifyNormal,
	event->root = DefaultRootWindow(disp);
	event->window = event->root;
	event->subwindow = None;

	event->type = 0x00;
	event->state = 0x00;
	event->x_root = 0;
	event->y_root = 0;
	event->x = 0;
	event->y = 0;

	return event;
}
#endif

UIOHOOK_API void hook_post_event(uiohook_event * const event) {
	#ifdef USE_XTEST
	// XTest does not have modifier support, so we fake it by depressing the
	// appropriate modifier keys.
	for (unsigned int i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) {
		if (event->mask & 1 << i) {
			XTestFakeKeyEvent(disp, XKeysymToKeycode(disp, keymask_lookup[i]), True, 0);
		}
	}

	for (unsigned int i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) {
		if (event->mask & btnmask_lookup[i]) {
			XTestFakeButtonEvent(disp, i + 1, True, 0);
		}
	}

	switch (event->type) {
		case EVENT_KEY_PRESSED:
			XTestFakeKeyEvent(
				disp,
				scancode_to_keycode(event->data.keyboard.keycode),
				True,
				0);
			break;

		case EVENT_KEY_RELEASED:
			XTestFakeKeyEvent(
				disp,
				scancode_to_keycode(event->data.keyboard.keycode),
				False,
				0);
			break;


		case EVENT_MOUSE_PRESSED:
			XTestFakeButtonEvent(disp, event->data.mouse.button, True, 0);
			break;

		case EVENT_MOUSE_RELEASED:
			XTestFakeButtonEvent(disp, event->data.mouse.button, False, 0);
			break;

		case EVENT_MOUSE_WHEEL:
			// Wheel events should be the same as click events on X11.
			// type, amount and rotation
			if (event->data.wheel.rotation < 0) {
				XTestFakeButtonEvent(disp, WheelUp, True, 0);
				XTestFakeButtonEvent(disp, WheelUp, False, 0);
			}
			else {
				XTestFakeButtonEvent(disp, WheelDown, True, 0);
				XTestFakeButtonEvent(disp, WheelDown, False, 0);
			}
			break;


		case EVENT_MOUSE_DRAGGED:
			// The button masks are all applied with the modifier masks.

		case EVENT_MOUSE_MOVED:
			XTestFakeMotionEvent(disp, -1, event->data.mouse.x, event->data.mouse.y, 0);
			break;


		case EVENT_MOUSE_CLICKED:
		case EVENT_KEY_TYPED:
			// Ignore clicked and typed events.

		case EVENT_HOOK_ENABLED:
		case EVENT_HOOK_DISABLED:
			// Ignore hook enabled / disabled events.

		default:
			// Ignore any other garbage.
			logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n",
				__FUNCTION__, __LINE__, event->type);
			break;
	}

	// Release the previously held modifier keys used to fake the event mask.
	for (unsigned int i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) {
		if (event->mask & 1 << i) {
			XTestFakeKeyEvent(disp, XKeysymToKeycode(disp, keymask_lookup[i]), False, 0);
		}
	}

	for (unsigned int i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) {
		if (event->mask & btnmask_lookup[i]) {
			XTestFakeButtonEvent(disp, i + 1, False, 0);
		}
	}
	#else
	XEvent *x_event;

	#if defined(USE_XINERAMA) || defined(USE_XRANDR)
	uint8_t screen_count;
	screen_data *screens;
	#endif

	switch (event->type) {
		case EVENT_KEY_PRESSED:
		case EVENT_KEY_RELEASED:
			// Allocate memory for XKeyEvent and pre-populate.
			x_event = (XEvent *) create_key_event();

			((XKeyEvent *) x_event)->state = convert_to_native_mask(event->mask);
			((XKeyEvent *) x_event)->keycode = XKeysymToKeycode(disp, scancode_to_keycode(event->data.keyboard.keycode));

			if (event->type == EVENT_KEY_PRESSED) {
				((XKeyEvent *) x_event)->type = KeyPress;
				XSendEvent(disp, InputFocus, False, KeyPressMask, x_event);
			}
			else {
				((XKeyEvent *) x_event)->type = KeyRelease;
				XSendEvent(disp, InputFocus, False, KeyReleaseMask, x_event);
			}

			free(x_event);
			break;


		case EVENT_MOUSE_PRESSED:
		case EVENT_MOUSE_RELEASED:
		case EVENT_MOUSE_WHEEL:
			// Allocate memory for XButtonEvent and pre-populate.
			x_event = (XEvent *) create_button_event();

			((XButtonEvent *) x_event)->state = convert_to_native_mask(event->mask);

			((XButtonEvent *) x_event)->x = event->data.mouse.x;
			((XButtonEvent *) x_event)->y = event->data.mouse.y;

			#if defined(USE_XINERAMA) || defined(USE_XRANDR)
			screens = hook_create_screen_info(&screen_count);
			if (screen_count > 1) {
				((XButtonEvent *) x_event)->x += screens[0].x;
				((XButtonEvent *) x_event)->y += screens[0].y;
			}

			if (screens != NULL) {
				free(screens);
			}
			#endif

			// These are the same because Window == Root Window.
			((XButtonEvent *) x_event)->x_root = ((XButtonEvent *) x_event)->x;
			((XButtonEvent *) x_event)->y_root = ((XButtonEvent *) x_event)->y;

			if (event->type == EVENT_MOUSE_WHEEL) {
				((XButtonEvent *) x_event)->type = ButtonPress;

				// type, amount and rotation
				if (event->data.wheel.rotation < 0) {
					((XButtonEvent *) x_event)->button = WheelUp;
				}
				else {
					((XButtonEvent *) x_event)->button = WheelDown;
				}
				XSendEvent(disp, InputFocus, False, ButtonPressMask, x_event);
			}

			if (event->type == EVENT_KEY_PRESSED) {
				((XButtonEvent *) x_event)->type = ButtonPress;
				XSendEvent(disp, InputFocus, False, ButtonPressMask, x_event);
			}
			else {
				((XButtonEvent *) x_event)->type = ButtonRelease;
				XSendEvent(disp, InputFocus, False, ButtonReleaseMask, x_event);
			}

			free(x_event);
			break;

		case EVENT_MOUSE_MOVED:
		case EVENT_MOUSE_DRAGGED:
			x_event = (XEvent *) create_motion_event();

			((XMotionEvent *) x_event)->state = convert_to_native_mask(event->mask);

			((XButtonEvent *) x_event)->x = event->data.mouse.x;
			((XButtonEvent *) x_event)->y = event->data.mouse.y;

			#if defined(USE_XINERAMA) || defined(USE_XRANDR)
			screens = hook_create_screen_info(&screen_count);
			if (screen_count > 1) {
				((XButtonEvent *) x_event)->x += screens[0].x;
				((XButtonEvent *) x_event)->y += screens[0].y;
			}

			if (screens != NULL) {
				free(screens);
			}
			#endif

			// These are the same because Window == Root Window.
			((XButtonEvent *) x_event)->x_root = ((XButtonEvent *) x_event)->x;
			((XButtonEvent *) x_event)->y_root = ((XButtonEvent *) x_event)->y;

			long int x_mask = NoEventMask;
			if (event->type == EVENT_MOUSE_DRAGGED) {
				#if Button1Mask == Button1MotionMask && \
					Button2Mask == Button2MotionMask && \
					Button3Mask == Button3MotionMask && \
					Button4Mask == Button4MotionMask && \
					Button5Mask == Button5MotionMask
				// This little trick only works if Button#MotionMasks align with
				// the Button#Masks.
				x_mask = ((XMotionEvent *) x_event)->state &
						(Button1MotionMask | Button2MotionMask |
						Button2MotionMask | Button3MotionMask | Button5MotionMask);
				#else
				// Fallback to some slightly larger...
				if (((XMotionEvent *) x_event)->state & Button1Mask) {
					x_mask |= Button1MotionMask;
				}

				if (((XMotionEvent *) x_event)->state & Button2Mask) {
					x_mask |= Button2MotionMask;
				}

				if (((XMotionEvent *) x_event)->state & Button3Mask) {
					x_mask |= Button3MotionMask;
				}

				if (((XMotionEvent *) x_event)->state & Button4Mask) {
					x_mask |= Button4MotionMask;
				}

				if (((XMotionEvent *) x_event)->state & Button5Mask) {
					x_mask |= Button5MotionMask;
				}
				#endif
			}

			// NOTE x_mask = NoEventMask.
			XSendEvent(disp, InputFocus, False, x_mask, x_event);
			free(x_event);
			break;


		case EVENT_MOUSE_CLICKED:
		case EVENT_KEY_TYPED:
			// Ignore clicked and typed events.

		case EVENT_HOOK_ENABLED:
		case EVENT_HOOK_DISABLED:
			// Ignore hook enabled / disabled events.

		default:
			// Ignore any other garbage.
			logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n",
				__FUNCTION__, __LINE__, event->type);
			break;
	}


	#endif

	// Don't forget to flush!
	XFlush(disp);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy