Re: [gdm-list] Bringing back a11y




Ray:

Jon toggled the switch yesterday to make AccessX sticky keys and slow
keys available via the normal shift key mechanisms (hold shift key
down for a long time and get slow keys, tap it several times and get
sticky keys).

Great news.

One thing we need to move foward though is some solution for what
GtkModulesList did in gdm-2-20.  That key is a list of gtk modules to
run when the greeter is started.

There isn't really any advantage to making these listeners gtk
modules, so it may make sense to rewrite them to just be normal
programs that run at greeter session startup.

If we put desktop files that point to these programs in

/usr/share/gdm/autostart/LoginWindow

then they will get run from within the greeter session.

For the key listener we should watch for events on the root window,

I have gone ahead and ported the GDM 2.20 gesture listeners to
GDM 2.21 SVN head, and modified them so they build as standalone
programs instead of GTK_MODULES.  I have changed the names of the
files slightly to be more consistent with the way GDM names other
binaries, and also to make the names a bit more clear.

I think "keymouse" and "dwellmouse" were a bit confusing.  Now the
files are named "keybutton" and "dwell", which is more clear,
I think.

The gdm-gesture-keybutton.c (previously know as keymouselistener)
seems to be working great.  You can test it in your session if you
want, though you have to kill nautilus for mouse button events to
be recognized by the listener (e.g. by running "gnome-session-remove
nautilus").  Set GDM_DEBUG_GESTURE=1, then run the the
gdm-gesture-keybutton program.  If you "tail -f /var/log/messages" you
should see the debug feedback when you press keys or the mouse buttons.
Also the AT programs should launch if you complete a gesture.

You might find editing the AccessKeyButton events file to make the
duration value for hotkeys 0.  It is much easier, for testing, to
just hit the key rather than having to hold it down for 1 second.
Likewise it is easier to test the Mouse button events if you make
the durations something like 0500 rather than 3000.

but one complication comes with the gesture listener?  How do we find
which window to watch for gestures on?

I think you mean the "dwell listener" rather than "gesture listener".
I agree with the below design, but this is not yet functional.  The
code isn't yet listening to any particular window, so it does not work
yet.

I think the answer is to add

"role", "greeter-login-window"

or

"title", "Login Window"

to the list of construct time properties in gdm_greeter_login_window_new

The listener can then use gdk_screen_get_toplevel_windows to get a
list of windows running in the session, iterate over them, use the
GDK_WINDOW_XWINDOW () macro on the resulting gdk windows to get their
corresponding XIDs and use  XGetProperty to find the window with the
_NET_WM_NAME set to "Login Window" or WM_WINDOW_ROLE set to
"greeter-login-window".

I am attaching a patch that is the current work-in-progress.  It should
not be too hard to finish up the work on the dwell listener, I expect
it to be a day or two of work.  However, I think we should consider
putting this code into SVN before Monday because the modules contain
some strings which we probably want translated.  Since it would be good
to support a11y in GDM 2.22, it would be best to get this code in
before the string freeze.  I also tried to fix up the code to follow the
current coding guidelines.

Also, I am not yet installing any
/usr/share/gdm/autostart/LoginWindow/*.desktop files to start either
of these gesture listeners with GDM yet.  I tried creating one for the
gdm-gesture-keybutton program but I could not seem to get it to work.
The gesture listener did not seem to run.  I am not sure what's
wrong.  Perhaps I setup my desktop file wrong or something?  At any
rate, I might need some help with getting this part integrated.

Brian




Index: Makefile.am
===================================================================
--- Makefile.am	(revision 5739)
+++ Makefile.am	(working copy)
@@ -1,6 +1,7 @@
 NULL =
 
 SUBDIRS =			\
+	a11y			\
 	simple-chooser		\
 	simple-greeter		\
 	user-switch-applet	\
Index: a11y/AccessKeyButtonEvents.in
===================================================================
--- a11y/AccessKeyButtonEvents.in	(revision 0)
+++ a11y/AccessKeyButtonEvents.in	(revision 0)
@@ -0,0 +1,91 @@
+# This is the configuration file for the gdm-gesture-keybutton gesture
+# listener.   The confiuration syntax is as follows:
+#
+# For keyboard gestures:
+#
+# <modifier>[<modifier>...]key  #times duration timeout executable_path +args
+#
+# For executable_path give the full executable path path of the program or the
+# DefaultPath is used.  For key, this can be one of the normal keys such as 'k'
+# for the letter 'k', or 'F10' for the F10 key.  If you wish to use one of the
+# 'modifier' keys you have to specify which one exactly, meaning usually
+# appending _L or _R depending on if it's the left or right one.  The useful
+# ones are: Shift_L, Shift_R, Control_L, Control_R, Meta_L, Meta_R, Alt_L, Alt_R.
+# Do note that the modifier is optional.
+#
+# e.g.
+#
+# <Control>k  1 1000 10000  @AT_BINDIR@/gok --login --access-method=directselection
+#
+# Means press Contol-k 5 times, holding each keypress down for at least 1000ms
+# (1s) each time and with no greater interval than 10000ms (10s) between each
+# event in the sequence.  A duration value of 0 indicates that a keypress of
+# any length is accepted.  The timeout value is only meaningful if the #times
+# value is > 1.  Completing the above example sequence will invoke the gnome on
+# screen keyboard program, gok.  Note that you cannot release the <Control> key
+# while pressing otherwise the sequence will be lost.
+#
+# e.g.
+# Shift_L  5 1000 10000  @AT_BINDIR@/gok --login --access-method=directselection
+# Shift_R  5 1000 10000  @AT_BINDIR@/gok --login --access-method=directselection
+#
+# Will start gok if you press either shift key 5 times holding it down for more
+# then 1 second each time.
+#
+# For mouse button gestures the format is the same except the mouse button number
+# is specified instead of a key gesture:
+#
+# <Mouse#>  #times duration timeout  executable_path +args
+#
+# e.g.
+#
+# <Mouse2>  4 3000 6000  @AT_BINDIR@/orca -n -d main-window
+#
+# Note that mouse numbers are 1-based so <Mouse1> is the left mouse button,
+# <Mouse3> is the right mouse button and <Mouse2> is the middle mouse button.
+#
+# It is possible to invoke multiple actions from a single gesture using the <Add>
+# keyword. Actions specified with <Add> are invoked by the previous gesture
+# defined in the file.  If the <Add> action is the first action defined in the
+# file, then it is ignored.
+#
+# e.g.
+#
+# <Add>  @AT_BINDIR@/gnome-mag
+#
+
+# AT Program - GOK (GNOME On-screen Keyboard)
+#
+# Include a gesture for both right and left mouse button, for both right
+# and left handed users.
+#
+# hold right or left mouse button 3 times for 3 seconds each time.
+<Mouse1> 3 3000 10000 @AT_BINDIR@/gok --login --access-method=directselection
+<Mouse3> 3 3000 10000 @AT_BINDIR@/gok --login --access-method=directselection
+
+# Also support Xinput switches
+#
+<Switch1> 1 5000 0 @AT_BINDIR@/gok --login --access-method=automaticscanning --scan-action=switch1 --select-action=switch1
+<Switch2> 3 50 3000 @AT_BINDIR@/gok --login --access-method=inversescanning --scan-action=switch1 --select-action=switch2
+<Switch3> 3 1000 10000 @AT_BINDIR@/gok --login  --access-method=automaticscanning --scan-action=switch3 --select-action=switch3
+
+# Although most GOK users would want to use a mouse/switch button to start GOK,
+# also include a keybinding for starting GOK with direct selection mode.
+#
+<Control>k  1 1000 10000  @AT_BINDIR@/gok --login --access-method=directselection
+
+# AT Program - ORCA
+#
+# press ctrl-s for 1 second to launch orca in speech mode
+#
+<Control>s  1 1000 10000  @AT_BINDIR@/orca -n -d main-window
+
+# press ctrl-m for 1 second to launch orca in mag mode
+#
+<Control>m  1 1000 10000  @AT_BINDIR@/orca -n -d main-window -d speech -e magnifier
+
+# press ctrl-o or ctrl-g for 1 second to launch orca in speech and mag mode
+#
+<Control>o  1 1000 10000  @AT_BINDIR@/orca -n -d main-window -e magnifier
+<Control>g  1 1000 10000  @AT_BINDIR@/orca -n -d main-window -e magnifier
+
Index: a11y/AccessDwellEvents.in
===================================================================
--- a11y/AccessDwellEvents.in	(revision 0)
+++ a11y/AccessDwellEvents.in	(revision 0)
@@ -0,0 +1,62 @@
+# This is the configuration file for the gdm-gesture-dwell gesture listener.
+# The confiuration syntax is as follows:
+#
+#   crossing inital_direction timeout executable_path +args
+#
+# For executable_path give the full executable path path of the program or the
+# DefaultPath is used.
+#
+# where:
+#
+# + crossing is a string that includes any combination of TBLR characters, where
+#   T means Top, B means Bottom, L means Left, and R means Right.
+#
+# + initial_direction can be either the I or O character which indicates
+#   that the initial crossing is a motion In or Out of the window.
+# 
+# e.g.
+# TBLR I 10000    @AT_BINDIR@/gok --login --access-method=dwellselection
+#
+# Means the user crosses into the top border, out the bottom border, into the left
+# border, and then out the right border (in that order).  The initial_direction
+# value of I is what specifies that the first crossing is into the top border
+# rather than out of the top border.  Each border crossing must occur within
+# 10000ms of the last border crossing for the gesture to be valid.
+#
+# It is possible to invoke multiple actions from a single gesture using the <Add>
+# keyword.  Action specified with <Add> are invoked by the previous gesture
+# defined in the file.  If the <Add> action is the first action defined in the
+# file, then it is ignored.
+#
+# e.g.
+#
+# <Add>           @AT_BINDIR@/orca -n -d main-window
+#
+
+# AT Progarm - GOK (GNOME On-screen Keyboard)
+#
+# Support several different options for different user needs.  Note these
+# gestures all start by moving the mouse into the top window border.
+#
+TBLR I 10000    @AT_BINDIR@/gok --login --access-method=dwellselection
+TLBR I 10000    @AT_BINDIR@/gok --login --access-method=automaticscanning --scan-action=switch1 --select-action=switch1
+TRBL I 10000    @AT_BINDIR@/gok --login --access-method=inversescanning --scan-action=switch1 --select-action=switch2
+TBRL I 10000    @AT_BINDIR@/gok --login  --access-method=automaticscanning --scan-action=switch3 --select-action=switch3
+
+# AT Program - ORCA
+#
+# Note these gestures all start by moving the mouse into the bottom window
+# border.
+#
+# Speech
+#
+BTRL I 10000    @AT_BINDIR@/orca -n -d main-window
+
+# Magnifier
+#
+BTLR I 10000    @AT_BINDIR@/orca -n -d main-window -d speech -e magnifier
+
+# Speech and Magnifier
+#
+BRTL I 10000    @AT_BINDIR@/orca -n -d main-window -e magnifier
+
Index: a11y/gdm-gesture-keybutton.c
===================================================================
--- a11y/gdm-gesture-keybutton.c	(revision 0)
+++ a11y/gdm-gesture-keybutton.c	(revision 0)
@@ -0,0 +1,1128 @@
+/* GDM - The Gnome Display Manager
+ * Copyright (C) 2003-2008  Sun Microsystems
+ *
+ * Written by: Brian A. Cameron  <Brian Cameron sun com>
+ *             Bill Haneman <Bill Haneman sun com>
+ *
+ * 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
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#include <glib.h>
+#include <gmodule.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#ifdef HAVE_XINPUT
+#include <X11/extensions/XInput.h>
+#endif
+
+/*
+ * Note that CONFIGFILE will have to be moved to somewhere more generic
+ * if this module is ever moved outside of gdm.
+ */
+#define CONFIGFILE GDMCONFDIR "/gestures/AccessKeyButtonEvents"
+#define iseol(ch)  ((ch) == '\r' || (ch) == '\f' || (ch) == '\0' || \
+                    (ch) == '\n')
+
+#define N_INPUT_TYPES 40
+
+typedef enum
+{
+        GESTURE_TYPE_KEY    = 1 << 0,
+        GESTURE_TYPE_MOUSE  = 1 << 1,
+        GESTURE_TYPE_BUTTON = 1 << 2
+} GestureType;
+
+typedef enum {
+  XINPUT_TYPE_MOTION = 0,
+  XINPUT_TYPE_BUTTON_PRESS   = 1,
+  XINPUT_TYPE_BUTTON_RELEASE = 2,
+  XINPUT_TYPE_KEY_PRESS      = 3,
+  XINPUT_TYPE_KEY_RELEASE    = 4
+} XInputEventType;
+
+typedef struct {
+        guint keysym;
+        GdkModifierType state;
+        guint keycode;
+} Key;
+
+typedef struct {
+        guint number;
+        GdkModifierType state;
+} Button;
+
+union Input {
+        Key key;
+        Button button;
+};
+
+typedef struct {
+        GestureType type;
+        union Input input;
+        char *gesture_str;
+        GSList *actions;
+        guint n_times;
+        guint duration;
+        guint timeout;
+        gint  start_time;
+        gint  seq_count;
+} Gesture;
+
+static int xinput_types[N_INPUT_TYPES];
+
+extern char **environ;
+
+static GSList   *gesture_list   = NULL;
+static gboolean  debug_gestures = FALSE;
+static int       lineno         = 0;
+
+static gchar *         screen_exec_display_string (GdkScreen *screen, const char *old);
+static void            create_event_watcher (void);
+static void            load_gestures (gchar *path);
+static gchar **        get_exec_environment (XEvent *xevent);
+static Gesture *       parse_line (gchar *buf);
+static GdkFilterReturn gestures_filter (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data);
+static gint            is_mouseX (const gchar *string);
+static gint            is_switchX (const gchar *string);
+
+#define gesture_list_get_matches(a, b, c) (g_slist_find_custom (a, b, c))
+
+static void
+free_gesture (Gesture *gesture)
+{
+        if (gesture == NULL)
+                return;
+
+        g_slist_foreach (gesture->actions, (GFunc)g_free, NULL);
+        g_slist_free (gesture->actions);
+        g_free (gesture->gesture_str);
+        g_free (gesture);
+}
+                        
+static gchar *
+screen_exec_display_string (GdkScreen *screen, const char *old)
+{
+        GString     *str;
+        const gchar *old_display;
+        gchar       *retval;
+        gchar       *p;
+  
+        g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+        old_display = gdk_display_get_name (gdk_screen_get_display (screen));
+
+        str = g_string_new ("DISPLAY=");
+        g_string_append (str, old_display);
+
+        p = strrchr (str->str, '.');
+        if (p && p >  strchr (str->str, ':'))
+                g_string_truncate (str, p - str->str);
+
+        g_string_append_printf (str, ".%d", gdk_screen_get_number (screen));
+
+        retval = str->str;
+
+        g_string_free (str, FALSE);
+
+        return retval;
+}
+
+static void
+init_xinput (GdkDisplay *display, GdkWindow *root)
+{
+#ifdef HAVE_XINPUT
+        XDeviceInfo  *devices;
+        XDevice      *device;
+        XEventClass   event_list[40];
+        int           i, j, number, num_devices; 
+
+        number  = 0;
+        device  = NULL;
+        devices = XListInputDevices (GDK_DISPLAY_XDISPLAY (display),
+                &num_devices);
+
+        if (debug_gestures)
+                syslog (LOG_WARNING, "checking %d input devices...", num_devices);
+
+        for (i=0; i < num_devices; i++) {
+                if (devices[i].use == IsXExtensionDevice &&
+                    devices[i].num_classes > 0)
+                {
+                        device = XOpenDevice (GDK_DISPLAY_XDISPLAY (display),
+                                devices[i].id);
+
+                        for (j=0; j < device->num_classes && number < 39; j++) {
+
+                                switch (device->classes[j].input_class) 
+                                {
+                                case KeyClass:
+                                        DeviceKeyPress (device, 
+                                            xinput_types[XINPUT_TYPE_KEY_PRESS], 
+                                            event_list[number]);
+                                        number++;
+                                        DeviceKeyRelease (device, 
+                                            xinput_types[XINPUT_TYPE_KEY_RELEASE], 
+                                            event_list[number]);
+                                        number++;
+                                        break;      
+                                case ButtonClass:
+                                        DeviceButtonPress (device, 
+                                            xinput_types[XINPUT_TYPE_BUTTON_PRESS], 
+                                            event_list[number]);
+                                        number++;
+                                        DeviceButtonRelease (device, 
+                                            xinput_types[XINPUT_TYPE_BUTTON_RELEASE], 
+                                            event_list[number]);
+                                        number++;
+                                        break;
+                                case ValuatorClass:
+                                        DeviceMotionNotify (device, 
+                                            xinput_types[XINPUT_TYPE_MOTION], 
+                                            event_list[number]);
+                                        number++;
+                                }
+                        }
+                }
+        }
+        XFreeDeviceList (devices);
+
+        if (debug_gestures)
+            syslog (LOG_WARNING, "%d event types available", number);
+
+        if (XSelectExtensionEvent (GDK_WINDOW_XDISPLAY (root), 
+                                   GDK_WINDOW_XWINDOW (root),
+                                   event_list, number)) {
+                if (debug_gestures)
+                        syslog (LOG_WARNING,
+                                "Can't select input device events!");
+        }
+#endif
+}
+
+static GSList *
+get_screens_list (void)
+{
+        GdkDisplay *display = gdk_display_get_default ();
+        GSList     *list;
+        int         n_screens;
+        int         i;
+
+        list      = NULL;
+        n_screens = gdk_display_get_n_screens (display);
+
+        if (n_screens == 1) {
+                list = g_slist_append (list, gdk_screen_get_default ());
+        } else {
+                for (i = 0; i < n_screens; i++) {
+                        GdkScreen *screen;
+
+                        screen = gdk_display_get_screen (display, i);
+                        if (screen != NULL) {
+                                list = g_slist_prepend (list, screen);
+                        }
+                }
+                list = g_slist_reverse (list);
+        }
+
+        return list;
+}
+
+static void create_event_watcher (void)
+{
+        GdkDisplay *display;
+        GSList     *screen_list;
+        GdkScreen  *screen;
+        GSList     *l;
+        GSList     *gl;
+        int         rc;
+
+        load_gestures (CONFIGFILE);
+
+        display = gdk_display_get_default ();
+        if (!display)
+                exit (-1);
+
+        screen_list = get_screens_list ();
+
+        for (l = screen_list; l; l = l->next) {
+                GdkScreen *screen = l->data;
+
+                gdk_window_set_events (gdk_screen_get_root_window (screen),
+                                       GDK_KEY_PRESS_MASK    | 
+                                       GDK_KEY_RELEASE_MASK  |
+                                       GDK_BUTTON_PRESS_MASK |
+                                       GDK_BUTTON_RELEASE_MASK);
+        }
+
+        for (gl = gesture_list; gl ; gl = gl->next) {
+                Gesture *gesture = gl->data;
+
+                /* Grab keys defined in gesture files for key events */
+                if (gesture->type != GESTURE_TYPE_KEY)
+                        continue;
+
+                for (l = screen_list; l; l = l->next) {
+                        GdkScreen *screen = l->data;
+
+                        gdk_error_trap_push ();
+                        rc = XGrabKey (GDK_DISPLAY (),
+                                       gesture->input.key.keycode,
+                                       gesture->input.key.state,
+                                       GDK_WINDOW_XID (gdk_screen_get_root_window (screen)),
+                                       True,
+                                       GrabModeAsync,
+                                       GrabModeAsync);
+                        gdk_flush ();
+                        if (gdk_error_trap_pop ()) {
+                                if (debug_gestures)
+                                        syslog (LOG_WARNING, "Key %s could not be grabbed.",
+                                                gesture->gesture_str);
+                        } else {
+                                if (debug_gestures)
+                                        syslog (LOG_WARNING, "key %s grab success.",
+                                               gesture->gesture_str);
+                        }
+                }
+        }
+
+        init_xinput (display, gdk_screen_get_root_window (
+                gdk_display_get_default_screen (display)));
+
+        /* Set up gestures filter */
+        for (l = screen_list; l; l = l->next) {
+                GdkScreen *screen = l->data;
+                gdk_window_add_filter (gdk_screen_get_root_window (screen), (GdkFilterFunc)gestures_filter, NULL);
+        }
+        return;
+}
+
+
+static void
+load_gestures (gchar *path)
+{
+        FILE *fp;
+        Gesture *tmp_gesture;
+        gchar buf[1024];
+
+        fp = fopen (path, "r");
+        if (fp == NULL) {
+                /* TODO - I18n */
+                if (debug_gestures) 
+                syslog (LOG_WARNING, _("Cannot open gestures file: %s"),
+                        path);
+                return;
+        }
+
+        while (fgets (buf, sizeof (buf), fp) != NULL) {
+
+                tmp_gesture = (Gesture *)parse_line (buf);
+                if (tmp_gesture) {
+
+                        /* Is the key already associated with an existing
+                           gesture? */
+                        if (strcmp (tmp_gesture->gesture_str, "<Add>") == 0) {
+
+                                /* Add another action to the last gesture */
+                                Gesture *last_gesture;
+                                GSList *last_item = g_slist_last (gesture_list);
+
+                                /* If there is no last_item to add onto ignore
+                                   the entry */
+                                if (last_item) {
+                                        last_gesture = (Gesture *)
+                                                last_item->data;
+
+                                        /* Add the action to the last gesture's
+                                           actions list */
+                                        last_gesture->actions = 
+                                                g_slist_append (last_gesture->actions,
+                                                g_strdup ((gchar *)tmp_gesture->actions->data));
+                                }
+                                free_gesture (tmp_gesture);
+
+                                /* We must be able to deal with multiple
+                                   unambiguous gestures attached to one
+                                   switch/button */
+                        } else {
+                                gesture_list = g_slist_append (gesture_list,
+                                        tmp_gesture);
+                        }
+                }
+        }
+        fclose (fp);
+}
+
+
+/**
+ * get_exec_environment:
+ *
+ * Description: Modifies the current program environment to
+ * ensure that $DISPLAY is set such that a launched application
+ * inheriting this environment would appear on screen.
+ *
+ * Returns: a newly-allocated %NULL-terminated array of strings or
+ * %NULL on error. Use g_strfreev () to free it.
+ *
+ * mainly ripped from egg_screen_exec_display_string in 
+ * gnome-panel/egg-screen-exec.c
+ **/
+static gchar **
+get_exec_environment (XEvent *xevent)
+{
+        gchar **retval;
+        gint    display_index;
+        gint    i;
+
+        display_index = -1;
+        retval        = NULL;
+
+        GdkScreen *screen = NULL;
+        GdkWindow *window = gdk_xid_table_lookup (xevent->xkey.root);
+
+        if (window)
+                screen = gdk_drawable_get_screen (GDK_DRAWABLE (window));
+
+        /* TODO: revisit this fallback, it's suspect since Xi events might not
+           have xkey.root set */
+
+        else
+                screen = gdk_display_get_default_screen (gdk_display_get_default ());   
+
+        g_assert (GDK_IS_SCREEN (screen));
+
+        for (i=0; environ [i]; i++)
+                if (!strncmp (environ [i], "DISPLAY", 7))
+                          display_index = i;
+
+        if (display_index == -1)
+                display_index = i++;
+
+        retval = g_new0 (char *, i + 1);
+
+        for (i=0; environ [i]; i++) {
+                 if (i == display_index)
+                          retval [i] = screen_exec_display_string (screen,
+                                environ[i]);
+                else
+                        retval [i] = g_strdup (environ [i]);
+        }
+
+        retval [i] = NULL;
+
+        return retval;
+}
+
+static Gesture *
+parse_line (gchar *buf)
+{
+        static GdkDisplay *display = NULL;
+        Gesture           *tmp_gesture;
+        gchar             *keystring;
+        gchar             *keyservice;
+        gint               button;
+        
+        button      = 0;
+        tmp_gesture = NULL;
+
+        if (!display) {
+                if ((display = gdk_display_get_default ()) == NULL)
+                        return NULL;
+        }
+        lineno++;
+
+        if ((*buf == '#') || (iseol (*buf)) || (buf == NULL))
+                return NULL;
+        
+        /* Find the key name */
+        keystring = strtok (buf, " \t\n\r\f");
+        if (keystring == NULL) {
+                /* TODO - Error messages */
+                return NULL;
+        }
+
+        tmp_gesture = g_new0 (Gesture, 1);
+        tmp_gesture->gesture_str = g_strdup (keystring);
+
+        if (strcmp (tmp_gesture->gesture_str, "<Add>") != 0) {
+                guint n, duration, timeout;
+                gchar *tmp_string;
+
+                tmp_gesture->start_time = 0;
+                tmp_gesture->seq_count = 0;
+
+                button = is_mouseX (tmp_gesture->gesture_str);
+                if (button > 0) {
+                        tmp_gesture->type = GESTURE_TYPE_MOUSE;
+                        tmp_gesture->input.button.number = button;
+                } else if ((button = is_switchX (tmp_gesture->gesture_str)) == TRUE) {
+                        tmp_gesture->type = GESTURE_TYPE_BUTTON;
+                        tmp_gesture->input.button.number = button;
+                } else {
+                        tmp_gesture->type = GESTURE_TYPE_KEY;
+                        gtk_accelerator_parse (tmp_gesture->gesture_str, 
+                                 &(tmp_gesture->input.key.keysym), 
+                                 &(tmp_gesture->input.key.state));
+                        if (tmp_gesture->input.key.keysym == 0 &&
+                            tmp_gesture->input.key.state == 0) {
+                                /* TODO - Error messages here */
+                                free_gesture (tmp_gesture);
+                                return NULL;
+                        }
+                        tmp_gesture->input.key.keycode = 
+                                XKeysymToKeycode (GDK_DISPLAY_XDISPLAY (display),
+                                tmp_gesture->input.key.keysym);
+                }
+
+                if (tmp_gesture->type == 0) {
+                        /* TODO - Error messages here */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+                /* [TODO] Need to clean up here. */
+                 
+                /* Find the repetition number */
+                tmp_string = strtok (NULL, " \t\n\r\f");
+                if (tmp_string == NULL) {
+                        /* TODO - Error messages */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+
+                /* TODO - the above doesn't check for the string to
+                   be all digits */
+                if ((n=atoi (tmp_string)) <= 0) {
+                        /* Add an error message */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+                tmp_gesture->n_times = n;
+
+                /*
+                 * Find the key press duration (in ms)
+                 */
+                tmp_string = strtok (NULL, " \t\n\r\f");
+                if (tmp_string == NULL) {
+                        /* TODO - Error messages */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+                /* TODO - the above doesn't check for the string to
+                   be all digits */
+
+                duration = atoi (tmp_string);
+                if (duration < 0) {
+                        /* Add an error message */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+                tmp_gesture->duration = duration;
+
+                /*
+                 * Find the timeout duration (in ms). Timeout value is the 
+                 * time within which consecutive keypress actions must be
+                 * performed by the user before the sequence is discarded.
+                 */
+                tmp_string = strtok (NULL, " \t\n\r\f");
+                if (tmp_string == NULL) {
+                        /* TODO - Error messages */
+                        free_gesture (tmp_gesture);
+                        return NULL;
+                }
+
+                /*
+                 * A gesture with an n_times value greater than 1 and a
+                 * non-positive timeout can never be triggered, so do not
+                 * accept such gestures.  The value of timeout is not used
+                 * if n_times is 1, so don't bother setting the timeout in
+                 * this case.
+                 */
+                tmp_gesture->timeout = 0;
+                if (tmp_gesture->n_times > 1) {
+                        if ((timeout=atoi (tmp_string)) <= 0) {
+                                /* Add an error message */;
+                                free_gesture (tmp_gesture);
+                                return NULL;
+                        }
+                        tmp_gesture->timeout = timeout;
+                }
+        }
+
+        /*
+         * Find service. Permit blank space so arguments can be supplied.
+         */
+        keyservice = strtok (NULL, "\n\r\f");
+        if (keyservice == NULL) {
+                /* TODO - Error messages */
+                free_gesture (tmp_gesture);
+                return NULL;
+        }
+        /* skip over initial whitespace */
+        while (*keyservice && isspace (*keyservice))
+                keyservice++;
+
+        tmp_gesture->actions = g_slist_append (tmp_gesture->actions,
+                g_strdup (keyservice));
+
+        if (debug_gestures)
+            syslog (LOG_WARNING, "gesture parsed for %s button %d", 
+                   (tmp_gesture->type == GESTURE_TYPE_MOUSE) ? "mouse" :
+                   ((tmp_gesture->type == GESTURE_TYPE_BUTTON) ? "switch" :
+                   "key"), tmp_gesture->input.button.number);
+
+        return tmp_gesture;
+}
+
+/* 
+ * These modifiers are ignored because they make no sense.
+ * .eg <NumLock>x 
+ *
+ * FIXME: [sadly, NumLock isn't always mapped to the same modifier, so the logic
+ *        below is faulty - bill]
+ */
+#define IGNORED_MODS (GDK_LOCK_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK)
+#define USED_MODS    (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | \
+                      GDK_MOD4_MASK | GDK_MOD5_MASK)
+
+static gboolean
+change_cursor_back (gpointer data)
+{
+        GdkCursor *cursor;
+
+        cursor = gdk_cursor_new (GDK_LEFT_PTR);
+        gdk_window_set_cursor (gdk_get_default_root_window (), cursor);
+        gdk_cursor_unref (cursor);
+
+        return FALSE;
+}
+
+static gint
+event_time (XEvent *ev)
+{
+        if ((ev->type == KeyPress) ||
+            (ev->type == KeyRelease))
+                return ((XKeyEvent *) ev)->time;
+        else if ((ev->type == ButtonPress) ||
+                 (ev->type == ButtonRelease))
+                return ((XButtonEvent *) ev)->time;
+        else if ((ev->type == xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
+                 (ev->type == xinput_types[XINPUT_TYPE_KEY_RELEASE]))
+                return ((XDeviceKeyEvent *) ev)->time;
+        else if ((ev->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS]) ||
+                 (ev->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE]))
+                return ((XDeviceButtonEvent *) ev)->time;
+        else
+                return 0;
+}
+
+static gint
+elapsed_time (XEvent *ev1, XEvent *ev2)
+{
+        return event_time (ev2) - event_time (ev1);
+}
+
+static gboolean
+keycodes_equal (XEvent *ev1, XEvent *ev2)
+{
+        if (ev1->type == ev2->type)
+        {
+                if (ev1->type == KeyPress || ev1->type == KeyRelease)
+                {
+                        return (((XKeyEvent *) ev1)->keycode == ((XKeyEvent *) ev2)->keycode);
+                }
+                else if (ev1->type == xinput_types[XINPUT_TYPE_KEY_PRESS] ||
+                         ev1->type == xinput_types[XINPUT_TYPE_KEY_RELEASE])
+                {
+                        return (((XDeviceKeyEvent *) ev1)->keycode == ((XDeviceKeyEvent *) ev2)->keycode);
+                }
+        }
+        return FALSE;
+}
+
+static gint
+key_gesture_compare_func (gconstpointer a, gconstpointer b)
+{
+        const Gesture *gesture = a;
+        const XEvent  *xev     = b;
+
+        if (gesture->type == GESTURE_TYPE_KEY) 
+        {
+            if (((xev->type == KeyPress) || (xev->type == KeyRelease)) &&
+                (xev->xkey.keycode == gesture->input.key.keycode) &&
+                ((xev->xkey.state & USED_MODS) == gesture->input.key.state))
+                return 0;
+            else if (((xev->type == xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
+                     (xev->type == xinput_types[XINPUT_TYPE_KEY_RELEASE])) &&
+                     (xev->xkey.keycode == gesture->input.key.keycode) &&
+                     ((xev->xkey.state & USED_MODS) == gesture->input.key.state))
+                return 0;
+            else
+                return 1;
+        }
+        else if ((gesture->type == GESTURE_TYPE_MOUSE) &&
+                 ((xev->type == ButtonPress) || (xev->type == ButtonRelease)) &&
+                 (xev->xbutton.button == gesture->input.button.number))
+                return 0;
+        else if ((gesture->type == GESTURE_TYPE_BUTTON) &&
+                 ((xev->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS]) ||
+                  (xev->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE])) &&
+                 ((XDeviceButtonEvent *) xev)->button == gesture->input.button.number)
+                return 0;
+        else
+                return 1;
+}
+
+#define event_is_gesture_type(xevent) (xevent->type == KeyPress ||\
+            xevent->type == KeyRelease ||\
+            xevent->type == ButtonPress ||\
+            xevent->type == ButtonRelease ||\
+            xevent->type == xinput_types[XINPUT_TYPE_KEY_PRESS] ||\
+            xevent->type == xinput_types[XINPUT_TYPE_KEY_RELEASE] ||\
+            xevent->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS] ||\
+            xevent->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE])
+
+static GdkFilterReturn
+gestures_filter (GdkXEvent *gdk_xevent,
+                 GdkEvent *event,
+                 gpointer data)
+{
+        static XEvent *last_event = NULL;
+        static gint    seq_count = 0;
+        XEvent        *xevent;
+        GSList        *li, *act_li;
+        Gesture       *curr_gesture;
+        XID            xinput_device;
+
+        xevent        = (XEvent *)gdk_xevent;
+        curr_gesture  = NULL;
+        xinput_device = None;
+
+        if (!event_is_gesture_type (xevent)) 
+                return GDK_FILTER_CONTINUE;
+
+        if (!last_event)
+                last_event = g_new0(XEvent, 1);
+
+        if ((xevent->type == KeyPress) ||
+            (xevent->type == xinput_types[XINPUT_TYPE_KEY_PRESS]))
+        {
+
+                if (debug_gestures)
+                        syslog (LOG_WARNING, "key press");
+
+                if (last_event->type == KeyPress &&
+                    last_event->xkey.keycode == xevent->xkey.keycode) {
+                        /* these come from auto key-repeat */
+
+                        if (debug_gestures)
+                            syslog (LOG_WARNING, "rejecting repeat");
+
+                        return GDK_FILTER_CONTINUE;
+                }
+
+                if (seq_count > 0 &&
+                    last_event->type != KeyRelease) {
+
+                    if (debug_gestures)
+                        syslog (LOG_WARNING,
+                           "last event wasn't a release, resetting seq");
+                        seq_count = 0;
+                }
+                else if (seq_count > 0 &&
+                    keycodes_equal (last_event, xevent)) {
+
+                        if (debug_gestures)
+                            syslog (LOG_WARNING,
+                                "keycode doesn't match last event, resetting seq");
+                        seq_count = 0;
+                }
+
+                /* Find the associated gesture for this keycode & state */
+                li = gesture_list_get_matches (gesture_list, xevent,
+                        key_gesture_compare_func);
+
+                if (li) {
+                        curr_gesture = li->data;
+                        if (debug_gestures)
+                            syslog (LOG_WARNING,
+                                "found a press match [%s]",
+                                curr_gesture->gesture_str);
+
+                        /* xevent time values are in milliseconds. */
+                        /* The config file spec is in ms           */
+                        if (curr_gesture->timeout > 0 && seq_count > 0 && 
+                            elapsed_time (last_event, xevent) >
+                            curr_gesture->timeout) {
+
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING,
+                                         "timeout exceeded: reset seq and gesture");
+
+                                 /* The timeout has been exceeded.
+                                   Reset the sequence. */
+                                seq_count = 0;
+                                curr_gesture = NULL;
+                        }
+                }
+        }
+        else if ((xevent->type == KeyRelease) ||
+                 (xevent->type == xinput_types[XINPUT_TYPE_KEY_RELEASE]))
+        {
+                if (debug_gestures)
+                        syslog (LOG_WARNING, "key release");
+
+                if (seq_count > 0 &&
+                    ((last_event->type != KeyPress &&
+                      last_event->type != xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
+                      ! keycodes_equal (last_event, xevent))) {
+
+                        if (debug_gestures)
+                            syslog (LOG_WARNING,
+                                "either last event not a keypress, or keycodes don't match. Resetting seq.");
+
+                        seq_count = 0;
+                }
+                
+                /*
+                 * Find the associated gesture for this keycode & state
+                 *
+                 * Note that here we check the state against the last_event,
+                 * otherwise key gestures based on modifier keys such as
+                 * Control_R won't work.
+                 */
+                li = gesture_list_get_matches (gesture_list, xevent,
+                        key_gesture_compare_func);
+
+                if (li) {
+                        curr_gesture = li->data;
+
+                        if (debug_gestures)
+                            syslog (LOG_WARNING, "found a release match [%s]",
+                                 curr_gesture->gesture_str);
+
+                        if ((curr_gesture->duration > 0) &&
+                            (elapsed_time (last_event, xevent) < curr_gesture->duration)) {
+
+                                seq_count = 0;
+                                curr_gesture = NULL;
+
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING, "setting current gesture to NULL");
+                        } else {
+                                seq_count++;
+
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING, "incrementing seq_count");
+                        }        
+                }
+        }
+        else if ((xevent->type == ButtonPress) ||
+                 (xevent->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS])) {
+                gint button = 0;
+                gint time = 0;
+
+                if (debug_gestures)
+                        syslog (LOG_WARNING, "button press");
+
+                if (xevent->type == ButtonPress) {
+                        button = xevent->xbutton.button;
+                        time = xevent->xbutton.time;
+
+                        if (debug_gestures)
+                                syslog (LOG_WARNING, "button press: %d", button);
+
+                        if (seq_count > 0 && (last_event->type != ButtonRelease))
+                                seq_count = 0;
+                        else if (seq_count > 0 && last_event->xbutton.button != button) {
+                                seq_count = 0;
+                        }
+                }
+#ifdef HAVE_XINPUT
+                else {
+                        button = ((XDeviceButtonEvent *) xevent)->button;
+                        time =  ((XDeviceButtonEvent *) xevent)->time;
+                        if (debug_gestures)
+                                syslog (LOG_WARNING, "xinput button press: %d", button);
+                        if (seq_count > 0 &&
+                            last_event->type != xinput_types[XINPUT_TYPE_BUTTON_RELEASE]) {
+                                seq_count = 0;
+                        }
+                        else if (seq_count > 0 &&
+                                ((XDeviceButtonEvent *) last_event)->button != button) {
+                                seq_count = 0;
+                        }
+                }
+#endif
+                
+                /*
+                 * Find the associated gesture for this button.
+                 */
+                li = gesture_list_get_matches (gesture_list, xevent,
+                        key_gesture_compare_func);
+                if (li) {
+                        if (debug_gestures)
+                                syslog (LOG_WARNING, "found match for press");
+
+                        curr_gesture = li->data;
+
+                        if (curr_gesture->timeout > 0 && seq_count > 0) {
+
+                                /* xevent time values are in milliseconds. */
+                                /* The config file spec is in ms           */
+                                if (elapsed_time (last_event, xevent) > curr_gesture->timeout) {
+
+                                         /* Timeout has elapsed. Reset the sequence. */
+                                        seq_count    = 0;
+                                        curr_gesture = NULL;
+
+                                        if (debug_gestures)
+                                            syslog (LOG_WARNING, "gesture timed out.");
+                                }
+                        }
+                }
+                else if (debug_gestures)
+                         syslog (LOG_WARNING, "no match for press %d", button);
+        }
+        else if ((xevent->type == ButtonRelease) ||
+                 (xevent->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE]))
+        {
+                gint button = 0;
+                gint time = 0;
+                
+                if (debug_gestures)
+                        syslog (LOG_WARNING, "button release");
+
+                if (xevent->type == ButtonRelease) 
+                {
+                        button = xevent->xbutton.button;
+                        time = xevent->xbutton.time;
+                        if (seq_count > 0 &&
+                            (last_event->type != ButtonPress ||
+                             last_event->xbutton.button != button)) {
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING,
+                                        "resetting count to zero, based on failure to match last event.");
+                                seq_count = 0;
+                        }
+                }
+#ifdef HAVE_XINPUT
+                else
+                {
+                        button        = ((XDeviceButtonEvent *)(xevent))->button;
+                        time          = ((XDeviceButtonEvent *)(xevent))->time;
+                        xinput_device = ((XDeviceButtonEvent *)(xevent))->deviceid;
+
+                        if (seq_count > 0 &&
+                            (last_event->type != xinput_types[XINPUT_TYPE_BUTTON_PRESS] ||
+                             ((XDeviceButtonEvent *) last_event)->button != button)) {
+
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING,
+                                         "resetting count to zero, based on failure to match last input event.");
+
+                                seq_count = 0;
+                        }
+                }
+#endif
+
+                li = gesture_list_get_matches (gesture_list, xevent,
+                        key_gesture_compare_func);
+
+                if (li) {
+                        if (debug_gestures)
+                            syslog (LOG_WARNING, "found match for release");
+                        curr_gesture = li->data;
+                        if ((curr_gesture->duration > 0) &&
+                            (elapsed_time (last_event, xevent) < curr_gesture->duration)) {
+                                seq_count = 0;
+                                curr_gesture = NULL;
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING, "insufficient duration.");
+                        } else {
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING, "duration OK");
+                                seq_count++;
+                        }
+                }
+                else if (debug_gestures)
+                        syslog (LOG_WARNING, "no match for release - button %d",
+                                button);
+        }
+
+        /*
+         * Did this event complete any gesture sequences?
+         */
+        last_event = memcpy (last_event, xevent, sizeof (XEvent));
+        if (curr_gesture) {
+                if (seq_count != curr_gesture->n_times) {
+
+                        if (debug_gestures)
+                            syslog (LOG_WARNING,
+                                 "waiting for %d more repetitions...",
+                                 curr_gesture->n_times - seq_count);
+
+                        return GDK_FILTER_CONTINUE;
+                } else {
+                        gboolean retval;
+                        gchar **argv = NULL;
+                        gchar **envp = NULL; 
+
+                        if (debug_gestures)
+                                syslog (LOG_WARNING, "gesture complete!");
+
+                        seq_count = 0;
+                        for (act_li = curr_gesture->actions;
+                             act_li != NULL; act_li = act_li->next) {
+                                gchar *action = (gchar *)act_li->data;
+#ifdef HAVE_CTRUN
+                                gchar *ctrun;
+#endif
+
+                                g_return_val_if_fail (action != NULL, GDK_FILTER_CONTINUE);
+
+#ifdef HAVE_CTRUN
+                                ctrun = g_strdup_printf (
+                                        "/bin/sh -c \"/usr/bin/ctrun -l child -i none %s\"",
+                                        action);
+                                if (!g_shell_parse_argv (ctrun, NULL, &argv, NULL)) {
+                                        g_free (ctrun);
+                                        continue;
+                                }
+
+                                g_free (ctrun);
+#else
+                                if (!g_shell_parse_argv (action, NULL, &argv, NULL))
+                                        continue;
+#endif
+
+                                envp = get_exec_environment (xevent);
+
+                                if (debug_gestures)
+                                    syslog (LOG_WARNING, "Action is %s", action);
+
+                                retval = g_spawn_async (NULL,
+                                                        argv,
+                                                        envp,
+                                                        G_SPAWN_SEARCH_PATH,
+                                                        NULL,
+                                                        NULL,
+                                                        NULL,
+                                                        NULL);
+
+                                g_strfreev (argv);
+                                g_strfreev (envp); 
+
+                                if ( ! retval) {
+                                        GtkWidget *dialog = 
+                                                gtk_message_dialog_new (NULL, 0,
+                                                        GTK_MESSAGE_ERROR,
+                                                        GTK_BUTTONS_OK,
+                                                        _("Error while trying to run (%s)\n"\
+                                                        "which is linked to (%s)"),
+                                                        action,
+                                                        curr_gesture->gesture_str);
+                                        gtk_dialog_set_has_separator (GTK_DIALOG (dialog),
+                                                                      FALSE);
+                                        g_signal_connect (dialog, "response",
+                                                G_CALLBACK (gtk_widget_destroy),
+                                                NULL);
+                                        gtk_widget_show (dialog);
+                                } else {
+                                        GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
+                                        gdk_window_set_cursor (gdk_get_default_root_window (),
+                                                cursor);
+                                        gdk_cursor_unref (cursor);
+                                        g_timeout_add (2000,
+                                                       change_cursor_back,
+                                                       NULL);
+                                }
+                        }
+                           return GDK_FILTER_CONTINUE;
+                }
+        }
+        return GDK_FILTER_CONTINUE;
+}
+
+                        
+static gint
+is_mouseX (const gchar *string)
+{
+        if ((string[0] == '<') &&
+          (string[1] == 'm' || string[1] == 'M') &&
+          (string[2] == 'o' || string[2] == 'O') &&
+          (string[3] == 'u' || string[3] == 'U') &&
+          (string[4] == 's' || string[4] == 'S') &&
+          (string[5] == 'e' || string[5] == 'E') &&
+          (isdigit (string[6]) && 
+                   (atoi (&string[6]) > 0) && 
+                (atoi (&string[6]) < 6)) &&
+          (string[7] == '>'))
+                return atoi (&string[6]);
+        else
+                return 0;
+}
+
+static gint
+is_switchX (const gchar *string)
+{
+        if ((string[0] == '<') &&
+            (string[1] == 's' || string[1] == 'S') &&
+            (string[2] == 'w' || string[2] == 'W') &&
+            (string[3] == 'i' || string[3] == 'I') &&
+            (string[4] == 't' || string[4] == 'T') &&
+            (string[5] == 'c' || string[5] == 'C') &&
+            (string[6] == 'h' || string[6] == 'H') &&
+            (isdigit (string[7]) && 
+            (atoi (&string[7]) > 0) && 
+            (atoi (&string[7]) < 6)) &&
+            (string[8] == '>'))
+                return atoi (&string[7]);
+        else
+                return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+        if (g_getenv ("GDM_DEBUG_GESTURES") != NULL)
+                debug_gestures = TRUE;
+
+        if (debug_gestures) {
+                openlog ("gesturelistener", LOG_PID, LOG_DAEMON);
+                syslog (LOG_WARNING, "keymouselistener loaded.");
+        }
+
+        gtk_init (&argc, &argv);
+
+        create_event_watcher ();
+
+        gtk_main ();
+
+        return 0;
+}
Index: a11y/Makefile.am
===================================================================
--- a11y/Makefile.am	(revision 0)
+++ a11y/Makefile.am	(revision 0)
@@ -0,0 +1,81 @@
+## Process this file with automake to produce makefile.in
+
+# TODO:
+#
+# - Need some magic to install desktop file to
+#   /usr/share/gdm/autostart/LoginWindow.  I tried this
+#   but it didn't seem to start this program.
+# - The dwellmouselistener is non-functional.  It doesn't
+#   attach to any windows to listen for the gesture yet.
+
+gestureconfdir = $(gdmconfdir)/gestures
+
+INCLUDES = \
+	-I.						\
+	-I..						\
+	-DGDMCONFDIR=\"${gdmconfdir}\"			\
+	$(GTK_CFLAGS)
+
+libexec_PROGRAMS =                      \
+        gdm-gesture-keybutton		\
+	gdm-gesture-dwell
+
+gdm_gesture_keybutton_SOURCES = \
+	gdm-gesture-keybutton.c
+
+gdm_gesture_keybutton_LDADD = \
+	$(COMMON_LIBS) \
+	$(GTK_LIBS)
+
+gdm_gesture_dwell_SOURCES = \
+	gdm-gesture-dwell.c
+
+gdm_gesture_dwell_LDADD = \
+	$(COMMON_LIBS) \
+	$(GTK_LIBS)
+
+noinst_DATA = \
+	AccessKeyButtonEvents \
+	AccessDwellEvents
+
+EXTRA_DIST = \
+	AccessKeyButtonEvents.in \
+	AccessDwellEvents.in
+
+CLEANFILES = AccessKeyButtonEvents AccessDwellEvents
+
+AccessKeyButtonEvents: $(srcdir)/AccessKeyButtonEvents.in
+	sed -e 's,[ ]AT_BINDIR[@],$(AT_BINDIR),g' \
+		<$(srcdir)/AccessKeyButtonEvents.in >AccessKeyButtonEvents
+
+AccessDwellEvents: $(srcdir)/AccessDwellEvents.in
+	sed -e 's,[ ]AT_BINDIR[@],$(AT_BINDIR),g' \
+		<$(srcdir)/AccessDwellEvents.in >AccessDwellEvents
+
+uninstall-hook: 
+	rm -f $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents \
+	$(DESTDIR)$(gestureconfdir)/factory-AccessKeyButtonEvents \
+	$(DESTDIR)$(gestureconfdir)/AccessDwellEvents \
+	$(DESTDIR)$(gestureconfdir)/factory-AccessDwellEvents
+
+install-data-hook: AccessKeyButtonEvents AccessDwellEvents
+	if test '!' -d $(DESTDIR)$(gestureconfdir); then \
+		$(mkinstalldirs) $(DESTDIR)$(gestureconfdir); \
+		chmod 755 $(DESTDIR)$(gestureconfdir); \
+	fi
+	-if cmp -s $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents $(DESTDIR)$(gestureconfdir)/factory-AccessKeyButtonEvents 2>&1 /dev/null ; then \
+		cp -f $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents.orig; \
+		$(INSTALL_DATA) AccessKeyButtonEvents $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents; \
+	fi
+	if test '!' -f $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents; then \
+		$(INSTALL_DATA) AccessKeyButtonEvents $(DESTDIR)$(gestureconfdir)/AccessKeyButtonEvents; \
+	fi
+	$(INSTALL_DATA) AccessKeyButtonEvents $(DESTDIR)$(gestureconfdir)/factory-AccessKeyButtonEvents
+	-if cmp -s $(DESTDIR)$(gestureconfdir)/AccessDwellEvents $(DESTDIR)$(gestureconfdir)/factory-AccessDwellEvents 2>&1 /dev/null ; then \
+		cp -f $(DESTDIR)$(gestureconfdir)/AccessDwellEvents $(DESTDIR)$(gestureconfdir)/AccessDwellEvents.orig; \
+		$(INSTALL_DATA) AccessDwellEvents $(DESTDIR)$(gestureconfdir)/AccessDwellEvents; \
+	fi
+	if test '!' -f $(DESTDIR)$(gestureconfdir)/AccessDwellEvents; then \
+		$(INSTALL_DATA) AccessDwellEvents $(DESTDIR)$(gestureconfdir)/AccessDwellEvents; \
+	fi
+	$(INSTALL_DATA) AccessDwellEvents $(DESTDIR)$(gestureconfdir)/factory-AccessDwellEvents
Index: a11y/gdm-gesture-dwell.c
===================================================================
--- a11y/gdm-gesture-dwell.c	(revision 0)
+++ a11y/gdm-gesture-dwell.c	(revision 0)
@@ -0,0 +1,673 @@
+/* GDM - The Gnome Display Manager
+ * Copyright (C) 2003-2008  Sun Microsystems 
+ *
+ * Written by: Brian A. Cameron  <Brian Cameron sun com>
+ *             Niall Power <Niall Power sun com>
+ *             Bill Haneman <Bill Haneman sun com>
+ *
+ * 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
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <math.h>
+
+#include <glib.h>
+#include <gmodule.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <X11/Xlib.h>
+ 
+/*
+ * Note that CONFIGFILE will have to be changed to something more generic
+ * if this module is ever moved outside of gdm.
+ */
+
+#define CONFIGFILE GDMCONFDIR "/gestures/AccessDwellEvents"
+#define iseol(ch) ((ch) == '\r' || (ch) == '\f' || (ch) == '\0' || \
+                   (ch) == '\n')
+
+typedef enum
+{
+        BINDING_DWELL_BORDER_TOP    = 1 << 0,
+        BINDING_DWELL_BORDER_BOTTOM = 1 << 1,
+        BINDING_DWELL_BORDER_RIGHT  = 1 << 2,
+        BINDING_DWELL_BORDER_LEFT   = 1 << 3,
+        BINDING_DWELL_BORDER_ERROR  = 1 << 4
+} BindingType;
+
+typedef enum
+{
+        BINDING_DWELL_DIRECTION_IN    = 1 << 0,
+        BINDING_DWELL_DIRECTION_OUT   = 1 << 1,
+        BINDING_DWELL_DIRECTION_ERROR = 1 << 2
+} BindingDirection;
+
+typedef struct {
+      int num_gestures;
+      BindingType *gesture; 
+      BindingDirection start_direction;
+} Dwell;
+
+typedef struct {
+        Dwell input;
+        char *binding_str;
+        GSList *actions;
+        guint timeout;
+} Binding;
+
+typedef struct {
+         BindingType type;
+         BindingDirection direction;
+         guint32 time;
+} Crossings;
+
+static int        lineno = 0;
+static GSList    *binding_list = NULL;
+extern char     **environ;
+static guint      enter_signal_id = 0;
+static guint      leave_signal_id = 0;
+static Crossings *crossings = NULL;
+static guint      max_crossings = 0;
+static int        cross_pos = 0;
+ 
+static void      create_event_watcher (void);
+static void      load_bindings (gchar *path);
+static gchar *   screen_exec_display_string (GdkScreen *screen, const char *old);
+static gchar **  get_exec_environment (GdkScreen *screen);
+static Binding * parse_line (gchar *buf);
+static gboolean  binding_already_used (Binding *binding);
+static gboolean  debug_gestures = FALSE;
+
+BindingType get_binding_type (char c);
+BindingDirection get_binding_direction (char c);
+
+static gchar *
+screen_exec_display_string (GdkScreen *screen, const char *old)
+{
+        GString     *str;
+        const gchar *old_display;
+        gchar       *retval;
+        gchar       *p;
+
+        g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+        old_display = gdk_display_get_name (gdk_screen_get_display (screen));
+
+        str = g_string_new ("DISPLAY=");
+        g_string_append (str, old_display);
+
+        p = strrchr (str->str, '.');
+        if (p && p >  strchr (str->str, ':'))
+                g_string_truncate (str, p - str->str);
+
+        g_string_append_printf (str, ".%d", gdk_screen_get_number (screen));
+
+        retval = str->str;
+
+        g_string_free (str, FALSE);
+
+        return retval;
+}
+
+/**
+ * get_exec_environment:
+ *
+ * Description: Modifies the current program environment to
+ * ensure that $DISPLAY is set such that a launched application
+ * inheriting this environment would appear on screen.
+ *
+ * Returns: a newly-allocated %NULL-terminated array of strings or
+ * %NULL on error. Use g_strfreev () to free it.
+ *
+ * mainly ripped from egg_screen_exec_display_string in
+ * gnome-panel/egg-screen-exec.c
+ **/
+static gchar **
+get_exec_environment (GdkScreen *screen)
+{
+        gchar **retval;
+        gint    i;
+        gint    display_index;
+
+        retval        = NULL;
+        display_index = -1;
+
+        g_assert (GDK_IS_SCREEN (screen));
+
+        for (i=0; environ [i]; i++)
+                if (strncmp (environ [i], "DISPLAY", 7) == 0)
+                        display_index = i;
+
+        if (display_index == -1)
+                display_index = i++;
+
+        retval = g_new0 (char *, i + 1);
+
+        for (i=0; environ [i]; i++)
+                if (i == display_index)
+                        retval [i] = screen_exec_display_string (screen,
+                                environ[i]);
+                else
+                        retval [i] = g_strdup (environ [i]);
+
+        retval [i] = NULL;
+
+        return retval;
+}
+
+BindingType
+get_binding_type (char c)
+{
+        BindingType rc;
+
+        if (c == toupper ('T'))
+                rc = BINDING_DWELL_BORDER_TOP;
+        else if (c == toupper ('B'))
+                rc = BINDING_DWELL_BORDER_BOTTOM;
+        else if (c == toupper ('R'))
+                rc = BINDING_DWELL_BORDER_RIGHT;
+        else if (c == toupper ('L'))
+                rc = BINDING_DWELL_BORDER_LEFT;
+        else
+                rc = BINDING_DWELL_BORDER_ERROR;
+
+        return rc;
+}
+
+BindingDirection
+get_binding_direction (char c)
+{
+        BindingDirection rc;
+
+        if (c == toupper ('I'))
+                rc = BINDING_DWELL_DIRECTION_IN;
+        else if (c == toupper ('O'))
+                rc = BINDING_DWELL_DIRECTION_OUT;
+        else
+                rc = BINDING_DWELL_DIRECTION_ERROR;
+
+        return rc;
+}
+
+static void
+free_binding (Binding *binding)
+{
+        if (binding == NULL)
+                return;
+
+        g_slist_foreach (binding->actions, (GFunc)g_free, NULL);
+        g_slist_free (binding->actions);
+        g_free (binding->binding_str);
+        g_free (binding->input.gesture);
+        g_free (binding);
+}
+
+static Binding *
+parse_line (gchar *buf)
+{
+        static GdkDisplay *display;
+        gchar             *keystring, *keyservice;
+        Binding           *tmp_binding;
+
+        tmp_binding = NULL;
+        display     = NULL;
+
+        lineno++;
+  
+        if (!display) {
+                if ((display = gdk_display_get_default ()) == NULL)
+                        return NULL;
+        }
+
+        if ((*buf == '#') || (iseol (*buf)) || (buf == NULL))
+                return NULL;
+
+        /* Find the binding name */
+        keystring = strtok (buf, " \t\n\r\f");
+        if (keystring == NULL) {
+                /* TODO - Add an error message */
+                return NULL;
+        }
+
+        tmp_binding = g_new0 (Binding, 1);
+        tmp_binding->binding_str = g_strdup (keystring);
+
+        if (strcmp (tmp_binding->binding_str, "<Add>") != 0) {
+                BindingType bt;
+                BindingDirection bd;
+                guint timeout;
+                gchar *tmp_string;
+                int i, j;
+
+                tmp_binding->input.gesture = g_new0 (BindingType,
+                        strlen (tmp_binding->binding_str));
+
+                j=0;
+                for (i=0; i < strlen (tmp_binding->binding_str); i++) {
+                        bt = get_binding_type (tmp_binding->binding_str[i]);
+
+                        if (bt == BINDING_DWELL_BORDER_ERROR) {
+                                if (debug_gestures)
+                                        syslog (LOG_WARNING,
+                                                "Invalid value in binding %s",
+                                                tmp_binding->binding_str);
+
+                                continue;
+                        }
+
+                        tmp_binding->input.gesture[j++] = bt;
+                }
+                tmp_binding->input.num_gestures = j;
+
+                if (j > max_crossings)
+                        max_crossings = j;
+
+                /* [TODO] Need to clean up here. */
+                tmp_string = strtok (NULL, " \t\n\r\f");
+                if (tmp_string == NULL) {
+                        /* TODO - Add an error message */
+                        free_binding (tmp_binding);
+                        return NULL;
+                }
+
+                bd = get_binding_direction (tmp_string[0]);
+
+                if (bd == BINDING_DWELL_DIRECTION_ERROR) {
+                        if (debug_gestures)
+                            syslog (LOG_WARNING, "Invalid value in binding %s",
+                               tmp_binding->binding_str);
+                } else {
+                        tmp_binding->input.start_direction = bd;
+                }
+
+                /*
+                 * Find the timeout duration (in ms). Timeout value is the 
+                 * time within which consecutive keypress actions must be
+                 * performed by the user before the sequence is discarded.
+                 */
+
+                tmp_string = strtok (NULL, " \t\n\r\f");
+                if (tmp_string == NULL) {
+                        /* TODO - Add an error message */
+                        free_binding (tmp_binding);
+                        return NULL;
+                }
+
+                timeout = atoi (tmp_string);
+                if (timeout <= 0) {
+                        /* TODO - Add an error message */;
+                        free_binding (tmp_binding);
+                        return NULL;
+                }
+                tmp_binding->timeout = timeout;
+        }
+
+        /* Find service. Permit blank space so arguments can be supplied. */
+        keyservice = strtok (NULL, "\n\r\f");
+        if (keyservice == NULL) {
+                /* TODO - Add an error message */
+                free_binding (tmp_binding);
+                return NULL;
+         }
+
+        /* skip over initial whitespace */
+        while (*keyservice && isspace (*keyservice))
+                keyservice++;
+
+        tmp_binding->actions = g_slist_append (tmp_binding->actions,
+                g_strdup (keyservice));
+
+        return tmp_binding;
+}
+
+static gboolean
+binding_already_used (Binding *binding)
+{
+        GSList *li;
+
+        for (li=binding_list; li != NULL; li = li->next) {
+                Binding *tmp_binding = (Binding*) li->data;
+
+                if (tmp_binding != binding &&
+                    tmp_binding->input.start_direction == binding->input.start_direction)
+                {
+                        int i;
+
+                        for (i=0; i < tmp_binding->input.num_gestures; i++) {
+                                if (tmp_binding->input.gesture !=
+                                    binding->input.gesture)
+                                        break;
+                        }
+
+                        if (i == tmp_binding->input.num_gestures)
+                                return TRUE;
+                }
+        }
+        return FALSE;
+}
+
+static void
+load_bindings (gchar *path)
+{
+        Binding *tmp_binding;
+        FILE    *fp;
+        gchar    buf[1024];
+
+        fp = fopen (path, "r");
+        if (fp == NULL) {
+                /* TODO - I18n */
+                if (debug_gestures)
+                        syslog (LOG_WARNING,
+                                "Cannot open bindings file: %s", path);
+                return;
+        }
+
+        while (fgets (buf, sizeof (buf), fp) != NULL) {
+                tmp_binding = (Binding *)parse_line (buf);
+
+                if (tmp_binding) {
+                        /* Is the key already associated with an existing */
+                        /* binding? */
+                        if (strcmp (tmp_binding->binding_str, "<Add>") == 0) {
+                                /* Add another action to the last binding */
+                                Binding *last_binding;
+                                GSList *last_item = g_slist_last (binding_list);
+
+                                /* If there is no last_item to add onto */
+                                /* ignore the entry */
+                                if (last_item) {
+                                    last_binding = (Binding *)last_item->data;
+
+                                    /* Add the action to the last */
+                                    /*  binding's actions list */
+                                    last_binding->actions =
+                                      g_slist_append (last_binding->actions,
+                                      g_strdup ((gchar *)tmp_binding->actions->data));
+                                }
+                                free_binding (tmp_binding);
+
+                                /* Ignore duplicate bindings */
+                        } else if (!binding_already_used (tmp_binding))
+                                binding_list = g_slist_append (binding_list,
+                                        tmp_binding);
+                        else
+                                free_binding (tmp_binding);
+                }
+        }
+        fclose (fp);
+}
+
+static gboolean
+change_cursor_back (gpointer data)
+{
+        GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);
+        gdk_window_set_cursor (gdk_get_default_root_window (), cursor);
+        gdk_cursor_unref (cursor);
+
+        return FALSE;
+}
+
+
+static gboolean
+leave_enter_emission_hook (GSignalInvocationHint        *ihint,
+                           guint                        n_param_values,
+                           const GValue                 *param_values,
+                           gpointer                     data)
+{
+        GdkEventCrossing *event;
+        GObject          *object;
+        GtkWidget        *widget;
+        GSList           *li;
+        GdkRectangle      rect;
+        double            mid_x, mid_y;
+        int               i;
+
+        object = g_value_get_object (param_values + 0);
+        event  = g_value_get_boxed (param_values + 1);
+        widget = GTK_WIDGET (object);
+
+        if (event->detail == GDK_NOTIFY_INFERIOR ||
+            !GTK_IS_WINDOW (object) || !GTK_WIDGET_TOPLEVEL (object)) {
+                return TRUE;
+        }
+
+        gdk_window_get_frame_extents (widget->window, &rect);
+
+        mid_x = rect.x + (rect.width  / 2);
+        mid_y = rect.y + (rect.height / 2);
+
+        /* avoid division by 0 */
+        if (fabs (event->x_root - mid_x) <= 0.001) {
+                if (event->x_root < mid_x)
+                        crossings[cross_pos].type = BINDING_DWELL_BORDER_LEFT;
+                else
+                        crossings[cross_pos].type = BINDING_DWELL_BORDER_RIGHT;
+        } else {
+                double slope = (event->y_root - mid_y) / (event->x_root - mid_x);
+
+                if (event->y_root < mid_y) {
+                        if (slope > 1 || slope < -1)
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_TOP;
+                        else if (slope >= 0)
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_LEFT;
+                        else
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_RIGHT;
+                } else {
+                        if (slope > 1 || slope < -1)
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_BOTTOM;
+                        else if (slope >= 0)
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_RIGHT;
+                        else
+                                crossings[cross_pos].type = BINDING_DWELL_BORDER_LEFT;
+                }
+        }
+
+        if (ihint->signal_id == enter_signal_id)
+                crossings[cross_pos].direction = BINDING_DWELL_DIRECTION_IN;
+        else if (ihint->signal_id == leave_signal_id)
+                crossings[cross_pos].direction = BINDING_DWELL_DIRECTION_OUT;
+
+        if (debug_gestures) {
+                if (crossings[cross_pos].type == BINDING_DWELL_BORDER_BOTTOM) 
+                        syslog (LOG_WARNING, "Crossing bottom.");
+                else if (crossings[cross_pos].type == BINDING_DWELL_BORDER_TOP) 
+                        syslog (LOG_WARNING, "Crossing top.");
+                else if (crossings[cross_pos].type == BINDING_DWELL_BORDER_LEFT) 
+                        syslog (LOG_WARNING, "Crossing left.");
+                else if (crossings[cross_pos].type == BINDING_DWELL_BORDER_RIGHT) 
+                        syslog (LOG_WARNING, "Crossing right.");
+
+                if (crossings[cross_pos].direction == BINDING_DWELL_DIRECTION_IN) 
+                        syslog (LOG_WARNING, "Crossing in.");
+                else if (crossings[cross_pos].direction == BINDING_DWELL_DIRECTION_OUT) 
+                        syslog (LOG_WARNING, "Crossing out.");
+        }
+
+        crossings[cross_pos].time = event->time;
+
+        /* Check to see if a gesture has been completed */
+        for (li=binding_list; li != NULL; li = li->next) {
+                Binding *curr_binding = (Binding *) li->data;
+                GSList *act_li;
+                gboolean retval;
+                gchar **argv  = NULL;
+                gchar **envp  = NULL;
+                int start_pos = (cross_pos - curr_binding->input.num_gestures + 1 +
+                        max_crossings) % max_crossings;
+
+                if (debug_gestures) {
+                        syslog (LOG_WARNING, "Checking against registered gestures");
+                }
+
+                /* being anal here */
+                if (start_pos < 0)
+                        start_pos = 0;
+
+                /* check direction */
+                if (curr_binding->input.start_direction != crossings[start_pos].direction) 
+                        continue;
+
+                /* check borders */
+                for (i=0; i < curr_binding->input.num_gestures; i++) {
+                        if (curr_binding->input.gesture[i] !=
+                            crossings[(start_pos + i) % max_crossings].type)
+                                break; 
+                }
+
+                if (i != curr_binding->input.num_gestures)
+                        continue;
+
+                /* check timeout values */
+                for (i=1; i < curr_binding->input.num_gestures; i++) {
+                        int cur_pos  = (start_pos + i) % max_crossings;
+                        int prev_pos = (start_pos + i - 1) % max_crossings; 
+                        guint32 diff_time = crossings[cur_pos].time -
+                                            crossings[prev_pos].time;
+  
+                        if (curr_binding->timeout != 0 &&
+                            curr_binding->timeout < diff_time)
+                                break; 
+                }
+
+                if (i != curr_binding->input.num_gestures)
+                        continue;
+
+                /* gesture recognized */
+                if (debug_gestures) {
+                        syslog (LOG_WARNING, "Found gesture");
+                }
+
+                for (act_li=curr_binding->actions; act_li != NULL; act_li=act_li->next) {
+                        gchar *action = (gchar *)act_li->data;
+#ifdef HAVE_CTRUN
+                        gchar *ctrun;
+#endif
+
+                        g_return_val_if_fail (action != NULL, TRUE);
+
+#ifdef HAVE_CTRUN
+                        ctrun = g_strdup_printf (
+                                "/bin/sh -c \"/usr/bin/ctrun -l child -i none %s\"",
+                                action);
+                        if (!g_shell_parse_argv (ctrun, NULL, &argv, NULL)) {
+                                g_free (ctrun);
+                                continue;
+                        }
+                                
+                        g_free (ctrun);
+#else
+                        if (!g_shell_parse_argv (action, NULL, &argv, NULL))
+                                continue;
+#endif
+
+                        envp = get_exec_environment (gtk_window_get_screen
+                                                     (GTK_WINDOW(widget)));
+
+                        if (debug_gestures)
+                                syslog (LOG_WARNING, "Action is %s", action);
+
+                        retval = g_spawn_async (NULL, argv, envp,
+                                G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
+
+                        g_strfreev (argv);
+                        g_strfreev (envp);
+
+                        if ( ! retval) {
+                                GtkWidget *dialog = gtk_message_dialog_new (NULL,
+                                                0, GTK_MESSAGE_ERROR,
+                                                GTK_BUTTONS_OK,
+                                                _("Error while trying to run (%s)\n"\
+                                                "which is linked to (%s)"),
+                                                action, curr_binding->binding_str);
+                                gtk_dialog_set_has_separator (GTK_DIALOG (dialog),
+                                        FALSE);
+                                g_signal_connect (dialog, "response",
+                                        G_CALLBACK (gtk_widget_destroy), NULL);
+                                gtk_widget_show (dialog);
+                        } else {
+                                GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
+                                gdk_window_set_cursor (gdk_get_default_root_window (),
+                                        cursor);
+                                gdk_cursor_unref (cursor);
+                                g_timeout_add (2000, change_cursor_back, NULL);
+                        }
+                }
+        }
+
+        cross_pos = (cross_pos + 1) % max_crossings;
+
+        return TRUE;
+}
+
+static void
+create_event_watcher (void)
+{
+        GdkDisplay *display;
+        gint        i;
+
+        display = gdk_display_get_default ();
+        if (!display)
+                return;
+
+        load_bindings (CONFIGFILE);
+
+        crossings = g_new0(Crossings, max_crossings);
+
+        for (i=0; i < max_crossings; i++) {
+                crossings[i].type      = BINDING_DWELL_BORDER_ERROR;
+                crossings[i].direction = BINDING_DWELL_DIRECTION_ERROR;
+                crossings[i].time      = 0;
+        }
+
+        /* set up emission hook */
+        gtk_type_class (GTK_TYPE_WIDGET);
+        enter_signal_id = g_signal_lookup ("enter-notify-event", GTK_TYPE_WIDGET);
+        leave_signal_id = g_signal_lookup ("leave-notify-event", GTK_TYPE_WIDGET);
+
+        g_signal_add_emission_hook (enter_signal_id, 0, 
+                leave_enter_emission_hook, NULL, (GDestroyNotify) NULL); 
+        g_signal_add_emission_hook (leave_signal_id, 0,
+                leave_enter_emission_hook, NULL, (GDestroyNotify) NULL); 
+}
+
+int
+main (int argc, char *argv[])
+{
+        if (g_getenv ("GDM_DEBUG_GESTURES") != NULL)
+                debug_gestures = TRUE;
+
+        if (debug_gestures) {
+                openlog ("gesturelistener", LOG_PID, LOG_DAEMON);
+                syslog (LOG_WARNING, "dwellmouselistener loaded.");
+        }
+
+        gtk_init (&argc, &argv);
+
+        create_event_watcher ();
+
+        gtk_main ();
+
+        return 0;
+}
+
+/* EOF */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]