Added simple scanning to the virtual keyboard demo, to show one way to
[platform/upstream/at-spi2-core.git] / test / keysynth-demo.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001 Sun Microsystems Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <stdlib.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkx.h>
26 #include "spi.h"
27
28 #define LABELMAXLEN 20
29 #define MIN_KEYCODE 9
30 #define CAPSLOCK_KEYCODE 66
31 #define SHIFT_L_KEYCODE 50
32 #define SHIFT_R_KEYCODE 62
33
34 /* these can be increased to access more keys */
35 #define MAX_ROWS 6 /* The last row only holds Quit and ShiftLatch, special-purpose keys */
36 #define MAX_COLUMNS 14
37
38 static AccessibleKeystrokeListener *key_listener;
39 static AccessibleKeystrokeListener *switch_listener;
40
41 static boolean shift_latched = False;
42 static boolean caps_lock = False;
43 static GtkButton **buttons[MAX_ROWS];
44
45 typedef enum {
46         SCAN_IDLE,
47         SCAN_LINES,
48         SCAN_LINES_DONE,
49         SCAN_KEYS,
50         SCAN_KEYS_DONE
51 } ScanTimerState;
52
53 typedef struct {
54         ScanTimerState timer_state;
55         gint scan_row;
56         gint scan_column;
57 } ScanState;
58
59 GdkColor *
60 button_default_bg_color (GtkButton *button)
61 {
62   static GdkColor bg_color;
63   static gboolean initialized = FALSE;
64   if (!initialized)
65   {
66     bg_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_NORMAL];
67     initialized = TRUE;
68   }
69   return &bg_color;
70 }
71
72 GdkColor *
73 button_default_selected_color (GtkButton *button)
74 {
75   static GdkColor selected_color;
76   static gboolean initialized = FALSE;
77   if (!initialized)
78   {
79     selected_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_SELECTED];
80     initialized = TRUE;
81   }
82   return &selected_color;
83 }
84
85 void
86 select_key (gint lineno, gint keyno)
87 {
88   /*
89    * Before we do this, we need to make sure we've saved the default normal bg.
90    * The button_default_bg_color() call caches this for us (as a side-effect).
91    * Probably we should do this a cleaner way...
92    */
93   button_default_bg_color (buttons [lineno][keyno]);    
94   gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
95                         GTK_STATE_NORMAL,
96                         button_default_selected_color (buttons [lineno][keyno]));
97 }
98
99 void
100 deselect_key (gint lineno, gint keyno)
101 {
102   gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
103                         GTK_STATE_NORMAL,
104                         button_default_bg_color (buttons [lineno][keyno]));
105 }
106
107 void
108 deselect_line (gint lineno)
109 {
110   int i;
111   int max_columns = MAX_COLUMNS;
112   if (lineno == MAX_ROWS-1) max_columns = 2;
113   for (i=0; i<max_columns; ++i)
114           deselect_key (lineno, i);
115 }
116
117 void
118 select_line (gint lineno)
119 {
120   int i;
121   int max_columns = MAX_COLUMNS;
122   if (lineno == MAX_ROWS-1) max_columns = 2;
123   for (i=0; i<max_columns; ++i)
124           select_key (lineno, i);
125 }
126
127
128 static ScanState*
129 scan_state ()
130 {
131   static ScanState state = {SCAN_IDLE, 0, 0};
132   return &state;
133 }
134
135 static gboolean
136 timeout_scan (gpointer data)
137 {
138   ScanState *state = (ScanState *) data;
139   state->timer_state = SCAN_IDLE;
140   deselect_key (state->scan_row, state->scan_column);
141   return FALSE;
142 }
143
144 static gboolean
145 increment_scan (gpointer data)
146 {
147   ScanState *state = (ScanState *) data;
148   int max_columns;
149   switch (state->timer_state)
150     {  
151       case SCAN_IDLE: 
152 /* happens if switch-break occurs before the timer fires, after SCAN_KEYS_DONE*/
153           return FALSE;
154       case SCAN_LINES:
155           deselect_line (state->scan_row);
156           state->scan_row = (++state->scan_row < MAX_ROWS) ? state->scan_row : 0;
157           select_line (state->scan_row);
158           g_print ("line %d\n", state->scan_row);
159           break;
160       case SCAN_KEYS:
161           deselect_key (state->scan_row, state->scan_column);
162           if (state->scan_row == MAX_ROWS-1) max_columns = 2;
163           else max_columns = MAX_COLUMNS; /* last row has only two keys */
164           state->scan_column = (++state->scan_column < max_columns) ? state->scan_column : 0;
165           select_key (state->scan_row, state->scan_column);
166           g_print ("row %d\n", state->scan_column);
167           break;
168       case SCAN_LINES_DONE:
169       case SCAN_KEYS_DONE:
170           return FALSE;
171       default:
172     }
173   return TRUE;
174 }
175
176 static void
177 scan_start (unsigned int timestamp)
178 {
179   ScanState *state = scan_state();
180   switch (state->timer_state)
181     {
182     case SCAN_IDLE:
183       state->timer_state = SCAN_LINES;
184       state->scan_column = 0;
185       state->scan_row = 0;
186       g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
187       select_line (state->scan_row);
188       break;
189     case SCAN_LINES_DONE:
190       state->timer_state = SCAN_KEYS;
191       g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
192       deselect_line (state->scan_row);
193       select_key (state->scan_row, state->scan_column);
194       break;
195     case SCAN_KEYS_DONE:
196       gtk_button_clicked (buttons[state->scan_row][state->scan_column]);
197       deselect_key (state->scan_row, state->scan_column);
198       state->timer_state = SCAN_IDLE;
199       break;
200     default:
201       g_print("unexpected state for 'scan start'\n");
202     }
203 }
204
205 static void
206 scan_stop (unsigned int timestamp)
207 {
208   /* find the element correspondin to this event's timestamp */
209   ScanState *state = scan_state();
210   switch (state->timer_state)
211     {
212     case SCAN_LINES:
213       state->timer_state = SCAN_LINES_DONE;
214       break;
215     case SCAN_KEYS:
216       state->timer_state = SCAN_KEYS_DONE;
217       g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 1200, timeout_scan, state, NULL);
218       break;
219     case SCAN_IDLE:
220       break;
221     default:
222       g_print("unexpected state for 'scan stop'\n");
223     }
224 }
225
226 static void
227 label_buttons(boolean shifted)
228 {
229   int i, j;
230   KeySym keysym;
231   KeyCode keycode = MIN_KEYCODE;
232   char label[LABELMAXLEN] = " ";
233   char *button_label;
234   char *keysymstring;
235   
236   for (i=0; i<MAX_ROWS-1; ++i) /* last row doesn't change */
237     {
238       for (j=0; j<MAX_COLUMNS; ++j, ++keycode)
239         {
240           keysym = (KeySym) XKeycodeToKeysym (GDK_DISPLAY(), keycode, shifted ? 1 : 0);
241           /* Note: these routines are not i18n-savvy,  we need to use XIM, other methods here */
242           if (keysym && g_ascii_isprint((int)keysym))
243             {
244               snprintf (label, 2, "%c", (int) keysym); 
245             }
246           else
247             {
248               keysymstring = XKeysymToString (keysym);
249               if (keysymstring)
250                 { 
251                   /* KP_ means keypad... we won't expose this difference */
252                   if (!strncmp (keysymstring, "KP_", 3))
253                        strncpy (label, (char *)(keysymstring+3), LABELMAXLEN);
254                   else strncpy (label, keysymstring, LABELMAXLEN);
255                 }
256               else *label = 0;
257             }
258           button_label =        
259           *label==' ' ? "   space   " : label;
260           gtk_button_set_label (buttons[i][j], button_label);    
261         }
262     }
263 }
264
265 static void
266 show_shift (GtkButton *button, boolean *is_shifted)
267 {
268  label_buttons (*is_shifted ^ caps_lock);       
269 }
270
271 static void
272 toggle_shift_latch (GtkButton *button) 
273
274   shift_latched = !shift_latched;
275   if (buttons) label_buttons (caps_lock || shift_latched);
276 }
277
278 static void
279 keysynth_exit()
280 {
281   deregisterAccessibleKeystrokeListener (key_listener, SPI_KEYMASK_ALT );
282   deregisterAccessibleKeystrokeListener (switch_listener, SPI_KEYMASK_UNMODIFIED );
283   SPI_exit ();
284 }
285
286 static void
287 keysynth_realize (GtkWidget *widget)
288 {
289   XWMHints wm_hints;
290   Atom wm_window_protocols[2];
291   static gboolean initialized = FALSE;
292   
293   if (!initialized)
294     {
295       wm_window_protocols[0] = gdk_x11_get_xatom_by_name ("WM_DELETE_WINDOW");
296       wm_window_protocols[1] = gdk_x11_get_xatom_by_name ("_NET_WM_PING");
297     }
298   
299   wm_hints.flags = InputHint;
300   wm_hints.input = False;
301   
302   XSetWMHints (GDK_WINDOW_XDISPLAY (widget->window),
303                GDK_WINDOW_XWINDOW (widget->window), &wm_hints);
304   
305   XSetWMProtocols (GDK_WINDOW_XDISPLAY (widget->window),
306                    GDK_WINDOW_XWINDOW (widget->window), wm_window_protocols, 2);
307 }
308
309 static void
310 button_exit(GtkButton *notused, void *alsonotused)
311 {
312   keysynth_exit();
313 }
314
315 static boolean
316 is_command_key (void *p)
317 {
318   AccessibleKeyStroke *key = (AccessibleKeyStroke *)p;
319   switch (key->keyID)
320     {
321     case 'Q':
322     case 'q':
323             keysynth_exit(); 
324             return TRUE; /* not reached */
325     }
326 }
327
328 static boolean
329 switch_callback (void *p)
330 {
331   AccessibleKeyStroke *key = (AccessibleKeyStroke *)p;
332   static is_down = FALSE;
333   if (key->type == Accessibility_KEY_RELEASED)
334     {
335       g_print ("spacebar up\n");
336       is_down = FALSE;
337       scan_stop (key->timestamp);
338     }
339   else 
340   if (!is_down)
341     {
342       g_print ("spacebar down\n");
343       is_down = TRUE;
344       scan_start (key->timestamp);
345     }
346   /* catch the first, ignore the rest (will repeat) until keyrelease */
347   return FALSE;
348 }
349
350 static void
351 synth_keycode (GtkButton *button, KeyCode *keycode)
352 {
353   static KeyCode shift_keycode = 0;
354   if (!shift_keycode) shift_keycode = XKeysymToKeycode(GDK_DISPLAY(), (KeySym) 0xFFE1);
355   /* Note: in a real onscreen keyboard shift keycode should not be hard-coded! */
356   if (*keycode)
357     {
358       if (*keycode == CAPSLOCK_KEYCODE)
359         {
360           caps_lock = !caps_lock;            
361           label_buttons (caps_lock || shift_latched);
362         }
363       if (shift_latched)
364               generateKeyEvent (shift_keycode, SPI_KEY_PRESS);
365       
366       generateKeyEvent ((long) *keycode, SPI_KEY_PRESSRELEASE);
367
368       if (shift_latched)
369         {
370           generateKeyEvent (shift_keycode, SPI_KEY_RELEASE);
371           toggle_shift_latch (button);
372         }
373     }
374 }
375
376 static void
377 create_vkbd()
378 {
379   GtkWidget *window, *button, *container, *hbox;
380   int i, j;
381   KeyCode *keycodeptr, keycode = MIN_KEYCODE;
382   static boolean true_val = True;
383   static boolean false_val = False;
384
385   window = g_object_connect (gtk_widget_new (gtk_window_get_type (),
386                                              "user_data", NULL,
387                                              "can_focus", FALSE,
388                                              "type", GTK_WINDOW_TOPLEVEL,
389                                              "window-position", GTK_WIN_POS_CENTER,
390                                              "title", "test",
391                                              "allow_grow", FALSE,
392                                              "allow_shrink", FALSE,
393                                              "border_width", 10,
394                                              NULL),
395                              "signal::realize", keysynth_realize, NULL,
396                              "signal::destroy", keysynth_exit, NULL,
397                              NULL);
398   
399   container = gtk_widget_new (GTK_TYPE_VBOX,
400                               "GtkWidget::parent", window,
401                               "GtkWidget::visible", TRUE,
402                               NULL);
403   for (i=0; i<MAX_ROWS-1; ++i)
404     {
405       hbox = gtk_widget_new (gtk_hbox_get_type(),
406                              "GtkWidget::parent", container,
407                              "GtkWidget::visible", TRUE,
408                              NULL);
409       buttons[i] = g_new0 (GtkButton*, MAX_COLUMNS);
410       for (j=0; j<MAX_COLUMNS; ++j)
411         {
412           keycodeptr = (KeyCode *) g_new0 (KeyCode, 1);
413           *keycodeptr = keycode;
414           buttons[i][j] = g_object_connect (gtk_widget_new (gtk_button_get_type (),
415                                                      "GtkWidget::parent", hbox,
416                                                      "GtkWidget::visible", TRUE,
417                                                      NULL),
418                                      "signal::clicked",
419                                      synth_keycode, keycodeptr,
420                                      NULL);
421           if (keycode == SHIFT_L_KEYCODE || keycode == SHIFT_R_KEYCODE)
422             {
423               g_object_connect (buttons[i][j], "signal::pressed", show_shift,
424                                 &true_val, NULL);  
425               g_object_connect (buttons[i][j], "signal::released", show_shift,
426                                 &false_val, NULL);  
427             }
428           ++keycode;
429         }
430     } 
431   hbox = gtk_widget_new (gtk_hbox_get_type(),
432                          "GtkWidget::parent", container,
433                          "GtkWidget::visible", TRUE,
434                          NULL);
435   buttons[i] = g_new0 (GtkButton*, 2);
436   buttons[i][0] = g_object_connect (gtk_widget_new (gtk_button_get_type (),
437                                                              "GtkButton::label", "Quit",
438                                                              "GtkWidget::parent", hbox,
439                                                              "GtkWidget::visible", TRUE,
440                                                              NULL),
441                                              "signal::clicked",
442                                              button_exit, NULL,
443                                              NULL);
444   buttons[i][1] = g_object_connect (gtk_widget_new (gtk_button_get_type (),
445                                                     "GtkButton::label", "ShiftLatch",
446                                                     "GtkWidget::parent", hbox,
447                                                     "GtkWidget::visible", TRUE,
448                                                     NULL),
449                                     "signal::clicked",
450                                     toggle_shift_latch, NULL,
451                                     NULL);
452   label_buttons (caps_lock || shift_latched);
453   gtk_widget_show (window);
454 }
455
456 int
457 main(int argc, char **argv)
458 {
459   AccessibleKeySet switch_set;
460   
461   if ((argc > 1) && (!strncmp(argv[1],"-h",2)))
462   {
463     printf ("Usage: keysynth-demo\n");
464     exit(0);
465   }
466
467   gtk_init (&argc, &argv); /* must call, because this program uses GTK+ */
468
469   SPI_init();
470
471   key_listener = createAccessibleKeystrokeListener(is_command_key);
472   /* will listen only to Alt-key combinations */
473   registerAccessibleKeystrokeListener(key_listener,
474                                       (AccessibleKeySet *) SPI_KEYSET_ALL_KEYS,
475                                       SPI_KEYMASK_ALT,
476                                       (unsigned long) ( KeyPress | KeyRelease),
477                                       SPI_KEYLISTENER_CANCONSUME | SPI_KEYLISTENER_ALL_WINDOWS);
478   create_vkbd();  
479
480   /*
481    * Register a listener on an 'unused' key, to serve as a 'single switch'.
482    * On most Intel boxes there is at least one 'special' system key that does not
483    * have a non-zero keycode assigned in the Xserver, so we will intercept any keycode
484    * that is 'zero'.  Often these the "windows" key or the "menu" key.
485    */
486   switch_set.keysyms = g_new0 (unsigned long, 1);
487   switch_set.keycodes = g_new0 (unsigned short, 1);
488   switch_set.len = 1;
489   switch_set.keysyms[0] = (unsigned long) 0;
490   switch_set.keycodes[0] = (unsigned short) 0;
491   switch_listener = createAccessibleKeystrokeListener(switch_callback);
492   registerAccessibleKeystrokeListener(switch_listener,
493                                       &switch_set,
494                                       SPI_KEYMASK_UNMODIFIED,
495                                       (unsigned long) ( KeyPress | KeyRelease),
496                                       SPI_KEYLISTENER_CANCONSUME);
497   
498   SPI_event_main(TRUE);
499 }