summaryrefslogtreecommitdiff
path: root/src/gs-grab-x11.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-grab-x11.c')
-rw-r--r--src/gs-grab-x11.c672
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);
+}