2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2001, 2002 Sun Microsystems Inc.,
6 * Copyright 2001, 2002 Ximian, Inc.
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.
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.
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.
31 #define LABELMAXLEN 20
33 #define CAPSLOCK_KEYCODE 66
34 #define SHIFT_L_KEYCODE 50
35 #define SHIFT_R_KEYCODE 62
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
41 static AccessibleKeystrokeListener *key_listener;
42 static AccessibleKeystrokeListener *switch_listener;
44 static SPIBoolean shift_latched = False;
45 static SPIBoolean caps_lock = False;
46 static GtkButton **buttons[MAX_ROWS];
57 ScanTimerState timer_state;
63 button_default_bg_color (GtkButton *button)
65 static GdkColor bg_color;
66 static gboolean initialized = FALSE;
69 bg_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_NORMAL];
76 button_default_selected_color (GtkButton *button)
78 static GdkColor selected_color;
79 static gboolean initialized = FALSE;
82 selected_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_SELECTED];
85 return &selected_color;
89 select_key (gint lineno, gint keyno)
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...
96 button_default_bg_color (buttons [lineno][keyno]);
97 gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
99 button_default_selected_color (buttons [lineno][keyno]));
103 deselect_key (gint lineno, gint keyno)
105 gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
107 button_default_bg_color (buttons [lineno][keyno]));
111 deselect_line (gint lineno)
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);
121 select_line (gint lineno)
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);
134 static ScanState state = {SCAN_IDLE, 0, 0};
139 timeout_scan (gpointer data)
141 ScanState *state = (ScanState *) data;
142 state->timer_state = SCAN_IDLE;
143 deselect_key (state->scan_row, state->scan_column);
148 increment_scan (gpointer data)
150 ScanState *state = (ScanState *) data;
152 switch (state->timer_state)
155 /* happens if switch-break occurs before the timer fires, after SCAN_KEYS_DONE*/
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);
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);
171 case SCAN_LINES_DONE:
180 scan_start (unsigned int timestamp)
182 ScanState *state = scan_state();
183 switch (state->timer_state)
186 state->timer_state = SCAN_LINES;
187 state->scan_column = 0;
189 g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
190 select_line (state->scan_row);
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);
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;
204 g_print("unexpected state for 'scan start'\n");
209 scan_stop (unsigned int timestamp)
211 /* find the element correspondin to this event's timestamp */
212 ScanState *state = scan_state();
213 switch (state->timer_state)
216 state->timer_state = SCAN_LINES_DONE;
219 state->timer_state = SCAN_KEYS_DONE;
220 g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 1200, timeout_scan, state, NULL);
225 g_print("unexpected state for 'scan stop'\n");
230 label_buttons(SPIBoolean shifted)
234 KeyCode keycode = MIN_KEYCODE;
235 char label[LABELMAXLEN] = " ";
239 for (i=0; i<MAX_ROWS-1; ++i) /* last row doesn't change */
241 for (j=0; j<MAX_COLUMNS; ++j, ++keycode)
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))
247 g_snprintf (label, 2, "%c", (int) keysym);
251 keysymstring = XKeysymToString (keysym);
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);
262 *label==' ' ? " space " : label;
263 gtk_button_set_label (buttons[i][j], button_label);
269 show_shift (GtkButton *button, SPIBoolean *is_shifted)
271 label_buttons (*is_shifted ^ caps_lock);
275 toggle_shift_latch (GtkButton *button)
277 shift_latched = !shift_latched;
278 if (buttons) label_buttons (caps_lock || shift_latched);
284 SPI_deregisterAccessibleKeystrokeListener (key_listener, SPI_KEYMASK_ALT);
285 AccessibleKeystrokeListener_unref (key_listener);
287 SPI_deregisterAccessibleKeystrokeListener (switch_listener, SPI_KEYMASK_UNMODIFIED);
288 AccessibleKeystrokeListener_unref (switch_listener);
294 keysynth_realize (GtkWidget *widget)
297 Atom wm_window_protocols[2];
298 static gboolean initialized = FALSE;
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");
306 wm_hints.flags = InputHint;
307 wm_hints.input = False;
309 XSetWMHints (GDK_WINDOW_XDISPLAY (widget->window),
310 GDK_WINDOW_XWINDOW (widget->window), &wm_hints);
312 XSetWMProtocols (GDK_WINDOW_XDISPLAY (widget->window),
313 GDK_WINDOW_XWINDOW (widget->window), wm_window_protocols, 2);
317 button_exit (GtkButton *notused, void *alsonotused)
323 is_command_key (const AccessibleKeystroke *key, void *user_data)
330 return TRUE; /* not reached */
336 switch_callback (const AccessibleKeystroke *key, void *user_data)
338 static SPIBoolean is_down = FALSE;
340 if (key->type == SPI_KEY_RELEASED)
342 g_print ("switch up\n");
344 scan_stop (key->timestamp);
349 g_print ("switch down\n");
351 scan_start (key->timestamp);
353 /* catch the first, ignore the rest (will repeat) until keyrelease */
358 synth_keycode (GtkButton *button, KeyCode *keycode)
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! */
366 if (*keycode == CAPSLOCK_KEYCODE)
368 caps_lock = !caps_lock;
369 label_buttons (caps_lock || shift_latched);
372 SPI_generateKeyboardEvent (shift_keycode, NULL, SPI_KEY_PRESS);
374 g_print ("generating key %d\n", (int) *keycode);
375 SPI_generateKeyboardEvent ((long) *keycode, NULL, SPI_KEY_PRESSRELEASE);
379 SPI_generateKeyboardEvent (shift_keycode, NULL, SPI_KEY_RELEASE);
380 toggle_shift_latch (button);
388 GtkWidget *window, *container, *hbox;
390 KeyCode *keycodeptr, keycode = MIN_KEYCODE;
391 static SPIBoolean true_val = True;
392 static SPIBoolean false_val = False;
394 window = g_object_connect (gtk_widget_new (gtk_window_get_type (),
397 "type", GTK_WINDOW_TOPLEVEL,
398 "window-position", GTK_WIN_POS_CENTER,
401 "allow_shrink", FALSE,
404 "signal::realize", keysynth_realize, NULL,
405 "signal::destroy", keysynth_exit, NULL,
408 container = gtk_widget_new (GTK_TYPE_VBOX,
409 "GtkWidget::parent", window,
410 "GtkWidget::visible", TRUE,
412 for (i=0; i<MAX_ROWS-1; ++i)
414 hbox = gtk_widget_new (gtk_hbox_get_type(),
415 "GtkWidget::parent", container,
416 "GtkWidget::visible", TRUE,
418 buttons[i] = g_new0 (GtkButton*, MAX_COLUMNS);
419 for (j=0; j<MAX_COLUMNS; ++j)
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,
428 synth_keycode, keycodeptr,
430 if (keycode == SHIFT_L_KEYCODE || keycode == SHIFT_R_KEYCODE)
432 g_object_connect (buttons[i][j], "signal::pressed", show_shift,
434 g_object_connect (buttons[i][j], "signal::released", show_shift,
440 hbox = gtk_widget_new (gtk_hbox_get_type(),
441 "GtkWidget::parent", container,
442 "GtkWidget::visible", TRUE,
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,
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,
459 toggle_shift_latch, NULL,
461 label_buttons (caps_lock || shift_latched);
462 gtk_widget_show (window);
466 main (int argc, char **argv)
468 AccessibleKeySet switch_set;
470 if ((argc > 1) && (!strncmp (argv[1], "-h", 2)))
472 printf ("Usage: keysynth-demo\n");
476 gtk_init (&argc, &argv); /* must call, because this program uses GTK+ */
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);
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.
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);
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,
505 SPI_KEYMASK_UNMODIFIED,
506 (unsigned long) ( SPI_KEY_PRESSED | SPI_KEY_RELEASED ),
507 SPI_KEYLISTENER_NOSYNC);