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