summaryrefslogtreecommitdiffstats
path: root/lassi-grab.c
diff options
context:
space:
mode:
Diffstat (limited to 'lassi-grab.c')
-rw-r--r--lassi-grab.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/lassi-grab.c b/lassi-grab.c
new file mode 100644
index 0000000..4a2be87
--- /dev/null
+++ b/lassi-grab.c
@@ -0,0 +1,407 @@
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XTest.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "lassi-server.h"
+#include "lassi-grab.h"
+
+#define TRIGGER_WIDTH 1
+
+static int local2global(LassiGrabInfo *i, int y) {
+ g_assert(i);
+ g_assert(y >= 0 && y <= gdk_screen_get_height(i->screen)-1);
+
+ /* Convert local screen coordinates (0 .. height) into global ones (0 . 65535) */
+ return (y * 0xFFFF) / (gdk_screen_get_height(i->screen)-1);
+}
+
+static int global2local(LassiGrabInfo *i, int y) {
+ g_assert(i);
+ g_assert(y >= 0 && y <= 0xFFFF);
+
+ /* Convert global screen coordinates (0 . 65535) into local ones (0 .. height) */
+ return (y * (gdk_screen_get_height(i->screen)-1)) / 0xFFFF;
+}
+
+static void move_pointer(LassiGrabInfo *i, int x, int y) {
+ g_assert(i);
+
+ /* Move the pointer ... */
+ gdk_display_warp_pointer(i->display, i->screen, x, y);
+
+ i->last_x = x;
+ i->last_y = y;
+}
+
+static void drop_motion_events(LassiGrabInfo *i) {
+ XEvent txe;
+
+ g_assert(i);
+
+ /* Drop all queued motion events */
+ while (XCheckTypedEvent(GDK_DISPLAY_XDISPLAY(i->display), MotionNotify, &txe))
+ ;
+}
+
+static int grab_input(LassiGrabInfo *i, GdkWindow *w) {
+ g_assert(i);
+ g_assert(w);
+
+ if (gdk_pointer_grab(w, TRUE,
+ GDK_POINTER_MOTION_MASK|
+ GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK,
+ NULL, i->empty_cursor, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) {
+ g_debug("pointer grab failed");
+ return -1;
+ }
+
+
+ if (gdk_keyboard_grab(w, TRUE, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) {
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+ g_debug("keyboard grab failed");
+ return -1;
+ }
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ if (i->grab_window != w) {
+ /* Now, rebase the pointer, so that we can easily calculate
+ * relative movements */
+ move_pointer(i, i->base_x, i->base_y);
+
+ i->grab_window = w;
+
+ i->left_shift = i->right_shift = i->double_shift = FALSE;
+
+ g_debug("Input now grabbed");
+ }
+
+ return 0;
+}
+
+int lassi_grab_start(LassiGrabInfo *i, gboolean to_left) {
+ g_assert(i);
+
+ return grab_input(i, to_left ? i->left_window : i->right_window);
+}
+
+void lassi_grab_stop(LassiGrabInfo *i, int y) {
+ int x;
+
+ g_assert(i);
+
+ if (!i->grab_window)
+ return;
+
+ /* Move the pointer back into our screen */
+ if (y >= 0 && y < 0xFFFF) {
+
+ /* We received a valid y coordinate, so let's use it */
+ y = global2local(i, y);
+
+ if (i->grab_window == i->left_window)
+ x = TRIGGER_WIDTH;
+ else
+ x = gdk_screen_get_width(i->screen)-TRIGGER_WIDTH-1;
+
+ } else {
+
+ /* We received an invlid y coordinate, so let's center the
+ * pointer */
+ x = i->base_x;
+ y = i->base_y;
+ }
+
+ move_pointer(i, x, y);
+
+ gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+
+ drop_motion_events(i);
+
+ i->grab_window = NULL;
+
+ g_debug("Input now ungrabbed");
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), True);
+}
+
+static void handle_motion(LassiGrabInfo *i, int x, int y) {
+ int dx, dy;
+ int r;
+ int w, h;
+
+ dx = x - i->last_x;
+ dy = y - i->last_y;
+
+ i->last_x = x;
+ i->last_y = y;
+
+ g_debug("rel motion %i %i", dx, dy);
+
+ w = gdk_screen_get_width(i->screen);
+ h = gdk_screen_get_height(i->screen);
+
+ if (x <= w/10 || y <= h/10 ||
+ x >= (w*9)/10 || y >= (h*9)/10) {
+
+ XEvent txe;
+
+ /* Pointer is too near to the edges, move cursor
+ * back to center, so that further movements are
+ * not clipped */
+
+ g_debug("centering");
+
+ /* First, make sure there is no further motion event in the queue */
+ while (XCheckTypedEvent(GDK_DISPLAY_XDISPLAY(i->display), MotionNotify, &txe)) {
+ dx += txe.xmotion.x - i->last_x;
+ dy += txe.xmotion.y - i->last_y;
+
+ i->last_x = txe.xmotion.x;
+ i->last_y = txe.xmotion.y;
+ }
+
+ move_pointer(i, i->base_x, i->base_y);
+ }
+
+ /* Filter out non-existant or too large motions */
+ if ((dx != 0 || dy != 0) &&
+ ((abs(dx) <= (w*9)/20) && (abs(dy) <= (h*9)/20))) {
+
+ g_debug("sending motion");
+
+ /* Send the event */
+ r = lassi_server_motion_event(i->server, dx, dy);
+ g_assert(r >= 0);
+ }
+}
+
+static GdkFilterReturn filter_func(GdkXEvent *gxe, GdkEvent *event, gpointer data) {
+ LassiGrabInfo *i = data;
+ XEvent *xe = (XEvent*) gxe;
+ GdkWindow *w = ((GdkEventAny*) event)->window;
+
+ g_assert(i);
+ g_assert(xe);
+ g_assert(event);
+
+ switch (xe->type){
+
+ case EnterNotify: {
+ XEnterWindowEvent *ewe = (XEnterWindowEvent*) xe;
+
+ if (ewe->mode == NotifyNormal && ewe->state == 0 && !i->grab_window) {
+ g_debug("enter %u %u", ewe->x_root, ewe->y_root);
+
+ /* Only honour this when no button/key is pressed */
+
+ if (lassi_server_change_grab(i->server, w == i->left_window, local2global(i, ewe->y_root)) >= 0)
+ grab_input(i, w);
+
+ } else if (i->grab_window)
+ handle_motion(i, ewe->x_root, ewe->y_root);
+
+ break;
+ }
+
+ case MotionNotify:
+
+ if (i->grab_window) {
+ XMotionEvent *me = (XMotionEvent*) xe;
+
+ g_debug("motion %u %u", me->x_root, me->y_root);
+ handle_motion(i, me->x_root, me->y_root);
+ }
+
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+
+ if (i->grab_window) {
+ int r;
+ XButtonEvent *be = (XButtonEvent*) xe;
+
+ g_debug("button press/release");
+ handle_motion(i, be->x_root, be->y_root);
+
+ /* Send the event */
+ r = lassi_server_button_event(i->server, be->button, xe->type == ButtonPress);
+ g_assert(r >= 0);
+ }
+ break;
+
+ case KeyPress:
+ case KeyRelease:
+
+ g_debug("raw key");
+
+ if (i->grab_window) {
+ int r;
+ XKeyEvent *ke = (XKeyEvent *) xe;
+ KeySym keysym;
+
+ keysym = XKeycodeToKeysym(GDK_DISPLAY_XDISPLAY(i->display), ke->keycode, 0);
+
+ if (keysym == XK_Shift_L)
+ i->left_shift = ke->type == KeyPress;
+ if (keysym == XK_Shift_R)
+ i->right_shift = xe->type == KeyPress;
+
+ if (i->left_shift && i->right_shift)
+ i->double_shift = TRUE;
+
+ g_debug("left_shift=%i right_shift=%i 0x04%x", i->left_shift, i->right_shift, (unsigned) keysym);
+
+ g_debug("key press/release");
+ handle_motion(i, ke->x_root, ke->y_root);
+
+ /* Send the event */
+ r = lassi_server_key_event(i->server, keysym, xe->type == KeyPress);
+ g_assert(r >= 0);
+
+ if (!i->left_shift && !i->right_shift && i->double_shift) {
+ g_debug("Got double shift");
+ lassi_server_acquire_grab(i->server);
+ lassi_grab_stop(i, -1);
+ }
+ }
+ break;
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+int lassi_grab_init(LassiGrabInfo *i, LassiServer *s) {
+ GdkWindowAttr wa;
+ GdkColor black = { 0, 0, 0, 0 };
+ const gchar cursor_data[1] = { 0 };
+ GdkBitmap *bitmap;
+ int xtest_event_base, xtest_error_base;
+ int major_version, minor_version;
+
+ memset(i, 0, sizeof(*i));
+ i->server = s;
+
+ i->screen = gdk_screen_get_default();
+ i->display = gdk_screen_get_display(i->screen);
+ i->root = gdk_screen_get_root_window(i->screen);
+
+ if (!XTestQueryExtension(GDK_DISPLAY_XDISPLAY(i->display), &xtest_event_base, &xtest_error_base, &major_version, &minor_version)) {
+ g_warning("XTest extension not supported.");
+ return -1;
+ }
+
+ g_debug("XTest %u.%u supported.", major_version, minor_version);
+
+ /* Create empty cursor */
+ bitmap = gdk_bitmap_create_from_data(NULL, cursor_data, 1, 1);
+ i->empty_cursor = gdk_cursor_new_from_pixmap(bitmap, bitmap, &black, &black, 0, 0);
+ gdk_pixmap_unref(bitmap);
+
+ /* Create trigger windows */
+ memset(&wa, 0, sizeof(wa));
+
+ wa.title = "Mango Lassi Left";
+ wa.event_mask = GDK_POINTER_MOTION_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK|GDK_ENTER_NOTIFY_MASK;
+ wa.x = 0;
+ wa.y = gdk_screen_get_height(i->screen)/20;
+ wa.width = TRIGGER_WIDTH;
+ wa.height = (gdk_screen_get_height(i->screen)*18)/20;
+ wa.wclass = GDK_INPUT_ONLY;
+ wa.window_type = GDK_WINDOW_FOREIGN;
+ wa.override_redirect = TRUE;
+ wa.type_hint = GDK_WINDOW_TYPE_HINT_DOCK;
+ wa.cursor = i->empty_cursor;
+
+ i->left_window = gdk_window_new(i->root, &wa, GDK_WA_TITLE|GDK_WA_X|GDK_WA_Y|GDK_WA_NOREDIR|GDK_WA_TYPE_HINT|GDK_WA_CURSOR);
+ gdk_window_set_keep_above(i->left_window, TRUE);
+ gdk_window_add_filter(i->left_window, filter_func, i);
+
+ wa.title = "Mango Lassi Right";
+ wa.x = gdk_screen_get_width(i->screen) - TRIGGER_WIDTH;
+
+ i->right_window = gdk_window_new(i->root, &wa, GDK_WA_TITLE|GDK_WA_X|GDK_WA_Y|GDK_WA_NOREDIR|GDK_WA_TYPE_HINT);
+ gdk_window_set_keep_above(i->right_window, TRUE);
+ gdk_window_add_filter(i->right_window, filter_func, i);
+
+ i->base_x = gdk_screen_get_width(i->screen)/2;
+ i->base_y = gdk_screen_get_height(i->screen)/2;
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), True);
+
+ return 0;
+}
+
+void lassi_grab_done(LassiGrabInfo *i) {
+ g_assert(i);
+
+ lassi_grab_stop(i, -1);
+
+ if (i->left_window)
+ gdk_window_destroy(i->left_window);
+
+ if (i->right_window)
+ gdk_window_destroy(i->right_window);
+
+ if (i->empty_cursor)
+ gdk_cursor_unref(i->empty_cursor);
+}
+
+void lassi_grab_enable_triggers(LassiGrabInfo *i, gboolean left, gboolean right) {
+ g_assert(i);
+
+ g_debug("Showing windows: left=%s, right=%s", left ? "yes" : "no", right ? "yes" : "no");
+
+ if (left)
+ gdk_window_show(i->left_window);
+ else
+ gdk_window_hide(i->left_window);
+
+ if (right)
+ gdk_window_show(i->right_window);
+ else
+ gdk_window_hide(i->right_window);
+}
+
+int lassi_grab_move_pointer_relative(LassiGrabInfo *i, int dx, int dy) {
+ g_assert(i);
+
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeRelativeMotionEvent(GDK_DISPLAY_XDISPLAY(i->display), dx, dy, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}
+
+int lassi_grab_press_button(LassiGrabInfo *i, unsigned button, gboolean is_press) {
+ g_assert(i);
+
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeButtonEvent(GDK_DISPLAY_XDISPLAY(i->display), button, is_press, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}
+
+int lassi_grab_press_key(LassiGrabInfo *i, unsigned key, gboolean is_press) {
+ g_assert(i);
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeKeyEvent(GDK_DISPLAY_XDISPLAY(i->display), XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(i->display), key), is_press, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}