2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2001 Sun Microsystems Inc.
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.
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.
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.
28 #define LABELMAXLEN 20
30 #define CAPSLOCK_KEYCODE 66
31 #define SHIFT_L_KEYCODE 50
32 #define SHIFT_R_KEYCODE 62
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
38 static AccessibleKeystrokeListener *key_listener;
39 static AccessibleKeystrokeListener *switch_listener;
41 static boolean shift_latched = False;
42 static boolean caps_lock = False;
43 static GtkButton **buttons[MAX_ROWS];
54 ScanTimerState timer_state;
60 button_default_bg_color (GtkButton *button)
62 static GdkColor bg_color;
63 static gboolean initialized = FALSE;
66 bg_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_NORMAL];
73 button_default_selected_color (GtkButton *button)
75 static GdkColor selected_color;
76 static gboolean initialized = FALSE;
79 selected_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_SELECTED];
82 return &selected_color;
86 select_key (gint lineno, gint keyno)
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...
93 button_default_bg_color (buttons [lineno][keyno]);
94 gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
96 button_default_selected_color (buttons [lineno][keyno]));
100 deselect_key (gint lineno, gint keyno)
102 gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
104 button_default_bg_color (buttons [lineno][keyno]));
108 deselect_line (gint lineno)
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);
118 select_line (gint lineno)
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);
131 static ScanState state = {SCAN_IDLE, 0, 0};
136 timeout_scan (gpointer data)
138 ScanState *state = (ScanState *) data;
139 state->timer_state = SCAN_IDLE;
140 deselect_key (state->scan_row, state->scan_column);
145 increment_scan (gpointer data)
147 ScanState *state = (ScanState *) data;
149 switch (state->timer_state)
152 /* happens if switch-break occurs before the timer fires, after SCAN_KEYS_DONE*/
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);
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);
168 case SCAN_LINES_DONE:
177 scan_start (unsigned int timestamp)
179 ScanState *state = scan_state();
180 switch (state->timer_state)
183 state->timer_state = SCAN_LINES;
184 state->scan_column = 0;
186 g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
187 select_line (state->scan_row);
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);
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;
201 g_print("unexpected state for 'scan start'\n");
206 scan_stop (unsigned int timestamp)
208 /* find the element correspondin to this event's timestamp */
209 ScanState *state = scan_state();
210 switch (state->timer_state)
213 state->timer_state = SCAN_LINES_DONE;
216 state->timer_state = SCAN_KEYS_DONE;
217 g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 1200, timeout_scan, state, NULL);
222 g_print("unexpected state for 'scan stop'\n");
227 label_buttons(boolean shifted)
231 KeyCode keycode = MIN_KEYCODE;
232 char label[LABELMAXLEN] = " ";
236 for (i=0; i<MAX_ROWS-1; ++i) /* last row doesn't change */
238 for (j=0; j<MAX_COLUMNS; ++j, ++keycode)
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))
244 snprintf (label, 2, "%c", (int) keysym);
248 keysymstring = XKeysymToString (keysym);
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);
259 *label==' ' ? " space " : label;
260 gtk_button_set_label (buttons[i][j], button_label);
266 show_shift (GtkButton *button, boolean *is_shifted)
268 label_buttons (*is_shifted ^ caps_lock);
272 toggle_shift_latch (GtkButton *button)
274 shift_latched = !shift_latched;
275 if (buttons) label_buttons (caps_lock || shift_latched);
281 deregisterAccessibleKeystrokeListener (key_listener, SPI_KEYMASK_ALT );
282 deregisterAccessibleKeystrokeListener (switch_listener, SPI_KEYMASK_UNMODIFIED );
287 keysynth_realize (GtkWidget *widget)
290 Atom wm_window_protocols[2];
291 static gboolean initialized = FALSE;
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");
299 wm_hints.flags = InputHint;
300 wm_hints.input = False;
302 XSetWMHints (GDK_WINDOW_XDISPLAY (widget->window),
303 GDK_WINDOW_XWINDOW (widget->window), &wm_hints);
305 XSetWMProtocols (GDK_WINDOW_XDISPLAY (widget->window),
306 GDK_WINDOW_XWINDOW (widget->window), wm_window_protocols, 2);
310 button_exit(GtkButton *notused, void *alsonotused)
316 is_command_key (void *p)
318 AccessibleKeyStroke *key = (AccessibleKeyStroke *)p;
324 return TRUE; /* not reached */
329 switch_callback (void *p)
331 AccessibleKeyStroke *key = (AccessibleKeyStroke *)p;
332 static is_down = FALSE;
333 if (key->type == Accessibility_KEY_RELEASED)
335 g_print ("spacebar up\n");
337 scan_stop (key->timestamp);
342 g_print ("spacebar down\n");
344 scan_start (key->timestamp);
346 /* catch the first, ignore the rest (will repeat) until keyrelease */
351 synth_keycode (GtkButton *button, KeyCode *keycode)
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! */
358 if (*keycode == CAPSLOCK_KEYCODE)
360 caps_lock = !caps_lock;
361 label_buttons (caps_lock || shift_latched);
364 generateKeyEvent (shift_keycode, SPI_KEY_PRESS);
366 generateKeyEvent ((long) *keycode, SPI_KEY_PRESSRELEASE);
370 generateKeyEvent (shift_keycode, SPI_KEY_RELEASE);
371 toggle_shift_latch (button);
379 GtkWidget *window, *button, *container, *hbox;
381 KeyCode *keycodeptr, keycode = MIN_KEYCODE;
382 static boolean true_val = True;
383 static boolean false_val = False;
385 window = g_object_connect (gtk_widget_new (gtk_window_get_type (),
388 "type", GTK_WINDOW_TOPLEVEL,
389 "window-position", GTK_WIN_POS_CENTER,
392 "allow_shrink", FALSE,
395 "signal::realize", keysynth_realize, NULL,
396 "signal::destroy", keysynth_exit, NULL,
399 container = gtk_widget_new (GTK_TYPE_VBOX,
400 "GtkWidget::parent", window,
401 "GtkWidget::visible", TRUE,
403 for (i=0; i<MAX_ROWS-1; ++i)
405 hbox = gtk_widget_new (gtk_hbox_get_type(),
406 "GtkWidget::parent", container,
407 "GtkWidget::visible", TRUE,
409 buttons[i] = g_new0 (GtkButton*, MAX_COLUMNS);
410 for (j=0; j<MAX_COLUMNS; ++j)
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,
419 synth_keycode, keycodeptr,
421 if (keycode == SHIFT_L_KEYCODE || keycode == SHIFT_R_KEYCODE)
423 g_object_connect (buttons[i][j], "signal::pressed", show_shift,
425 g_object_connect (buttons[i][j], "signal::released", show_shift,
431 hbox = gtk_widget_new (gtk_hbox_get_type(),
432 "GtkWidget::parent", container,
433 "GtkWidget::visible", TRUE,
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,
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,
450 toggle_shift_latch, NULL,
452 label_buttons (caps_lock || shift_latched);
453 gtk_widget_show (window);
457 main(int argc, char **argv)
459 AccessibleKeySet switch_set;
461 if ((argc > 1) && (!strncmp(argv[1],"-h",2)))
463 printf ("Usage: keysynth-demo\n");
467 gtk_init (&argc, &argv); /* must call, because this program uses GTK+ */
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,
476 (unsigned long) ( KeyPress | KeyRelease),
477 SPI_KEYLISTENER_CANCONSUME | SPI_KEYLISTENER_ALL_WINDOWS);
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.
486 switch_set.keysyms = g_new0 (unsigned long, 1);
487 switch_set.keycodes = g_new0 (unsigned short, 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,
494 SPI_KEYMASK_UNMODIFIED,
495 (unsigned long) ( KeyPress | KeyRelease),
496 SPI_KEYLISTENER_CANCONSUME);
498 SPI_event_main(TRUE);