Added C binding for key synthesis, and a new test/demo program
authorbillh <billh@e2bd861d-eb25-0410-b326-f6ed22b6b98c>
Sun, 11 Nov 2001 00:28:07 +0000 (00:28 +0000)
committerbillh <billh@e2bd861d-eb25-0410-b326-f6ed22b6b98c>
Sun, 11 Nov 2001 00:28:07 +0000 (00:28 +0000)
that creates a simple onscreen keyboard (mouse-operated) and
injects events into the currently focussed window.
Added support for several types of key synthesis: KEY_PRESS,
KEY_RELEASE, KEY_PRESSRELEASE (pair), KEY_SYM (pair).

git-svn-id: http://svn.gnome.org/svn/at-spi/trunk@84 e2bd861d-eb25-0410-b326-f6ed22b6b98c

ChangeLog
configure.in
cspi/spi.h
cspi/spi_registry.c
idl/Accessibility_Registry.idl
idl/Registry.idl
libspi/deviceeventcontroller.c
registryd/deviceeventcontroller.c
test/Makefile.am
test/keysynth-demo.c [new file with mode: 0644]

index 40ffba9..19db2dd 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,22 @@
-2001-11-09  Bill Haneman <bill.haneman@sun.com
+2001-11-11  Bill Haneman <bill.haneman@sun.com>
+
+       * test/Makefile.am:
+       * test/keysynth-demo.c:
+       Added new test of key synthesis, which creates a simple
+       (mouse-operated) onscreen keyboard.  It inserts key events into
+       the currently-focused window, thus it does not grab keyboard focus
+       itself.
+
+       * cspi/spi_registry.c:
+       Added C binding for AT-SPI generateKeyEvent.
+
+       * libspi/deviceeventcontroller.c:
+       Added call to XFilterEvent so that key listener works with XIM (we
+       hope).  Added event_synth_type to generateKeyEvent, so that we can
+       produce KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE (pair), or
+       synthesize a press/release pair for KeySyms.    
+       
+2001-11-09  Bill Haneman <bill.haneman@sun.com>
 
        * libspi/Makefile.am: 
        * registryd/Makefile.am:
 
        * libspi/Makefile.am: 
        * registryd/Makefile.am:
index 9a30f0d..55383b0 100644 (file)
@@ -91,7 +91,7 @@ PKG_CHECK_MODULES(REGISTRYD, bonobo-activation-2.0 >= 0.9.1 libbonobo-2.0 >= 1.9
 AC_SUBST(REGISTRYD_LIBS)
 AC_SUBST(REGISTRYD_CFLAGS)
 
 AC_SUBST(REGISTRYD_LIBS)
 AC_SUBST(REGISTRYD_CFLAGS)
 
-PKG_CHECK_MODULES(TESTS, bonobo-activation-2.0 >= 0.9.1 libbonobo-2.0 >= 1.97.0 ORBit-2.0  >= 2.3.94 atk >= 0.2)
+PKG_CHECK_MODULES(TESTS, bonobo-activation-2.0 >= 0.9.1 libbonobo-2.0 >= 1.97.0 ORBit-2.0  >= 2.3.94 atk >= 0.2 gtk+-2.0 >= 1.3.2)
 AC_SUBST(TESTS_LIBS)
 AC_SUBST(TESTS_CFLAGS)
 
 AC_SUBST(TESTS_LIBS)
 AC_SUBST(TESTS_CFLAGS)
 
index f83d20c..b9c559f 100644 (file)
@@ -62,6 +62,13 @@ typedef enum _KeyEventType {
   KEY_RELEASED
 } KeyEventType;
 
   KEY_RELEASED
 } KeyEventType;
 
+typedef enum _KeySynthType {
+  KEY_PRESS,
+  KEY_RELEASE, 
+  KEY_PRESSRELEASE,
+  KEY_SYM
+} KeySynthType;
+
 typedef enum _KeyListenerSyncType {
   KEYLISTENER_SYNCHRONOUS = 1,
   KEYLISTENER_CANCONSUME = 2,
 typedef enum _KeyListenerSyncType {
   KEYLISTENER_SYNCHRONOUS = 1,
   KEYLISTENER_CANCONSUME = 2,
@@ -332,16 +339,16 @@ registerKeystrokeListener (KeystrokeListener *listener,
  * generateKeyEvent:
  * @keycode: a #long indicating the keycode of the key event
  *           being synthesized.
  * generateKeyEvent:
  * @keycode: a #long indicating the keycode of the key event
  *           being synthesized.
- * @meta: a #long indicating the key modifiers to be sent
- *        with the event, if any.
+ * @synth_type: a #KeySynthType indicating whether this should be a
+ *        KEY_PRESS, KEY_RELEASE, both (KEY_PRESSRELEASE), or
+ *        a press/release pair for a KEYSYM.
  *
  * Synthesize a keyboard event (as if a hardware keyboard event occurred in the
  * current UI context).
  *
  * Synthesize a keyboard event (as if a hardware keyboard event occurred in the
  * current UI context).
- * Not Yet Implemented.
  *
  **/
 void
  *
  **/
 void
-generateKeyEvent (long keyCode, long meta);
+generateKeyEvent (long keyCode, KeySynthType synth_type);
 
 /**
  * generateMouseEvent:
 
 /**
  * generateMouseEvent:
index 6be863a..aa294c1 100644 (file)
@@ -266,9 +266,17 @@ deregisterKeystrokeListener (KeystrokeListener *listener, KeyMaskType keymask)
  *
  **/
 void
  *
  **/
 void
-generateKeyEvent (long keyCode, long meta)
+generateKeyEvent (long keyval, KeySynthType type)
 {
 {
-  ;
+/* TODO: check current modifier status and
+ *  send keycode to alter, if necessary
+ */
+  Accessibility_DeviceEventController device_event_controller = 
+         Accessibility_Registry_getDeviceEventController (registry, &ev);
+  Accessibility_DeviceEventController_generateKeyEvent (device_event_controller,
+                                                       keyval,
+                                                       (unsigned long) type,
+                                                       &ev);
 }
 
 /**
 }
 
 /**
index 3d519ff..5908106 100644 (file)
@@ -157,6 +157,13 @@ module Accessibility {
     KEY_RELEASED
   };
 
     KEY_RELEASED
   };
 
+  enum KeySynthType {
+    KEY_PRESS,
+    KEY_RELEASE,
+    KEY_PRESSRELEASE,
+    KEY_SYM
+  };
+
   enum ModifierType {
     MODIFIER_SHIFT,
     MODIFIER_ALT,
   enum ModifierType {
     MODIFIER_SHIFT,
     MODIFIER_ALT,
@@ -234,13 +241,20 @@ module Accessibility {
     
         /**
          * generateKeyEvent:
     
         /**
          * generateKeyEvent:
-         * @keyEventID: a long integer indicating which keypress is synthesized.
+         * @keycode: a long integer indicating the keycode of
+        *          the keypress to be synthesized.
+        *
+        * Note that this long may be truncated before being
+        *          processed, as keycode length may be platform-dependent
+        *          and keycode ranges are generally much smaller than
+        *          CORBA_long. 
+        *
          * Returns: void
          *
          * Synthesize a keypress event.
          *
          **/
          * Returns: void
          *
          * Synthesize a keypress event.
          *
          **/
-         void generateKeyEvent (in long keyEventID);
+         void generateKeyEvent (in long keycode, in KeySynthType type);
 
         /**
          * generateMouseEvent:
 
         /**
          * generateMouseEvent:
index 3d519ff..5908106 100644 (file)
@@ -157,6 +157,13 @@ module Accessibility {
     KEY_RELEASED
   };
 
     KEY_RELEASED
   };
 
+  enum KeySynthType {
+    KEY_PRESS,
+    KEY_RELEASE,
+    KEY_PRESSRELEASE,
+    KEY_SYM
+  };
+
   enum ModifierType {
     MODIFIER_SHIFT,
     MODIFIER_ALT,
   enum ModifierType {
     MODIFIER_SHIFT,
     MODIFIER_ALT,
@@ -234,13 +241,20 @@ module Accessibility {
     
         /**
          * generateKeyEvent:
     
         /**
          * generateKeyEvent:
-         * @keyEventID: a long integer indicating which keypress is synthesized.
+         * @keycode: a long integer indicating the keycode of
+        *          the keypress to be synthesized.
+        *
+        * Note that this long may be truncated before being
+        *          processed, as keycode length may be platform-dependent
+        *          and keycode ranges are generally much smaller than
+        *          CORBA_long. 
+        *
          * Returns: void
          *
          * Synthesize a keypress event.
          *
          **/
          * Returns: void
          *
          * Synthesize a keypress event.
          *
          **/
-         void generateKeyEvent (in long keyEventID);
+         void generateKeyEvent (in long keycode, in KeySynthType type);
 
         /**
          * generateMouseEvent:
 
         /**
          * generateMouseEvent:
index f96d7b0..e3271bc 100644 (file)
@@ -235,6 +235,7 @@ _check_key_event (DeviceEventController *controller)
        while (XPending(display))
          {
            XNextEvent (display, x_event);
        while (XPending(display))
          {
            XNextEvent (display, x_event);
+           if (XFilterEvent (x_event, None)) continue;   
            if (x_event->type == KeyPress)
              {
                x_key_event = (XKeyEvent *)x_event;
            if (x_event->type == KeyPress)
              {
                x_key_event = (XKeyEvent *)x_event;
@@ -410,21 +411,51 @@ impl_register_mouse_listener (PortableServer_Servant     servant,
 }
 */
 
 }
 */
 
+static KeyCode
+keycode_for_keysym (long keysym)
+{
+  return XKeysymToKeycode (display, (KeySym) keysym);
+}
+
 /*
  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
  *     method implementation
  */
 static void
 impl_generate_key_event (PortableServer_Servant     servant,
 /*
  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
  *     method implementation
  */
 static void
 impl_generate_key_event (PortableServer_Servant     servant,
-                        const CORBA_long           keyEventID,
-                        CORBA_Environment         *ev)
+                        const CORBA_long           keycode,
+                        const CORBA_long           synth_type,
+                        CORBA_Environment          *ev)
 {
 {
+       long key_synth_code;
 #ifdef SPI_DEBUG
 #ifdef SPI_DEBUG
-       fprintf (stderr, "synthesizing keystroke %ld\n", (long) keyEventID);
+       fprintf (stderr, "synthesizing keystroke %ld\n", (long) keycode);
 #endif
        /* TODO: hide/wrap/remove X dependency */
 #endif
        /* TODO: hide/wrap/remove X dependency */
-       XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keyEventID, True, CurrentTime);
-       XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keyEventID, False, CurrentTime);
+
+       /* TODO: be accessX-savvy so that keyrelease occurs after sufficient timeout */
+       
+       /*
+        * TODO: when initializing, query for XTest extension before using,
+        * and fall back to XSendEvent() if XTest is not available.
+        */
+       
+       switch (synth_type)
+       {
+       case Accessibility_KEY_PRESS:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
+               break;
+       case Accessibility_KEY_PRESSRELEASE:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
+       case Accessibility_KEY_RELEASE:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, False, CurrentTime);
+               break;
+       case Accessibility_KEY_SYM:
+               key_synth_code = keycode_for_keysym (keycode);
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, True, CurrentTime);
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, False, CurrentTime);
+               break;
+       }
 }
 
 /*
 }
 
 /*
index f96d7b0..e3271bc 100644 (file)
@@ -235,6 +235,7 @@ _check_key_event (DeviceEventController *controller)
        while (XPending(display))
          {
            XNextEvent (display, x_event);
        while (XPending(display))
          {
            XNextEvent (display, x_event);
+           if (XFilterEvent (x_event, None)) continue;   
            if (x_event->type == KeyPress)
              {
                x_key_event = (XKeyEvent *)x_event;
            if (x_event->type == KeyPress)
              {
                x_key_event = (XKeyEvent *)x_event;
@@ -410,21 +411,51 @@ impl_register_mouse_listener (PortableServer_Servant     servant,
 }
 */
 
 }
 */
 
+static KeyCode
+keycode_for_keysym (long keysym)
+{
+  return XKeysymToKeycode (display, (KeySym) keysym);
+}
+
 /*
  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
  *     method implementation
  */
 static void
 impl_generate_key_event (PortableServer_Servant     servant,
 /*
  * CORBA Accessibility::DeviceEventController::registerKeystrokeListener
  *     method implementation
  */
 static void
 impl_generate_key_event (PortableServer_Servant     servant,
-                        const CORBA_long           keyEventID,
-                        CORBA_Environment         *ev)
+                        const CORBA_long           keycode,
+                        const CORBA_long           synth_type,
+                        CORBA_Environment          *ev)
 {
 {
+       long key_synth_code;
 #ifdef SPI_DEBUG
 #ifdef SPI_DEBUG
-       fprintf (stderr, "synthesizing keystroke %ld\n", (long) keyEventID);
+       fprintf (stderr, "synthesizing keystroke %ld\n", (long) keycode);
 #endif
        /* TODO: hide/wrap/remove X dependency */
 #endif
        /* TODO: hide/wrap/remove X dependency */
-       XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keyEventID, True, CurrentTime);
-       XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keyEventID, False, CurrentTime);
+
+       /* TODO: be accessX-savvy so that keyrelease occurs after sufficient timeout */
+       
+       /*
+        * TODO: when initializing, query for XTest extension before using,
+        * and fall back to XSendEvent() if XTest is not available.
+        */
+       
+       switch (synth_type)
+       {
+       case Accessibility_KEY_PRESS:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
+               break;
+       case Accessibility_KEY_PRESSRELEASE:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, True, CurrentTime);
+       case Accessibility_KEY_RELEASE:
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) keycode, False, CurrentTime);
+               break;
+       case Accessibility_KEY_SYM:
+               key_synth_code = keycode_for_keysym (keycode);
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, True, CurrentTime);
+               XTestFakeKeyEvent (GDK_DISPLAY(), (unsigned int) key_synth_code, False, CurrentTime);
+               break;
+       }
 }
 
 /*
 }
 
 /*
index d9a3ade..09e8d6a 100644 (file)
@@ -1,6 +1,6 @@
 NULL=
 
 NULL=
 
-noinst_PROGRAMS = at app simple-at
+noinst_PROGRAMS = at app simple-at keysynth-demo
 
 at_SOURCES = at.c
 
 
 at_SOURCES = at.c
 
@@ -8,6 +8,8 @@ app_SOURCES = app.c
 
 simple_at_SOURCES = simple-at.c 
 
 
 simple_at_SOURCES = simple-at.c 
 
+keysynth_demo_SOURCES = keysynth-demo.c 
+
 INCLUDES = -I$(top_srcdir)           \
            -I$(top_builddir)         \
            -I$(top_srcdir)/libspi    \
 INCLUDES = -I$(top_srcdir)           \
            -I$(top_builddir)         \
            -I$(top_srcdir)/libspi    \
diff --git a/test/keysynth-demo.c b/test/keysynth-demo.c
new file mode 100644 (file)
index 0000000..9bf7130
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * AT-SPI - Assistive Technology Service Provider Interface
+ * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
+ *
+ * Copyright 2001 Sun Microsystems Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "spi.h"
+
+#define LABELMAXLEN 20
+#define MIN_KEYCODE 9
+#define CAPSLOCK_KEYCODE 66
+
+/* these can be increased to access more keys */
+#define MAX_ROWS 5
+#define MAX_COLUMNS 14
+
+static KeystrokeListener *key_listener;
+
+static boolean shift_latched = False;
+static boolean caps_lock = False;
+static GtkButton **buttons[MAX_ROWS];
+
+static void
+label_buttons()
+{
+  int i, j;
+  KeySym keysym;
+  KeyCode keycode = MIN_KEYCODE;
+  char label[LABELMAXLEN] = " ";
+  char *button_label;
+  char *keysymstring;
+  boolean shifted;
+  
+  for (i=0; i<MAX_ROWS; ++i)
+    {
+      for (j=0; j<MAX_COLUMNS; ++j, ++keycode)
+        {
+         shifted = caps_lock || shift_latched;
+         keysym = (KeySym) XKeycodeToKeysym (GDK_DISPLAY(), keycode, shifted ? 1 : 0);
+          /* Note: these routines are not i18n-savvy,  we need to use XIM, other methods here */
+         if (keysym && g_ascii_isprint((int)keysym))
+           {
+             snprintf (label, 2, "%c", (int) keysym); 
+           }
+         else
+           {
+             keysymstring = XKeysymToString (keysym);
+             if (keysymstring)
+               { 
+                 /* KP_ means keypad... we won't expose this difference */
+                 if (!strncmp (keysymstring, "KP_", 3))
+                      strncpy (label, (char *)(keysymstring+3), LABELMAXLEN);
+                 else strncpy (label, keysymstring, LABELMAXLEN);
+               }
+             else *label = 0;
+           }
+         button_label =        
+         *label==' ' ? "   space   " : label;
+         gtk_button_set_label (buttons[i][j], button_label);    
+        }
+    }
+}
+
+static void
+do_shift (GtkButton *button)
+{
+  static KeyCode shift_keycode = 0;
+  if (!shift_keycode) shift_keycode = XKeysymToKeycode(GDK_DISPLAY(), (KeySym) 0xFFE1);
+  /* Note: in a real onscreen keyboard shift keycode should not be hard-coded! */
+  shift_latched = !shift_latched;
+  generateKeyEvent (shift_keycode, shift_latched ? KEY_PRESS : KEY_RELEASE);
+  if (buttons) label_buttons (buttons);
+}
+
+static void
+keysynth_exit()
+{
+  if (shift_latched) do_shift (NULL);
+  deregisterKeystrokeListener (key_listener, KEYMASK_ALT );
+  SPI_exit ();
+}
+
+static void
+button_exit(GtkButton *notused, void *alsonotused)
+{
+  keysynth_exit();
+}
+
+static boolean
+is_command_key (void *p)
+{
+  KeyStroke *key = (KeyStroke *)p;
+  switch (key->keyID)
+    {
+    case 'Q':
+    case 'q':
+           keysynth_exit(); 
+           return TRUE; /* not reached */
+    }
+}
+
+static void
+synth_keycode (GtkButton *button, KeyCode *keycode)
+{
+  if (*keycode)  generateKeyEvent ((long) *keycode, KEY_PRESSRELEASE);
+  if (shift_latched) do_shift (button);
+  if (*keycode == CAPSLOCK_KEYCODE)
+    {
+      caps_lock = !caps_lock;      
+      label_buttons ();
+    }
+}
+
+static void
+create_vkbd()
+{
+  GtkWidget *window, *button, *container, *hbox;
+  int i, j;
+  KeyCode *keycodeptr, keycode = MIN_KEYCODE;
+
+  window = g_object_connect (gtk_widget_new (gtk_window_get_type (),
+                                            "user_data", NULL,
+                                            "can_focus", FALSE,
+                                            "type", GTK_WINDOW_POPUP,
+                                            "window-position", GTK_WIN_POS_CENTER,
+                                            "title", "test",
+                                            "allow_grow", FALSE,
+                                            "allow_shrink", FALSE,
+                                            "border_width", 10,
+                                            NULL),
+                            "signal::destroy", keysynth_exit, NULL,
+                            NULL);
+  
+  container = gtk_widget_new (GTK_TYPE_VBOX,
+                             "GtkWidget::parent", window,
+                             "GtkWidget::visible", TRUE,
+                             NULL);
+  for (i=0; i<MAX_ROWS; ++i)
+    {
+      hbox = gtk_widget_new (gtk_hbox_get_type(),
+                            "GtkWidget::parent", container,
+                            "GtkWidget::visible", TRUE,
+                            NULL);
+      buttons[i] = g_new0 (GtkButton*, MAX_COLUMNS);
+      for (j=0; j<MAX_COLUMNS; ++j)
+       {
+         keycodeptr = (KeyCode *) g_new0 (KeyCode, 1);
+         *keycodeptr = keycode;
+         ++keycode;
+         buttons[i][j] = g_object_connect (gtk_widget_new (gtk_button_get_type (),
+                                                    "GtkWidget::parent", hbox,
+                                                    "GtkWidget::visible", TRUE,
+                                                    NULL),
+                                    "signal::clicked",
+                                    synth_keycode, keycodeptr,
+                                    NULL);
+       }
+    } 
+  hbox = gtk_widget_new (gtk_hbox_get_type(),
+                        "GtkWidget::parent", container,
+                        "GtkWidget::visible", TRUE,
+                        NULL);
+  button = g_object_connect (gtk_widget_new (gtk_button_get_type (),
+                                            "GtkButton::label", "Quit",
+                                            "GtkWidget::parent", hbox,
+                                            "GtkWidget::visible", TRUE,
+                                            NULL),
+                            "signal::clicked",
+                            button_exit, NULL,
+                            NULL);
+  button = g_object_connect (gtk_widget_new (gtk_button_get_type (),
+                                            "GtkButton::label", "ShiftLatch",
+                                            "GtkWidget::parent", hbox,
+                                            "GtkWidget::visible", TRUE,
+                                            NULL),
+                            "signal::clicked",
+                            do_shift, NULL,
+                            NULL);
+  label_buttons ();
+  gtk_widget_show (window);
+
+}
+
+int
+main(int argc, char **argv)
+{
+  if ((argc > 1) && (!strncmp(argv[1],"-h",2)))
+  {
+    printf ("Usage: keysynth-demo\n");
+    exit(0);
+  }
+
+  gtk_init (&argc, &argv); /* must call, because this program uses GTK+ */
+
+  SPI_init();
+
+  key_listener = createKeystrokeListener(is_command_key);
+  /* will listen only to Alt-key combinations */
+  registerKeystrokeListener(key_listener,
+                           (KeySet *) ALL_KEYS,
+                           KEYMASK_ALT,
+                           (unsigned long) ( KeyPress | KeyRelease),
+                           KEYLISTENER_CANCONSUME);
+  create_vkbd();  
+
+  SPI_event_main(TRUE);
+}