1 /* -*- c-basic-offset:2; tab-width:2; indent-tabs-mode:nil -*- */
2 
3 #include "ui_shortcut.h"
4 
5 #include <stdio.h>       /* sscanf */
6 #include <string.h>      /* strchr/memcpy */
7 #include <pobl/bl_def.h> /* HAVE_WINDOWS_H */
8 #include <pobl/bl_mem.h>
9 #include <pobl/bl_debug.h>
10 #include <pobl/bl_file.h>
11 #include <pobl/bl_conf_io.h>
12 #include <pobl/bl_str.h> /* strdup */
13 
14 #ifndef CommandMask
15 #define CommandMask (0)
16 #endif
17 
18 /*
19  * !! Notice !!
20  * Mod1Mask - Mod5Mask are not distinguished.
21  */
22 
23 typedef struct key_func_table {
24   char *name;
25   ui_key_func_t func;
26 
27 } key_func_table_t;
28 
29 /* --- static variables --- */
30 
31 static char *key_file = "mlterm/key";
32 
33 /*
34  * Button*Mask is disabled until Button* is specified in ~/.mlterm/key to avoid
35  * such a problem as
36  * http://sourceforge.net/mailarchive/message.php?msg_id=30866232
37  */
38 static int button_mask = 0;
39 
40 /* --- static variables --- */
41 
42 static key_func_table_t key_func_table[] = {
43   { "IM_HOTKEY", IM_HOTKEY, },
44   { "EXT_KBD", EXT_KBD, },
45   { "OPEN_SCREEN", OPEN_SCREEN, },
46   { "OPEN_PTY", OPEN_PTY, },
47   { "NEXT_PTY", NEXT_PTY, },
48   { "PREV_PTY", PREV_PTY, },
49   { "VSPLIT_SCREEN", VSPLIT_SCREEN, },
50   { "HSPLIT_SCREEN", HSPLIT_SCREEN, },
51   { "NEXT_SCREEN", NEXT_SCREEN, },
52   { "PREV_SCREEN", PREV_SCREEN, },
53   { "CLOSE_SCREEN", CLOSE_SCREEN, },
54   { "HEXPAND_SCREEN", HEXPAND_SCREEN, },
55   { "VEXPAND_SCREEN", VEXPAND_SCREEN, },
56   { "PAGE_UP", PAGE_UP, },
57   { "SCROLL_UP", SCROLL_UP, },
58   { "SCROLL_UP_TO_MARK", SCROLL_UP_TO_MARK, },
59   { "SCROLL_DOWN_TO_MARK", SCROLL_DOWN_TO_MARK, },
60   { "INSERT_SELECTION", INSERT_SELECTION, },
61   { "RESET", RESET, },
62   { "COPY_MODE", COPY_MODE, },
63   { "SET_MARK", SET_MARK, },
64   { "EXIT_PROGRAM", EXIT_PROGRAM, },
65 
66   /* obsoleted: alias of OPEN_SCREEN */
67   { "NEW_PTY", OPEN_SCREEN, },
68 };
69 
70 /* --- static functions --- */
71 
read_conf(ui_shortcut_t * shortcut,char * filename)72 static int read_conf(ui_shortcut_t *shortcut, char *filename) {
73   bl_file_t *from;
74   char *key;
75   char *value;
76 
77   if (!(from = bl_file_open(filename, "r"))) {
78 #ifdef DEBUG
79     bl_warn_printf(BL_DEBUG_TAG " %s couldn't be opened.\n", filename);
80 #endif
81 
82     return 0;
83   }
84 
85   while (bl_conf_io_read(from, &key, &value)) {
86     /*
87      * [shortcut key]=[operation]
88      */
89     ui_shortcut_parse(shortcut, key, value);
90   }
91 
92   bl_file_close(from);
93 
94   return 1;
95 }
96 
97 /* --- global functions --- */
98 
ui_shortcut_init(ui_shortcut_t * shortcut)99 void ui_shortcut_init(ui_shortcut_t *shortcut) {
100   char *rcpath;
101 
102   ui_key_t default_key_map[] = {
103     /* IM_HOTKEY */
104     { 0, 0, 0, },
105 
106     /* EXT_KBD(obsolete) */
107     { 0, 0, 0, },
108 
109 #if defined(USE_QUARTZ) || (defined(USE_SDL2) && defined(__APPLE__))
110     /* OPEN_SCREEN */
111     { XK_F1, CommandMask, 1, },
112 
113     /* OPEN_PTY */
114     { XK_F2, CommandMask, 1, },
115 
116     /* NEXT_PTY */
117     { XK_F3, CommandMask, 1, },
118 
119     /* PREV_PTY */
120     { XK_F4, CommandMask, 1, },
121 #else
122     /* OPEN_SCREEN */
123     { XK_F1, ControlMask, 1, },
124 
125     /* OPEN_PTY */
126     { XK_F2, ControlMask, 1, },
127 
128     /* NEXT_PTY */
129     { XK_F3, ControlMask, 1, },
130 
131     /* PREV_PTY */
132     { XK_F4, ControlMask, 1, },
133 #endif
134 
135     /* HSPLIT_SCREEN */
136     { XK_F1, ShiftMask, 1, },
137 
138     /* VSPLIT_SCREEN */
139     { XK_F2, ShiftMask, 1, },
140 
141     /* NEXT_SCREEN */
142     { XK_F3, ShiftMask, 1, },
143 
144     /* PREV_SCREEN */
145     { XK_F4, ShiftMask, 1, },
146 
147     /* CLOSE_SCREEN */
148     { XK_F5, ShiftMask, 1, },
149 
150     /* HEXPAND_SCREEN */
151     { XK_F6, ShiftMask, 1, },
152 
153     /* VEXPAND_SCREEN */
154     { XK_F7, ShiftMask, 1, },
155 
156     /* PAGE_UP */
157     { XK_Prior, ShiftMask, 1, },
158 
159     /* SCROLL_UP */
160     { XK_Up, ShiftMask, 1, },
161 
162     /* SCROLL_UP_TO_MARK */
163     { XK_Up, ControlMask|ShiftMask, 1, },
164 
165     /* SCROLL_DOWN_TO_MARK */
166     { XK_Down, ControlMask|ShiftMask, 1, },
167 
168     /* INSERT_SELECTION */
169 #if defined(USE_QUARTZ) || (defined(USE_SDL2) && defined(__APPLE__))
170     { 'v', CommandMask, 1, },
171 #else
172     { XK_Insert, ShiftMask, 1, },
173 #endif
174 
175     /* RESET */
176     { XK_Pause, 0, 1, },
177 
178     /* COPY_MODE */
179     { XK_Return, ControlMask|ShiftMask, 1, },
180 
181     /* SET_MARK */
182     { 'm', ControlMask|ShiftMask, 1, },
183 
184 #ifdef DEBUG
185     /* EXIT_PROGRAM(only for debug) */
186     { XK_F1, ControlMask | ShiftMask, 1, },
187 #else
188     { 0, 0, 0, },
189 #endif
190   };
191 
192   memcpy(&shortcut->map, &default_key_map, sizeof(default_key_map));
193 
194   if ((shortcut->str_map = malloc(2 * sizeof(ui_str_key_t)))) {
195     shortcut->str_map_size = 2;
196 
197     shortcut->str_map[0].ksym = 0;
198     shortcut->str_map[0].state = Button1Mask | ControlMask;
199 
200     shortcut->str_map[0].str =
201 #ifdef HAVE_WINDOWS_H
202       strdup("menu:mlterm-menu.exe");
203 #else
204       strdup("menu:mlterm-menu");
205 #endif
206 
207     shortcut->str_map[1].ksym = 0;
208     shortcut->str_map[1].state = Button3Mask | ControlMask;
209     shortcut->str_map[1].str =
210 #ifdef HAVE_WINDOWS_H
211       strdup("menu:mlconfig.exe");
212 #else
213       strdup("menu:mlconfig");
214 #endif
215     button_mask |= (Button1Mask | Button3Mask);
216   } else {
217     shortcut->str_map_size = 0;
218   }
219 
220   if ((rcpath = bl_get_sys_rc_path(key_file))) {
221     read_conf(shortcut, rcpath);
222     free(rcpath);
223   }
224 
225   if ((rcpath = bl_get_user_rc_path(key_file))) {
226     read_conf(shortcut, rcpath);
227     free(rcpath);
228   }
229 }
230 
ui_shortcut_final(ui_shortcut_t * shortcut)231 void ui_shortcut_final(ui_shortcut_t *shortcut) {
232   u_int count;
233 
234   for (count = 0; count < shortcut->str_map_size; count++) {
235     free(shortcut->str_map[count].str);
236   }
237 
238   free(shortcut->str_map);
239 }
240 
ui_shortcut_match(ui_shortcut_t * shortcut,ui_key_func_t func,KeySym ksym,u_int state)241 int ui_shortcut_match(ui_shortcut_t *shortcut, ui_key_func_t func, KeySym ksym, u_int state) {
242   if (shortcut->map[func].is_used == 0) {
243     return 0;
244   }
245 
246   if ('A' <= ksym && ksym <= 'Z') {
247     ksym += 0x20;
248   }
249 
250   /* ingoring except these masks */
251   state &= (ModMask | ControlMask | ShiftMask | CommandMask | button_mask);
252 
253   if (state & button_mask) {
254     state &= ~Mod2Mask;  /* XXX NumLock */
255   }
256 
257   if (shortcut->map[func].ksym == ksym &&
258       shortcut->map[func].state ==
259           (state |
260            ((state & ModMask) && (shortcut->map[func].state & ModMask) == ModMask ? ModMask : 0))) {
261     return 1;
262   } else {
263     return 0;
264   }
265 }
266 
ui_shortcut_str(ui_shortcut_t * shortcut,KeySym ksym,u_int state)267 char *ui_shortcut_str(ui_shortcut_t *shortcut, KeySym ksym, u_int state) {
268   u_int count;
269 
270   if ('A' <= ksym && ksym <= 'Z') {
271     ksym += 0x20;
272   }
273 
274   /* ingoring except these masks */
275   state &= (ModMask | ControlMask | ShiftMask | CommandMask | button_mask);
276 
277   if (state & button_mask) {
278     state &= ~Mod2Mask;  /* XXX NumLock */
279   }
280 
281   for (count = 0; count < shortcut->str_map_size; count++) {
282     if (shortcut->str_map[count].ksym == ksym &&
283         shortcut->str_map[count].state ==
284             (state |
285              ((state & ModMask) && (shortcut->str_map[count].state & ModMask) == ModMask ? ModMask
286                                                                                          : 0))) {
287       return shortcut->str_map[count].str;
288     }
289   }
290 
291   return NULL;
292 }
293 
ui_shortcut_parse(ui_shortcut_t * shortcut,char * key,char * oper)294 int ui_shortcut_parse(ui_shortcut_t *shortcut, char *key, char *oper) {
295   char *p;
296   KeySym ksym;
297   u_int state;
298   int count;
299 
300   if (strcmp(key, "UNUSED") == 0) {
301     goto replace_shortcut_map;
302   }
303 
304   state = 0;
305 
306   while ((p = strchr(key, '+')) != NULL) {
307     *(p++) = '\0';
308 
309     if (strcmp(key, "Control") == 0) {
310       state |= ControlMask;
311     } else if (strcmp(key, "Shift") == 0) {
312       state |= ShiftMask;
313     } else if (strcmp(key, "Mod") == 0 || strcmp(key, "Alt") == 0) {
314       state |= ModMask;
315     } else if (strncmp(key, "Mod", 3) == 0) {
316       switch (key[3]) {
317         case 0:
318           state |= ModMask;
319           break;
320         case '1':
321           state |= Mod1Mask;
322           break;
323         case '2':
324           state |= Mod2Mask;
325           break;
326         case '3':
327           state |= Mod3Mask;
328           break;
329         case '4':
330           state |= Mod4Mask;
331           break;
332         case '5':
333           state |= Mod5Mask;
334           break;
335 #ifdef DEBUG
336         default:
337           bl_warn_printf(BL_DEBUG_TAG " unrecognized Mod mask(%s)\n", key);
338           break;
339 #endif
340       }
341     } else if (strcmp(key, "Command") == 0) {
342       state |= CommandMask;
343     }
344 #ifdef DEBUG
345     else {
346       bl_warn_printf(BL_DEBUG_TAG " unrecognized mask(%s)\n", key);
347     }
348 #endif
349 
350     key = p;
351   }
352 
353   if (strncmp(key, "Button", 6) == 0) {
354     state |= (Button1Mask << (key[6] - '1'));
355     ksym = 0;
356   } else if ((ksym = XStringToKeysym(key)) != NoSymbol) {
357     if ('A' <= ksym && ksym <= 'Z') {
358       ksym += 0x20;
359     }
360   } else {
361     return 0;
362   }
363 
364   for (count = 0; count < sizeof(key_func_table) / sizeof(key_func_table_t); count++) {
365     ui_key_t *map_entry;
366 
367     map_entry = shortcut->map + key_func_table[count].func;
368     if (map_entry->ksym == ksym && map_entry->state == state) {
369       map_entry->is_used = 0;
370       break;
371     }
372   }
373 
374   for (count = 0; count < shortcut->str_map_size; count++) {
375     if (shortcut->str_map[count].ksym == ksym && shortcut->str_map[count].state == state) {
376       free(shortcut->str_map[count].str);
377       shortcut->str_map[count] = shortcut->str_map[--shortcut->str_map_size];
378       break;
379     }
380   }
381 
382   if (*oper == '"') {
383     char *str;
384     char *p;
385     ui_str_key_t *str_map;
386 
387     if (!(str = bl_str_unescape(++oper)) || !(p = strrchr(str, '\"')) ||
388         !(str_map =
389               realloc(shortcut->str_map, sizeof(ui_str_key_t) * (shortcut->str_map_size + 1)))) {
390       free(str);
391 
392       return 0;
393     }
394 
395     *p = '\0';
396     str_map[shortcut->str_map_size].ksym = ksym;
397     str_map[shortcut->str_map_size].state = state;
398     str_map[shortcut->str_map_size].str = str;
399 
400     shortcut->str_map_size++;
401     shortcut->str_map = str_map;
402   } else {
403   replace_shortcut_map:
404     for (count = 0; count < sizeof(key_func_table) / sizeof(key_func_table_t); count++) {
405       if (strcmp(oper, key_func_table[count].name) == 0) {
406         if (strcmp(key, "UNUSED") == 0) {
407           shortcut->map[key_func_table[count].func].is_used = 0;
408 
409           return 1;
410         } else {
411           shortcut->map[key_func_table[count].func].is_used = 1;
412           shortcut->map[key_func_table[count].func].ksym = ksym;
413           shortcut->map[key_func_table[count].func].state = state;
414 
415           goto success;
416         }
417       }
418     }
419 
420     return 0;
421   }
422 
423 success:
424   if (state & ButtonMask) {
425     int mask;
426 
427     for (mask = Button1Mask; mask <= Button7Mask; mask <<= 1) {
428       if (state & mask) {
429         button_mask |= mask;
430         break;
431       }
432     }
433   }
434 
435   return 1;
436 }
437