#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #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 = (char*) "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 = (char*) "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; }