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