diff options
Diffstat (limited to 'src/gs-grab-x11.c')
-rw-r--r-- | src/gs-grab-x11.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/gs-grab-x11.c b/src/gs-grab-x11.c new file mode 100644 index 0000000..9eca1ec --- /dev/null +++ b/src/gs-grab-x11.c @@ -0,0 +1,672 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2006 William Jon McCann <[email protected]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: William Jon McCann <[email protected]> + * + */ + +#include "config.h" +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#ifdef HAVE_XF86MISCSETGRABKEYSSTATE +# include <X11/extensions/xf86misc.h> +#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */ + +#include "gs-window.h" +#include "gs-grab.h" +#include "gs-debug.h" + +static void gs_grab_class_init (GSGrabClass *klass); +static void gs_grab_init (GSGrab *grab); +static void gs_grab_finalize (GObject *object); + +#define GS_GRAB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_GRAB, GSGrabPrivate)) + +G_DEFINE_TYPE (GSGrab, gs_grab, G_TYPE_OBJECT) + +static gpointer grab_object = NULL; + +struct GSGrabPrivate +{ + guint mouse_hide_cursor : 1; + GdkWindow *mouse_grab_window; + GdkWindow *keyboard_grab_window; + GdkScreen *mouse_grab_screen; + GdkScreen *keyboard_grab_screen; + + GtkWidget *invisible; +}; + +static GdkCursor * +get_cursor (void) +{ + GdkBitmap *empty_bitmap; + GdkCursor *cursor; + GdkColor useless; + char invisible_cursor_bits [] = { 0x0 }; + + useless.red = useless.green = useless.blue = 0; + useless.pixel = 0; + + empty_bitmap = gdk_bitmap_create_from_data (NULL, + invisible_cursor_bits, + 1, 1); + + cursor = gdk_cursor_new_from_pixmap (empty_bitmap, + empty_bitmap, + &useless, + &useless, 0, 0); + + g_object_unref (empty_bitmap); + + return cursor; +} + +static const char * +grab_string (int status) +{ + switch (status) + { + case GDK_GRAB_SUCCESS: + return "GrabSuccess"; + case GDK_GRAB_ALREADY_GRABBED: + return "AlreadyGrabbed"; + case GDK_GRAB_INVALID_TIME: + return "GrabInvalidTime"; + case GDK_GRAB_NOT_VIEWABLE: + return "GrabNotViewable"; + case GDK_GRAB_FROZEN: + return "GrabFrozen"; + default: + { + static char foo [255]; + sprintf (foo, "unknown status: %d", status); + return foo; + } + } +} + +#ifdef HAVE_XF86MISCSETGRABKEYSSTATE +/* This function enables and disables the Ctrl-Alt-KP_star and + Ctrl-Alt-KP_slash hot-keys, which (in XFree86 4.2) break any + grabs and/or kill the grabbing client. That would effectively + unlock the screen, so we don't like that. + + The Ctrl-Alt-KP_star and Ctrl-Alt-KP_slash hot-keys only exist + if AllowDeactivateGrabs and/or AllowClosedownGrabs are turned on + in XF86Config. I believe they are disabled by default. + + This does not affect any other keys (specifically Ctrl-Alt-BS or + Ctrl-Alt-F1) but I wish it did. Maybe it will someday. + */ +static void +xorg_lock_smasher_set_active (GSGrab *grab, + gboolean active) +{ + int status, event, error; + + if (!XF86MiscQueryExtension (GDK_DISPLAY (), &event, &error)) + { + gs_debug ("No XFree86-Misc extension present"); + return; + } + + if (active) + { + gs_debug ("Enabling the x.org grab smasher"); + } + else + { + gs_debug ("Disabling the x.org grab smasher"); + } + + gdk_error_trap_push (); + + status = XF86MiscSetGrabKeysState (GDK_DISPLAY (), active); + + gdk_display_sync (gdk_display_get_default ()); + gdk_error_trap_pop (); + + if (active && status == MiscExtGrabStateAlready) + { + /* shut up, consider this success */ + status = MiscExtGrabStateSuccess; + } + + gs_debug ("XF86MiscSetGrabKeysState(%s) returned %s\n", + active ? "on" : "off", + (status == MiscExtGrabStateSuccess ? "MiscExtGrabStateSuccess" : + status == MiscExtGrabStateLocked ? "MiscExtGrabStateLocked" : + status == MiscExtGrabStateAlready ? "MiscExtGrabStateAlready" : + "unknown value")); +} +#else +static void +xorg_lock_smasher_set_active (GSGrab *grab, + gboolean active) +{ +} +#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */ + +static int +gs_grab_get_keyboard (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen) +{ + GdkGrabStatus status; + + g_return_val_if_fail (window != NULL, FALSE); + g_return_val_if_fail (screen != NULL, FALSE); + + gs_debug ("Grabbing keyboard widget=%X", (guint32) GDK_WINDOW_XID (window)); + status = gdk_keyboard_grab (window, FALSE, GDK_CURRENT_TIME); + + if (status == GDK_GRAB_SUCCESS) + { + if (grab->priv->keyboard_grab_window != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (grab->priv->keyboard_grab_window), + (gpointer *) &grab->priv->keyboard_grab_window); + } + grab->priv->keyboard_grab_window = window; + + g_object_add_weak_pointer (G_OBJECT (grab->priv->keyboard_grab_window), + (gpointer *) &grab->priv->keyboard_grab_window); + + grab->priv->keyboard_grab_screen = screen; + } + else + { + gs_debug ("Couldn't grab keyboard! (%s)", grab_string (status)); + } + + return status; +} + +static int +gs_grab_get_mouse (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen, + gboolean hide_cursor) +{ + GdkGrabStatus status; + GdkCursor *cursor; + + g_return_val_if_fail (window != NULL, FALSE); + g_return_val_if_fail (screen != NULL, FALSE); + + cursor = get_cursor (); + + gs_debug ("Grabbing mouse widget=%X", (guint32) GDK_WINDOW_XID (window)); + status = gdk_pointer_grab (window, TRUE, 0, NULL, + (hide_cursor ? cursor : NULL), + GDK_CURRENT_TIME); + + if (status == GDK_GRAB_SUCCESS) + { + if (grab->priv->mouse_grab_window != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (grab->priv->mouse_grab_window), + (gpointer *) &grab->priv->mouse_grab_window); + } + grab->priv->mouse_grab_window = window; + + g_object_add_weak_pointer (G_OBJECT (grab->priv->mouse_grab_window), + (gpointer *) &grab->priv->mouse_grab_window); + + grab->priv->mouse_grab_screen = screen; + grab->priv->mouse_hide_cursor = hide_cursor; + } + + gdk_cursor_unref (cursor); + + return status; +} + +void +gs_grab_keyboard_reset (GSGrab *grab) +{ + if (grab->priv->keyboard_grab_window != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (grab->priv->keyboard_grab_window), + (gpointer *) &grab->priv->keyboard_grab_window); + } + grab->priv->keyboard_grab_window = NULL; + grab->priv->keyboard_grab_screen = NULL; +} + +static gboolean +gs_grab_release_keyboard (GSGrab *grab) +{ + gs_debug ("Ungrabbing keyboard"); + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + + gs_grab_keyboard_reset (grab); + + return TRUE; +} + +void +gs_grab_mouse_reset (GSGrab *grab) +{ + if (grab->priv->mouse_grab_window != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (grab->priv->mouse_grab_window), + (gpointer *) &grab->priv->mouse_grab_window); + } + + grab->priv->mouse_grab_window = NULL; + grab->priv->mouse_grab_screen = NULL; +} + +gboolean +gs_grab_release_mouse (GSGrab *grab) +{ + gs_debug ("Ungrabbing pointer"); + gdk_pointer_ungrab (GDK_CURRENT_TIME); + + gs_grab_mouse_reset (grab); + + return TRUE; +} + +static gboolean +gs_grab_move_mouse (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen, + gboolean hide_cursor) +{ + gboolean result; + GdkWindow *old_window; + GdkScreen *old_screen; + gboolean old_hide_cursor; + + /* if the pointer is not grabbed and we have a + mouse_grab_window defined then we lost the grab */ + if (! gdk_pointer_is_grabbed ()) + { + gs_grab_mouse_reset (grab); + } + + if (grab->priv->mouse_grab_window == window) + { + gs_debug ("Window %X is already grabbed, skipping", + (guint32) GDK_WINDOW_XID (grab->priv->mouse_grab_window)); + return TRUE; + } + +#if 0 + gs_debug ("Intentionally skipping move pointer grabs"); + /* FIXME: GTK doesn't like having the pointer grabbed */ + return TRUE; +#else + if (grab->priv->mouse_grab_window) + { + gs_debug ("Moving pointer grab from %X to %X", + (guint32) GDK_WINDOW_XID (grab->priv->mouse_grab_window), + (guint32) GDK_WINDOW_XID (window)); + } + else + { + gs_debug ("Getting pointer grab on %X", + (guint32) GDK_WINDOW_XID (window)); + } +#endif + + gs_debug ("*** doing X server grab"); + gdk_x11_grab_server (); + + old_window = grab->priv->mouse_grab_window; + old_screen = grab->priv->mouse_grab_screen; + old_hide_cursor = grab->priv->mouse_hide_cursor; + + if (old_window) + { + gs_grab_release_mouse (grab); + } + + result = gs_grab_get_mouse (grab, window, screen, hide_cursor); + + if (result != GDK_GRAB_SUCCESS) + { + sleep (1); + result = gs_grab_get_mouse (grab, window, screen, hide_cursor); + } + + if ((result != GDK_GRAB_SUCCESS) && old_window) + { + gs_debug ("Could not grab mouse for new window. Resuming previous grab."); + gs_grab_get_mouse (grab, old_window, old_screen, old_hide_cursor); + } + + gs_debug ("*** releasing X server grab"); + gdk_x11_ungrab_server (); + gdk_flush (); + + return (result == GDK_GRAB_SUCCESS); +} + +static gboolean +gs_grab_move_keyboard (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen) +{ + gboolean result; + GdkWindow *old_window; + GdkScreen *old_screen; + + if (grab->priv->keyboard_grab_window == window) + { + gs_debug ("Window %X is already grabbed, skipping", + (guint32) GDK_WINDOW_XID (grab->priv->keyboard_grab_window)); + return TRUE; + } + + if (grab->priv->keyboard_grab_window != NULL) + { + gs_debug ("Moving keyboard grab from %X to %X", + (guint32) GDK_WINDOW_XID (grab->priv->keyboard_grab_window), + (guint32) GDK_WINDOW_XID (window)); + } + else + { + gs_debug ("Getting keyboard grab on %X", + (guint32) GDK_WINDOW_XID (window)); + + } + + gs_debug ("*** doing X server grab"); + gdk_x11_grab_server (); + + old_window = grab->priv->keyboard_grab_window; + old_screen = grab->priv->keyboard_grab_screen; + + if (old_window) + { + gs_grab_release_keyboard (grab); + } + + result = gs_grab_get_keyboard (grab, window, screen); + + if (result != GDK_GRAB_SUCCESS) + { + sleep (1); + result = gs_grab_get_keyboard (grab, window, screen); + } + + if ((result != GDK_GRAB_SUCCESS) && old_window) + { + gs_debug ("Could not grab keyboard for new window. Resuming previous grab."); + gs_grab_get_keyboard (grab, old_window, old_screen); + } + + gs_debug ("*** releasing X server grab"); + gdk_x11_ungrab_server (); + gdk_flush (); + + return (result == GDK_GRAB_SUCCESS); +} + +static void +gs_grab_nuke_focus (void) +{ + Window focus = 0; + int rev = 0; + + gs_debug ("Nuking focus"); + + gdk_error_trap_push (); + + XGetInputFocus (GDK_DISPLAY (), &focus, &rev); + + XSetInputFocus (GDK_DISPLAY (), None, RevertToNone, CurrentTime); + + gdk_display_sync (gdk_display_get_default ()); + gdk_error_trap_pop (); +} + +void +gs_grab_release (GSGrab *grab) +{ + gs_debug ("Releasing all grabs"); + + gs_grab_release_mouse (grab); + gs_grab_release_keyboard (grab); + + /* FIXME: is it right to enable this ? */ + xorg_lock_smasher_set_active (grab, TRUE); + + gdk_display_sync (gdk_display_get_default ()); + gdk_flush (); +} + +gboolean +gs_grab_grab_window (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen, + gboolean hide_cursor) +{ + gboolean mstatus = FALSE; + gboolean kstatus = FALSE; + int i; + int retries = 4; + gboolean focus_fuckus = FALSE; + +AGAIN: + + for (i = 0; i < retries; i++) + { + kstatus = gs_grab_get_keyboard (grab, window, screen); + if (kstatus == GDK_GRAB_SUCCESS) + { + break; + } + + /* else, wait a second and try to grab again. */ + sleep (1); + } + + if (kstatus != GDK_GRAB_SUCCESS) + { + if (!focus_fuckus) + { + focus_fuckus = TRUE; + gs_grab_nuke_focus (); + goto AGAIN; + } + } + + for (i = 0; i < retries; i++) + { + mstatus = gs_grab_get_mouse (grab, window, screen, hide_cursor); + if (mstatus == GDK_GRAB_SUCCESS) + { + break; + } + + /* else, wait a second and try to grab again. */ + sleep (1); + } + + if (mstatus != GDK_GRAB_SUCCESS) + { + gs_debug ("Couldn't grab pointer! (%s)", + grab_string (mstatus)); + } + +#if 0 + /* FIXME: release the pointer grab so GTK will work */ + gs_grab_release_mouse (grab); +#endif + + /* When should we allow blanking to proceed? The current theory + is that both a keyboard grab and a mouse grab are mandatory + + - If we don't have a keyboard grab, then we won't be able to + read a password to unlock, so the kbd grab is manditory. + + - If we don't have a mouse grab, then we might not see mouse + clicks as a signal to unblank, on-screen widgets won't work ideally, + and gs_grab_move_to_window() will spin forever when it gets called. + */ + + if (kstatus != GDK_GRAB_SUCCESS || mstatus != GDK_GRAB_SUCCESS) + { + /* Do not blank without a keyboard and mouse grabs. */ + + /* Release keyboard or mouse which was grabbed. */ + if (kstatus == GDK_GRAB_SUCCESS) + { + gs_grab_release_keyboard (grab); + } + if (mstatus == GDK_GRAB_SUCCESS) + { + gs_grab_release_mouse (grab); + } + + return FALSE; + } + + /* Grab is good, go ahead and blank. */ + return TRUE; +} + +/* this is used to grab the keyboard and mouse to the root */ +gboolean +gs_grab_grab_root (GSGrab *grab, + gboolean hide_cursor) +{ + GdkDisplay *display; + GdkWindow *root; + GdkScreen *screen; + gboolean res; + + gs_debug ("Grabbing the root window"); + + display = gdk_display_get_default (); + gdk_display_get_pointer (display, &screen, NULL, NULL, NULL); + root = gdk_screen_get_root_window (screen); + + res = gs_grab_grab_window (grab, root, screen, hide_cursor); + + return res; +} + +/* this is used to grab the keyboard and mouse to an offscreen window */ +gboolean +gs_grab_grab_offscreen (GSGrab *grab, + gboolean hide_cursor) +{ + GdkScreen *screen; + gboolean res; + + gs_debug ("Grabbing an offscreen window"); + + screen = gtk_invisible_get_screen (GTK_INVISIBLE (grab->priv->invisible)); + res = gs_grab_grab_window (grab, grab->priv->invisible->window, screen, hide_cursor); + + return res; +} + +/* This is similar to gs_grab_grab_window but doesn't fail */ +void +gs_grab_move_to_window (GSGrab *grab, + GdkWindow *window, + GdkScreen *screen, + gboolean hide_cursor) +{ + gboolean result = FALSE; + + g_return_if_fail (GS_IS_GRAB (grab)); + + xorg_lock_smasher_set_active (grab, FALSE); + + do + { + result = gs_grab_move_keyboard (grab, window, screen); + gdk_flush (); + } + while (!result); + + do + { + result = gs_grab_move_mouse (grab, window, screen, hide_cursor); + gdk_flush (); + } + while (!result); +} + +static void +gs_grab_class_init (GSGrabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gs_grab_finalize; + + g_type_class_add_private (klass, sizeof (GSGrabPrivate)); +} + +static void +gs_grab_init (GSGrab *grab) +{ + grab->priv = GS_GRAB_GET_PRIVATE (grab); + + grab->priv->mouse_hide_cursor = FALSE; + grab->priv->invisible = gtk_invisible_new (); + gtk_widget_show (grab->priv->invisible); +} + +static void +gs_grab_finalize (GObject *object) +{ + GSGrab *grab; + + g_return_if_fail (object != NULL); + g_return_if_fail (GS_IS_GRAB (object)); + + grab = GS_GRAB (object); + + g_return_if_fail (grab->priv != NULL); + + gtk_widget_destroy (grab->priv->invisible); + + G_OBJECT_CLASS (gs_grab_parent_class)->finalize (object); +} + +GSGrab * +gs_grab_new (void) +{ + if (grab_object) + { + g_object_ref (grab_object); + } + else + { + grab_object = g_object_new (GS_TYPE_GRAB, NULL); + g_object_add_weak_pointer (grab_object, + (gpointer *) &grab_object); + } + + return GS_GRAB (grab_object); +} |