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