]> git.lyx.org Git - lyx.git/blob - development/autotests/xvkbd/xvkbd.c
ctests: mark failing LyXHTML tests as "lyxbugs"
[lyx.git] / development / autotests / xvkbd / xvkbd.c
1 /*
2  * xvkbd - Virtual Keyboard for X Window System
3  * (Version 3.2, 2010-03-14)
4  *
5  * Copyright (C) 2000-2010 by Tom Sato <VEF00200@nifty.ne.jp>
6  * http://homepage3.nifty.com/tsato/xvkbd/
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or any later version.
12  *
13  * This program 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.
16  * See the GNU General Public License for more details.
17  */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <signal.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <fnmatch.h>
27 #include <limits.h>
28
29 #include <X11/Intrinsic.h>
30 #include <X11/StringDefs.h>
31 #include <X11/Shell.h>
32 #include <X11/keysym.h>
33 #include <X11/cursorfont.h>
34 #include <X11/Xproto.h>  /* to get request code */
35 #include <X11/Xaw/Box.h>
36 #include <X11/Xaw/Form.h>
37 #include <X11/Xaw/Command.h>
38 #include <X11/Xaw/Repeater.h>
39 #include <X11/Xaw/Label.h>
40 #include <X11/Xaw/MenuButton.h>
41 #include <X11/Xaw/SimpleMenu.h>
42 #include <X11/Xaw/SmeBSB.h>
43 #include <X11/Xaw/SmeLine.h>
44 #include <X11/Xaw/AsciiText.h>
45 #include <X11/Xaw/Viewport.h>
46 #include <X11/Xaw/List.h>
47 #include <X11/Xaw/Toggle.h>
48 #include <X11/Xmu/WinUtil.h>
49 #include <X11/Xatom.h>
50
51 #ifdef USE_I18N
52 # include <X11/Xlocale.h>
53 #endif
54
55 #ifdef USE_XTEST
56 # include <X11/extensions/XTest.h>
57 #endif
58
59 #include "resources.h"
60 #define PROGRAM_NAME_WITH_VERSION "xvkbd (v3.2)"
61
62 /*
63  * Default keyboard layout is hardcoded here.
64  * Layout of the main keyboard can be redefined by resources.
65  */
66 #define NUM_KEY_ROWS    25
67 #define NUM_KEY_COLS    25
68
69 static char *keys_normal[NUM_KEY_ROWS][NUM_KEY_COLS] = {
70   { "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "BackSpace" },
71   { "Escape", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "\\", "`" },
72   { "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "Delete" },
73   { "Control_L", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Return" },
74   { "Shift_L", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Multi_key", "Shift_R" },
75   { "MainMenu", "Caps_Lock", "Alt_L", "Meta_L", "space", "Meta_R", "Alt_R",
76     "Left", "Right", "Up", "Down", "Focus" },
77 };
78 static char *keys_shift[NUM_KEY_ROWS][NUM_KEY_COLS] = {
79   { "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "BackSpace" },
80   { "Escape", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "|", "~" },
81   { "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "Delete" },
82   { "Control_L", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Return" },
83   { "Shift_L", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "Multi_key", "Shift_R" },
84   { "MainMenu", "Caps_Lock", "Alt_L", "Meta_L", "space", "Meta_R", "Alt_R",
85     "Left", "Right", "Up", "Down", "Focus" },
86 };
87 static char *keys_altgr[NUM_KEY_ROWS][NUM_KEY_COLS] = { { NULL } };
88 static char *keys_shift_altgr[NUM_KEY_ROWS][NUM_KEY_COLS] = { { NULL } };
89
90 static char *key_labels[NUM_KEY_ROWS][NUM_KEY_COLS] = {
91   { "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backspace" },
92   { "Esc", "!\n1", "@\n2", "#\n3", "$\n4", "%\n5", "^\n6",
93     "&\n7", "*\n8", "(\n9", ")\n0", "_\n-", "+\n=", "|\n\\", "~\n`" },
94   { "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{\n[", "}\n]", "Del" },
95   { "Control", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":\n;", "\"\n'", "Return" },
96   { "Shift", "Z", "X", "C", "V", "B", "N", "M", "<\n,", ">\n.", "?\n/", "Com\npose", "Shift" },
97   { "MainMenu", "Caps\nLock", "Alt", "Meta", "", "Meta", "Alt",
98     "left", "right", "up", "down", "Focus" },
99 };
100 static char *normal_key_labels[NUM_KEY_ROWS][NUM_KEY_COLS] = {
101   { "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backspace" },
102   { "Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "\\", "`" },
103   { "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "Del" },
104   { "Ctrl", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Return" },
105   { "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Comp", "Shift" },
106   { "MainMenu", "Caps", "Alt", "Meta", "", "Meta", "Alt",
107     "left", "right", "up", "down", "Focus" },
108 };
109 static char *shift_key_labels[NUM_KEY_ROWS][NUM_KEY_COLS] = {
110   { "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backspace" },
111   { "Esc", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "|", "~" },
112   { "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "Del" },
113   { "Ctrl", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Return" },
114   { "Shift", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "Comp", "Shift" },
115   { "MainMenu", "Caps", "Alt", "Meta", "", "Meta", "Alt",
116     "left", "right", "up", "down", "Focus" },
117 };
118 static char *altgr_key_labels[NUM_KEY_ROWS][NUM_KEY_COLS] = { { NULL } };
119 static char *shift_altgr_key_labels[NUM_KEY_ROWS][NUM_KEY_COLS] = { { NULL } };
120
121
122 #define NUM_KEYPAD_ROWS  NUM_KEY_ROWS
123 #define NUM_KEYPAD_COLS  NUM_KEY_COLS
124
125 static char *keypad[NUM_KEYPAD_ROWS][NUM_KEYPAD_COLS] = {
126   { "Num_Lock",  "KP_Divide",   "KP_Multiply", "Focus"       },
127   { "Home",      "Up",          "Page_Up",     "KP_Add"      },
128   { "Left",      "5",           "Right",       "KP_Subtract" },
129   { "End",       "Down",        "Page_Down",   "KP_Enter"    },
130   { "Insert",                   "Delete"                     },
131 };
132 static char *keypad_shift[NUM_KEYPAD_ROWS][NUM_KEYPAD_COLS] = {
133   { "Num_Lock",  "KP_Divide",   "KP_Multiply", "Focus"       },
134   { "KP_7",      "KP_8",        "KP_9",        "KP_Add"      },
135   { "KP_4",      "KP_5",        "KP_6",        "KP_Subtract" },
136   { "KP_1",      "KP_2",        "KP_3",        "KP_Enter"    },
137   { "KP_0",                     "."                          },
138 };
139 static char *keypad_label[NUM_KEYPAD_ROWS][NUM_KEYPAD_COLS] = {
140   { "Num\nLock", "/",           "*",           "Focus"       },
141   { "7\nHome",   "8\nUp  ",     "9\nPgUp",     "+"           },
142   { "4\nLeft",   "5\n    ",     "6\nRight",    "-"           },
143   { "1\nEnd ",   "2\nDown",     "3\nPgDn",     "Enter"       },
144   { "0\nIns ",                  ".\nDel "                    },
145 };
146
147 #define NUM_SUN_FKEY_ROWS 6
148 #define NUM_SUN_FKEY_COLS 3
149
150 static char *sun_fkey[NUM_SUN_FKEY_ROWS][NUM_SUN_FKEY_COLS] = {
151   { "L1", "L2"  },
152   { "L3", "L4"  },
153   { "L5", "L6"  },
154   { "L7", "L8"  },
155   { "L9", "L10" },
156   { "Help"      },
157 };
158 static char *sun_fkey_label[NUM_SUN_FKEY_ROWS][NUM_SUN_FKEY_COLS] = {
159   { "Stop \nL1", "Again\nL2"  },
160   { "Props\nL3", "Undo \nL4"  },
161   { "Front\nL5", "Copy \nL6"  },
162   { "Open \nL7", "Paste\nL8"  },
163   { "Find \nL9", "Cut  \nL10" },
164   { "Help"                    },
165 };
166
167 /*
168  * Image for arrow keys
169  */
170 #define up_width 7
171 #define up_height 13
172 static unsigned char up_bits[] = {
173    0x08, 0x1c, 0x1c, 0x3e, 0x2a, 0x49, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
174    0x08};
175
176 #define down_width 7
177 #define down_height 13
178 static unsigned char down_bits[] = {
179    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x49, 0x2a, 0x3e, 0x1c, 0x1c,
180    0x08};
181
182 #define left_width 13
183 #define left_height 13
184 static unsigned char left_bits[] = {
185    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x18, 0x00, 0x0e, 0x00,
186    0xff, 0x1f, 0x0e, 0x00, 0x18, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
187    0x00, 0x00};
188
189 #define right_width 13
190 #define right_height 13
191 static unsigned char right_bits[] = {
192    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x0e,
193    0xff, 0x1f, 0x00, 0x0e, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
194    0x00, 0x00};
195
196 #define check_width 16
197 #define check_height 16
198 static unsigned char check_bits[] = {
199   0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1e, 0x08, 0x0f,
200   0x8c, 0x07, 0xde, 0x03, 0xfe, 0x03, 0xfc, 0x01, 0xf8, 0x00, 0xf0, 0x00,
201   0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00};
202
203 #define back_width 18
204 #define back_height 13
205 static unsigned char back_bits[] = {
206    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
207    0x78, 0x00, 0x00, 0xfe, 0xff, 0x03, 0xff, 0xff, 0x03, 0xfe, 0xff, 0x03,
208    0x78, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
209    0x00, 0x00, 0x00};
210
211 /*
212  * Resources and options
213  */
214 #define Offset(entry) XtOffset(struct appres_struct *, entry)
215 static XtResource application_resources[] = {
216   { "description", "Description", XtRString, sizeof(char *),
217     Offset(description), XtRImmediate,
218     PROGRAM_NAME_WITH_VERSION " - virtual keyboard for X window system\n\n"
219     "Copyright (C) 2000-2010 by Tom Sato <VEF00200@nifty.ne.jp>\n"
220     "http://homepage3.nifty.com/tsato/xvkbd/\n\n"
221     "This program is free software with ABSOLUTELY NO WARRANTY,\n"
222     "distributed under the terms of the GNU General Public License.\n" },
223   { "showManualCommand", "ShowManualCommand", XtRString, sizeof(char *),
224     Offset(show_manual_command), XtRImmediate, "xterm -e man xvkbd &" },
225
226   { "windowGeometry", "Geometry", XtRString, sizeof(char *),
227     Offset(geometry), XtRImmediate, "" },
228   { "inheritGeoemetry", "Inherit", XtRBoolean, sizeof(Boolean),
229      Offset(inherit_geometry), XtRImmediate, (XtPointer)TRUE },
230   { "debug", "Debug", XtRBoolean, sizeof(Boolean),
231      Offset(debug), XtRImmediate, (XtPointer)FALSE },
232   { "version", "Version", XtRBoolean, sizeof(Boolean),
233      Offset(version), XtRImmediate, (XtPointer)FALSE },
234 #ifdef USE_XTEST
235   { "xtest", "XTest", XtRBoolean, sizeof(Boolean),
236      Offset(xtest), XtRImmediate, (XtPointer)TRUE },
237 #else
238   { "xtest", "XTest", XtRBoolean, sizeof(Boolean),
239      Offset(xtest), XtRImmediate, (XtPointer)FALSE },
240 #endif
241   { "noSync", "NoSync", XtRBoolean, sizeof(Boolean),
242      Offset(no_sync), XtRImmediate, (XtPointer)FALSE },
243   { "alwaysOnTop", "AlwaysOnTop", XtRBoolean, sizeof(Boolean),
244      Offset(always_on_top), XtRImmediate, (XtPointer)FALSE },
245   { "wmToolbar", "WmToolbar", XtRBoolean, sizeof(Boolean),
246      Offset(wm_toolbar), XtRImmediate, (XtPointer)FALSE },
247   { "jumpPointer", "JumpPointer", XtRBoolean, sizeof(Boolean),
248      Offset(jump_pointer), XtRImmediate, (XtPointer)TRUE },
249   { "jumpPointerAlways", "JumpPointer", XtRBoolean, sizeof(Boolean),
250      Offset(jump_pointer_always), XtRImmediate, (XtPointer)TRUE },
251   { "jumpPointerBack", "JumpPointer", XtRBoolean, sizeof(Boolean),
252      Offset(jump_pointer_back), XtRImmediate, (XtPointer)TRUE },
253   { "quickModifiers", "QuickModifiers", XtRBoolean, sizeof(Boolean),
254      Offset(quick_modifiers), XtRImmediate, (XtPointer)TRUE },
255   { "altgrLock", "ModifiersLock", XtRBoolean, sizeof(Boolean),
256      Offset(altgr_lock), XtRImmediate, (XtPointer)FALSE },
257   { "shiftLock", "ModifiersLock", XtRBoolean, sizeof(Boolean),
258      Offset(shift_lock), XtRImmediate, (XtPointer)FALSE },
259   { "modifiersLock", "ModifiersLock", XtRBoolean, sizeof(Boolean),
260      Offset(modifiers_lock), XtRImmediate, (XtPointer)FALSE },
261   { "numLockState", "NumLockState", XtRBoolean, sizeof(Boolean),
262      Offset(num_lock_state), XtRImmediate, (XtPointer)TRUE },
263   { "autoRepeat", "AutoRepeat", XtRBoolean, sizeof(Boolean),
264      Offset(auto_repeat), XtRImmediate, (XtPointer)TRUE },
265   { "modalKeytop", "ModalKeytop", XtRBoolean, sizeof(Boolean),
266      Offset(modal_keytop), XtRImmediate, (XtPointer)FALSE },
267   { "minimizable", "Minimizable", XtRBoolean, sizeof(Boolean),
268      Offset(minimizable), XtRImmediate, (XtPointer)FALSE },
269   { "secure", "Secure", XtRBoolean, sizeof(Boolean),
270      Offset(secure), XtRImmediate, (XtPointer)FALSE },
271   { "no_root", "NoRoot", XtRBoolean, sizeof(Boolean),
272      Offset(no_root), XtRImmediate, (XtPointer)FALSE },
273   { "wait_idle", "Text", XtRString, sizeof(char *),
274     Offset(wait_idle), XtRImmediate, "" },
275   { "nonexitable", "Secure", XtRBoolean, sizeof(Boolean),
276      Offset(nonexitable), XtRImmediate, (XtPointer)FALSE },
277   { "modalKeytop", "ModalKeytop", XtRBoolean, sizeof(Boolean),
278      Offset(modal_keytop), XtRImmediate, (XtPointer)FALSE },
279   { "modalThreshold", "ModalThreshold", XtRInt, sizeof(int),
280      Offset(modal_threshold), XtRImmediate, (XtPointer)150 },
281   { "keypad", "Keypad", XtRBoolean, sizeof(Boolean),
282      Offset(keypad), XtRImmediate, (XtPointer)TRUE },
283   { "functionkey", "FunctionKey", XtRBoolean, sizeof(Boolean),
284      Offset(function_key), XtRImmediate, (XtPointer)TRUE },
285   { "compact", "Compact", XtRBoolean, sizeof(Boolean),
286      Offset(compact), XtRImmediate, (XtPointer)FALSE },
287   { "keypadOnly", "KeypadOnly", XtRBoolean, sizeof(Boolean),
288      Offset(keypad_only), XtRImmediate, (XtPointer)FALSE },
289   { "keypadKeysym", "KeypadKeysym", XtRBoolean, sizeof(Boolean),
290      Offset(keypad_keysym), XtRImmediate, (XtPointer)FALSE },
291   { "autoAddKeysym", "AutoAddKeysym", XtRBoolean, sizeof(Boolean),
292      Offset(auto_add_keysym), XtRImmediate, (XtPointer)TRUE },
293   { "listWidgets", "Debug", XtRBoolean, sizeof(Boolean),
294      Offset(list_widgets), XtRImmediate, (XtPointer)FALSE },
295   { "positiveModifiers", "PositiveModifiers", XtRString, sizeof(char *),
296     Offset(positive_modifiers), XtRImmediate, "" },
297   { "text", "Text", XtRString, sizeof(char *),
298     Offset(text), XtRImmediate, "" },
299   { "file", "File", XtRString, sizeof(char *),
300     Offset(file), XtRImmediate, "" },
301   { "window", "Window", XtRString, sizeof(char *),
302     Offset(window), XtRImmediate, "" },
303   { "instance", "Instance", XtRString, sizeof(char *),
304     Offset(instance), XtRImmediate, "" },
305   { "widget", "Widget", XtRString, sizeof(char *),
306     Offset(widget), XtRImmediate, "" },
307   { "generalFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
308       Offset(general_font), XtRString, XtDefaultFont},
309   { "letterFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
310       Offset(letter_font), XtRString, XtDefaultFont},
311   { "specialFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
312       Offset(special_font), XtRString, XtDefaultFont},
313   { "keypadFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
314       Offset(keypad_font), XtRString, XtDefaultFont},
315   { "generalBackground", XtCBackground, XtRPixel, sizeof(Pixel),
316      Offset(general_background), XtRString, "gray" },
317   { "specialBackground", XtCBackground, XtRPixel, sizeof(Pixel),
318      Offset(special_background), XtRString, "gray" },
319   { "specialForeground", XtCForeground, XtRPixel, sizeof(Pixel),
320      Offset(special_foreground), XtRString, "black" },
321 #ifdef USE_I18N
322   { "specialFontSet", XtCFontSet, XtRFontSet, sizeof(XFontSet),
323       Offset(special_fontset), XtRString, XtDefaultFontSet},
324 #endif
325   { "highlightBackground", XtCBackground, XtRPixel, sizeof(Pixel),
326      Offset(highlight_background), XtRString, "gray" },
327   { "highlightForeground", XtCForeground, XtRPixel, sizeof(Pixel),
328      Offset(highlight_foreground), XtRString, "forestgreen" },
329   { "focusBackground", XtCBackground, XtRPixel, sizeof(Pixel),
330      Offset(focus_background), XtRString, "gray" },
331   { "remoteFocusBackground", XtCBackground, XtRPixel, sizeof(Pixel),
332      Offset(remote_focus_background), XtRString, "cyan" },
333   { "balloonBackground", XtCBackground, XtRPixel, sizeof(Pixel),
334      Offset(balloon_background), XtRString, "LightYellow1" },
335   { "launchBalloonBackground", XtCBackground, XtRPixel, sizeof(Pixel),
336      Offset(launch_balloon_background), XtRString, "SkyBlue1" },
337
338   { "normalkeys", "NormalKeys", XtRString, sizeof(char *),
339     Offset(keys_normal), XtRImmediate, "" },
340   { "shiftkeys", "ShiftKeys", XtRString, sizeof(char *),
341     Offset(keys_shift), XtRImmediate, "" },
342   { "altgrkeys", "AltgrKeys", XtRString, sizeof(char *),
343     Offset(keys_altgr), XtRImmediate, "" },
344   { "shiftaltgrkeys", "ShiftAltgrKeys", XtRString, sizeof(char *),
345     Offset(keys_shift_altgr), XtRImmediate, "" },
346   { "keylabels", "KeyLabels", XtRString, sizeof(char *),
347     Offset(key_labels), XtRImmediate, "" },
348   { "normalkeylabels", "NormalKeyLabels", XtRString, sizeof(char *),
349     Offset(normal_key_labels), XtRImmediate, "" },
350   { "shiftkeylabels", "ShiftKeyLabels", XtRString, sizeof(char *),
351     Offset(shift_key_labels), XtRImmediate, "" },
352   { "altgrkeylabels", "AltgrKeyLabels", XtRString, sizeof(char *),
353     Offset(altgr_key_labels), XtRImmediate, "" },
354   { "shiftaltgrkeylabels", "ShiftAltgrKeyLabels", XtRString, sizeof(char *),
355     Offset(shift_altgr_key_labels), XtRImmediate, "" },
356
357   { "normalkeypad", "NormalKeypad", XtRString, sizeof(char *),
358     Offset(keypad_normal), XtRImmediate, "" },
359   { "shiftkeypad", "ShiftKeypad", XtRString, sizeof(char *),
360     Offset(keypad_shift), XtRImmediate, "" },
361   { "keypad_labels", "KeypadLabels", XtRString, sizeof(char *),
362     Offset(keypad_labels), XtRImmediate, "" },
363
364   { "deadkeys", "DeadKeys", XtRString, sizeof(char *),
365     Offset(deadkeys), XtRImmediate, "" },
366   { "altgrKeycode", "AltgrKeycode", XtRInt, sizeof(int),
367     Offset(altgr_keycode), XtRImmediate, (XtPointer)0 },
368
369   { "keyFile", "KeyFile", XtRString, sizeof(char *),
370     Offset(key_file), XtRImmediate, ".xvkbd" },
371   { "dictFile", "DictFile", XtRString, sizeof(char *),
372     Offset(dict_file), XtRImmediate, "/usr/share/dict/words" },
373   { "customizations", "Customizations", XtRString, sizeof(char *),
374     Offset(customizations), XtRImmediate, "default" },
375   { "editableFunctionKeys", "EditableFunctionKeys", XtRInt, sizeof(int),
376      Offset(editable_function_keys), XtRImmediate, (XtPointer)12 },
377
378   { "maxWidthRatio", "MaxRatio", XtRFloat, sizeof(float),
379      Offset(max_width_ratio), XtRString, "0.9" },
380   { "maxHeightRatio", "MaxRatio", XtRFloat, sizeof(float),
381      Offset(max_height_ratio), XtRString, "0.5" },
382   { "textDelay", "TextDelay", XtRInt, sizeof(int),
383      Offset(text_delay), XtRImmediate, (XtPointer)0 },
384
385   { "keyClickPitch", "KeyClickPitch", XtRInt, sizeof(int),
386      Offset(key_click_pitch), XtRImmediate, (XtPointer)1000 },
387   { "keyClickDuration", "KeyClickDuration", XtRInt, sizeof(int),
388      Offset(key_click_duration), XtRImmediate, (XtPointer)1 },
389   { "autoClickDelay", "AutoClickDelay", XtRInt, sizeof(int),
390      Offset(autoclick_delay), XtRImmediate, (XtPointer)0 },
391 };
392 #undef Offset
393
394 static XrmOptionDescRec options[] = {
395   { "-geometry", ".windowGeometry", XrmoptionSepArg, NULL },
396   { "-windowgeometry", ".windowGeometry", XrmoptionSepArg, NULL },
397   { "-debug", ".debug", XrmoptionNoArg, "True" },
398 #ifdef USE_XTEST
399   { "-xtest", ".xtest", XrmoptionNoArg, "True" },
400   { "-xsendevent", ".xtest", XrmoptionNoArg, "False" },
401   { "-no-jump-pointer", ".jumpPointer", XrmoptionNoArg, "False" },
402   { "-no-back-pointer", ".jumpPointerBack", XrmoptionNoArg, "False" },
403 #endif
404   { "-no-sync", ".noSync", XrmoptionNoArg, "True" },
405   { "-always-on-top", ".alwaysOnTop", XrmoptionNoArg, "True" },  /* EXPERIMENTAL */
406   { "-quick", ".quickModifiers", XrmoptionNoArg, "True" },
407   { "-modifiers", ".positiveModifiers", XrmoptionSepArg, NULL },
408   { "-text", ".text", XrmoptionSepArg, NULL },
409   { "-file", ".file", XrmoptionSepArg, NULL },
410   { "-delay", ".textDelay", XrmoptionSepArg, NULL },
411   { "-window", ".window", XrmoptionSepArg, NULL },
412   { "-instance", ".instance", XrmoptionSepArg, NULL },
413   { "-widget", ".widget", XrmoptionSepArg, NULL },
414   { "-altgr-lock", ".altgrLock", XrmoptionNoArg, "True" },
415   { "-no-altgr-lock", ".altgrLock", XrmoptionNoArg, "False" },
416   { "-no-repeat", ".autoRepeat", XrmoptionNoArg, "False" },
417   { "-norepeat", ".autoRepeat", XrmoptionNoArg, "False" },
418   { "-no-keypad", ".keypad", XrmoptionNoArg, "False" },
419   { "-nokeypad", ".keypad", XrmoptionNoArg, "False" },
420   { "-no-functionkey", ".functionkey", XrmoptionNoArg, "False" },
421   { "-nofunctionkey", ".functionkey", XrmoptionNoArg, "False" },
422   { "-highlight", ".highlightForeground", XrmoptionSepArg, NULL },
423   { "-compact", ".compact", XrmoptionNoArg, "True" },
424   { "-keypad", ".keypadOnly", XrmoptionNoArg, "True" },
425   { "-true-keypad", ".keypadKeysym", XrmoptionNoArg, "True" },
426   { "-truekeypad", ".keypadKeysym", XrmoptionNoArg, "True" },
427   { "-no-add-keysym", ".autoAddKeysym", XrmoptionNoArg, "False" },
428   { "-altgr-keycode", ".altgrKeycode", XrmoptionSepArg, NULL },
429   { "-list", ".listWidgets", XrmoptionNoArg, "True" },
430   { "-modal", ".modalKeytop", XrmoptionNoArg, "True" },
431   { "-minimizable", ".minimizable", XrmoptionNoArg, "True" },
432   { "-secure", ".secure", XrmoptionNoArg, "True" },
433   { "-no_root", ".no_root", XrmoptionNoArg, "True" },
434   { "-wait_idle", ".wait_idle", XrmoptionSepArg, NULL },
435   { "-nonexitable", ".nonexitable", XrmoptionNoArg, "True" },
436   { "-xdm", ".Secure", XrmoptionNoArg, "True" },
437   { "-dict", ".dictFile", XrmoptionSepArg, NULL },
438   { "-keyfile", ".keyFile", XrmoptionSepArg, NULL },
439   { "-customizations", ".customizations", XrmoptionSepArg, NULL },
440   { "-version", ".version", XrmoptionNoArg, "True" },
441   { "-help", ".version", XrmoptionNoArg, "True" },
442 };
443
444 /*
445  * Global variables
446  */
447 static char dict_filename[PATH_MAX] = "";
448
449 static int argc1;
450 static char **argv1;
451
452 static XtAppContext app_con;
453 static Widget toplevel;
454 static Widget key_widgets[NUM_KEY_ROWS][NUM_KEY_COLS];
455 static Widget main_menu = None;
456
457 static Dimension toplevel_height = 1000;
458
459 static Display *dpy;
460 static Atom wm_delete_window = None;
461
462 static KeySym *keysym_table = NULL;
463 static int min_keycode, max_keycode;
464 static int keysym_per_keycode;
465 static Boolean error_detected;
466
467 static int alt_mask = 0;
468 static int meta_mask = 0;
469 static int altgr_mask = 0;
470 static KeySym altgr_keysym = NoSymbol;
471
472 static int shift_state = 0;
473 static int mouse_shift = 0;
474
475 static Display *target_dpy = NULL;
476
477 static Window toplevel_parent = None;
478 static Window focused_window = None;
479 static Window focused_subwindow = None;
480
481 static Pixmap xvkbd_pixmap = None;
482
483 static int AddKeysym(KeySym keysym, Boolean top);  /* forward */
484 static void SendString(const unsigned char *str);
485 static void MakeKeyboard(Boolean remake);
486 static void MakeKeypad(Widget form, Widget from_vert, Widget from_horiz);
487 static void MakeSunFunctionKey(Widget form, Widget from_vert, Widget from_horiz);
488 static void MakeDeadkeyPanel(Widget form);
489 static void RefreshMainMenu(void);
490 static void PopupFunctionKeyEditor(void);
491 static void DeleteWindowProc(Widget w, XEvent *event, String *pars, Cardinal *n_pars);
492
493 /*
494  * Search for window which has specified instance name (WM_NAME)
495  * or class name (WM_CLASS).
496  */
497 static Window FindWindow(Window top, char *name)
498 {
499   Window w;
500   Window *children, dummy;
501   unsigned int nchildren;
502   int i;
503   XClassHint hint;
504   char *win_name;
505
506   w = None;
507
508   if (appres.debug) fprintf(stderr, "FindWindow: id=0x%lX", (long)top);
509
510   if (XGetClassHint(target_dpy, top, &hint)) {
511     if (hint.res_name) {
512       if (appres.debug) fprintf(stderr, " instance=\"%s\"", hint.res_name);
513       if ((strlen(name) > 0 && fnmatch(name, hint.res_name, 0) == 0)
514           || (strlen(appres.instance) > 0 && fnmatch(appres.instance, hint.res_name, 0) == 0)) w = top;
515       XFree(hint.res_name);
516     }
517     if (strlen(name) > 0 && hint.res_class) {
518       if (appres.debug) fprintf(stderr, " class=\"%s\"", hint.res_class);
519       if (strlen(name) > 0 && fnmatch(name, hint.res_class, 0) == 0) w = top;
520       XFree(hint.res_class);
521     }
522   }
523   if (XFetchName(target_dpy, top, &win_name)) { /* window title */
524     if (appres.debug) fprintf(stderr, " title=\"%s\"", win_name);
525     if (strlen(name) > 0 && fnmatch(name, win_name, 0) == 0) w = top;
526     XFree(win_name);
527   }
528
529   if (appres.debug) fprintf(stderr, "%s\n", (w == None) ? "" : " [matched]");
530
531   if (w == None &&
532       XQueryTree(target_dpy, top, &dummy, &dummy, &children, &nchildren)) {
533     for (i = 0; i < nchildren; i++) {
534       w = FindWindow(children[i], name);
535       if (w != None) break;
536     }
537     if (children) XFree((char *)children);
538   }
539
540   return(w);
541 }
542
543 /*
544  * This will be called to get window to set input focus,
545  * when user pressed the "Focus" button.
546  */
547 static void GetFocusedWindow(void)
548 {
549   Cursor cursor;
550   XEvent event;
551   Window target_root, child;
552   int junk_i;
553   unsigned junk_u;
554   Window junk_w;
555   int scrn;
556   int cur_x, cur_y, last_x, last_y;
557   double x_ratio, y_ratio;
558
559   XFlush(target_dpy);
560   target_root = RootWindow(target_dpy, DefaultScreen(target_dpy));
561
562   cursor = XCreateFontCursor(dpy, (target_dpy == dpy) ? XC_crosshair : XC_dot);
563   if (XGrabPointer(dpy, RootWindow(dpy, DefaultScreen(dpy)), False, ButtonPressMask,
564                    GrabModeSync, GrabModeAsync, None,
565                    cursor, CurrentTime) == 0) {
566     if (appres.debug)
567       fprintf(stderr, "Grab pointer - waiting for button press\n");
568     last_x = -1;
569     last_y = -1;
570     x_ratio = ((double)WidthOfScreen(DefaultScreenOfDisplay(target_dpy))
571                / WidthOfScreen(XtScreen(toplevel)));
572     y_ratio = ((double)HeightOfScreen(DefaultScreenOfDisplay(target_dpy))
573                / HeightOfScreen(XtScreen(toplevel)));
574     do {
575       XAllowEvents(dpy, SyncPointer, CurrentTime);
576       if (target_dpy == dpy) {
577         XNextEvent(dpy, &event);
578       } else {
579         XCheckTypedEvent(dpy, ButtonPress, &event);
580         if (XQueryPointer(dpy, RootWindow(dpy, DefaultScreen(dpy)), &junk_w, &junk_w,
581                           &cur_x, &cur_y, &junk_i, &junk_i, &junk_u)) {
582           cur_x = cur_x * x_ratio;
583           cur_y = cur_y * y_ratio;
584         }
585         if (cur_x != last_x || cur_y != last_y) {
586           if (appres.debug) fprintf(stderr, "Moving pointer to (%d, %d) on %s\n",
587                                     cur_x, cur_y, XDisplayString(target_dpy));
588           XWarpPointer(target_dpy, None, target_root, 0, 0, 0, 0, cur_x, cur_y);
589           XFlush(target_dpy);
590           last_x = cur_x;
591           last_y = cur_y;
592           XQueryPointer(target_dpy, target_root, &junk_w, &child,
593                         &cur_x, &cur_y, &junk_i, &junk_i, &junk_u);
594           usleep(10000);
595         } else {
596           usleep(100000);
597         }
598       }
599     } while (event.type != ButtonPress);
600     XUngrabPointer(dpy, CurrentTime);
601
602     focused_window = None;
603     if (target_dpy == dpy) focused_window = event.xbutton.subwindow;
604     if (focused_window == None) {
605       XFlush(target_dpy);
606       for (scrn = 0; scrn < ScreenCount(target_dpy); scrn++) {
607         if (XQueryPointer(target_dpy, RootWindow(target_dpy, scrn), &junk_w, &child,
608                           &junk_i, &junk_i, &junk_i, &junk_i, &junk_u)) {
609           if (appres.debug)
610             fprintf(stderr, "Window on the other display/screen (screen #%d of %s) focused\n",
611                     scrn, XDisplayString(target_dpy));
612           target_root = RootWindow(target_dpy, scrn);
613           focused_window = child;
614           break;
615         }
616       }
617     }
618     if (focused_window == None) focused_window = target_root;
619     else focused_window = XmuClientWindow(target_dpy, focused_window);
620     if (appres.debug) fprintf(stderr, "Selected window is: 0x%lX on %s\n",
621                               focused_window, XDisplayString(target_dpy));
622
623     if (target_dpy == dpy && XtWindow(toplevel) == focused_window) {
624       focused_window = None;
625       focused_subwindow = focused_window;
626       return;
627     }
628
629     focused_subwindow = focused_window;
630     do {  /* search the child window */
631       XQueryPointer(target_dpy, focused_subwindow, &junk_w, &child,
632                     &junk_i, &junk_i, &junk_i, &junk_i, &junk_u);
633       if (child != None) {
634         focused_subwindow = child;
635         if (appres.debug) fprintf(stderr, "  going down: 0x%lX\n", focused_subwindow);
636       }
637     } while (child != None);
638     if (appres.list_widgets || strlen(appres.widget) != 0) {
639       child = FindWidget(toplevel, focused_window, appres.widget);
640       if (child != None) focused_subwindow = child;
641     }
642   } else {
643     fprintf(stderr, "%s: cannot grab pointer\n", PROGRAM_NAME);
644   }
645 }
646
647 /*
648  * Read keyboard mapping and modifier mapping.
649  * Keyboard mapping is used to know what keys are in shifted position.
650  * Modifier mapping is required because we should know Alt and Meta
651  * key are used as which modifier.
652  */
653 static void Highlight(char *name, Boolean state);
654
655 static void ReadKeymap(void)
656 {
657   int i;
658   int keycode, inx, pos;
659   KeySym keysym;
660   XModifierKeymap *modifiers;
661   Widget w;
662   int last_altgr_mask;
663
664   XDisplayKeycodes(target_dpy, &min_keycode, &max_keycode);
665   if (keysym_table != NULL) XFree(keysym_table);
666   keysym_table = XGetKeyboardMapping(target_dpy,
667                              min_keycode, max_keycode - min_keycode + 1,
668                              &keysym_per_keycode);
669   for (keycode = min_keycode; keycode <= max_keycode; keycode++) {
670     /* if the first keysym is alphabet and the second keysym is NoSymbol,
671        it is equivalent to pair of lowercase and uppercase alphabet */
672     inx = (keycode - min_keycode) * keysym_per_keycode;
673     if (keysym_table[inx + 1] == NoSymbol
674         && ((XK_A <= keysym_table[inx] && keysym_table[inx] <= XK_Z)
675             || (XK_a <= keysym_table[inx] && keysym_table[inx] <= XK_z))) {
676       if (XK_A <= keysym_table[inx] && keysym_table[inx] <= XK_Z)
677         keysym_table[inx] = keysym_table[inx] - XK_A + XK_a;
678       keysym_table[inx + 1] = keysym_table[inx] - XK_a + XK_A;
679     }
680   }
681
682   last_altgr_mask = altgr_mask;
683   alt_mask = 0;
684   meta_mask = 0;
685   altgr_mask = 0;
686   altgr_keysym = NoSymbol;
687   modifiers = XGetModifierMapping(target_dpy);
688   for (i = 0; i < 8; i++) {
689     for (pos = 0; pos < modifiers->max_keypermod; pos++) {
690       keycode = modifiers->modifiermap[i * modifiers->max_keypermod + pos];
691       if (keycode < min_keycode || max_keycode < keycode) continue;
692
693       keysym = keysym_table[(keycode - min_keycode) * keysym_per_keycode];
694       if (keysym == XK_Alt_L || keysym == XK_Alt_R) {
695         alt_mask = 1 << i;
696       } else if (keysym == XK_Meta_L || keysym == XK_Meta_R) {
697         meta_mask = 1 << i;
698       } else if (keysym == XK_Mode_switch) {
699         if (appres.debug)
700           fprintf(stderr, "%s: found Mode_switch at %dth modifier\n", PROGRAM_NAME, i);
701         if (altgr_keysym == XK_ISO_Level3_Shift) {
702           if (appres.debug)
703             fprintf(stderr, "%s: both ISO_Level3_Shift and Mode_switch found - ISO_Level3_Shift prefered\n", PROGRAM_NAME);
704         } else {
705           altgr_mask = 0x0101 << i;
706           /* I don't know why, but 0x2000 was required for mod3 on my Linux box */
707           altgr_keysym = keysym;
708         }
709       } else if (keysym == XK_ISO_Level3_Shift) {
710         /* if no Mode_switch, try to use ISO_Level3_Shift instead */
711         /* however, it may not work as intended - I don't know why */
712         if (appres.debug)
713           fprintf(stderr, "%s: found ISO_Level3_Shift at %dth modifier\n", PROGRAM_NAME, i);
714         if (altgr_keysym == XK_Mode_switch) {
715           if (appres.debug)
716             fprintf(stderr, "%s: both ISO_Level3_Shift and Mode_switch found - ISO_Level3_Shift prefered\n", PROGRAM_NAME);
717         }
718         altgr_mask = 1 << i;
719         altgr_keysym = keysym;
720       }
721     }
722   }
723   XFreeModifiermap(modifiers);
724
725   if (altgr_keysym != XK_Mode_switch) {
726     fprintf(stderr, "%s: Mode_switch not available as a modifier\n", PROGRAM_NAME);
727     if (altgr_keysym == XK_ISO_Level3_Shift)
728       fprintf(stderr, "%s: although ISO_Level3_Shift is used instead, AltGr may not work correctly\n", PROGRAM_NAME);
729     else
730       fprintf(stderr, "%s: AltGr can't be used\n", PROGRAM_NAME);
731   }
732
733   w = XtNameToWidget(toplevel, "*Multi_key");
734   if (w != None) {
735     if (XKeysymToKeycode(target_dpy, XK_Multi_key) == NoSymbol) {
736       if (!appres.auto_add_keysym || AddKeysym(XK_Multi_key, FALSE) == NoSymbol)
737         XtSetSensitive(w, FALSE);
738     }
739   }
740   w = XtNameToWidget(toplevel, "*Mode_switch");
741   if (w != None) {
742     if (appres.xtest && 0 < appres.altgr_keycode) {
743       XtSetSensitive(w, TRUE);
744       if (appres.debug)
745         fprintf(stderr, "%s: keycode %d will be used for AltGr - it was specified with altgrKeycode\n",
746                 PROGRAM_NAME, appres.altgr_keycode);
747     } else if (altgr_mask) {
748       XtSetSensitive(w, TRUE);
749     } else {
750       XtSetSensitive(w, FALSE);
751       if (shift_state & last_altgr_mask) {
752         shift_state &= ~last_altgr_mask;
753         Highlight("Mode_switch", FALSE);
754       }
755     }
756   }
757 }
758
759 /*
760  * This will called when X error is detected when attempting to
761  * send a event to a client window;  this will normally caused
762  * when the client window is destroyed.
763  */
764 static int MyErrorHandler(Display *my_dpy, XErrorEvent *event)
765 {
766   char msg[200];
767
768   error_detected = TRUE;
769   if (event->error_code == BadWindow) {
770     if (appres.debug)
771       fprintf(stderr, "%s: BadWindow - couldn't find target window 0x%lX (destroyed?)\n",
772               PROGRAM_NAME, (long)focused_window);
773     return 0;
774   }
775   XGetErrorText(my_dpy, event->error_code, msg, sizeof(msg) - 1);
776   fprintf(stderr, "X error trapped: %s, request-code=%d\n", msg, event->request_code);
777   return 0;
778 }
779
780 /*
781  * Send event to the focused window.
782  * If input focus is specified explicitly, select the window
783  * before send event to the window.
784  */
785 static void SendEvent(XKeyEvent *event)
786 {
787   static Boolean first = TRUE;
788
789   if (!appres.no_sync) {
790     XSync(event->display, FALSE);
791     XSetErrorHandler(MyErrorHandler);
792   }
793
794   error_detected = FALSE;
795   if (focused_window != None) {
796     /* set input focus if input focus is set explicitly */
797     if (appres.debug)
798       fprintf(stderr, "Set input focus to window 0x%lX (0x%lX)\n",
799               (long)focused_window, (long)event->window);
800     XSetInputFocus(event->display, focused_window, RevertToParent, CurrentTime);
801     if (!appres.no_sync) XSync(event->display, FALSE);
802   }
803   if (!error_detected) {
804     if (appres.xtest) {
805 #ifdef USE_XTEST
806       if (appres.debug)
807         fprintf(stderr, "XTestFakeKeyEvent(0x%lx, %ld, %d)\n",
808                 (long)event->display, (long)event->keycode, event->type == KeyPress);
809       if (appres.jump_pointer) {
810         Window root, child, w;
811         int root_x, root_y, x, y;
812         unsigned int mask;
813         int revert_to;
814
815         w = None;
816         if (first || strlen(appres.text) == 0 || appres.jump_pointer_back) {
817           first = FALSE;
818
819           w = focused_subwindow;
820           if (w == None && appres.jump_pointer_always)
821             XGetInputFocus(event->display, &w, &revert_to);
822
823           if (w != None) {
824             if (appres.debug)
825               fprintf(stderr, "SendEvent: jump pointer to window 0x%lx\n", (long)w);
826
827             XQueryPointer(event->display, w,
828                           &root, &child, &root_x, &root_y, &x, &y, &mask);
829             XWarpPointer(event->display, None, w, 0, 0, 0, 0, 1, 1);
830             XFlush(event->display);
831           }
832         }
833
834         XTestFakeKeyEvent(event->display, event->keycode, event->type == KeyPress, 0);
835         XFlush(event->display);
836
837         if (w != None && appres.jump_pointer_back) {
838           XWarpPointer(event->display, None, root, 0, 0, 0, 0, root_x, root_y);
839           XFlush(event->display);
840         }
841       } else {
842         XTestFakeKeyEvent(event->display, event->keycode, event->type == KeyPress, 0);
843         XFlush(event->display);
844       }
845 #else
846       fprintf(stderr, "%s: this binary is compiled without XTEST support\n",
847               PROGRAM_NAME);
848 #endif
849     } else {
850       XSendEvent(event->display, event->window, TRUE, KeyPressMask, (XEvent *)event);
851       if (!appres.no_sync) XSync(event->display, FALSE);
852
853       if (error_detected
854           && (focused_subwindow != None) && (focused_subwindow != event->window)) {
855         error_detected = FALSE;
856         event->window = focused_subwindow;
857         if (appres.debug)
858           fprintf(stderr, "   retry: send event to window 0x%lX (0x%lX)\n",
859                   (long)focused_window, (long)event->window);
860         XSendEvent(event->display, event->window, TRUE, KeyPressMask, (XEvent *)event);
861         if (!appres.no_sync) XSync(event->display, FALSE);
862       }
863     }
864   }
865
866   if (error_detected) {
867     /* reset focus because focused window is (probably) no longer exist */
868     XBell(dpy, 0);
869     focused_window = None;
870     focused_subwindow = None;
871   }
872
873   XSetErrorHandler(NULL);
874 }
875
876 /*
877  * Insert a specified keysym to unused position in the keymap table.
878  * This will be called to add required keysyms on-the-fly.
879  * if the second parameter is TRUE, the keysym will be added to the
880  * non-shifted position - this may be required for modifier keys
881  * (e.g. Mode_switch) and some special keys (e.g. F20).
882  */
883 static int AddKeysym(KeySym keysym, Boolean top)
884 {
885   int keycode, pos, max_pos, inx, phase;
886
887   if (top) {
888     max_pos = 0;
889   } else {
890     max_pos = keysym_per_keycode - 1;
891     if (4 <= max_pos) max_pos = 3;
892     if (2 <= max_pos && altgr_keysym != XK_Mode_switch) max_pos = 1;
893   }
894
895   for (phase = 0; phase < 2; phase++) {
896     for (keycode = max_keycode; min_keycode <= keycode; keycode--) {
897       for (pos = max_pos; 0 <= pos; pos--) {
898         inx = (keycode - min_keycode) * keysym_per_keycode;
899         if ((phase != 0 || keysym_table[inx] == NoSymbol) && keysym_table[inx] < 0xFF00) {
900           /* In the first phase, to avoid modifing existing keys, */
901           /* add the keysym only to the keys which has no keysym in the first position. */
902           /* If no place fuond in the first phase, add the keysym for any keys except */
903           /* for modifier keys and other special keys */
904           if (keysym_table[inx + pos] == NoSymbol) {
905             if (appres.debug)
906               fprintf(stderr, "*** Adding keysym \"%s\" at keycode %d position %d/%d\n",
907                       XKeysymToString(keysym), keycode, pos, keysym_per_keycode);
908             keysym_table[inx + pos] = keysym;
909             XChangeKeyboardMapping(target_dpy, keycode, keysym_per_keycode, &keysym_table[inx], 1);
910             XFlush(target_dpy);
911             return keycode;
912           }
913         }
914       }
915     }
916   }
917   fprintf(stderr, "%s: couldn't add \"%s\" to keymap\n",
918           PROGRAM_NAME, XKeysymToString(keysym));
919   XBell(dpy, 0);
920   return NoSymbol;
921 }
922
923 /*
924  * Add the specified key as a new modifier.
925  * This is used to use Mode_switch (AltGr) as a modifier.
926  */
927 static void AddModifier(KeySym keysym)
928 {
929   XModifierKeymap *modifiers;
930   int keycode, i, pos;
931
932   keycode = XKeysymToKeycode(target_dpy, keysym);
933   if (keycode == NoSymbol) keycode = AddKeysym(keysym, TRUE);
934   
935   modifiers = XGetModifierMapping(target_dpy);
936   for (i = 7; 3 < i; i--) {
937     if (modifiers->modifiermap[i * modifiers->max_keypermod] == NoSymbol
938         || ((keysym_table[(modifiers->modifiermap[i * modifiers->max_keypermod]
939                            - min_keycode) * keysym_per_keycode]) == XK_ISO_Level3_Shift
940             && keysym == XK_Mode_switch)) {
941       for (pos = 0; pos < modifiers->max_keypermod; pos++) {
942         if (modifiers->modifiermap[i * modifiers->max_keypermod + pos] == NoSymbol) {
943           if (appres.debug)
944             fprintf(stderr, "Adding modifier \"%s\" as %dth modifier\n",
945                     XKeysymToString(keysym), i);
946           modifiers->modifiermap[i * modifiers->max_keypermod + pos] = keycode;
947           XSetModifierMapping(target_dpy, modifiers);
948           return;
949         }
950       }
951     }
952   }
953   fprintf(stderr, "%s: couldn't add \"%s\" as modifier\n",
954           PROGRAM_NAME, XKeysymToString(keysym));
955   XBell(dpy, 0);
956 }
957
958 /*
959  * Send sequence of KeyPressed/KeyReleased events to the focused
960  * window to simulate keyboard.  If modifiers (shift, control, etc)
961  * are set ON, many events will be sent.
962  */
963 static void SendKeyPressedEvent(KeySym keysym, unsigned int shift)
964 {
965   Window cur_focus;
966   int revert_to;
967   XKeyEvent event;
968   int keycode;
969   Window root, *children;
970   unsigned int n_children;
971   int phase, inx;
972   Boolean found;
973
974   if (focused_subwindow != None)
975     cur_focus = focused_subwindow;
976   else
977     XGetInputFocus(target_dpy, &cur_focus, &revert_to);
978
979   if (appres.debug) {
980     char ch = '?';
981     if ((keysym & ~0x7f) == 0 && isprint(keysym)) ch = keysym;
982     fprintf(stderr, "SendKeyPressedEvent: focus=0x%lX, key=0x%lX (%c), shift=0x%lX\n",
983             (long)cur_focus, (long)keysym, ch, (long)shift);
984   }
985
986   if (XtWindow(toplevel) != None) {
987     if (toplevel_parent == None) {
988       XQueryTree(target_dpy, RootWindow(target_dpy, DefaultScreen(target_dpy)),
989                  &root, &toplevel_parent, &children, &n_children);
990       XFree(children);
991     }
992     if (cur_focus == None || cur_focus == PointerRoot
993         || cur_focus == XtWindow(toplevel) || cur_focus == toplevel_parent) {
994       /* notice user when no window focused or the xvkbd window is focused */
995       XBell(dpy, 0);
996       return;
997     }
998   }
999
1000   found = FALSE;
1001   keycode = 0;
1002   if (keysym != NoSymbol) {
1003     for (phase = 0; phase < 2; phase++) {
1004       for (keycode = min_keycode; !found && (keycode <= max_keycode); keycode++) {
1005         /* Determine keycode for the keysym:  we use this instead
1006            of XKeysymToKeycode() because we must know shift_state, too */
1007         inx = (keycode - min_keycode) * keysym_per_keycode;
1008         if (keysym_table[inx] == keysym) {
1009           shift &= ~altgr_mask;
1010           if (keysym_table[inx + 1] != NoSymbol) shift &= ~ShiftMask;
1011           found = TRUE;
1012           break;
1013         } else if (keysym_table[inx + 1] == keysym) {
1014           shift &= ~altgr_mask;
1015           shift |= ShiftMask;
1016           found = TRUE;
1017           break;
1018         }
1019       }
1020       if (!found && altgr_mask && 3 <= keysym_per_keycode) {
1021         for (keycode = min_keycode; !found && (keycode <= max_keycode); keycode++) {
1022           inx = (keycode - min_keycode) * keysym_per_keycode;
1023           if (keysym_table[inx + 2] == keysym) {
1024             shift &= ~ShiftMask;
1025             shift |= altgr_mask;
1026             found = TRUE;
1027             break;
1028           } else if (4 <= keysym_per_keycode && keysym_table[inx + 3] == keysym) {
1029             shift |= ShiftMask | altgr_mask;
1030             found = TRUE;
1031             break;
1032           }
1033         }
1034       }
1035       if (found || !appres.auto_add_keysym) break;
1036
1037       if (0xF000 <= keysym) {
1038         /* for special keys such as function keys,
1039            first try to add it in the non-shifted position of the keymap */
1040         if (AddKeysym(keysym, TRUE) == NoSymbol) AddKeysym(keysym, FALSE);
1041       } else {
1042         AddKeysym(keysym, FALSE);
1043       }
1044     }
1045     if (appres.debug) {
1046       if (found)
1047         fprintf(stderr, "SendKeyPressedEvent: keysym=0x%lx, keycode=%ld, shift=0x%lX\n",
1048                 (long)keysym, (long)keycode, (long)shift);
1049       else
1050         fprintf(stderr, "SendKeyPressedEvent: keysym=0x%lx - keycode not found\n",
1051                 (long)keysym);
1052     }
1053   }
1054
1055   event.display = target_dpy;
1056   event.window = cur_focus;
1057   event.root = RootWindow(event.display, DefaultScreen(event.display));
1058   event.subwindow = None;
1059   event.time = CurrentTime;
1060   event.x = 1;
1061   event.y = 1;
1062   event.x_root = 1;
1063   event.y_root = 1;
1064   event.same_screen = TRUE;
1065
1066 #ifdef USE_XTEST
1067   if (appres.xtest) {
1068     Window root, child;
1069     int root_x, root_y, x, y;
1070     unsigned int mask;
1071
1072     XQueryPointer(target_dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &mask);
1073
1074     event.type = KeyRelease;
1075     event.state = 0;
1076     if (mask & ControlMask) {
1077       event.keycode = XKeysymToKeycode(target_dpy, XK_Control_L);
1078       SendEvent(&event);
1079     }
1080     if (mask & alt_mask) {
1081       event.keycode = XKeysymToKeycode(target_dpy, XK_Alt_L);
1082       SendEvent(&event);
1083     }
1084     if (mask & meta_mask) {
1085       event.keycode = XKeysymToKeycode(target_dpy, XK_Meta_L);
1086       SendEvent(&event);
1087     }
1088     if (mask & altgr_mask) {
1089       if (0 < appres.altgr_keycode)
1090         event.keycode = appres.altgr_keycode;
1091       else
1092         event.keycode = XKeysymToKeycode(target_dpy, altgr_keysym);
1093       SendEvent(&event);
1094     }
1095     if (mask & ShiftMask) {
1096       event.keycode = XKeysymToKeycode(target_dpy, XK_Shift_L);
1097       SendEvent(&event);
1098     }
1099     if (mask & LockMask) {
1100       event.keycode = XKeysymToKeycode(target_dpy, XK_Caps_Lock);
1101       SendEvent(&event);
1102     }
1103   }
1104 #endif
1105
1106   event.type = KeyPress;
1107   event.state = 0;
1108   if (shift & ControlMask) {
1109     event.keycode = XKeysymToKeycode(target_dpy, XK_Control_L);
1110     SendEvent(&event);
1111     event.state |= ControlMask;
1112   }
1113   if (shift & alt_mask) {
1114     event.keycode = XKeysymToKeycode(target_dpy, XK_Alt_L);
1115     SendEvent(&event);
1116     event.state |= alt_mask;
1117   }
1118   if (shift & meta_mask) {
1119     event.keycode = XKeysymToKeycode(target_dpy, XK_Meta_L);
1120     SendEvent(&event);
1121     event.state |= meta_mask;
1122   }
1123   if (shift & altgr_mask) {
1124     if (0 < appres.altgr_keycode)
1125       event.keycode = appres.altgr_keycode;
1126     else
1127       event.keycode = XKeysymToKeycode(target_dpy, altgr_keysym);
1128     SendEvent(&event);
1129     event.state |= altgr_mask;
1130   }
1131   if (shift & ShiftMask) {
1132     event.keycode = XKeysymToKeycode(target_dpy, XK_Shift_L);
1133     SendEvent(&event);
1134     event.state |= ShiftMask;
1135   }
1136
1137   if (keysym != NoSymbol) {  /* send event for the key itself */
1138     event.keycode = found ? keycode : XKeysymToKeycode(target_dpy, keysym);
1139     if (event.keycode == NoSymbol) {
1140       if ((keysym & ~0x7f) == 0 && isprint(keysym))
1141         fprintf(stderr, "%s: no such key: %c\n",
1142                 PROGRAM_NAME, (char)keysym);
1143       else if (XKeysymToString(keysym) != NULL)
1144         fprintf(stderr, "%s: no such key: keysym=%s (0x%lX)\n",
1145                 PROGRAM_NAME, XKeysymToString(keysym), (long)keysym);
1146       else
1147         fprintf(stderr, "%s: no such key: keysym=0x%lX\n",
1148                 PROGRAM_NAME, (long)keysym);
1149       XBell(dpy, 0);
1150     } else {
1151       SendEvent(&event);
1152       event.type = KeyRelease;
1153       SendEvent(&event);
1154     }
1155   }
1156
1157   event.type = KeyRelease;
1158   if (shift & ShiftMask) {
1159     event.keycode = XKeysymToKeycode(target_dpy, XK_Shift_L);
1160     SendEvent(&event);
1161     event.state &= ~ShiftMask;
1162   }
1163   if (shift & altgr_mask) {
1164     if (0 < appres.altgr_keycode)
1165       event.keycode = appres.altgr_keycode;
1166     else
1167       event.keycode = XKeysymToKeycode(target_dpy, altgr_keysym);
1168     SendEvent(&event);
1169     event.state &= ~altgr_mask;
1170   }
1171   if (shift & meta_mask) {
1172     event.keycode = XKeysymToKeycode(target_dpy, XK_Meta_L);
1173     SendEvent(&event);
1174     event.state &= ~meta_mask;
1175   }
1176   if (shift & alt_mask) {
1177     event.keycode = XKeysymToKeycode(target_dpy, XK_Alt_L);
1178     SendEvent(&event);
1179     event.state &= ~alt_mask;
1180   }
1181   if (shift & ControlMask) {
1182     event.keycode = XKeysymToKeycode(target_dpy, XK_Control_L);
1183     SendEvent(&event);
1184     event.state &= ~ControlMask;
1185   }
1186
1187   if (appres.no_sync) XFlush(dpy);
1188 }
1189
1190 /*
1191  * Word completion - list of words which match the prefix entered
1192  * via xvkbd can be listed, and choosing one of them will send the
1193  * suffix to the clients.
1194  * Words for completion will be read from dictionary file specified
1195  * with xvkbd.dictFile resource, such as /usr/dict/words.
1196  */
1197 static Widget completion_panel = None;
1198 static Widget completion_entry = None;
1199 static Widget completion_list = None;
1200
1201 static Widget props_dict_entry = None;
1202
1203 static char completion_text[100] = "";
1204
1205 #define HASH_SIZE 100
1206
1207 #define Hash(str)   ((toupper(str[0]) * 26 + toupper(str[1])) % HASH_SIZE)
1208
1209 static struct WORDLIST {
1210   struct WORDLIST *next;
1211   char *word;
1212 } completion_words[HASH_SIZE];
1213 static int n_completion_words = 0;
1214
1215 #define MAX_WORDS 200
1216
1217 static String word_list[MAX_WORDS + 1];
1218 static int n_word_list = 0;
1219
1220 static void SetDefaultDictionary(void)
1221 {
1222   strncpy(dict_filename, appres.dict_file, sizeof(dict_filename));
1223   XtVaSetValues(props_dict_entry, XtNstring, dict_filename, NULL);
1224 }
1225
1226 static void ReadCompletionDictionary(void)
1227 {
1228   static Boolean first = TRUE;
1229   static char cur_dict_filename[PATH_MAX] = "";
1230   FILE *fp;
1231   struct WORDLIST *node_ptr;
1232   char str[50];
1233   int i;
1234   struct WORDLIST *p;
1235
1236   if (strcmp(cur_dict_filename, dict_filename) == 0) return;
1237   strcpy(cur_dict_filename, dict_filename);
1238
1239   if (!first) {
1240     int cnt = 0;
1241     for (i = 0; i < HASH_SIZE; i++) {
1242       while (completion_words[i].next != NULL) {
1243         p = completion_words[i].next;
1244         completion_words[i].next = p->next;
1245         free(p);
1246         cnt++;
1247       }
1248     }
1249     if (appres.debug)
1250       fprintf(stderr, "ReadCompletionDictionary: %d words freed\n", cnt);
1251   }
1252   first = FALSE;
1253
1254   for (i = 0; i < HASH_SIZE; i++) {
1255     completion_words[i].next = NULL;
1256     completion_words[i].word = NULL;
1257   }
1258
1259   n_completion_words = 0;
1260   fp = fopen(dict_filename, "r");
1261   if (fp == NULL) {
1262     fprintf(stderr, "%s: can't read dictionary file %s: %s\n",
1263             PROGRAM_NAME, dict_filename, strerror(errno));
1264   } else {
1265     while (fgets(str, sizeof(str) - 1, fp)) {
1266       if (3 < strlen(str)) {
1267         str[strlen(str) - 1] = '\0';
1268         node_ptr = &completion_words[Hash(str)];
1269         while (node_ptr->word != NULL) node_ptr = node_ptr->next;
1270
1271         node_ptr->word = XtNewString(str);
1272         node_ptr->next = malloc(sizeof(struct WORDLIST));
1273         node_ptr->next->next = NULL;
1274         node_ptr->next->word = NULL;
1275         n_completion_words++;
1276       }
1277     }
1278     fclose(fp);
1279
1280     if (appres.debug)
1281       fprintf(stderr, "ReadCompletionDictionary: %d words allocated\n", n_completion_words);
1282   }
1283 }
1284
1285 static void AddToCompletionText(KeySym keysym)
1286 {
1287   int len;
1288   struct WORDLIST *node_ptr;
1289
1290   if (completion_entry != None) {
1291
1292     if (n_completion_words == 0) {
1293       XtVaSetValues(completion_entry, XtNlabel, "Couldn't read dictionary file", NULL);
1294       return;
1295     }
1296
1297     len = strlen(completion_text);
1298     if (keysym == XK_BackSpace || keysym == XK_Delete) {
1299       if (0 < len) completion_text[len - 1] = '\0';
1300     } else if (keysym != NoSymbol
1301                && !ispunct((char)keysym) && !isspace((char)keysym)) {
1302       if (len < sizeof(completion_text) - 2) {
1303         completion_text[len + 1] = '\0';
1304         completion_text[len] = keysym;
1305       }
1306     } else {
1307       completion_text[0] = '\0';
1308     }
1309     XtVaSetValues(completion_entry, XtNlabel, completion_text, NULL);
1310
1311     n_word_list = 0;
1312     if (2 <= strlen(completion_text)) {
1313       node_ptr = &completion_words[Hash(completion_text)];
1314       while (node_ptr->word != NULL && n_word_list < MAX_WORDS) {
1315         if (strlen(completion_text) + 1 < strlen(node_ptr->word) &&
1316             strncasecmp(node_ptr->word, completion_text, strlen(completion_text)) == 0) {
1317           word_list[n_word_list] = node_ptr->word;
1318           n_word_list = n_word_list + 1;
1319         }
1320         node_ptr = node_ptr->next;
1321       }
1322     }
1323     word_list[n_word_list] = NULL;
1324     XawListChange(completion_list, word_list, 0, 0, TRUE);
1325   }
1326 }
1327
1328 static void CompletionWordSelected(Widget w, XtPointer client_data, XtPointer call_data)
1329 {
1330   Boolean capitalize;
1331   unsigned char ch;
1332   int n, i;
1333
1334   n = ((XawListReturnStruct *)call_data)->list_index;
1335   if (0 <= n && n < n_word_list) {
1336     capitalize = TRUE;
1337     for (i = 0; completion_text[i] != '\0'; i++) {
1338       if (islower(completion_text[i])) capitalize = FALSE;
1339     }
1340     for (i = strlen(completion_text); word_list[n][i] != '\0'; i++) {
1341       ch = word_list[n][i];
1342       if (capitalize) ch = toupper(ch);
1343       SendKeyPressedEvent(ch, 0);
1344     }
1345   }
1346   AddToCompletionText(NoSymbol);
1347 }
1348
1349 static void PopupCompletionPanel(void)
1350 {
1351   Widget form, label, view;
1352   char msg[100];
1353
1354   if (completion_panel == None) {
1355     completion_panel = XtVaCreatePopupShell("completion_panel", transientShellWidgetClass,
1356                                         toplevel, NULL);
1357     form = XtVaCreateManagedWidget("form", formWidgetClass, completion_panel, NULL);
1358     label = XtVaCreateManagedWidget("label", labelWidgetClass, form, NULL);
1359     completion_entry = XtVaCreateManagedWidget("entry", labelWidgetClass, form,
1360                                     XtNfromHoriz, label, NULL);
1361     view = XtVaCreateManagedWidget("view", viewportWidgetClass, form,
1362                                     XtNfromVert, label, NULL);
1363     completion_list = XtVaCreateManagedWidget("list", listWidgetClass, view, NULL);
1364     XtAddCallback(completion_list, XtNcallback, CompletionWordSelected, NULL);
1365     XtRealizeWidget(completion_panel);
1366     XSetWMProtocols(dpy, XtWindow(completion_panel), &wm_delete_window, 1);
1367
1368     XtPopup(completion_panel, XtGrabNone);
1369     AddToCompletionText(NoSymbol);
1370     XFlush(dpy);
1371   } else {
1372     XtPopup(completion_panel, XtGrabNone);
1373   }
1374
1375   ReadCompletionDictionary();
1376
1377   sprintf(msg, "%d words in the dictionary", n_completion_words);
1378   XtVaSetValues(completion_entry, XtNlabel, msg, NULL);
1379
1380   completion_text[0] = '\0';
1381   n_word_list = 0;
1382   word_list[n_word_list] = NULL;
1383   XawListChange(completion_list, word_list, 0, 0, TRUE);
1384 }
1385
1386 /*
1387  * Send given string to the focused window as if the string
1388  * is typed from a keyboard.
1389  */
1390 static void KeyPressed(Widget w, char *key, char *data);
1391
1392 static void SendString(const unsigned char *str)
1393 {
1394   const unsigned char *cp, *cp2;  /* I remember "unsigned" might be required for some systems */
1395   char key[50];
1396   int len;
1397   int val;
1398   Window target_root, child, junk_w;
1399   int junk_i;
1400   unsigned junk_u;
1401   int cur_x, cur_y;
1402
1403   shift_state = 0;
1404   for (cp = str; *cp != '\0'; cp++) {
1405     if (0 < appres.text_delay)
1406       usleep(appres.text_delay * 1000);
1407     if (appres.wait_idle && strlen(appres.wait_idle) > 0) {
1408       int pid = atoi(appres.wait_idle);
1409       int ret;
1410       do {
1411         char cmd[80];
1412         snprintf(cmd, sizeof(cmd), "/proc/%d/status", pid);
1413         FILE *f = fopen(cmd, "r");
1414         if (f == NULL) {
1415           fprintf(stderr, "Process not found: %d\n", pid);
1416           exit(-1);
1417         }
1418         fclose(f);
1419         snprintf(cmd, sizeof(cmd), "grep 'State.*running' /proc/%d/status", pid);
1420         ret = system(cmd);
1421         if (ret == 0)
1422           usleep(50);
1423       } while (ret == 0);
1424     }
1425     if (*cp == '\\') {
1426       cp++;
1427       switch (*cp) {
1428       case '\0':
1429         fprintf(stderr, "%s: missing character after \"\\\"\n",
1430                 PROGRAM_NAME);
1431         return;
1432       case '[':  /* we can write any keysym as "\[the-keysym]" here */
1433         cp2 = strchr(cp, ']');
1434         if (cp2 == NULL) {
1435           fprintf(stderr, "%s: no closing \"]\" after \"\\[\"\n",
1436                   PROGRAM_NAME);
1437         } else {
1438           len = cp2 - cp - 1;
1439           if (sizeof(key) <= len) len = sizeof(key) - 1;
1440           strncpy(key, cp + 1, len);
1441           key[len] = '\0';
1442           KeyPressed(None, key, NULL);
1443           cp = cp2;
1444         }
1445         break;
1446       case 'S': shift_state |= ShiftMask; break;
1447       case 'C': shift_state |= ControlMask; break;
1448       case 'A': shift_state |= alt_mask; break;
1449       case 'M': shift_state |= meta_mask; break;
1450       case 'b': SendKeyPressedEvent(XK_BackSpace, shift_state); shift_state = 0; break;
1451       case 't': SendKeyPressedEvent(XK_Tab, shift_state); shift_state = 0; break;
1452       case 'n': SendKeyPressedEvent(XK_Linefeed, shift_state); shift_state = 0; break;
1453       case 'r': SendKeyPressedEvent(XK_Return, shift_state); shift_state = 0; break;
1454       case 'e': SendKeyPressedEvent(XK_Escape, shift_state); shift_state = 0; break;
1455       case 'd': SendKeyPressedEvent(XK_Delete, shift_state); shift_state = 0; break;
1456       case 'D':  /* delay */
1457         cp++;
1458         if ('1' <= *cp && *cp <= '9') {
1459           usleep((*cp - '0') * 100000);
1460         } else {
1461           fprintf(stderr, "%s: no digit after \"\\m\"\n",
1462                   PROGRAM_NAME);
1463         }
1464         break;
1465       case 'm':  /* simulate click mouse button */
1466         cp++;
1467         if ('1' <= *cp && *cp <= '9') {
1468           if (appres.debug) fprintf(stderr, "XTestFakeButtonEvent(%d)\n", *cp - '0');
1469           XTestFakeButtonEvent(target_dpy, *cp - '0', True, CurrentTime);
1470           XTestFakeButtonEvent(target_dpy, *cp - '0', False, CurrentTime);
1471           XFlush(dpy);
1472         } else {
1473           fprintf(stderr, "%s: no digit after \"\\m\"\n",
1474                   PROGRAM_NAME);
1475         }
1476         break;
1477       case 'x':
1478       case 'y':  /* move mouse pointer */
1479         sscanf(cp + 1, "%d", &val);
1480         target_root = RootWindow(target_dpy, DefaultScreen(target_dpy));
1481         XQueryPointer(target_dpy, target_root, &junk_w, &child,
1482                       &cur_x, &cur_y, &junk_i, &junk_i, &junk_u);
1483         if (*cp == 'x') {
1484           if (isdigit(*(cp + 1))) cur_x = val;
1485           else cur_x += val;
1486         } else {
1487           if (isdigit(*(cp + 1))) cur_y = val;
1488           else cur_y += val;
1489         }
1490         XWarpPointer(target_dpy, None, target_root, 0, 0, 0, 0, cur_x, cur_y);
1491         XFlush(dpy);
1492         cp++;
1493         while (isdigit(*(cp + 1)) || *(cp + 1) == '+' || *(cp + 1) == '-') cp++;
1494         break;
1495       default:
1496         SendKeyPressedEvent(*cp, shift_state);
1497         shift_state = 0;
1498         break;
1499       }
1500     } else {
1501       SendKeyPressedEvent(*cp, shift_state);
1502       shift_state = 0;
1503     }
1504   }
1505 }
1506
1507 /*
1508  * Send content of the file as if the it is typed from a keyboard.
1509  */
1510 static void SendFileContent(const char *file)
1511 {
1512   FILE *fp;
1513   int ch;
1514
1515   fp = stdin;
1516   if (strcmp(file, "-") != 0) fp = fopen(file, "r");
1517
1518   if (fp == NULL) {
1519     fprintf(stderr, "%s: can't read the file: %s\n", PROGRAM_NAME, file);
1520     exit(1);
1521   }
1522   while ((ch = fgetc(fp)) != EOF) {
1523     if (0 < appres.text_delay) usleep(appres.text_delay * 1000);
1524     if (ch == '\n') {  /* newline - send Return instead */
1525       SendKeyPressedEvent(XK_Return, 0);
1526     } else if (ch == '\033') {  /* ESC */
1527       SendKeyPressedEvent(XK_Escape, 0);
1528     } else if (ch == '\177') {  /* DEL */
1529       SendKeyPressedEvent(XK_Delete, 0);
1530     } else if (1 <= ch && ch <= 26) {  /* Ctrl-x */
1531       SendKeyPressedEvent('a' + ch - 1, ControlMask);
1532     } else {  /* normal characters */
1533       SendKeyPressedEvent(ch, 0);
1534     }
1535   }
1536   if (strcmp(file, "-") != 0) fclose(fp);
1537 }
1538
1539 /*
1540  * Highlight/unhighligh spcified modifier key on the screen.
1541  */
1542 static void Highlight(char *name, Boolean state)
1543 {
1544   char name1[50];
1545   Widget w;
1546
1547   sprintf(name1, "*%s", name);
1548   w = XtNameToWidget(toplevel, name1);
1549   if (w != None) {
1550     if (strstr(name, "Focus") != NULL) {
1551       if (target_dpy == dpy)
1552         XtVaSetValues(w, XtNbackground, appres.focus_background, NULL);
1553       else
1554         XtVaSetValues(w, XtNbackground, appres.remote_focus_background, NULL);
1555       if (state)
1556         XtVaSetValues(w, XtNforeground, appres.highlight_foreground, NULL);
1557       else
1558         XtVaSetValues(w, XtNforeground, appres.special_foreground, NULL);
1559     } else {
1560       if (state)
1561         XtVaSetValues(w, XtNbackground, appres.highlight_background,
1562                       XtNforeground, appres.highlight_foreground, NULL);
1563       else
1564         XtVaSetValues(w, XtNbackground, appres.special_background,
1565                       XtNforeground, appres.special_foreground, NULL);
1566     }
1567   }
1568 }
1569
1570 /*
1571  * Highlight/unhighligh keys on the screen to reflect the state.
1572  */
1573 static void RefreshShiftState(Boolean force)
1574 {
1575   static Boolean first = TRUE;
1576   static int last_shift_state = 0;
1577   static int last_mouse_shift = 0;
1578   static int last_num_lock_state = FALSE;
1579   static Display *last_target_dpy = NULL;
1580   static long last_focus = 0;
1581   int cur_shift;
1582   int changed;
1583   int first_row, row, col;
1584   Boolean shifted;
1585   char *label;
1586   int mask;
1587
1588   cur_shift = shift_state | mouse_shift;
1589   changed = cur_shift ^ (last_shift_state | last_mouse_shift);
1590   if (first || force) changed = 0xffff;
1591
1592   if (changed & ShiftMask) {
1593     Highlight("Shift_L", cur_shift & ShiftMask);
1594     Highlight("Shift_R", cur_shift & ShiftMask);
1595   }
1596   if (changed & ControlMask) {
1597     Highlight("Control_L", cur_shift & ControlMask);
1598     Highlight("Control_R", cur_shift & ControlMask);
1599   }
1600   if (changed & alt_mask) {
1601     Highlight("Alt_L", cur_shift & alt_mask);
1602     Highlight("Alt_R", cur_shift & alt_mask);
1603   }
1604   if (changed & meta_mask) {
1605     Highlight("Meta_L", cur_shift & meta_mask);
1606     Highlight("Meta_R", cur_shift & meta_mask);
1607   }
1608   if (changed & LockMask) {
1609     Highlight("Caps_Lock", cur_shift & LockMask);
1610   }
1611   if (changed & altgr_mask) {
1612     Highlight("Mode_switch", cur_shift & altgr_mask);
1613   }
1614   if (last_num_lock_state != appres.num_lock_state) {
1615     Highlight("Num_Lock", appres.num_lock_state);
1616     Highlight("keypad_panel*Num_Lock", appres.num_lock_state);
1617   }
1618   if (last_target_dpy != target_dpy || last_focus != focused_window) {
1619     Highlight("Focus", focused_window != 0);
1620     Highlight("keypad*Focus", focused_window != 0);
1621     Highlight("keypad_panel*Focus", focused_window != 0);
1622     last_target_dpy = target_dpy;
1623     last_focus = focused_window;
1624   }
1625
1626   mask = ShiftMask | LockMask | altgr_mask;
1627   changed = (shift_state & mask) ^ (last_shift_state & mask);
1628   if (first || force) changed = TRUE;
1629   if (changed && !appres.keypad_only
1630       && (appres.modal_keytop || toplevel_height < appres.modal_threshold)) {
1631     first_row = appres.function_key ? 0 : 1;
1632     for (row = first_row; row < NUM_KEY_ROWS; row++) {
1633       for (col = 0; col < NUM_KEY_COLS; col++) {
1634         shifted = (shift_state & ShiftMask);
1635         if (key_widgets[row][col] != None) {
1636           if ((shift_state & altgr_mask) && altgr_key_labels[row][col] != NULL) {
1637             if (shifted && shift_altgr_key_labels[row][col] != NULL)
1638               label = shift_altgr_key_labels[row][col];
1639             else
1640               label = altgr_key_labels[row][col];
1641           } else {
1642             if ((shift_state & LockMask)
1643                 && isalpha(keys_normal[row][col][0]) && keys_normal[row][col][1] == '\0')
1644               shifted = !shifted;
1645             if (shifted && shift_key_labels[row][col] != NULL)
1646               label = shift_key_labels[row][col];
1647             else
1648               label = normal_key_labels[row][col];
1649           }
1650           if (label == NULL) {
1651             fprintf(stderr, "%s: no label for key %d,%d\n", PROGRAM_NAME, row, col);
1652             label = "";
1653           }
1654           if (strcmp(label, "space") == 0) label = "";
1655           XtVaSetValues(key_widgets[row][col], XtNlabel, label, NULL);
1656         }
1657       }
1658     }
1659   }
1660
1661   last_shift_state = shift_state;
1662   last_mouse_shift = mouse_shift;
1663   last_num_lock_state = appres.num_lock_state;
1664   first = FALSE;
1665
1666 #ifdef USE_XTEST
1667   if (appres.xtest && strlen(appres.positive_modifiers) != 0) {
1668     /* modifiers specified in positiveModifiers resouce will be hold down
1669        so that it can be used with, for example, mouse operations */
1670
1671     Window root, child;
1672     int root_x, root_y, x, y;
1673     unsigned int mask;
1674
1675     XKeyEvent event;
1676
1677     event.display = target_dpy;
1678     event.window = RootWindow(event.display, DefaultScreen(event.display));
1679     event.root = event.window;
1680     event.subwindow = None;
1681     event.time = CurrentTime;
1682     event.x = 1;
1683     event.y = 1;
1684     event.x_root = 1;
1685     event.y_root = 1;
1686     event.same_screen = TRUE;
1687     event.state = 0;
1688
1689     XQueryPointer(target_dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &mask);
1690
1691     if (strstr(appres.positive_modifiers, "shift") != NULL
1692         && (shift_state & ShiftMask) != (mask & ShiftMask)) {
1693       event.keycode = XKeysymToKeycode(target_dpy, XK_Shift_L);
1694       event.type = (shift_state & ShiftMask) ? KeyPress : KeyRelease;
1695       SendEvent(&event);
1696     }
1697     if (strstr(appres.positive_modifiers, "control") != NULL
1698         && (shift_state & ControlMask) != (mask & ControlMask)) {
1699       event.keycode = XKeysymToKeycode(target_dpy, XK_Control_L);
1700       event.type = (shift_state & ControlMask) ? KeyPress : KeyRelease;
1701       SendEvent(&event);
1702     }
1703     if (strstr(appres.positive_modifiers, "alt") != NULL
1704         && (shift_state & alt_mask) != (mask & alt_mask)) {
1705       event.keycode = XKeysymToKeycode(target_dpy, XK_Alt_L);
1706       event.type = (shift_state & alt_mask) ? KeyPress : KeyRelease;
1707       SendEvent(&event);
1708     }
1709     if (strstr(appres.positive_modifiers, "meta") != NULL
1710         && (shift_state & meta_mask) != (mask & meta_mask)) {
1711       event.keycode = XKeysymToKeycode(target_dpy, XK_Meta_L);
1712       event.type = (shift_state & meta_mask) ? KeyPress : KeyRelease;
1713       SendEvent(&event);
1714     }
1715   }
1716 #endif
1717 }
1718
1719
1720 /*
1721  * This function will be called when mouse button is pressed on a key
1722  * on the screen.  Most operation will be performed in KeyPressed()
1723  * which will be called as callback for the Command widgets, and we
1724  * only need to check which mouse button is pressed here.
1725  */
1726 static unsigned int n_key_repeat;
1727
1728 static void ButtonDownAction(Widget w, XEvent *event, String *pars, Cardinal *n_pars)
1729 {
1730   n_key_repeat = 0;
1731
1732   switch (event->xbutton.button) {
1733   case Button2:
1734     mouse_shift |= ControlMask;
1735     break;
1736   case Button3:
1737   case Button4:
1738     mouse_shift |= ShiftMask;
1739     break;
1740   }
1741   RefreshShiftState(FALSE);
1742 }
1743
1744 /*
1745  * This function will be called when mouse button is released on a key
1746  * on the screen, after callback is called.
1747  */
1748 static void ButtonUpAction(Widget w, XEvent *event, String *pars, Cardinal *n_pars)
1749 {
1750   if (appres.quick_modifiers) {
1751     if (n_key_repeat == 1) XtCallCallbacks(w, XtNcallback, NULL);
1752   }
1753   mouse_shift = 0;
1754   RefreshShiftState(FALSE);
1755 }
1756
1757 /*
1758  * Get the geometry of the base window.
1759  */
1760 static char *GetWindowGeometry(Widget w)
1761 {
1762   static char geom[50];
1763
1764   Position x0, y0;
1765   Window root;
1766   int x1, y1;
1767   unsigned int wd, ht, bd, dp;
1768
1769   XtVaGetValues(w, XtNx, &x0, XtNy, &y0, NULL);
1770   XGetGeometry(dpy, XtWindow(w), &root, &x1, &y1, &wd, &ht, &bd, &dp);
1771   sprintf(geom, "%dx%d+%d+%d", wd, ht, (int)(x0 - x1), (int)(y0 - y1));
1772
1773   return geom;
1774 }
1775
1776 /*
1777  * Set window manager hint.
1778  * ("Extended Window Manager Hints", http://standards.freedesktop.org/wm-spec/)
1779  */
1780 static void SetWindowManagerHint(Boolean initial)
1781 {
1782   if (initial) {
1783     if (appres.wm_toolbar) {
1784       Atom net_wm_window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
1785       Atom net_wm_window_type_toolbar = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False);
1786       XChangeProperty(dpy, XtWindow(toplevel),
1787           net_wm_window_type, XA_ATOM, 32,
1788           PropModeReplace,
1789           (unsigned char *) &net_wm_window_type_toolbar, 1);
1790       if (appres.debug)
1791         fprintf(stderr, "SetWindowManagerHint: set _NET_WM_WINDOW_TYPE_TOOLBAR\n");
1792     }
1793   }
1794
1795   if (!initial || appres.always_on_top) {
1796     const int net_wm_state_remove = 0;
1797     const int net_wm_state_add = 1;
1798     Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
1799     Atom net_wm_state_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
1800     XClientMessageEvent ev;
1801     ev.type = ClientMessage;
1802     ev.display = dpy;
1803     ev.window = XtWindow(toplevel);
1804     ev.message_type = net_wm_state;
1805     ev.format = 32;
1806     ev.data.l[0] = appres.always_on_top ? net_wm_state_add : net_wm_state_remove;
1807     ev.data.l[1] = net_wm_state_above;
1808     ev.data.l[2] = 0;
1809     XSendEvent(dpy, RootWindow(dpy, DefaultScreen(dpy)),
1810                FALSE, SubstructureNotifyMask | SubstructureRedirectMask,
1811                (XEvent *)&ev);
1812     if (appres.debug)
1813       fprintf(stderr, "SetWindowManagerHint: _NET_WM_STATE_ABOVE = %d\n", ev.data.l[0]);
1814   }
1815 }
1816
1817 /*
1818  * Restart the program to (possibly) change the keyboard layout,
1819  * by loading the app-default file for the selected "customization".
1820  */
1821 static void LayoutSelected(Widget w, char *key, char *data)
1822 {
1823   static char *env_lang, *env_xenv;
1824   char name[50];
1825   char customization[30] = "", lang[30] = "C";
1826   char *xenv = NULL;
1827
1828   int i;
1829
1830   if (key != NULL) {
1831     if (strcmp(key, "default") != 0) {
1832       sscanf(key, "%29[^/]/%29s", customization, lang);
1833       sprintf(name, "XVkbd-%s", customization);
1834       xenv = XtResolvePathname(dpy, "app-defaults", name, NULL, NULL, NULL, 0, NULL);
1835       if (xenv == NULL) {
1836         fprintf(stderr, "%s: app-default file \"%s\" not installed\n",
1837                 PROGRAM_NAME, name);
1838       }
1839     }
1840
1841     env_lang = malloc(strlen("LC_ALL=") + strlen(lang) + 1);
1842     sprintf(env_lang, "LC_ALL=%s", lang);
1843     putenv(env_lang);
1844     if (xenv != NULL) {
1845       env_xenv = malloc(strlen("XENVIRONMENT=") + strlen(xenv) + 1);
1846       sprintf(env_xenv, "XENVIRONMENT=%s", xenv);
1847       putenv(env_xenv);
1848     } else if (getenv("XENVIRONMENT") != NULL) {
1849       putenv("XENVIRONMENT=");
1850     }
1851   }
1852
1853   for (i = 1; i < argc1; i++) {
1854     if (strncmp(argv1[i], "-geom", strlen("-geom")) == 0) {
1855       if (appres.inherit_geometry) {
1856         argv1[i + 1] = GetWindowGeometry(toplevel);
1857       } else if (i + 2 == argc1) {
1858         argv1[i] = NULL;
1859         argc1 = i;
1860       }
1861       break;
1862     }
1863   }
1864   if (i == argc1 && appres.inherit_geometry) {
1865     argv1[argc1++] = "-geometry";
1866     argv1[argc1++] = GetWindowGeometry(toplevel);
1867     argv1[argc1] = NULL;
1868   }
1869
1870   if (appres.debug) {
1871     fprintf(stderr, "XENVIRONMENT=%s, LC_ALL=%s\n", (xenv != NULL) ? xenv : "", lang);
1872     fprintf(stderr, "Exec:");
1873     for (i = 0; i < argc1; i++) fprintf(stderr, " %s", argv1[i]);
1874     fprintf(stderr, "\n");
1875   }
1876
1877   execvp(argv1[0], argv1);
1878 }
1879
1880 /*
1881  * Popup a window to select the (possibly) keyboard layout.
1882  * The "XVkbd.customizations" resource will define the list,
1883  * such as "default,german,swissgerman,french,latin1,jisx6004/ja".
1884  * For example, "german" here will make this program to load
1885  * "XVkbd-german" app-default file.  Locale for each configuration
1886  * can be specified by putting the locale name after "/".
1887  */
1888 static void PopupLayoutPanel(void)
1889 {
1890   static Widget layout_panel = None;
1891
1892   char *customizations;
1893   char *cp, *cp2;
1894   Widget box, button;
1895
1896   if (layout_panel == None) {
1897     layout_panel = XtVaCreatePopupShell("layout_panel", transientShellWidgetClass,
1898                                         toplevel, NULL);
1899     box = XtVaCreateManagedWidget("box", boxWidgetClass, layout_panel, NULL);
1900
1901     customizations = XtNewString(appres.customizations);
1902     cp = strtok(customizations, " \t,");
1903     while (cp != NULL) {
1904       cp2 = strchr(cp, '/');
1905       if (cp2 != NULL) *cp2 = '\0';  /* temporary remove '/' */
1906       button = XtVaCreateManagedWidget(cp, commandWidgetClass, box, NULL);
1907       if (cp2 != NULL) *cp2 = '/';
1908       XtAddCallback(button, XtNcallback, (XtCallbackProc)LayoutSelected, XtNewString(cp));
1909       cp = strtok(NULL, " \t,");
1910     }
1911     XtRealizeWidget(layout_panel);
1912     XSetWMProtocols(dpy, XtWindow(layout_panel), &wm_delete_window, 1);
1913
1914     XtFree(customizations);
1915   }
1916
1917   XtPopup(layout_panel, XtGrabNone);
1918 }
1919
1920 /*
1921  * Property panel
1922  */
1923 static void SaveFunctionKey(void);  /* forward */
1924
1925 static Widget props_panel = None;
1926 static Widget autoclick_buttons = None;
1927 static Widget click_buttons = None;
1928 static Boolean props_panel_active = FALSE;
1929
1930 static void PropsItemToggled(Widget w, char *key, char *data)
1931 {
1932   Boolean last_wm_toolbar = appres.wm_toolbar;
1933
1934   if (!props_panel_active) return;
1935
1936 #ifdef USE_XTEST
1937   XtVaGetValues(XtNameToWidget(props_panel, "*use_xtest"),
1938                 XtNstate, &appres.xtest, NULL);
1939 #endif
1940   XtVaGetValues(XtNameToWidget(props_panel, "*quick_modifiers"),
1941                 XtNstate, &appres.quick_modifiers, NULL);
1942   XtVaGetValues(XtNameToWidget(props_panel, "*shift_lock"),
1943                 XtNstate, &appres.shift_lock, NULL);
1944   XtVaGetValues(XtNameToWidget(props_panel, "*altgr_lock"),
1945                 XtNstate, &appres.altgr_lock, NULL);
1946   XtVaGetValues(XtNameToWidget(props_panel, "*modifiers_lock"),
1947                 XtNstate, &appres.modifiers_lock, NULL);
1948   XtVaGetValues(XtNameToWidget(props_panel, "*always_on_top"),
1949                 XtNstate, &appres.always_on_top, NULL);
1950   XtVaGetValues(XtNameToWidget(props_panel, "*wm_toolbar"),
1951                 XtNstate, &appres.wm_toolbar, NULL);
1952   XtVaGetValues(XtNameToWidget(props_panel, "*jump_pointer"),
1953                 XtNstate, &appres.jump_pointer, NULL);
1954
1955   appres.key_click_duration = (int)XawToggleGetCurrent(click_buttons);
1956   appres.autoclick_delay = (int)XawToggleGetCurrent(autoclick_buttons);
1957
1958   SaveFunctionKey();
1959   SetWindowManagerHint(FALSE);
1960
1961   if (appres.wm_toolbar != last_wm_toolbar) LayoutSelected(None, NULL, NULL);
1962 }
1963
1964 static void PropsSetState(void)
1965 {
1966 #ifdef USE_XTEST
1967   XtVaSetValues(XtNameToWidget(props_panel, "*use_xtest"),
1968                 XtNstate, appres.xtest, NULL);
1969 #endif
1970   XtVaSetValues(XtNameToWidget(props_panel, "*quick_modifiers"),
1971                 XtNstate, appres.quick_modifiers, NULL);
1972   XtVaSetValues(XtNameToWidget(props_panel, "*shift_lock"),
1973                 XtNstate, appres.shift_lock, NULL);
1974   if (XtNameToWidget(toplevel, "*Mode_switch") == None) {
1975     XtSetSensitive(XtNameToWidget(props_panel, "*altgr_lock"), FALSE);
1976     XtVaSetValues(XtNameToWidget(props_panel, "*altgr_lock"),
1977                   XtNstate, FALSE, NULL);
1978   } else {
1979     XtVaSetValues(XtNameToWidget(props_panel, "*altgr_lock"),
1980                   XtNstate, appres.altgr_lock, NULL);
1981   }
1982   XtVaSetValues(XtNameToWidget(props_panel, "*modifiers_lock"),
1983                 XtNstate, appres.modifiers_lock, NULL);
1984   XtVaSetValues(XtNameToWidget(props_panel, "*always_on_top"),
1985                 XtNstate, appres.always_on_top, NULL);
1986   XtVaSetValues(XtNameToWidget(props_panel, "*wm_toolbar"),
1987                 XtNstate, appres.wm_toolbar, NULL);
1988
1989   XtVaSetValues(XtNameToWidget(props_panel, "*jump_pointer"),
1990                 XtNstate, appres.jump_pointer, NULL);
1991
1992   XawToggleSetCurrent(click_buttons, (XtPointer)appres.key_click_duration);
1993   XawToggleSetCurrent(autoclick_buttons, (XtPointer)appres.autoclick_delay);
1994 }
1995
1996 static void ClosePropsPanel(void)
1997 {
1998   XtPopdown(props_panel);
1999   XFlush(dpy);
2000
2001   SaveFunctionKey();
2002   if (completion_panel != None) XtPopdown(completion_panel);
2003 }
2004
2005 static void PopupPropsPanel(void)
2006 {
2007   static char *props_items[] = {
2008     "quick_modifiers",
2009     "shift_lock", "altgr_lock", "modifiers_lock",
2010     "always_on_top",
2011     "wm_toolbar",
2012 #ifdef USE_XTEST
2013     "use_xtest",
2014 #endif
2015     "jump_pointer",
2016   };
2017
2018   if (props_panel == None) {
2019     Widget label, button;
2020     Widget form, w;
2021     int i;
2022     int val;
2023
2024     props_panel = XtVaCreatePopupShell("props_panel", transientShellWidgetClass,
2025                                        toplevel, NULL);
2026     form = XtVaCreateManagedWidget("form", formWidgetClass, props_panel, NULL);
2027
2028     w = None;
2029     for (i = 0; i < XtNumber(props_items); i++) {
2030       w = XtVaCreateManagedWidget(props_items[i], toggleWidgetClass,
2031                                   form, XtNfromVert, w, NULL);
2032       XtAddCallback(w, XtNcallback, (XtCallbackProc)PropsItemToggled,
2033                     (XtPointer)props_items[i]);
2034     }
2035
2036     label = XtVaCreateManagedWidget("click", labelWidgetClass,
2037                                     form, XtNfromVert, w, NULL);
2038     button = XtVaCreateManagedWidget("OFF", toggleWidgetClass,
2039                                      form, XtNfromVert, w, XtNfromHoriz, label,
2040                                      XtNwidth, 0, XtNhorizDistance, 0, NULL);
2041     XtVaSetValues(button, XtNradioGroup, button, XtNradioData, (XtPointer)0, NULL);
2042     XtAddCallback(button, XtNcallback, (XtCallbackProc)PropsItemToggled,
2043                   (XtPointer)0);
2044     click_buttons = button;
2045     for (val = 1; val <= 50; val *= 2) {
2046       char s1[10];
2047       sprintf(s1, "%dms", val);
2048       button = XtVaCreateManagedWidget(s1, toggleWidgetClass,
2049                                form, XtNfromVert, w, XtNfromHoriz, button,
2050                                XtNradioData, (XtPointer)val,
2051                                XtNradioGroup, click_buttons,
2052                                XtNwidth, 0, XtNhorizDistance, 0, NULL);
2053       XtAddCallback(button, XtNcallback, (XtCallbackProc)PropsItemToggled,
2054                     NULL);
2055     }
2056
2057     w = label;
2058     label = XtVaCreateManagedWidget("autoclick", labelWidgetClass,
2059                                     form, XtNfromVert, w, NULL);
2060     button = XtVaCreateManagedWidget("OFF", toggleWidgetClass,
2061                                      form, XtNfromVert, w, XtNfromHoriz, label,
2062                                      XtNwidth, 0, XtNhorizDistance, 0, NULL);
2063     XtVaSetValues(button, XtNradioGroup, button, XtNradioData, (XtPointer)0, NULL);
2064     XtAddCallback(button, XtNcallback, (XtCallbackProc)PropsItemToggled,
2065                   (XtPointer)0);
2066     autoclick_buttons = button;
2067     for (val = 500; val <= 1000; val += 100) {
2068       char s1[10];
2069       sprintf(s1, "%dms", val);
2070       button = XtVaCreateManagedWidget(s1, toggleWidgetClass,
2071                                form, XtNfromVert, w, XtNfromHoriz, button,
2072                                XtNradioData, (XtPointer)val,
2073                                XtNradioGroup, autoclick_buttons,
2074                                XtNwidth, 0, XtNhorizDistance, 0, NULL);
2075       XtAddCallback(button, XtNcallback, (XtCallbackProc)PropsItemToggled,
2076                     (XtPointer)val);
2077     }
2078
2079     w = label;
2080     label = XtVaCreateManagedWidget("dict_entry_label", labelWidgetClass,
2081                                     form, XtNfromVert, w, NULL);
2082     props_dict_entry = XtVaCreateManagedWidget("dict_entry", asciiTextWidgetClass, form,
2083                                           XtNfromVert, w, XtNfromHoriz, label,
2084                                           XtNuseStringInPlace, True,
2085                                           XtNstring, dict_filename,
2086                                           XtNeditType, XawtextEdit,
2087                                           XtNlength, sizeof(dict_filename) - 1,
2088                                           NULL);
2089     button = XtVaCreateManagedWidget("dict_default_button", commandWidgetClass,
2090                              form, XtNfromVert, w, XtNfromHoriz, props_dict_entry,
2091                              NULL);
2092     XtAddCallback(button, XtNcallback, (XtCallbackProc)SetDefaultDictionary, NULL);
2093     
2094
2095     w = XtVaCreateManagedWidget("dismiss", commandWidgetClass, form,
2096                                 XtNfromVert, label, NULL);
2097     XtAddCallback(w, XtNcallback, (XtCallbackProc)ClosePropsPanel, NULL);
2098
2099     XtRealizeWidget(props_panel);
2100     XSetWMProtocols(dpy, XtWindow(props_panel), &wm_delete_window, 1);
2101   }
2102   XtPopup(props_panel, XtGrabNone);
2103   PropsSetState();
2104
2105   props_panel_active = TRUE;
2106
2107   if (completion_panel != None) XtPopdown(completion_panel);
2108 }
2109
2110 /*
2111  * Callback for main menu (activated from "xvkbd" logo).
2112  */
2113 static Widget about_panel = None;
2114 static Widget keypad_panel = None;
2115 static Widget sun_fkey_panel = None;
2116 static Widget deadkey_panel = None;
2117 static Widget display_panel = None;
2118 static Widget display_status = None;
2119
2120 #define DISPLAY_NAME_LENGTH 50
2121
2122 static void OpenRemoteDisplay(Widget w, char *display_name, char *data)
2123 {
2124   static char name[DISPLAY_NAME_LENGTH + 10];
2125   char *cp;
2126
2127   focused_window = None;
2128   focused_subwindow = None;
2129   if (target_dpy != NULL && target_dpy != dpy) XCloseDisplay(target_dpy);
2130
2131   strncpy(name, (display_name == NULL) ? "" : display_name, sizeof(name));
2132   for (cp = name; isascii(*cp) && isprint(*cp); cp++) ;
2133   *cp = '\0';
2134
2135   if (strlen(name) == 0) {
2136     target_dpy = dpy;
2137     XtVaSetValues(display_status, XtNlabel, "Disconnected - local display selected", NULL);
2138     XtPopdown(display_panel);
2139   } else {
2140     if (strchr(name, ':') == NULL) strcat(name, ":0");
2141     target_dpy = XOpenDisplay(name);
2142     if (target_dpy == NULL) {
2143       XtVaSetValues(display_status, XtNlabel, "Couldn't connect to the display", NULL);
2144       target_dpy = dpy;
2145       XBell(dpy, 0);
2146     } else {
2147       XtVaSetValues(display_status, XtNlabel, "Connected", NULL);
2148       XtPopdown(display_panel);
2149     }
2150   }
2151
2152   ReadKeymap();
2153   if (!altgr_mask && appres.auto_add_keysym) AddModifier(XK_Mode_switch);
2154
2155   RefreshMainMenu();
2156   RefreshShiftState(FALSE);
2157 }
2158
2159 static void MenuSelected(Widget w, char *key)
2160 {
2161   Widget form;
2162   
2163   if (strcmp(key, "man") == 0) {
2164     if (!appres.secure) system(appres.show_manual_command);
2165   } else if (strcmp(key, "about") == 0) {
2166     if (about_panel == None) {
2167       about_panel = XtVaCreatePopupShell("about_panel", transientShellWidgetClass,
2168                                           toplevel, NULL);
2169       XtVaCreateManagedWidget("message", labelWidgetClass, about_panel,
2170                               XtNlabel, appres.description, NULL);
2171       XtRealizeWidget(about_panel);
2172       XSetWMProtocols(dpy, XtWindow(about_panel), &wm_delete_window, 1);
2173     }
2174     XtPopup(about_panel, XtGrabNone);
2175   } else if (strcmp(key, "keypad") == 0) {
2176     if (keypad_panel == None) {
2177       keypad_panel = XtVaCreatePopupShell("keypad_panel", transientShellWidgetClass,
2178                                           toplevel, NULL);
2179       form = XtVaCreateManagedWidget("form", formWidgetClass, keypad_panel, NULL);
2180       MakeKeypad(form, None, None);
2181       XtRealizeWidget(keypad_panel);
2182       XSetWMProtocols(dpy, XtWindow(keypad_panel), &wm_delete_window, 1);
2183     }
2184     XtPopup(keypad_panel, XtGrabNone);
2185   } else if (strcmp(key, "sun_fkey") == 0) {
2186     if (sun_fkey_panel == None) {
2187       sun_fkey_panel = XtVaCreatePopupShell("sun_fkey_panel", transientShellWidgetClass,
2188                                           toplevel, NULL);
2189       form = XtVaCreateManagedWidget("form", formWidgetClass, sun_fkey_panel, NULL);
2190       MakeSunFunctionKey(form, None, None);
2191       XtRealizeWidget(sun_fkey_panel);
2192       XSetWMProtocols(dpy, XtWindow(sun_fkey_panel), &wm_delete_window, 1);
2193     }
2194     XtPopup(sun_fkey_panel, XtGrabNone);
2195   } else if (strcmp(key, "deadkey") == 0) {
2196     if (deadkey_panel == None) {
2197       deadkey_panel = XtVaCreatePopupShell("deadkey_panel", transientShellWidgetClass,
2198                                           toplevel, NULL);
2199       form = XtVaCreateManagedWidget("form", formWidgetClass, deadkey_panel, NULL);
2200       MakeDeadkeyPanel(form);
2201       XtRealizeWidget(deadkey_panel);
2202       XSetWMProtocols(dpy, XtWindow(deadkey_panel), &wm_delete_window, 1);
2203     }
2204     XtPopup(deadkey_panel, XtGrabNone);
2205   } else if (strcmp(key, "completion") == 0) {
2206     PopupCompletionPanel();
2207   } else if (strcmp(key, "select_layout") == 0) {
2208     PopupLayoutPanel();
2209   } else if (strcmp(key, "edit_fkey") == 0) {
2210     PopupFunctionKeyEditor();
2211   } else if (strcmp(key, "show_keypad") == 0
2212              || strcmp(key, "show_functionkey") == 0) {
2213     if (strcmp(key, "show_keypad") == 0) appres.keypad = !appres.keypad;
2214     else appres.function_key = !appres.function_key;
2215     MakeKeyboard(TRUE);
2216   } else if (strcmp(key, "props") == 0) {
2217     PopupPropsPanel();
2218   } else if (strcmp(key, "open_display") == 0) {
2219     if (display_panel == None) {
2220       Widget label, entry, button;
2221       static char display_name[DISPLAY_NAME_LENGTH] = "";
2222       display_panel = XtVaCreatePopupShell("display_panel", transientShellWidgetClass,
2223                                           toplevel, NULL);
2224       form = XtVaCreateManagedWidget("form", formWidgetClass, display_panel, NULL);
2225       label = XtVaCreateManagedWidget("label", labelWidgetClass, form, NULL);
2226       entry = XtVaCreateManagedWidget("entry", asciiTextWidgetClass, form,
2227                                       XtNfromHoriz, label,
2228                                       XtNuseStringInPlace, True,
2229                                       XtNeditType, XawtextEdit,
2230                                       XtNstring, display_name,
2231                                       XtNlength, sizeof(display_name) - 1,
2232                                       NULL);
2233
2234       button = XtVaCreateManagedWidget("ok", commandWidgetClass, form,
2235                                        XtNfromHoriz, entry, NULL);
2236       XtAddCallback(button, XtNcallback, (XtCallbackProc)OpenRemoteDisplay, (XtPointer)display_name);
2237
2238       display_status = XtVaCreateManagedWidget("status", labelWidgetClass, form,
2239                                                XtNfromVert, label,
2240                                                XtNlabel, "", NULL);
2241       XtRealizeWidget(display_panel);
2242       XSetWMProtocols(dpy, XtWindow(display_panel), &wm_delete_window, 1);
2243
2244       XtSetKeyboardFocus(display_panel, entry);
2245     }
2246     XtPopup(display_panel, XtGrabNone);
2247   } else if (strcmp(key, "close_display") == 0) {
2248     OpenRemoteDisplay(None, NULL, NULL);
2249   } else if (strcmp(key, "quit") == 0) {
2250     DeleteWindowProc(None, NULL, NULL, NULL);
2251   }
2252 }
2253
2254 static void ClosePopupPanel(Widget w)
2255 {
2256   if (w == keypad_panel) {
2257     XtDestroyWidget(w);
2258     keypad_panel = None;
2259   } else if (w == props_panel) {
2260     ClosePropsPanel();
2261   } else {
2262     XtPopdown(w);
2263   }
2264 }
2265
2266 /*
2267  * Iconify/uniconify the xvkbd window even if window manager is not
2268  * available.
2269  */
2270 static void IconifyWindow(Widget w, Boolean iconify)
2271 {
2272   static Widget iconified_window = None;
2273   static Widget uniconify_button = None;
2274   static Position x0, y0;
2275   static int x1, y1;
2276   static unsigned int wd, ht, bd, dp;
2277
2278   if (iconify) {
2279     Window root;
2280     int i;
2281
2282     XUnmapWindow(dpy, XtWindow(toplevel));
2283
2284     if (iconified_window == None) {
2285       Widget box;
2286
2287       iconified_window = XtVaCreatePopupShell("iconified_window", transientShellWidgetClass,
2288                                               toplevel, XtNoverrideRedirect, TRUE, NULL);
2289       box = XtVaCreateManagedWidget("form", boxWidgetClass, iconified_window, NULL);
2290       uniconify_button = XtVaCreateManagedWidget("uniconify_button", commandWidgetClass, box,
2291                                                  XtNbitmap, xvkbd_pixmap,
2292                                                  XtNhorizDistance, 10, XtNvertDistance, 0,
2293                                                  NULL);
2294       XtAddCallback(uniconify_button, XtNcallback, (XtCallbackProc)IconifyWindow, FALSE);
2295
2296       XtRealizeWidget(iconified_window);
2297       XSetWMProtocols(dpy, XtWindow(iconified_window), &wm_delete_window, 1);
2298     }
2299
2300     XtVaGetValues(toplevel, XtNx, &x0, XtNy, &y0, NULL);
2301     XGetGeometry(dpy, XtWindow(toplevel), &root, &x1, &y1, &wd, &ht, &bd, &dp);
2302
2303     XMoveResizeWindow(dpy, XtWindow(iconified_window), x0 + bd, y0 + bd, wd, ht);
2304     XtPopup(iconified_window, XtGrabNone);
2305     for (i = 9; 0 < i; i--) {
2306       Dimension btn_wd, btn_ht;
2307       Dimension wd1, ht1;
2308
2309       wd1 = wd * i / 10;
2310       ht1 = ht * i / 10;
2311       XtVaGetValues(uniconify_button, XtNwidth, &btn_wd, XtNheight, &btn_ht, NULL);
2312       if (i == 1 || wd1 < btn_wd) wd1 = btn_wd;
2313       if (i == 1 || ht1 < btn_ht) ht1 = btn_ht;
2314       XMoveResizeWindow(dpy, XtWindow(iconified_window), x0 + bd, y0 + (ht - ht1) + bd, wd1, ht1);
2315       XFlush(dpy);
2316       usleep(10000);
2317     }
2318   } else {
2319     if (iconified_window != None) XtPopdown(iconified_window);
2320     XMapWindow(dpy, XtWindow(toplevel));
2321   }
2322 }
2323
2324 static void SignalUser1(void)
2325 {
2326   XWindowAttributes attr;
2327   XGetWindowAttributes(dpy, XtWindow(toplevel), &attr);
2328   IconifyWindow(None, attr.map_state != IsUnmapped);
2329   XSync(dpy, FALSE);
2330 }
2331
2332 /*
2333  * This will be called when user pressed a key on the screen.
2334  */
2335 static const char *FindFunctionKeyValue(const char *key, Boolean shiftable);
2336 static void ShowBalloon(Widget w, XEvent *event, String *pars, Cardinal *n_pars);
2337 static void KeyClick(void);
2338 static void StopAutoclick(void);
2339
2340 static void KeyPressed(Widget w, char *key, char *data)
2341 {
2342   int row, col;
2343   int cur_shift;
2344   char *key1;
2345   KeySym keysym;
2346   Boolean shifted;
2347   const char *value;
2348   Boolean found;
2349
2350   if (appres.debug) fprintf(stderr, "KeyPressed: key=%s, widget=%lx\n", key, (long)w);
2351
2352   value = FindFunctionKeyValue(key, TRUE);
2353   if (value != NULL) {
2354     if (appres.debug) fprintf(stderr, "Assigned string: %s\n", value);
2355     if (value[0] == '!') {
2356       if (appres.debug) fprintf(stderr, "Launching: %s\n", value + 1);
2357       if (!appres.secure) system(value + 1);
2358     } else {
2359       if (value[0] == '\\') value = value + 1;
2360       if (appres.debug) fprintf(stderr, "Sending: %s\n", value);
2361       SendString(value);
2362     }
2363     ShowBalloon(w, NULL, NULL, NULL);
2364     return;
2365   }
2366
2367   if (strncmp(key, "Shift", strlen("Shift")) == 0) {
2368     if (shift_state & ShiftMask) SendKeyPressedEvent(NoSymbol, shift_state);
2369     shift_state ^= ShiftMask;
2370   } else if (strncmp(key, "Control", strlen("Control")) == 0) {
2371     if (shift_state & ControlMask) SendKeyPressedEvent(NoSymbol, shift_state);
2372     shift_state ^= ControlMask;
2373   } else if (strncmp(key, "Alt", strlen("Alt")) == 0) {
2374     if (shift_state & alt_mask) SendKeyPressedEvent(NoSymbol, shift_state);
2375     shift_state ^= alt_mask;
2376   } else if (strncmp(key, "Meta", strlen("Meta")) == 0) {
2377     if (shift_state & meta_mask) SendKeyPressedEvent(NoSymbol, shift_state);
2378     shift_state ^= meta_mask;
2379   } else if (strcmp(key, "Caps_Lock") == 0) {
2380     if (shift_state & LockMask) SendKeyPressedEvent(NoSymbol, shift_state);
2381     shift_state ^= LockMask;
2382   } else if (strcmp(key, "Mode_switch") == 0) {
2383     if (shift_state & altgr_mask) SendKeyPressedEvent(NoSymbol, shift_state);
2384     shift_state ^= altgr_mask;
2385   } else if (strcmp(key, "Num_Lock") == 0) {
2386     appres.num_lock_state = !appres.num_lock_state;
2387   } else if (strcmp(key, "Focus") == 0) {
2388     cur_shift = shift_state | mouse_shift;
2389     if (cur_shift & ShiftMask) {
2390       focused_window = None;
2391       focused_subwindow = None;
2392     } else {
2393       GetFocusedWindow();
2394     }
2395   } else {
2396     if (appres.quick_modifiers && mouse_shift == 0 && w != None) {
2397       Window junk_w;
2398       int junk_i;
2399       unsigned junk_u;
2400       int cur_x, cur_y;
2401       Dimension btn_wd, btn_ht;
2402
2403       n_key_repeat = n_key_repeat + 1;
2404       if (n_key_repeat == 1) return;
2405
2406       XtVaGetValues(w, XtNwidth, &btn_wd, XtNheight, &btn_ht, NULL);
2407       XQueryPointer(dpy, XtWindow(w), &junk_w, &junk_w,
2408                     &junk_i, &junk_i, &cur_x, &cur_y, &junk_u);
2409
2410       mouse_shift = 0;
2411       if (cur_x < 0 && btn_ht < cur_y) {
2412         mouse_shift |= alt_mask;  /* left-down */
2413       } else {
2414         if (cur_y < 0) mouse_shift |= ShiftMask;  /* up */
2415         else if (btn_ht < cur_y) mouse_shift |= meta_mask;  /* down */
2416         if (cur_x < 0) mouse_shift |= ControlMask;  /* left */
2417         else if (btn_wd < cur_x) mouse_shift |= altgr_mask;  /* right */
2418       }
2419     }
2420     cur_shift = shift_state | mouse_shift;
2421     shifted = (cur_shift & ShiftMask);
2422     key1 = key;
2423     if (w != None) {
2424       if (sscanf(key, "pad%d,%d", &row, &col) == 2) {
2425         if (appres.num_lock_state) shifted = !shifted;
2426         key1 = shifted ? keypad_shift[row][col]: keypad[row][col];
2427       } else {
2428         found = FALSE;
2429         if (sscanf(key, "%d,%d", &row, &col) == 2) {
2430           found = TRUE;
2431         } else if (w != None) {
2432           int first_row = appres.function_key ? 0 : 1;
2433           for (row = first_row; row < NUM_KEY_ROWS; row++) {
2434             for (col = 0; col < NUM_KEY_COLS; col++) {
2435               if (key_widgets[row][col] == w) {
2436                 found = TRUE;
2437                 break;
2438               }
2439             }
2440             if (col < NUM_KEY_COLS) break;
2441           }
2442         }
2443         if (found) {
2444           if ((cur_shift & LockMask)
2445               && isalpha(keys_normal[row][col][0]) && keys_normal[row][col][1] == '\0')
2446             shifted = !shifted;
2447           if ((cur_shift & altgr_mask) && keys_altgr[row][col] != NULL) {
2448             if (shifted && keys_shift_altgr[row][col] != NULL) {
2449               key1 = keys_shift_altgr[row][col];
2450               if (strcmp(keys_altgr[row][col], keys_shift_altgr[row][col]) != 0)
2451                 cur_shift &= ~ShiftMask;
2452             } else {
2453               key1 = keys_altgr[row][col];
2454             }
2455           } else {
2456             if (shifted && keys_shift[row][col] != NULL) {
2457               key1 = keys_shift[row][col];
2458               if (strcmp(keys_normal[row][col], keys_shift[row][col]) != 0)
2459                 cur_shift &= ~ShiftMask;
2460             } else {
2461               key1 = keys_normal[row][col];
2462             }
2463           }
2464         }  /* if (found) ... */
2465       }  /* if (sscanf(key, "pad%d,%d", ... */
2466     }  /* if (w != None) ... */
2467     if (strlen(key1) == 1) {
2468       SendKeyPressedEvent((KeySym)*key1 & 0xff, cur_shift);
2469       AddToCompletionText((KeySym)*key1);
2470     } else {
2471       if (islower(key1[0]) && key1[1] == ':') {
2472         switch (key1[0]) {
2473         case 's': cur_shift |= ShiftMask; break;
2474         case 'c': cur_shift |= ControlMask; break;
2475         case 'a': cur_shift |= alt_mask; break;
2476         case 'm': cur_shift |= meta_mask; break;
2477         default: fprintf(stderr, "%s: unknown modidier: %s\n",
2478                          PROGRAM_NAME, key1); break;
2479         }
2480         key1 = key1 + 2;
2481       }
2482       keysym = XStringToKeysym(key1);
2483       if ((!appres.keypad_keysym && strncmp(key1, "KP_", 3) == 0)
2484           || XKeysymToKeycode(target_dpy, keysym) == NoSymbol) {
2485         switch ((unsigned)keysym) {
2486         case XK_KP_Equal: keysym = XK_equal; break;
2487         case XK_KP_Divide: keysym = XK_slash; break;
2488         case XK_KP_Multiply: keysym = XK_asterisk; break;
2489         case XK_KP_Add: keysym = XK_plus; break;
2490         case XK_KP_Subtract: keysym = XK_minus; break;
2491         case XK_KP_Enter: keysym = XK_Return; break;
2492         case XK_KP_1: keysym = XK_1; break;
2493         case XK_KP_2: keysym = XK_2; break;
2494         case XK_KP_3: keysym = XK_3; break;
2495         case XK_KP_4: keysym = XK_4; break;
2496         case XK_KP_5: keysym = XK_5; break;
2497         case XK_KP_6: keysym = XK_6; break;
2498         case XK_KP_7: keysym = XK_7; break;
2499         case XK_KP_8: keysym = XK_8; break;
2500         case XK_KP_9: keysym = XK_9; break;
2501         case XK_Shift_L: keysym = XK_Shift_R; break;
2502         case XK_Shift_R: keysym = XK_Shift_L; break;
2503         case XK_Control_L: keysym = XK_Control_R; break;
2504         case XK_Control_R: keysym = XK_Control_L; break;
2505         case XK_Alt_L: keysym = XK_Alt_R; break;
2506         case XK_Alt_R: keysym = XK_Alt_L; break;
2507         case XK_Meta_L: keysym = XK_Meta_R; break;
2508         case XK_Meta_R: keysym = XK_Meta_L; break;
2509         default:
2510           if (keysym == NoSymbol || !appres.auto_add_keysym)
2511             fprintf(stderr, "%s: no such key: %s\n",
2512                     PROGRAM_NAME, key1); break;
2513         }
2514       }
2515       SendKeyPressedEvent(keysym, cur_shift);
2516       AddToCompletionText(keysym);
2517
2518       if ((cur_shift & ControlMask) && (cur_shift & alt_mask)) {
2519         if (strstr(XServerVendor(dpy), "XFree86") != NULL) {
2520           if (strcmp(key1, "KP_Add") == 0) {
2521             if (!appres.secure) system("xvidtune -next");
2522           } else if (strcmp(key1, "KP_Subtract") == 0) {
2523             if (!appres.secure) system("xvidtune -prev");
2524           }
2525         }
2526       }
2527     }
2528     if (!appres.shift_lock)
2529       shift_state &= ~ShiftMask;
2530     if (!appres.modifiers_lock)
2531       shift_state &= ~(ControlMask | alt_mask | meta_mask);
2532     if (!appres.altgr_lock)
2533       shift_state &= ~altgr_mask;
2534   }
2535   RefreshShiftState(FALSE);
2536
2537   if (w != None) {
2538     KeyClick();
2539 /*     StopAutoclick(); */
2540   }
2541 }
2542
2543 /*
2544  * Redefine keyboard layout.
2545  * "spec" is a sequence of words separated with spaces, and it is
2546  * usally specified in app-defaults file, as:
2547  * 
2548  *   xvkbd.AltGrKeys: \
2549  *      F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 BackSpace \n\
2550  *      Escape \271 \262 \263 \243 \254 \251 { [ ] } \\ ' ^ ' \n\
2551  *      ...
2552  *
2553  * White spaces separate the keys, and " \n" (note that white space
2554  * before the \n) separate the rows of keys.
2555  */
2556 static void RedefineKeys(char *array[NUM_KEY_ROWS][NUM_KEY_COLS], const char *spec)
2557 {
2558   char *s = XtNewString(spec);
2559   char *cp;
2560   int row, col;
2561   int key_rows = NUM_KEY_ROWS;
2562   int key_cols = NUM_KEY_COLS;
2563
2564   for (row = 0; row < key_rows; row++) {
2565     for (col = 0; col < key_cols; col++) array[row][col] = NULL;
2566   }
2567   row = 0;
2568   col = 0;
2569   cp = strtok(s, " ");
2570   while (cp != NULL) {
2571     if (*cp == '\n') {
2572       row = row + 1;
2573       col = 0;
2574       cp = cp + 1;
2575     }
2576     if (*cp != '\0') {
2577       if (key_rows <= row) {
2578         fprintf(stderr, "%s: too many key rows: \"%s\" ignored\n",
2579                 PROGRAM_NAME, cp);
2580       } else if (key_cols <= col) {
2581         fprintf(stderr, "%s: too many keys in a row: \"%s\" ignored\n",
2582                 PROGRAM_NAME, cp);
2583       } else {
2584         array[row][col] = XtNewString(cp);
2585         col = col + 1;
2586       }
2587     }
2588     cp = strtok(NULL, " ");
2589   }
2590   XtFree(s);
2591 }
2592
2593 /*
2594  * Create keyboard on the screen.
2595  */
2596 static Widget MakeKey(Widget parent, const char *name, const char *label, Pixel color)
2597 {
2598   static Pixmap up_pixmap = None;
2599   static Pixmap down_pixmap = None;
2600   static Pixmap left_pixmap = None;
2601   static Pixmap right_pixmap = None;
2602   static Pixmap back_pixmap = None;
2603   Widget w;
2604   Window scr = RootWindow(dpy, DefaultScreen(dpy));
2605   char str[50];
2606   int len;
2607
2608   if (!appres.auto_repeat
2609       || strncmp(name, "Shift", strlen("Shift")) == 0
2610       || strncmp(name, "Control", strlen("Control")) == 0
2611       || strncmp(name, "Alt", strlen("Alt")) == 0
2612       || strncmp(name, "Meta", strlen("Meta")) == 0
2613       || strcmp(name, "Caps_Lock") == 0
2614       || strcmp(name, "Mode_switch") == 0
2615       || strcmp(name, "Num_Lock") == 0
2616       || strcmp(name, "Focus") == 0) {
2617     w = XtVaCreateManagedWidget(name, commandWidgetClass, parent,
2618                                 XtNbackground, color, NULL);
2619   } else {
2620     w = XtVaCreateManagedWidget(name, repeaterWidgetClass, parent,
2621                                 XtNbackground, color, NULL);
2622   }
2623   XtAddCallback(w, XtNcallback, (XtCallbackProc)KeyPressed, (XtPointer)name);
2624
2625   if (label != NULL) {
2626     strncpy(str, label, sizeof(str) - 1);
2627     if (strcmp(str, "space") == 0) strcpy(str, "");
2628     len = strlen(str);
2629     if (3 <= len) {
2630       if (str[1] == '_') str[1] = ' ';
2631       if (str[len - 2] == '_') str[len - 2] = ' ';
2632     }
2633     XtVaSetValues(w, XtNlabel, str, NULL);
2634
2635     if (strcmp(label, "up") == 0) {
2636       if (up_pixmap == None)
2637         up_pixmap = XCreateBitmapFromData(dpy, scr,
2638                                 (char *)up_bits, up_width, up_height);
2639       XtVaSetValues(w, XtNbitmap, up_pixmap, NULL);
2640     } else if (strcmp(label, "down") == 0) {
2641       if (down_pixmap == None)
2642         down_pixmap = XCreateBitmapFromData(dpy, scr,
2643                                 (char *)down_bits, down_width, down_height);
2644       XtVaSetValues(w, XtNbitmap, down_pixmap, NULL);
2645     } else if (strcmp(label, "left") == 0) {
2646       if (left_pixmap == None)
2647         left_pixmap = XCreateBitmapFromData(dpy, scr,
2648                                 (char *)left_bits, left_width, left_height);
2649       XtVaSetValues(w, XtNbitmap, left_pixmap, NULL);
2650     } else if (strcmp(label, "right") == 0) {
2651       if (right_pixmap == None)
2652         right_pixmap = XCreateBitmapFromData(dpy, scr,
2653                                 (char *)right_bits, right_width, right_height);
2654       XtVaSetValues(w, XtNbitmap, right_pixmap, NULL);
2655     } else if (strcmp(label, "back") == 0) {
2656       if (back_pixmap == None)
2657         back_pixmap = XCreateBitmapFromData(dpy, scr,
2658                                 (char *)back_bits, back_width, back_height);
2659       XtVaSetValues(w, XtNbitmap, back_pixmap, NULL);
2660     }
2661   }
2662
2663   return w;
2664 }
2665
2666 static void MakeKeypad(Widget form, Widget from_vert, Widget from_horiz)
2667 {
2668   Widget key, left, upper;
2669   Pixel color;
2670   XFontStruct *font;
2671   int row, col;
2672   Widget keypad_box;
2673   Widget keypad_row[NUM_KEYPAD_ROWS];
2674   char name[50];
2675
2676   keypad_box = XtVaCreateManagedWidget("keypad", formWidgetClass, form, NULL);
2677   if (from_horiz != None)
2678     XtVaSetValues(keypad_box, XtNfromHoriz, from_horiz, NULL);
2679   else
2680     XtVaSetValues(keypad_box, XtNhorizDistance, 0, NULL);
2681   if (from_vert != None)
2682     XtVaSetValues(keypad_box, XtNfromVert, from_vert, NULL);
2683   else
2684     XtVaSetValues(keypad_box, XtNvertDistance, 0, NULL);
2685   upper = None;
2686   for (row = 0; row < NUM_KEYPAD_ROWS; row++) {
2687     left = None;
2688     for (col = 0; keypad[row][col] != NULL; col++) {
2689       font = appres.keypad_font;
2690       if (strlen(keypad_label[row][col]) == 1) font = appres.letter_font;
2691       color = appres.special_background;
2692       if (strcmp(keypad[row][col], "Focus") == 0)
2693         color = appres.focus_background;
2694       else if (strcmp(keypad_shift[row][col], ".") == 0
2695                || (strncmp(keypad_shift[row][col], "KP_", 3) == 0
2696                    && isdigit(keypad_shift[row][col][3])))
2697         color = appres.general_background;
2698       strcpy(name, keypad[row][col]);
2699       if (strcmp(name, "Focus") != 0 && strcmp(name, "Num_Lock") != 0)
2700         sprintf(name, "pad%d,%d", row, col);
2701       key = MakeKey(keypad_box, XtNewString(name),
2702                     keypad_label[row][col], color);
2703       XtVaSetValues(key, XtNfont, font, NULL);
2704       if (row != 0)
2705         XtVaSetValues(key, XtNfromVert, keypad_row[row - 1], NULL);
2706       if (left != None)
2707         XtVaSetValues(key, XtNfromHoriz, left, NULL);
2708       if (col == 0)
2709         keypad_row[row] = key;
2710       left = key;
2711     }
2712   }
2713 }
2714
2715 static void MakeSunFunctionKey(Widget form, Widget from_vert, Widget from_horiz)
2716 {
2717   Widget key, left, upper;
2718   int row, col;
2719   Widget fkey_box;
2720   Widget fkey_row[NUM_SUN_FKEY_ROWS];
2721
2722   fkey_box = XtVaCreateManagedWidget("fkey", formWidgetClass, form, NULL);
2723   if (from_horiz != None)
2724     XtVaSetValues(fkey_box, XtNfromHoriz, from_horiz, NULL);
2725   else
2726     XtVaSetValues(fkey_box, XtNhorizDistance, 0, NULL);
2727   if (from_vert != None)
2728     XtVaSetValues(fkey_box, XtNfromVert, from_vert, NULL);
2729   else
2730     XtVaSetValues(fkey_box, XtNvertDistance, 0, NULL);
2731   upper = None;
2732   for (row = 0; row < NUM_SUN_FKEY_ROWS; row++) {
2733     left = None;
2734     for (col = 0; sun_fkey[row][col] != NULL; col++) {
2735       key = MakeKey(fkey_box, sun_fkey[row][col],
2736                     sun_fkey_label[row][col], appres.special_background);
2737       XtVaSetValues(key, XtNfont, appres.keypad_font, NULL);
2738       if (row != 0)
2739         XtVaSetValues(key, XtNfromVert, fkey_row[row - 1], NULL);
2740       if (left != None)
2741         XtVaSetValues(key, XtNfromHoriz, left, NULL);
2742       if (col == 0)
2743         fkey_row[row] = key;
2744       left = key;
2745     }
2746   }
2747 }
2748
2749 static void MakeDeadkeyPanel(Widget form)
2750 {
2751   Widget deadkey_box, left, key;
2752   char *deadkeys, *cp, *cp2;
2753
2754   deadkeys = XtNewString(appres.deadkeys);
2755
2756   deadkey_box = XtVaCreateManagedWidget("deadkey", formWidgetClass, form, NULL);
2757   left = None;
2758   cp = strtok(deadkeys, " \t,");
2759   while (cp != NULL) {
2760     cp2 = XtNewString(cp);
2761     key = MakeKey(deadkey_box, cp2, NULL, appres.general_background);
2762     if (left != None) XtVaSetValues(key, XtNfromHoriz, left, NULL);
2763     left = key;
2764     cp = strtok(NULL, " \t,");
2765   }
2766   XtFree(deadkeys);
2767 }
2768
2769 static void RefreshMainMenu(void)
2770 {
2771   static Pixmap check_pixmap = None;
2772
2773   if (check_pixmap == None) {
2774     check_pixmap = XCreateBitmapFromData(dpy, RootWindow(dpy, DefaultScreen(dpy)),
2775                                  (char *)check_bits, check_width, check_height);
2776   }
2777   XtVaSetValues(XtNameToWidget(main_menu, "*show_keypad"),
2778                 XtNrightBitmap, appres.keypad ? check_pixmap : None, NULL);
2779   XtVaSetValues(XtNameToWidget(main_menu, "*show_functionkey"),
2780                 XtNrightBitmap, appres.function_key ? check_pixmap : None, NULL);
2781
2782   XtSetSensitive(XtNameToWidget(main_menu, "*edit_fkey"), appres.function_key);
2783   XtSetSensitive(XtNameToWidget(main_menu, "*close_display"), target_dpy != dpy);
2784 }
2785
2786 static void MakeKeyboard(Boolean remake)
2787 {
2788   static char *main_menu_items[] = {
2789     "about", "man", "keypad", "sun_fkey", "deadkey", "completion", "",
2790     "select_layout",
2791     "edit_fkey",
2792     "show_keypad",
2793     "show_functionkey",
2794     "props",
2795     "",
2796     "open_display", "close_display", "",
2797     "quit" };
2798
2799   Widget form, key, left;
2800   Pixel color;
2801   XFontStruct *font;
2802   Dimension wd, max_wd;
2803   int row, col, first_row;
2804   char name[50], *label;
2805   Widget key_box[NUM_KEY_ROWS];
2806   Widget menu_entry;
2807   int i;
2808
2809 #include "xvkbd.xbm"
2810 #include "iconify.xbm"
2811
2812   if (remake) {
2813     appres.geometry = GetWindowGeometry(toplevel);
2814     XtUnrealizeWidget(toplevel);
2815     XtDestroyWidget(XtNameToWidget(toplevel, "form"));
2816   }
2817
2818   form = XtVaCreateManagedWidget("form", formWidgetClass, toplevel, NULL);
2819
2820   key_box[0] = None;
2821   key_box[1] = None;
2822   first_row = appres.function_key ? 0 : 1;
2823   if (!appres.keypad_only) {
2824     for (row = first_row; row < NUM_KEY_ROWS; row++) {
2825       if (keys_normal[row][0] == NULL) continue;
2826
2827       sprintf(name, "row%d", row);
2828       key_box[row] = XtVaCreateManagedWidget(name, formWidgetClass, form, NULL);
2829       key_box[row + 1] = None;
2830       if (row != first_row)
2831         XtVaSetValues(key_box[row], XtNfromVert, key_box[row - 1], NULL);
2832       else if (!appres.function_key)
2833         XtVaSetValues(key_box[row], XtNvertDistance, 0, NULL);
2834         
2835       left = None;
2836       for (col = 0; keys_normal[row][col] != NULL; col++) {
2837         strcpy(name, keys_normal[row][col]);
2838         if (strcmp(name, "MainMenu") == 0) {
2839           Widget iconify_button = None;
2840
2841           if (appres.minimizable) {
2842             Pixmap iconify_pixmap = XCreateBitmapFromData(dpy, RootWindow(dpy, DefaultScreen(dpy)),
2843                                                   (char *)iconify_bits, iconify_width, iconify_height);
2844             iconify_button = XtVaCreateManagedWidget("Iconify", commandWidgetClass, key_box[row],
2845                                                      XtNbitmap, iconify_pixmap, NULL);
2846             XtAddCallback(iconify_button, XtNcallback, (XtCallbackProc)IconifyWindow, (void *)TRUE);
2847           }
2848
2849           xvkbd_pixmap = XCreateBitmapFromData(dpy, RootWindow(dpy, DefaultScreen(dpy)),
2850                                (char *)xvkbd_bits, xvkbd_width, xvkbd_height);
2851           key = XtVaCreateManagedWidget("MainMenu", menuButtonWidgetClass, key_box[row],
2852                                         XtNbitmap, xvkbd_pixmap, XtNfromHoriz, iconify_button, NULL);
2853           main_menu = XtVaCreatePopupShell("menu", simpleMenuWidgetClass, key, NULL);
2854           for (i = 0; i < XtNumber(main_menu_items); i++) {
2855             if (strlen(main_menu_items[i]) == 0) {
2856               XtVaCreateManagedWidget("separator", smeLineObjectClass, main_menu, NULL);
2857             } else {
2858               menu_entry = XtVaCreateManagedWidget(main_menu_items[i], smeBSBObjectClass,
2859                                                    main_menu, NULL);
2860               XtAddCallback(menu_entry, XtNcallback, (XtCallbackProc)MenuSelected,
2861                             (XtPointer)main_menu_items[i]);
2862             }
2863           }
2864         } else {
2865           label = appres.modal_keytop ? normal_key_labels[row][col] : key_labels[row][col];
2866           if (isascii(name[0]) && isupper(name[0])) {
2867             if (strcmp(name, "Focus") == 0) {
2868               color = appres.focus_background;
2869               font = appres.keypad_font;
2870             } else {
2871               color = appres.special_background;
2872               if (label != NULL && strchr(label, '\n') != NULL) font = appres.keypad_font;
2873               else font = appres.special_font;
2874             }
2875           } else {
2876             color = appres.general_background;
2877             font = appres.general_font;
2878             if (isalpha(name[0])) font = appres.letter_font;
2879             if (strcmp(name, "space") != 0) sprintf(name, "%d,%d", row, col);
2880           }
2881           key = MakeKey(key_box[row], XtNewString(name), label, color);
2882           XtVaGetValues(key, XtNwidth, &wd, NULL);
2883           if (wd <= 1) {
2884             /* keys can be removed by setting its width to 1 */
2885             XtDestroyWidget(key);
2886             key = None;
2887           } else {
2888             XtVaSetValues(key, XtNfont, font, NULL);
2889 #ifdef USE_I18N
2890             if (font == appres.special_font || font == appres.keypad_font)
2891               XtVaSetValues(key, XtNfontSet, appres.special_fontset, NULL);
2892 #endif
2893           }
2894         }
2895         if (key != None) {
2896           if (left != None) XtVaSetValues(key, XtNfromHoriz, left, NULL);
2897           left = key;
2898         }
2899         key_widgets[row][col] = key;
2900       }
2901     }
2902   }
2903
2904    if (appres.keypad) MakeKeypad(form, key_box[0], key_box[1]);
2905
2906   if (!appres.keypad_only && appres.function_key && appres.keypad) {
2907     XtVaCreateManagedWidget("banner", labelWidgetClass, form,
2908                             XtNfromHoriz, key_box[1],
2909                             XtNlabel, PROGRAM_NAME_WITH_VERSION, NULL);
2910   }
2911
2912
2913   XtRealizeWidget(toplevel);
2914   SetWindowManagerHint(TRUE);
2915
2916   if (!remake && strlen(appres.geometry) == 0) {
2917     Window root;
2918     int x1, y1;
2919     unsigned int wd, ht, bd, dp;
2920     int max_wd, max_ht;
2921
2922     XGetGeometry(dpy, XtWindow(toplevel), &root, &x1, &y1, &wd, &ht, &bd, &dp);
2923     max_wd = XtScreen(toplevel)->width * appres.max_width_ratio;
2924     max_ht = XtScreen(toplevel)->height * appres.max_height_ratio;
2925     if (appres.debug)
2926       fprintf(stderr, "window size: %dx%d, max size: %dx%d\n", wd, ht, max_wd, max_ht);
2927     if (max_wd < wd || max_ht < ht) {
2928       if (max_wd < wd) wd = max_wd;
2929       if (max_ht < ht) ht = max_ht;
2930       if (appres.debug)
2931         fprintf(stderr, "setting window size: %dx%d\n", wd, ht);
2932       XResizeWindow(dpy, XtWindow(toplevel), wd, ht);
2933     }
2934   }
2935
2936   if (!appres.debug && key_box[first_row] != None) {
2937     if (appres.keypad) {
2938       XtVaGetValues(key_box[1], XtNwidth, &max_wd, NULL);
2939     } else {
2940       max_wd = 0;
2941       for (row = first_row; row < NUM_KEY_ROWS && key_box[row] != None; row++) {
2942         XtVaGetValues(key_box[row], XtNwidth, &wd, NULL);
2943         if (max_wd < wd) max_wd = wd;
2944       }
2945     }
2946     for (row = first_row; row < NUM_KEY_ROWS && key_box[row] != None; row++) {
2947       XtVaSetValues(key_box[row], XtNwidth, max_wd, NULL);
2948     }
2949   }
2950   if (0 < strlen(appres.geometry)) {
2951     if (appres.wm_toolbar) {
2952       if (appres.debug)
2953         fprintf(stderr, "window fgeometry ignored; _NET_WM_WINDOW_TYPE_TOOLBAR set on\n");
2954     } else {
2955       if (appres.debug)
2956         fprintf(stderr, "setting window geometry: %s\n", appres.geometry);
2957       XtVaSetValues(toplevel, XtNgeometry, appres.geometry, NULL);
2958       XtUnrealizeWidget(toplevel);
2959       XtRealizeWidget(toplevel);
2960     }
2961   }
2962
2963   ReadKeymap();
2964   if (main_menu != None) RefreshMainMenu();
2965   RefreshShiftState(FALSE);
2966
2967   XtMapWidget(toplevel);
2968
2969   if (wm_delete_window == None)
2970     wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", FALSE);
2971   XSetWMProtocols(dpy, XtWindow(toplevel), &wm_delete_window, 1);
2972
2973   XtVaGetValues(toplevel, XtNheight, &toplevel_height, NULL);
2974 }
2975
2976 /*
2977  * WM_DELETE_WINDOW has been sent - terminate the program.
2978  */
2979 static void DeleteWindowProc(Widget w, XEvent *event,
2980                              String *pars, Cardinal *n_pars)
2981 {
2982   if (appres.nonexitable) {
2983     XBell(dpy, 0);
2984   } else {
2985     shift_state = 0;
2986     RefreshShiftState(TRUE);
2987     XtDestroyApplicationContext(XtWidgetToApplicationContext(toplevel));
2988     exit(0);
2989   }
2990 }
2991
2992 /*
2993  * Callback for ConfigureNotify event, which will be invoked when
2994  * the toplevel window is resized.
2995  * We may need to switch the keytop labels when window becomes
2996  * smaller than appres.modal_threshold, and vice versa.
2997  */
2998 static void WindowResized(Widget w, XEvent *event,
2999                           String *pars, Cardinal *n_pars)
3000 {
3001   Dimension ht;
3002
3003   XtVaGetValues(toplevel, XtNheight, &ht, NULL);
3004   if (appres.modal_threshold <= ht) {
3005     if (toplevel_height < appres.modal_threshold) MakeKeyboard(TRUE);
3006   } else {
3007     toplevel_height = ht;
3008   }
3009   RefreshShiftState(TRUE);
3010 }
3011
3012 /*
3013  * Load list of text to be assigned to function keys.
3014  * Each line contains name of the key (with optional modifier)
3015  * and the text to be assigned to the key, as:
3016  *
3017  *   F1 text for F1
3018  *   s:F2 text for Shift-F2
3019  */
3020 #ifndef PATH_MAX
3021 # define PATH_MAX 300
3022 #endif
3023
3024 static char fkey_filename[PATH_MAX] = "";
3025
3026 static struct fkey_struct {
3027   struct fkey_struct *next;
3028   char *value;
3029 } *fkey_list = NULL;
3030
3031 static void ReadFuncionKeys(void)
3032 {
3033   FILE *fp;
3034   char str[200], key[200];
3035   struct fkey_struct *sp = NULL, *new_node;
3036   char len;
3037   int val;
3038   const char *home;
3039
3040   /* If KeyFile is not started with "/", consider the filename is relative to $HOME */
3041   /* and put value of the $HOME environment variable before the KeyFile. */
3042   /* To avoid possible buffer overflow, $HOME will not be added when resulting filename */
3043   /* is too long. */
3044   home = getenv("HOME");
3045   if (appres.key_file[0] != '/' && home != NULL
3046       && strlen(home) + strlen(appres.key_file) + 1 < sizeof(fkey_filename))
3047     sprintf(fkey_filename, "%s/%s", home, appres.key_file);
3048   else
3049     strncpy(fkey_filename, appres.key_file, sizeof(fkey_filename));
3050
3051   strncpy(dict_filename, appres.dict_file, sizeof(dict_filename));
3052
3053   fp = fopen(fkey_filename, "r");
3054   if (fp == NULL) return;
3055
3056   while (fgets(str, sizeof(str) - 1, fp)) {
3057     if (str[0] == '#') {
3058       sscanf(&str[1], "%s %d", key, &val);
3059       if (strcmp(key, "quick_modifiers") == 0)
3060         appres.quick_modifiers = val;
3061       else if (strcmp(key, "shift_lock") == 0)
3062         appres.shift_lock = val;
3063       else if (strcmp(key, "altgr_lock") == 0)
3064         appres.altgr_lock = val;
3065       else if (strcmp(key, "modifiers_lock") == 0)
3066         appres.modifiers_lock = val;
3067       else if (strcmp(key, "key_click") == 0)
3068         appres.key_click_duration = val;
3069       else if (strcmp(key, "autoclick") == 0)
3070         appres.autoclick_delay = val;
3071       else if (strcmp(key, "always_on_top") == 0)
3072         appres.always_on_top = val;
3073       else if (strcmp(key, "wm_toolbar") == 0)
3074         appres.wm_toolbar = val;
3075       else if (strcmp(key, "jump_pointer") == 0)
3076         appres.jump_pointer = val;
3077       else if (strcmp(key, "dict_file") == 0) {
3078         sscanf(&str[1], "%*s %s", &key);
3079         strncpy(dict_filename, key, sizeof(dict_filename));
3080       }
3081     } else if (isalpha(str[0])) {
3082       len = strlen(str);
3083       if (str[len - 1] == '\n') str[len - 1] = '\0';
3084
3085       new_node = malloc(sizeof(struct fkey_struct));
3086       if (fkey_list == NULL) fkey_list = new_node;
3087       else sp->next = new_node;
3088       sp = new_node;
3089
3090       sp->next = NULL;
3091       sp->value = XtNewString(str);
3092     }
3093   }
3094   fclose(fp);
3095 }
3096
3097 /*
3098  * Edit string assigned for function keys.
3099  * Modifiers (Shift, Ctrl, etc.) can't be handled here.
3100  */
3101 static Widget edit_fkey_panel = None;
3102 static Widget fkey_menu_button = None;
3103 static Widget fkey_value_menu_button = None;
3104 static Widget fkey_value_entry = None;
3105 static char fkey_value[100] = "";
3106 static char cur_fkey[20] = "";
3107 static char *cur_fkey_value_mode = "";
3108
3109 static void FKeyValueMenuSelected(Widget w, char *key)
3110 {
3111   char *key1, *cp;
3112
3113   if (key[0] == 'c') {
3114     cur_fkey_value_mode = "command";
3115     key1 = "*command";
3116   } else {
3117     cur_fkey_value_mode = "string";
3118     key1 = "*string";
3119   }
3120   XtVaGetValues(XtNameToWidget(fkey_value_menu_button, key1), XtNlabel, &cp, NULL);
3121   XtVaSetValues(fkey_value_menu_button, XtNlabel, cp, NULL);
3122 }
3123
3124 static void FKeyMenuSelected(Widget w, char *key)
3125 {
3126   struct fkey_struct *sp, *sp2;
3127   int len;
3128   const char *value, *prefix;
3129   char key2[20];
3130
3131   if (key == NULL)
3132     strcpy(key2, "");
3133   else if (strncmp(key, "Shift-", strlen("Shift-")) == 0)
3134     sprintf(key2, "s:%s", &key[strlen("Shift-")]);
3135   else
3136     strcpy(key2, key);
3137
3138   if (strcmp(cur_fkey, key2) != 0) {
3139     if (strlen(cur_fkey) != 0) {
3140       len = strlen(cur_fkey);
3141       sp2 = NULL;
3142       for (sp = fkey_list; sp != NULL; sp = sp->next) {
3143         if (strncmp(sp->value, cur_fkey, len) == 0 && isspace(sp->value[len]))
3144           break;
3145         sp2 = sp;
3146       }
3147       if (strlen(fkey_value) != 0) {  /* assign new string for the function key */
3148         if (sp == NULL) {  /* it was not defined before now */
3149           sp = malloc(sizeof(struct fkey_struct));
3150           if (fkey_list == NULL) fkey_list = sp;
3151           else sp2->next = sp;
3152           sp->next = NULL;
3153           sp->value = NULL;
3154         }
3155         sp->value = realloc(sp->value, len + strlen(fkey_value) + 5);
3156         prefix = "";
3157         if (cur_fkey_value_mode[0] == 'c') prefix = "!";
3158         else if (fkey_value[0] == '!') prefix = "\\";
3159         sprintf(sp->value, "%s %s%s", cur_fkey, prefix, fkey_value);
3160       } else {  /* empty string - remove the entry for the function key */
3161         if (sp != NULL) {
3162           if (sp2 != NULL) sp2->next = sp->next;
3163           else fkey_list = sp->next;
3164           free(sp->value);
3165           free(sp);
3166         }
3167       }
3168     }
3169
3170     if (key != NULL) {
3171       XtVaSetValues(fkey_menu_button, XtNlabel, key, NULL);
3172   
3173       value = FindFunctionKeyValue(key2, FALSE);
3174       if (value == NULL) value = "";
3175
3176       FKeyValueMenuSelected(None, (value[0] == '!') ? "command" : "string");
3177
3178       if (value[0] == '!' || value[0] == '\\') value = value + 1;
3179       strncpy(fkey_value, value, sizeof(fkey_value) - 1);
3180       XtVaSetValues(fkey_value_entry, XtNstring, fkey_value, NULL);
3181
3182       strcpy(cur_fkey, key2);
3183     }
3184   }
3185 }
3186
3187 static void CloseFunctionKeyPanel(void)
3188 {
3189   XtPopdown(edit_fkey_panel);
3190 }
3191
3192 static void SaveFunctionKey(void)
3193 {
3194   struct fkey_struct *sp;
3195   FILE *fp;
3196
3197   if (appres.debug) fprintf(stderr, "SaveFunctionKey\n");
3198
3199   if (edit_fkey_panel != None) FKeyMenuSelected(None, NULL);
3200
3201   fp = fopen(fkey_filename, "w");
3202   if (fp == NULL) {
3203     fprintf(stderr, "%s: can't open \"%s\": %s\n",
3204             PROGRAM_NAME, fkey_filename, strerror(errno));
3205     return;
3206   }
3207   fprintf(fp, "#quick_modifiers %d\n", appres.quick_modifiers);
3208   fprintf(fp, "#shift_lock %d\n", appres.shift_lock);
3209   fprintf(fp, "#altgr_lock %d\n", appres.altgr_lock);
3210   fprintf(fp, "#modifiers_lock %d\n", appres.modifiers_lock);
3211   fprintf(fp, "#key_click %d\n", appres.key_click_duration);
3212   fprintf(fp, "#autoclick %d\n", appres.autoclick_delay);
3213   fprintf(fp, "#always_on_top %d\n", appres.always_on_top);
3214   fprintf(fp, "#wm_toolbar %d\n", appres.wm_toolbar);
3215   fprintf(fp, "#jump_pointer %d\n", appres.jump_pointer);
3216   fprintf(fp, "#dict_file %s\n", dict_filename);
3217   for (sp = fkey_list; sp != NULL; sp = sp->next) {
3218     fprintf(fp, "%s\n", sp->value);
3219   }
3220   fclose(fp);
3221
3222   if (edit_fkey_panel != None) CloseFunctionKeyPanel();
3223 }
3224
3225 static void PopupFunctionKeyEditor(void)
3226 {
3227   Widget form, form2, menu, menu_entry, button;
3228   char label[20];
3229   char *key;
3230   int i, j;
3231
3232   if (edit_fkey_panel == None) {
3233     edit_fkey_panel = XtVaCreatePopupShell("edit_fkey_panel", transientShellWidgetClass,
3234                                            toplevel, NULL);
3235     form = XtVaCreateManagedWidget("form", formWidgetClass, edit_fkey_panel, NULL);
3236
3237     form2 = XtVaCreateManagedWidget("form2", formWidgetClass, form, NULL);
3238     XtVaCreateManagedWidget("fkey_label", labelWidgetClass, form2, NULL);
3239     fkey_menu_button = XtVaCreateManagedWidget("fkey_menu", menuButtonWidgetClass,
3240                                                form2, NULL);
3241     menu = XtVaCreatePopupShell("menu", simpleMenuWidgetClass, fkey_menu_button, NULL);
3242     for (j = 0; j <= 1; j++) {
3243       for (i = 1; i <= appres.editable_function_keys; i++) {
3244         if (j == 0)
3245           sprintf(label, "F%d", i);
3246         else 
3247           sprintf(label, "Shift-F%d", i);
3248         key = XtNewString(label);
3249         menu_entry = XtVaCreateManagedWidget(key, smeBSBObjectClass, menu, NULL);
3250         XtAddCallback(menu_entry, XtNcallback, (XtCallbackProc)FKeyMenuSelected,
3251                       (XtPointer)key);
3252       }
3253     }
3254
3255     fkey_value_menu_button = XtVaCreateManagedWidget("fkey_value_menu", menuButtonWidgetClass,
3256                                                      form2, NULL);
3257     menu = XtVaCreatePopupShell("menu", simpleMenuWidgetClass, fkey_value_menu_button, NULL);
3258     menu_entry = XtVaCreateManagedWidget("string", smeBSBObjectClass, menu, NULL);
3259     XtAddCallback(menu_entry, XtNcallback, (XtCallbackProc)FKeyValueMenuSelected,
3260                   (XtPointer)"string");
3261     menu_entry = XtVaCreateManagedWidget("command", smeBSBObjectClass, menu, NULL);
3262     XtAddCallback(menu_entry, XtNcallback, (XtCallbackProc)FKeyValueMenuSelected,
3263                   (XtPointer)"command");
3264
3265     XtVaCreateManagedWidget("fkey_value_sep", labelWidgetClass, form2, NULL);
3266
3267     fkey_value_entry = XtVaCreateManagedWidget("fkey_value", asciiTextWidgetClass, form2,
3268                                                XtNuseStringInPlace, True,
3269                                                XtNeditType, XawtextEdit,
3270                                                XtNstring, fkey_value,
3271                                                XtNlength, sizeof(fkey_value) - 1,
3272                                                NULL);
3273
3274     button = XtVaCreateManagedWidget("save_button", commandWidgetClass, form, NULL);
3275     XtAddCallback(button, XtNcallback, (XtCallbackProc)SaveFunctionKey, NULL);
3276
3277     button = XtVaCreateManagedWidget("close_button", commandWidgetClass, form, NULL);
3278     XtAddCallback(button, XtNcallback, (XtCallbackProc)CloseFunctionKeyPanel, NULL);
3279
3280     XtRealizeWidget(edit_fkey_panel);
3281     XSetWMProtocols(dpy, XtWindow(edit_fkey_panel), &wm_delete_window, 1);
3282
3283     XtSetKeyboardFocus(edit_fkey_panel, fkey_value_entry);
3284
3285     FKeyMenuSelected(None, "F1");
3286   }
3287
3288   XtPopup(edit_fkey_panel, XtGrabNone);
3289 }
3290
3291 /*
3292  * If text is assigned to the specified function key,
3293  * return the text.  Otherwise, return NULL.
3294  */
3295 static const char *FindFunctionKeyValue(const char *key, Boolean shiftable)
3296 {
3297   char label[50];
3298   char prefix;
3299   struct fkey_struct *sp;
3300   int len;
3301
3302   prefix = '\0';
3303   if (shiftable) {
3304     if (shift_state & meta_mask) prefix = 'm';
3305     else if (shift_state & alt_mask) prefix = 'a';
3306     else if (shift_state & ControlMask) prefix = 'c';
3307     else if (shift_state & ShiftMask) prefix = 's';
3308   }
3309   if (prefix == '\0') sprintf(label, "%s", key);
3310   else sprintf(label, "%c:%s", prefix, key);
3311   len = strlen(label);
3312   
3313   for (sp = fkey_list; sp != NULL; sp = sp->next) {
3314     if (strncmp(sp->value, label, len) == 0 && isspace(sp->value[len]))
3315       return &(sp->value[len + 1]);
3316   }
3317   return NULL;
3318 }
3319
3320 /*
3321  * Key click
3322  */
3323 void KeyClick(void)
3324 {
3325   XKeyboardState ks;
3326   XKeyboardControl kc;
3327
3328   if (0 < appres.key_click_duration) {
3329     XGetKeyboardControl(dpy, &ks);
3330
3331     kc.bell_duration = ks.bell_duration;
3332     kc.bell_pitch = appres.key_click_pitch;
3333     kc.bell_duration = appres.key_click_duration;
3334     XChangeKeyboardControl(dpy, KBBellPitch | KBBellDuration, &kc);
3335     XBell(dpy, 0);
3336     XSync(dpy, FALSE);
3337
3338     kc.bell_pitch = ks.bell_pitch;
3339     kc.bell_duration = ks.bell_duration;
3340     XChangeKeyboardControl(dpy, KBBellPitch | KBBellDuration, &kc);
3341     XSync(dpy, FALSE);
3342   }
3343 }
3344
3345 /*
3346  * Display balloon message for the function keys,
3347  * if text is assigned to the key.
3348  */
3349 static Boolean balloon_panel_open = FALSE;
3350 static Widget balloon_panel = None;
3351
3352 static  XtIntervalId autoclick_id = (XtIntervalId)0;
3353
3354 static void StopAutoclick(void)
3355 {
3356   if (autoclick_id != (XtIntervalId)0) {
3357     if (appres.debug) fprintf(stderr, "StopAutoclick: %lx\n", (long)autoclick_id);
3358
3359     XtRemoveTimeOut(autoclick_id);
3360     autoclick_id = (XtIntervalId)0;
3361   }
3362 }
3363
3364 static void Autoclick(void)
3365 {
3366   StopAutoclick();
3367
3368   XTestFakeButtonEvent(target_dpy, 1, True, CurrentTime);
3369   XTestFakeButtonEvent(target_dpy, 1, False, CurrentTime);
3370 }
3371
3372 static void ShowBalloon(Widget w, XEvent *event, String *pars, Cardinal *n_pars)
3373 {
3374   static Widget message;
3375   Position x, y;
3376   Dimension ht;
3377   const char *value;
3378
3379   if (strcmp(XtName(w), "MainMenu") == 0) {
3380     value = "Main menu";
3381   } else {
3382     if (0 < appres.autoclick_delay) {
3383       autoclick_id = XtAppAddTimeOut(app_con, (long)appres.autoclick_delay,
3384                            (XtTimerCallbackProc)Autoclick, (XtPointer)w);
3385
3386       if (appres.debug) fprintf(stderr, "ShowBalloon: auto click triggerd: %lx, %d\n",
3387                                 (long)autoclick_id, appres.autoclick_delay);
3388     }
3389     value = FindFunctionKeyValue(XtName(w), TRUE);
3390     if (value == NULL) return;
3391   }
3392
3393   if (balloon_panel == None) {
3394     balloon_panel = XtVaCreatePopupShell("balloon_panel", transientShellWidgetClass, toplevel,
3395                                          XtNoverrideRedirect, TRUE, NULL);
3396     message = XtVaCreateManagedWidget("message", labelWidgetClass, balloon_panel, NULL);
3397   }
3398   XtVaGetValues(w, XtNheight, &ht, NULL);
3399   XtTranslateCoords(w, 6, ht + 2, &x, &y);
3400   XtVaSetValues(balloon_panel, XtNx, x, XtNy, y, NULL);
3401   if (value[0] == '!') {
3402     if (appres.secure) return;
3403     XtVaSetValues(message, XtNlabel, value + 1,
3404                   XtNbackground, appres.launch_balloon_background, NULL);
3405   } else {
3406     if (value[0] == '\\') value = value + 1;
3407     XtVaSetValues(message, XtNlabel, value,
3408                   XtNbackground, appres.balloon_background, NULL);
3409   }
3410   XtPopup(balloon_panel, XtGrabNone);
3411
3412   balloon_panel_open = TRUE;
3413 }
3414
3415 static void CloseBalloon(Widget w, XEvent *event, String *pars, Cardinal *n_pars)
3416 {
3417   StopAutoclick();
3418   if (balloon_panel_open) {
3419     XtPopdown(balloon_panel);
3420     balloon_panel_open = FALSE;
3421   }
3422 }
3423
3424 /*
3425  * Set icon image.
3426  */
3427 static void SetIconBitmap(Widget w)
3428 {
3429 #include "xvkbd_icon.xbm"
3430 #include "xvkbd_iconmask.xbm"
3431
3432   Pixmap icon_pixmap, icon_mask;
3433
3434   icon_pixmap = XCreateBitmapFromData(XtDisplay(w), XtWindow(w),
3435                                       (char *)xvkbd_icon_bits,
3436                                       xvkbd_icon_width, xvkbd_icon_height);;
3437   icon_mask = XCreateBitmapFromData(XtDisplay(w), XtWindow(w),
3438                                     (char *)xvkbd_iconmask_bits,
3439                                     xvkbd_iconmask_width, xvkbd_iconmask_height);
3440   XtVaSetValues(w, XtNiconPixmap, icon_pixmap, XtNiconMask, icon_mask, NULL);
3441 }
3442
3443 /*
3444  * Callback for VisibilityChanged event, which will be invoked
3445  * when xvkbd window is hidden by other window.  ** EXPERIMENTAL **
3446  */
3447 static void VisibilityChanged(Widget w, XEvent *event,
3448                               String *pars, Cardinal *n_pars)
3449 {
3450   static cnt = 0;
3451   static time_t t1 = 0;
3452   time_t t2;
3453
3454   if (!appres.always_on_top) return;
3455
3456   if (balloon_panel_open) return;
3457
3458   if (main_menu != None && XtWindow(main_menu) != None) {
3459     XWindowAttributes attr;
3460     XGetWindowAttributes(dpy, XtWindow(main_menu), &attr);
3461     if (attr.map_state != IsUnmapped) return;
3462   }
3463
3464   t2 = time(NULL);
3465   if (t1 != t2) cnt = 0;
3466   t1 = t2;
3467   cnt = cnt + 1;
3468   if (appres.debug)
3469     fprintf(stderr, "%s: visibility of the window changed (cnt = %d)\n", PROGRAM_NAME, cnt);
3470   if (cnt < 5)
3471     XRaiseWindow(XtDisplay(toplevel), XtWindow(toplevel));
3472 }
3473
3474 /*
3475  * The main program.
3476  */
3477 int main(int argc, char *argv[])
3478 {
3479   static XtActionsRec actions[] = {
3480     { "DeleteWindowProc", DeleteWindowProc },
3481     { "WindowResized", WindowResized },
3482     { "VisibilityChanged", VisibilityChanged },
3483     { "ReadKeymap", (XtActionProc)ReadKeymap },
3484     { "ButtonDownAction", ButtonDownAction },
3485     { "ButtonUpAction", ButtonUpAction },
3486     { "ShowBalloon", ShowBalloon },
3487     { "CloseBalloon", CloseBalloon },
3488     { "ClosePopupPanel", (XtActionProc)ClosePopupPanel },
3489   };
3490   static String fallback_resources[] = {
3491 #include "XVkbd-common.h"
3492     NULL,
3493   };
3494
3495   Boolean open_keypad_panel = FALSE;
3496   char ch;
3497   Window child;
3498   int op, ev, err;
3499
3500   argc1 = argc;
3501   argv1 = malloc(sizeof(char *) * (argc1 + 5));
3502   memcpy(argv1, argv, sizeof(char *) * argc1);
3503   argv1[argc1] = NULL;
3504
3505 #ifdef USE_I18N
3506   XtSetLanguageProc(NULL, NULL, NULL);
3507 #endif
3508
3509   toplevel = XtVaAppInitialize(NULL, "XVkbd",
3510                                options, XtNumber(options),
3511                                &argc, argv, fallback_resources, NULL);
3512   dpy = XtDisplay(toplevel);
3513   app_con = XtWidgetToApplicationContext(toplevel);
3514   XtAppAddActions(app_con, actions, XtNumber(actions));
3515
3516   target_dpy = dpy;
3517
3518   if (1 < argc) {
3519     fprintf(stderr, "%s: illegal option: %s\n\n", PROGRAM_NAME, argv[1]);
3520   }
3521
3522   XtGetApplicationResources(toplevel, &appres,
3523             application_resources, XtNumber(application_resources),
3524             NULL, 0);
3525   if (appres.version) {
3526     fprintf(stdout, "%s\n", appres.description);
3527     exit(1);
3528   }
3529
3530   if (appres.compact) {
3531     appres.keypad = FALSE;
3532     appres.function_key = FALSE;
3533   }
3534   if (appres.keypad_only && !appres.keypad) {
3535     appres.keypad_only = FALSE;
3536     open_keypad_panel = TRUE;
3537   }
3538
3539   if (appres.no_sync) {
3540     XSync(dpy, FALSE);
3541     XSetErrorHandler(MyErrorHandler);
3542   }
3543   
3544   if (0 < strlen(appres.window) || 0 < strlen(appres.instance)) {
3545     if (strcmp(appres.window, "root") == 0) {
3546       focused_window = RootWindow(dpy, DefaultScreen(dpy));
3547     } else if (sscanf(appres.window, "0x%lX%c", &focused_window, &ch) != 1) {
3548       if (sscanf(appres.window, "%ld%c", &focused_window, &ch) != 1) {
3549         focused_window = FindWindow(RootWindow(dpy, DefaultScreen(dpy)),
3550                                     appres.window);
3551         if (focused_window == None) {
3552           fprintf(stderr, "%s: no such window: window=%s and class=%s\n", PROGRAM_NAME, appres.window, appres.instance);
3553           if (appres.no_root)
3554             exit(-1);
3555         }
3556       }
3557     }
3558   }
3559   focused_subwindow = focused_window;
3560
3561   ReadKeymap();
3562   if (!altgr_mask && appres.auto_add_keysym) AddModifier(XK_Mode_switch);
3563
3564   if (strlen(appres.text) != 0 || strlen(appres.file) != 0) {
3565     appres.keypad_keysym = TRUE;
3566     if (focused_window != None &&
3567         (appres.list_widgets || strlen(appres.widget) != 0)) {
3568       XtVaSetValues(toplevel, XtNwidth, 1, XtNheight, 1, NULL);
3569       XtRealizeWidget(toplevel);
3570       child = FindWidget(toplevel, focused_window, appres.widget);
3571       if (child != None) focused_subwindow = child;
3572     }
3573     if (strlen(appres.text) != 0)
3574       SendString(appres.text);
3575     else
3576       SendFileContent(appres.file);
3577     exit(0);
3578  } else {
3579     ReadFuncionKeys();
3580
3581     if (0 < strlen(appres.keys_normal))
3582       RedefineKeys(keys_normal, appres.keys_normal);
3583     if (0 < strlen(appres.keys_shift))
3584       RedefineKeys(keys_shift, appres.keys_shift);
3585     if (0 < strlen(appres.keys_altgr))
3586       RedefineKeys(keys_altgr, appres.keys_altgr);
3587     if (0 < strlen(appres.keys_shift_altgr))
3588       RedefineKeys(keys_shift_altgr, appres.keys_shift_altgr);
3589
3590     if (0 < strlen(appres.key_labels))
3591       RedefineKeys(key_labels, appres.key_labels);
3592     if (0 < strlen(appres.normal_key_labels))
3593       RedefineKeys(normal_key_labels, appres.normal_key_labels);
3594     if (0 < strlen(appres.shift_key_labels))
3595       RedefineKeys(shift_key_labels, appres.shift_key_labels);
3596     if (0 < strlen(appres.altgr_key_labels))
3597       RedefineKeys(altgr_key_labels, appres.altgr_key_labels);
3598     if (0 < strlen(appres.shift_altgr_key_labels))
3599       RedefineKeys(shift_altgr_key_labels, appres.shift_altgr_key_labels);
3600
3601     if (0 < strlen(appres.keypad_normal)) {
3602       RedefineKeys(keypad, appres.keypad_normal);
3603       RedefineKeys(keypad_shift, appres.keypad_normal);
3604       RedefineKeys(keypad_label, appres.keypad_normal);
3605     }
3606     if (0 < strlen(appres.keypad_shift))
3607       RedefineKeys(keypad_shift, appres.keypad_shift);
3608     if (0 < strlen(appres.keypad_labels))
3609       RedefineKeys(keypad_label, appres.keypad_labels);
3610
3611     MakeKeyboard(FALSE);
3612
3613     if (focused_window != None &&
3614         (appres.list_widgets || strlen(appres.widget) != 0)) {
3615       child = FindWidget(toplevel, focused_window, appres.widget);
3616       if (child != None) focused_subwindow = child;
3617     }
3618
3619     if (main_menu != None) {
3620       if (strlen(dict_filename) == 0)
3621         XtSetSensitive(XtNameToWidget(main_menu, "*completion"), FALSE);
3622       if (strlen(appres.customizations) == 0)
3623         XtSetSensitive(XtNameToWidget(main_menu, "*select_layout"), FALSE);
3624       if (appres.nonexitable)
3625         XtSetSensitive(XtNameToWidget(main_menu, "*quit"), FALSE);
3626       if (appres.secure) {
3627         XtSetSensitive(XtNameToWidget(main_menu, "*man"), FALSE);
3628         XtSetSensitive(XtNameToWidget(main_menu, "*open_display"), FALSE);
3629       }
3630     }
3631
3632 #ifdef USE_XTEST
3633     if (!XQueryExtension(dpy, "XTEST", &op, &ev, &err)) {
3634       if (appres.xtest) {
3635         fprintf(stderr, "%s: XTEST extension is not supported by the X server\n",
3636                 PROGRAM_NAME);
3637         fprintf(stderr, "%s: XSendEvent will be used instead\n",
3638                 PROGRAM_NAME);
3639         appres.xtest = FALSE;
3640       }
3641       if (main_menu != None) {
3642         XtSetSensitive(XtNameToWidget(main_menu, "*use_xtest"), FALSE);
3643         RefreshMainMenu();
3644       }
3645     }
3646 #endif
3647
3648     if (!appres.debug) {
3649 #ifdef SYSV
3650       signal(SIGINT, SIG_IGN);
3651       signal(SIGQUIT, SIG_IGN);
3652 #else
3653       struct sigaction sigact;
3654       sigact.sa_handler = SIG_IGN;
3655       sigemptyset(&sigact.sa_mask);
3656       sigact.sa_flags = 0;
3657       sigaction(SIGINT, &sigact, NULL);
3658       sigaction(SIGQUIT, &sigact, NULL);
3659 #endif
3660     }
3661
3662     {
3663 #ifdef SYSV
3664       signal(SIGUSR1, SignalUser1);
3665 #else
3666       struct sigaction sigact;
3667       sigact.sa_handler = SignalUser1;
3668       sigemptyset(&sigact.sa_mask);
3669       sigact.sa_flags = 0;
3670       sigaction(SIGUSR1, &sigact, NULL);
3671 #endif
3672     }
3673
3674     SetIconBitmap(toplevel);
3675
3676     if (open_keypad_panel) MenuSelected(None, "keypad");
3677
3678
3679     XtAppMainLoop(app_con);
3680   }
3681   exit(0);
3682 }
3683
3684 /*
3685  * Replace setlocale() in the standard library here, because
3686  * it may not support some locales used for localized keyboards.
3687  */
3688 #if defined(USE_I18N) && !defined(HAVE_SETLOCALE)
3689
3690 char *setlocale(int category, const char *locale)
3691 {
3692   static char old_locale[100] = "C";
3693   static char cur_locale[100] = "C";
3694   const char *s;
3695   if (locale == NULL) {
3696     return cur_locale;
3697   } else if (category == LC_ALL) {
3698     strcpy(old_locale, cur_locale);
3699     if (locale[0] == '\0') {
3700       s = getenv("LC_ALL");
3701       if (s == NULL) s = "C";  /* LC_ALL not defined */
3702     } else {
3703       s = locale;
3704     }
3705     strncpy(cur_locale, s, sizeof(cur_locale) - 1);
3706     return old_locale;
3707   } else {
3708     return cur_locale;
3709   }
3710 }
3711 #endif  /* HAVE_SETLOCALE */