1 /*
2  * MOC - music on console
3  * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15 
16 #include <string.h>
17 #include <strings.h>
18 #include <assert.h>
19 
20 #ifdef HAVE_NCURSESW_H
21 # include <ncursesw/curses.h>
22 #elif HAVE_NCURSES_H
23 # include <ncurses.h>
24 #elif HAVE_CURSES_H
25 # include <curses.h>
26 #endif
27 
28 #include <stdio.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include <ctype.h>
32 
33 #define DEBUG
34 
35 #include "common.h"
36 #include "keys.h"
37 #include "interface.h"
38 #include "interface_elements.h"
39 #include "options.h"
40 #include "log.h"
41 #include "files.h"
42 
43 /* ^c version of c */
44 #ifndef CTRL
45 # define CTRL(c) ((c) & CTRL_KEY_CODE)
46 #endif
47 
48 struct command
49 {
50 	enum key_cmd cmd;	/* the command */
51 	char *name;		/* name of the command (in keymap file) */
52 	char *help;		/* help string for the command */
53 	enum key_context context; /* context - where the command isused */
54 	int keys[6];		/* array of keys ended with -1 */
55 	int default_keys;	/* number of default keys */
56 };
57 
58 /* Array of commands - each element is a list of keys for this command. */
59 static struct command commands[] = {
60 	{
61 		KEY_CMD_QUIT_CLIENT,
62 		"quit_client",
63 		"Detach MOC from the server",
64 		CON_MENU,
65 		{ 'q', -1 },
66 		1
67 	},
68 	{
69 		KEY_CMD_GO,
70 		"go",
71 		"Start playing at this file or go to this directory",
72 		CON_MENU,
73 		{ '\n',	-1 },
74 		1
75 	},
76 	{
77 		KEY_CMD_MENU_DOWN,
78 		"menu_down",
79 		"Move down in the menu",
80 		CON_MENU,
81 		{ KEY_DOWN, -1 },
82 		1
83 	},
84 	{
85 		KEY_CMD_MENU_UP,
86 		"menu_up",
87 		"Move up in the menu",
88 		CON_MENU,
89 		{ KEY_UP, -1 },
90 		1
91 	},
92 	{
93 		KEY_CMD_MENU_NPAGE,
94 		"menu_page_down",
95 		"Move one page down",
96 		CON_MENU,
97 		{ KEY_NPAGE, -1},
98 		1
99 	},
100 	{
101 		KEY_CMD_MENU_PPAGE,
102 		"menu_page_up",
103 		"Move one page up",
104 		CON_MENU,
105 		{ KEY_PPAGE, -1},
106 		1
107 	},
108 	{
109 		KEY_CMD_MENU_FIRST,
110 		"menu_first_item",
111 		"Move to the first item in the menu",
112 		CON_MENU,
113 		{ KEY_HOME, -1 },
114 		1
115 	},
116 	{
117 		KEY_CMD_MENU_LAST,
118 		"menu_last_item",
119 		"Move to the last item in the menu",
120 		CON_MENU,
121 		{ KEY_END, -1 },
122 		1
123 	},
124 	{
125 		KEY_CMD_QUIT,
126 		"quit",
127 		"Quit",
128 		CON_MENU,
129 		{ 'Q', -1 },
130 		1
131 	},
132 	{
133 		KEY_CMD_STOP,
134 		"stop",
135 		"Stop",
136 		CON_MENU,
137 		{ 's', -1 },
138 		1
139 	},
140 	{
141 		KEY_CMD_NEXT,
142 		"next",
143 		"Play next file",
144 		CON_MENU,
145 		{ 'n', -1 },
146 		1
147 	},
148 	{
149 		KEY_CMD_PREVIOUS,
150 		"previous",
151 		"Play previous file",
152 		CON_MENU,
153 		{ 'b', -1 },
154 		1
155 	},
156 	{
157 		KEY_CMD_PAUSE,
158 		"pause",
159 		"Pause",
160 		CON_MENU,
161 		{ 'p', ' ', -1 },
162 		2
163 	},
164 	{
165 		KEY_CMD_TOGGLE_READ_TAGS,
166 		"toggle_read_tags",
167 		"Toggle ReadTags option",
168 		CON_MENU,
169 		{ 'f', -1 },
170 		1
171 	},
172 	{
173 		KEY_CMD_TOGGLE_SHUFFLE,
174 		"toggle_shuffle",
175 		"Toggle Shuffle",
176 		CON_MENU,
177 		{ 'S', -1 },
178 		1
179 	},
180 	{
181 		KEY_CMD_TOGGLE_REPEAT,
182 		"toggle_repeat",
183 		"Toggle Repeat",
184 		CON_MENU,
185 		{ 'R', -1 },
186 		1
187 	},
188 	{
189 		KEY_CMD_TOGGLE_AUTO_NEXT,
190 		"toggle_auto_next",
191 		"Toggle AutoNext",
192 		CON_MENU,
193 		{ 'X', -1 },
194 		1
195 	},
196 	{
197 		KEY_CMD_TOGGLE_MENU,
198 		"toggle_menu",
199 		"Switch between playlist and file list",
200 		CON_MENU,
201 		{ '\t', -1 },
202 		1
203 	},
204 	{
205 		KEY_CMD_TOGGLE_LAYOUT,
206 		"toggle_layout",
207 		"Switch between layouts",
208 		CON_MENU,
209 		{ 'l', -1 },
210 		1
211 	},
212 	{
213 		KEY_CMD_TOGGLE_PERCENT,
214 		"toggle_percent",
215 		"Switch on/off play time percentage",
216 		CON_MENU,
217 		{ -1 },
218 		0
219 	},
220 	{
221 		KEY_CMD_PLIST_ADD_FILE,
222 		"add_file",
223 		"Add a file/directory to the playlist",
224 		CON_MENU,
225 		{ 'a', -1 },
226 		1
227 	},
228 	{
229 		KEY_CMD_PLIST_CLEAR,
230 		"clear_playlist",
231 		"Clear the playlist",
232 		CON_MENU,
233 		{ 'C', -1 },
234 		1
235 	},
236 	{
237 		KEY_CMD_PLIST_ADD_DIR,
238 		"add_directory",
239 		"Add a directory recursively to the playlist",
240 		CON_MENU,
241 		{ 'A', -1 },
242 		1
243 	},
244 	{
245 		KEY_CMD_PLIST_REMOVE_DEAD_ENTRIES,
246 		"remove_dead_entries",
247 		"Remove playlist entries for non-existent files",
248 		CON_MENU,
249 		{ 'Y', -1 },
250 		1
251 	},
252 	{
253 		KEY_CMD_MIXER_DEC_1,
254 		"volume_down_1",
255 		"Decrease volume by 1%",
256 		CON_MENU,
257 		{ '<', -1 },
258 		1
259 	},
260 	{
261 		KEY_CMD_MIXER_INC_1,
262 		"volume_up_1",
263 		"Increase volume by 1%",
264 		CON_MENU,
265 		{ '>', -1 },
266 		1
267 	},
268 	{
269 		KEY_CMD_MIXER_DEC_5,
270 		"volume_down_5",
271 		"Decrease volume by 5%",
272 		CON_MENU,
273 		{ ',', -1 },
274 		1
275 	},
276 	{
277 		KEY_CMD_MIXER_INC_5,
278 		"volume_up_5",
279 		"Increase volume by 5%",
280 		CON_MENU,
281 		{ '.', -1 },
282 		1
283 	},
284 	{
285 		KEY_CMD_SEEK_FORWARD,
286 		"seek_forward",
287 		"Seek forward by n-s",
288 		CON_MENU,
289 		{ KEY_RIGHT, -1 },
290 		1
291 	},
292 	{
293 		KEY_CMD_SEEK_BACKWARD,
294 		"seek_backward",
295 		"Seek backward by n-s",
296 		CON_MENU,
297 		{ KEY_LEFT, -1},
298 		1
299 	},
300 	{
301 		KEY_CMD_HELP,
302 		"help",
303 		"Show the help screen",
304 		CON_MENU,
305 		{ 'h', '?', -1 },
306 		2
307 	},
308 	{
309 		KEY_CMD_HIDE_MESSAGE,
310 		"hide_message",
311 		"Hide error/informative message",
312 		CON_MENU,
313 		{ 'M', -1 },
314 		1
315 	},
316 	{
317 		KEY_CMD_REFRESH,
318 		"refresh",
319 		"Refresh the screen",
320 		CON_MENU,
321 		{ CTRL('r'), CTRL('l'), -1},
322 		2
323 	},
324 	{
325 		KEY_CMD_RELOAD,
326 		"reload",
327 		"Reread directory content",
328 		CON_MENU,
329 		{ 'r', -1 },
330 		1
331 	},
332 	{
333 		KEY_CMD_TOGGLE_SHOW_HIDDEN_FILES,
334 		"toggle_hidden_files",
335 		"Toggle ShowHiddenFiles option",
336 		CON_MENU,
337 		{ 'H', -1 },
338 		1
339 	},
340 	{
341 		KEY_CMD_GO_MUSIC_DIR,
342 		"go_to_music_directory",
343 		"Go to the music directory (requires config option)",
344 		CON_MENU,
345 		{ 'm', -1 },
346 		1
347 	},
348 	{
349 		KEY_CMD_PLIST_DEL,
350 		"delete_from_playlist",
351 		"Delete an item from the playlist",
352 		CON_MENU,
353 		{ 'd', -1 },
354 		1
355 	},
356 	{
357 		KEY_CMD_MENU_SEARCH,
358 		"search_menu",
359 		"Search the menu",
360 		CON_MENU,
361 		{ 'g', '/', -1 },
362 		2
363 	},
364 	{
365 		KEY_CMD_PLIST_SAVE,
366 		"save_playlist",
367 		"Save the playlist",
368 		CON_MENU,
369 		{ 'V', -1 },
370 		1
371 	},
372 	{
373 		KEY_CMD_TOGGLE_SHOW_TIME,
374 		"toggle_show_time",
375 		"Toggle ShowTime option",
376 		CON_MENU,
377 		{ CTRL('t'), -1},
378 		1
379 	},
380 	{
381 		KEY_CMD_TOGGLE_SHOW_FORMAT,
382 		"toggle_show_format",
383 		"Toggle ShowFormat option",
384 		CON_MENU,
385 		{ CTRL('f'), -1 },
386 		1
387 	},
388 	{
389 		KEY_CMD_GO_URL,
390 		"go_url",
391 		"Play from the URL",
392 		CON_MENU,
393 		{ 'o', -1 },
394 		1
395 	},
396 	{
397 		KEY_CMD_GO_TO_PLAYING_FILE,
398 		"go_to_playing_file",
399 		"Go to the currently playing file's directory",
400 		CON_MENU,
401 		{ 'G', -1 },
402 		1
403 	},
404 	{
405 		KEY_CMD_GO_DIR,
406 		"go_to_a_directory",
407 		"Go to a directory",
408 		CON_MENU,
409 		{ 'i', -1 },
410 		1
411 	},
412 	{
413 		KEY_CMD_GO_DIR_UP,
414 		"go_up",
415 		"Go to '..'",
416 		CON_MENU,
417 		{ 'U', -1 },
418 		1
419 	},
420 	{
421 		KEY_CMD_NEXT_SEARCH,
422 		"next_search",
423 		"Find the next matching item",
424 		CON_ENTRY_SEARCH,
425 		{ CTRL('g'), CTRL('n'), -1 },
426 		2
427 	},
428 	{
429 		KEY_CMD_CANCEL,
430 		"cancel",
431 		"Exit from an entry",
432 		CON_ENTRY,
433 		{ CTRL('x'), KEY_ESCAPE, -1 },
434 		2
435 	},
436 	{
437 		KEY_CMD_SEEK_FORWARD_5,
438 		"seek_forward_fast",
439 		"Silent seek forward by 5s",
440 		CON_MENU,
441 		{ ']', -1 },
442 		1
443 	},
444 	{
445 		KEY_CMD_SEEK_BACKWARD_5,
446 		"seek_backward_fast",
447 		"Silent seek backward by 5s",
448 		CON_MENU,
449 		{ '[', -1 },
450 		1
451 	},
452 	{
453 		KEY_CMD_VOLUME_10,
454 		"volume_10",
455 		"Set volume to 10%",
456 		CON_MENU,
457 		{ '1' | META_KEY_FLAG, -1 },
458 		1
459 	},
460 	{
461 		KEY_CMD_VOLUME_20,
462 		"volume_20",
463 		"Set volume to 20%",
464 		CON_MENU,
465 		{ '2' | META_KEY_FLAG, -1 },
466 		1
467 	},
468 	{
469 		KEY_CMD_VOLUME_30,
470 		"volume_30",
471 		"Set volume to 30%",
472 		CON_MENU,
473 		{ '3' | META_KEY_FLAG, -1 },
474 		1
475 	},
476 	{
477 		KEY_CMD_VOLUME_40,
478 		"volume_40",
479 		"Set volume to 40%",
480 		CON_MENU,
481 		{ '4' | META_KEY_FLAG, -1 },
482 		1
483 	},
484 	{
485 		KEY_CMD_VOLUME_50,
486 		"volume_50",
487 		"Set volume to 50%",
488 		CON_MENU,
489 		{ '5' | META_KEY_FLAG, -1 },
490 		1
491 	},
492 	{
493 		KEY_CMD_VOLUME_60,
494 		"volume_60",
495 		"Set volume to 60%",
496 		CON_MENU,
497 		{ '6' | META_KEY_FLAG, -1 },
498 		1
499 	},
500 	{
501 		KEY_CMD_VOLUME_70,
502 		"volume_70",
503 		"Set volume to 70%",
504 		CON_MENU,
505 		{ '7' | META_KEY_FLAG, -1 },
506 		1
507 	},
508 	{
509 		KEY_CMD_VOLUME_80,
510 		"volume_80",
511 		"Set volume to 80%",
512 		CON_MENU,
513 		{ '8' | META_KEY_FLAG, -1 },
514 		1
515 	},
516 	{
517 		KEY_CMD_VOLUME_90,
518 		"volume_90",
519 		"Set volume to 90%",
520 		CON_MENU,
521 		{ '9' | META_KEY_FLAG, -1 },
522 		1
523 	},
524  	{
525  		KEY_CMD_MARK_START,
526  		"mark_start",
527  		"Mark the start of a block",
528  		CON_MENU,
529  		{ '\'', -1 },
530  		1
531  	},
532  	{
533  		KEY_CMD_MARK_END,
534  		"mark_end",
535  		"Mark the end of a block",
536  		CON_MENU,
537  		{ '\"', -1 },
538  		1
539  	},
540  	{
541  		KEY_CMD_FAST_DIR_1,
542  		"go_to_fast_dir1",
543  		"Go to a fast dir 1",
544  		CON_MENU,
545  		{ '!', -1 },
546  		1
547  	},
548  	{
549  		KEY_CMD_FAST_DIR_2,
550  		"go_to_fast_dir2",
551  		"Go to a fast dir 2",
552  		CON_MENU,
553  		{ '@', -1 },
554  		1
555  	},
556  	{
557  		KEY_CMD_FAST_DIR_3,
558  		"go_to_fast_dir3",
559  		"Go to a fast dir 3",
560  		CON_MENU,
561  		{ '#', -1 },
562  		1
563  	},
564  	{
565  		KEY_CMD_FAST_DIR_4,
566  		"go_to_fast_dir4",
567  		"Go to a fast dir 4",
568  		CON_MENU,
569  		{ '$', -1 },
570  		1
571  	},
572  	{
573  		KEY_CMD_FAST_DIR_5,
574  		"go_to_fast_dir5",
575  		"Go to a fast dir 5",
576  		CON_MENU,
577  		{ '%', -1 },
578  		1
579  	},
580  	{
581  		KEY_CMD_FAST_DIR_6,
582  		"go_to_fast_dir6",
583  		"Go to a fast dir 6",
584  		CON_MENU,
585  		{ '^', -1 },
586  		1
587  	},
588  	{
589  		KEY_CMD_FAST_DIR_7,
590  		"go_to_fast_dir7",
591  		"Go to a fast dir 7",
592  		CON_MENU,
593  		{ '&', -1 },
594  		1
595  	},
596  	{
597  		KEY_CMD_FAST_DIR_8,
598  		"go_to_fast_dir8",
599  		"Go to a fast dir 8",
600  		CON_MENU,
601  		{ '*', -1 },
602  		1
603  	},
604  	{
605  		KEY_CMD_FAST_DIR_9,
606  		"go_to_fast_dir9",
607  		"Go to a fast dir 9",
608  		CON_MENU,
609  		{ '(', -1 },
610  		1
611  	},
612  	{
613  		KEY_CMD_FAST_DIR_10,
614  		"go_to_fast_dir10",
615  		"Go to a fast dir 10",
616  		CON_MENU,
617  		{ ')', -1 },
618  		1
619   	},
620  	{
621  		KEY_CMD_HISTORY_UP,
622  		"history_up",
623  		"Go to the previous entry in the history (entry)",
624  		CON_ENTRY,
625  		{ KEY_UP, -1 },
626  		1
627  	},
628  	{
629  		KEY_CMD_HISTORY_DOWN,
630  		"history_down",
631  		"Go to the next entry in the history (entry)",
632  		CON_ENTRY,
633  		{ KEY_DOWN, -1 },
634  		1
635   	},
636  	{
637  		KEY_CMD_DELETE_START,
638  		"delete_to_start",
639  		"Delete to start of line (entry)",
640  		CON_ENTRY,
641  		{ CTRL('u'), -1 },
642  		1
643   	},
644  	{
645  		KEY_CMD_DELETE_END,
646  		"delete_to_end",
647  		"Delete to end of line (entry)",
648  		CON_ENTRY,
649  		{ CTRL('k'), -1 },
650  		1
651   	},
652  	{
653  		KEY_CMD_TOGGLE_MIXER,
654  		"toggle_mixer",
655  		"Toggles the mixer channel",
656  		CON_MENU,
657  		{ 'x', -1 },
658  		1
659   	},
660  	{
661  		KEY_CMD_TOGGLE_SOFTMIXER,
662  		"toggle_softmixer",
663  		"Toggles the software-mixer",
664  		CON_MENU,
665  		{ 'w', -1 },
666  		1
667   	},
668  	{
669  		KEY_CMD_TOGGLE_EQUALIZER,
670  		"toggle_equalizer",
671  		"Toggles the equalizer",
672  		CON_MENU,
673  		{ 'E', -1 },
674  		1
675   	},
676  	{
677  		KEY_CMD_EQUALIZER_REFRESH,
678  		"equalizer_refresh",
679  		"Reload EQ-presets",
680  		CON_MENU,
681  		{ 'e', -1 },
682  		1
683   	},
684  	{
685  		KEY_CMD_EQUALIZER_PREV,
686  		"equalizer_prev",
687  		"Select previous equalizer-preset",
688  		CON_MENU,
689  		{ 'K', -1 },
690  		1
691   	},
692  	{
693  		KEY_CMD_EQUALIZER_NEXT,
694  		"equalizer_next",
695  		"Select next equalizer-preset",
696  		CON_MENU,
697  		{ 'k', -1 },
698  		1
699   	},
700  	{
701  		KEY_CMD_TOGGLE_MAKE_MONO,
702  		"toggle_make_mono",
703  		"Toggle mono-mixing (when softmixer enabled)",
704  		CON_MENU,
705  		{ 'J', -1 },
706  		1
707   	},
708  	{
709  		KEY_CMD_PLIST_MOVE_UP,
710  		"plist_move_up",
711  		"Move playlist item up",
712  		CON_MENU,
713  		{ 'u', -1 },
714  		1
715   	},
716  	{
717  		KEY_CMD_PLIST_MOVE_DOWN,
718  		"plist_move_down",
719  		"Move playlist item down",
720  		CON_MENU,
721  		{ 'j', -1 },
722  		1
723   	},
724  	{
725  		KEY_CMD_ADD_STREAM,
726  		"plist_add_stream",
727  		"Add a URL to the playlist using entry",
728  		CON_MENU,
729  		{ CTRL('U'), -1 },
730  		1
731   	},
732  	{
733  		KEY_CMD_THEME_MENU,
734  		"theme_menu",
735  		"Switch to the theme selection menu",
736  		CON_MENU,
737  		{ 'T', -1 },
738  		1
739   	},
740  	{
741  		KEY_CMD_EXEC1,
742  		"exec_command1",
743  		"Execute ExecCommand1",
744  		CON_MENU,
745  		{ KEY_F(1), -1 },
746  		1
747   	},
748  	{
749  		KEY_CMD_EXEC2,
750  		"exec_command2",
751  		"Execute ExecCommand2",
752  		CON_MENU,
753  		{ KEY_F(2), -1 },
754  		1
755   	},
756  	{
757  		KEY_CMD_EXEC3,
758  		"exec_command3",
759  		"Execute ExecCommand3",
760  		CON_MENU,
761  		{ KEY_F(3), -1 },
762  		1
763   	},
764  	{
765  		KEY_CMD_EXEC4,
766  		"exec_command4",
767  		"Execute ExecCommand4",
768  		CON_MENU,
769  		{ KEY_F(4), -1 },
770  		1
771   	},
772  	{
773  		KEY_CMD_EXEC5,
774  		"exec_command5",
775  		"Execute ExecCommand5",
776  		CON_MENU,
777  		{ KEY_F(5), -1 },
778  		1
779   	},
780  	{
781  		KEY_CMD_EXEC6,
782  		"exec_command6",
783  		"Execute ExecCommand6",
784  		CON_MENU,
785  		{ KEY_F(6), -1 },
786  		1
787   	},
788  	{
789  		KEY_CMD_EXEC7,
790  		"exec_command7",
791  		"Execute ExecCommand7",
792  		CON_MENU,
793  		{ KEY_F(7), -1 },
794  		1
795   	},
796  	{
797  		KEY_CMD_EXEC8,
798  		"exec_command8",
799  		"Execute ExecCommand8",
800  		CON_MENU,
801  		{ KEY_F(8), -1 },
802  		1
803   	},
804  	{
805  		KEY_CMD_EXEC9,
806  		"exec_command9",
807  		"Execute ExecCommand9",
808  		CON_MENU,
809  		{ KEY_F(9), -1 },
810  		1
811   	},
812  	{
813  		KEY_CMD_EXEC10,
814  		"exec_command10",
815  		"Execute ExecCommand10",
816  		CON_MENU,
817  		{ KEY_F(10), -1 },
818  		1
819   	},
820 	{
821 		KEY_CMD_LYRICS,
822 		"show_lyrics",
823 		"Display lyrics of the current song (if available)",
824 		CON_MENU,
825 		{ 'L',	-1 },
826 		1
827 	},
828  	{
829  		KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS,
830  		"playlist_full_paths",
831  		"Toggle displaying full paths in the playlist",
832  		CON_MENU,
833  		{ 'P', -1 },
834  		1
835   	},
836 	{
837 		KEY_CMD_QUEUE_TOGGLE_FILE,
838 		"enqueue_file",
839 		"Add (or remove) a file to (from) queue",
840 		CON_MENU,
841 		{ 'z', -1 },
842 		1
843 	},
844 	{
845 		KEY_CMD_QUEUE_CLEAR,
846 		"clear_queue",
847 		"Clear the queue",
848 		CON_MENU,
849 		{ 'Z', -1 },
850 		1
851 	}
852 };
853 
854 static struct special_keys
855 {
856 	char *name;
857 	int key;
858 } special_keys[] = {
859 	{ "DOWN",		KEY_DOWN },
860 	{ "UP",			KEY_UP },
861 	{ "LEFT",		KEY_LEFT },
862 	{ "RIGHT",		KEY_RIGHT },
863 	{ "HOME",		KEY_HOME },
864 	{ "BACKSPACE",		KEY_BACKSPACE },
865 	{ "DEL",		KEY_DC },
866 	{ "INS",		KEY_IC },
867 	{ "ENTER",		'\n' },
868 	{ "PAGE_UP",		KEY_PPAGE },
869 	{ "PAGE_DOWN",		KEY_NPAGE },
870 	{ "TAB",		'\t' },
871 	{ "END",		KEY_END },
872 	{ "KEYPAD_CENTER",	KEY_B2 },
873 	{ "SPACE",		' ' },
874 	{ "ESCAPE",		KEY_ESCAPE },
875 	{ "F1",			KEY_F(1) },
876 	{ "F2",			KEY_F(2) },
877 	{ "F3",			KEY_F(3) },
878 	{ "F4",			KEY_F(4) },
879 	{ "F5",			KEY_F(5) },
880 	{ "F6",			KEY_F(6) },
881 	{ "F7",			KEY_F(7) },
882 	{ "F8",			KEY_F(8) },
883 	{ "F9",			KEY_F(9) },
884 	{ "F10",		KEY_F(10) },
885 	{ "F11",		KEY_F(11) },
886 	{ "F12",		KEY_F(12) }
887 };
888 
889 #define COMMANDS_NUM		(ARRAY_SIZE(commands))
890 #define SPECIAL_KEYS_NUM	(ARRAY_SIZE(special_keys))
891 
892 /* Number of chars from the left where the help message starts
893  * (skipping the key list). */
894 #define HELP_INDENT	15
895 
896 static char *help[COMMANDS_NUM];
897 
get_key_cmd(const enum key_context context,const struct iface_key * key)898 enum key_cmd get_key_cmd (const enum key_context context,
899                           const struct iface_key *key)
900 {
901 	int k;
902 	size_t i;
903 
904 	k = (key->type == IFACE_KEY_CHAR) ? key->key.ucs : key->key.func;
905 
906 	for (i = 0; i < COMMANDS_NUM; i += 1) {
907 		if (commands[i].context == context) {
908 			int j = 0;
909 
910 			while (commands[i].keys[j] != -1) {
911 				if (commands[i].keys[j++] == k)
912 					return commands[i].cmd;
913 			}
914 		}
915 	}
916 
917 	return KEY_CMD_WRONG;
918 }
919 
920 /* Return the path to the keymap file or NULL if none was specified. */
find_keymap_file()921 static char *find_keymap_file ()
922 {
923 	char *file;
924 	static char path[PATH_MAX];
925 
926 	if ((file = options_get_str("Keymap"))) {
927 		if (file[0] == '/') {
928 
929 			/* Absolute path */
930 			strncpy (path, file, sizeof(path));
931 			if (path[sizeof(path)-1])
932 				fatal ("Keymap path too long!");
933 			return path;
934 		}
935 
936 		strncpy (path, create_file_name(file), sizeof(path));
937 		if (path[sizeof(path)-1])
938 			fatal ("Keymap path too long!");
939 
940 		return path;
941 	}
942 
943 	return NULL;
944 }
945 
keymap_parse_error(const int line,const char * msg)946 static void keymap_parse_error (const int line, const char *msg)
947 {
948 	fatal ("Parse error in the keymap file line %d: %s", line, msg);
949 }
950 
951 /* Return a key for the symbolic key name (^c, M-F, etc.).
952  * Return -1 on error. */
parse_key(const char * symbol)953 static int parse_key (const char *symbol)
954 {
955 	size_t i;
956 
957 	if (strlen(symbol) == 1) {
958 		/* Just a regular char */
959 		static bool digit_key_warned = false;
960 		if (!digit_key_warned && isdigit (symbol[0])) {
961 			fprintf (stderr,
962 			         "\n\tUsing digits as keys is deprecated as they may"
963 			         "\n\tbe used for specific purposes in release 2.6.\n");
964 			sleep (5);
965 			digit_key_warned = true;
966 		}
967 		return symbol[0];
968 	}
969 
970 	if (symbol[0] == '^') {
971 
972 		/* CTRL sequence */
973 		if (strlen(symbol) != 2)
974 			return -1;
975 
976 		return CTRL(symbol[1]);
977 	}
978 
979 	if (!strncasecmp(symbol, "M-", 2)) {
980 
981 		/* Meta char */
982 		if (strlen(symbol) != 3)
983 			return -1;
984 
985 		return symbol[2] | META_KEY_FLAG;
986 	}
987 
988 	/* Special keys. */
989 	for (i = 0; i < SPECIAL_KEYS_NUM; i += 1) {
990 		if (!strcasecmp(special_keys[i].name, symbol))
991 			return special_keys[i].key;
992 	}
993 
994 	return -1;
995 }
996 
997 /* Remove a single key from the default key definition for a command. */
clear_default_key(int key)998 static void clear_default_key (int key)
999 {
1000 	size_t cmd_ix;
1001 
1002 	for (cmd_ix = 0; cmd_ix < COMMANDS_NUM; cmd_ix += 1) {
1003 		int key_ix;
1004 
1005 		for (key_ix = 0; key_ix < commands[cmd_ix].default_keys; key_ix++) {
1006 			if (commands[cmd_ix].keys[key_ix] == key)
1007 				break;
1008 		}
1009 
1010 		if (key_ix == commands[cmd_ix].default_keys)
1011 				continue;
1012 
1013 		while (commands[cmd_ix].keys[key_ix] != -1) {
1014 			commands[cmd_ix].keys[key_ix] = commands[cmd_ix].keys[key_ix + 1];
1015 			key_ix += 1;
1016 		}
1017 
1018 		commands[cmd_ix].default_keys -= 1;
1019 
1020 		break;
1021 	}
1022 }
1023 
1024 /* Remove default keys definition for a command. Return 0 on error. */
clear_default_keys(size_t cmd_ix)1025 static void clear_default_keys (size_t cmd_ix)
1026 {
1027 	assert (cmd_ix < COMMANDS_NUM);
1028 
1029 	commands[cmd_ix].default_keys = 0;
1030 	commands[cmd_ix].keys[0] = -1;
1031 }
1032 
1033 /* Add a key to the command defined in the keymap file in line
1034  * line_num (used only when reporting an error). */
add_key(const int line_num,size_t cmd_ix,const char * key_symbol)1035 static void add_key (const int line_num, size_t cmd_ix, const char *key_symbol)
1036 {
1037 	int i, key;
1038 
1039 	assert (cmd_ix < COMMANDS_NUM);
1040 
1041 	key = parse_key (key_symbol);
1042 	if (key == -1)
1043 		keymap_parse_error (line_num, "bad key sequence");
1044 
1045 	clear_default_key (key);
1046 
1047 	for (i = commands[cmd_ix].default_keys;
1048 	     commands[cmd_ix].keys[i] != -1;
1049 	     i += 1) {
1050 		if (commands[cmd_ix].keys[i] == key)
1051 			return;
1052 	}
1053 
1054 	if (i == ARRAY_SIZE(commands[cmd_ix].keys) - 1)
1055 		keymap_parse_error (line_num, "too many keys defined");
1056 
1057 	commands[cmd_ix].keys[i] = key;
1058 	commands[cmd_ix].keys[i + 1] = -1;
1059 }
1060 
1061 /* Find command entry by command name; return COMMANDS_NUM if not found. */
find_command_name(const char * command)1062 static size_t find_command_name (const char *command)
1063 {
1064 	size_t result;
1065 
1066 	for (result = 0; result < COMMANDS_NUM; result += 1) {
1067 		if (!(strcasecmp(commands[result].name, command)))
1068 			break;
1069 	}
1070 
1071 	return result;
1072 }
1073 
1074 /* Load a key map from the file. */
load_key_map(const char * file_name)1075 static void load_key_map (const char *file_name)
1076 {
1077 	FILE *file;
1078 	char *line;
1079 	int line_num = 0;
1080 	size_t cmd_ix;
1081 
1082 	if (!(file = fopen(file_name, "r")))
1083 		fatal ("Can't open keymap file: %s", strerror(errno));
1084 
1085 	/* Read lines in format:
1086 	 * COMMAND = KEY [KEY ...]
1087 	 * Blank lines and beginning with # are ignored, see example_keymap. */
1088 	while ((line = read_line(file))) {
1089 		char *command, *tmp, *key;
1090 
1091 		line_num++;
1092 		if (line[0] == '#' || !(command = strtok(line, " \t"))) {
1093 
1094 			/* empty line or a comment */
1095 			free (line);
1096 			continue;
1097 		}
1098 
1099 		cmd_ix = find_command_name (command);
1100 		if (cmd_ix == COMMANDS_NUM)
1101 			keymap_parse_error (line_num, "unknown command");
1102 
1103 		tmp = strtok(NULL, " \t");
1104 		if (!tmp || (strcmp(tmp, "=") && strcmp(tmp, "+=")))
1105 			keymap_parse_error (line_num, "expected '=' or '+='");
1106 
1107 		if (strcmp(tmp, "+=")) {
1108 			if (commands[cmd_ix].keys[commands[cmd_ix].default_keys] != -1)
1109 				keymap_parse_error (line_num, "command previously bound");
1110 			clear_default_keys (cmd_ix);
1111 		}
1112 
1113 		while ((key = strtok(NULL, " \t")))
1114 			add_key (line_num, cmd_ix, key);
1115 
1116 		free (line);
1117 	}
1118 
1119 	fclose (file);
1120 }
1121 
1122 /* Get a nice key name.
1123  * Returned memory may be static. */
get_key_name(const int key)1124 static char *get_key_name (const int key)
1125 {
1126 	size_t i;
1127 	static char key_str[4];
1128 
1129 	/* Search for special keys */
1130 	for (i = 0; i < SPECIAL_KEYS_NUM; i += 1) {
1131 		if (special_keys[i].key == key)
1132 			return special_keys[i].name;
1133 	}
1134 
1135 	/* CTRL combination */
1136 	if (!(key & ~CTRL_KEY_CODE)) {
1137 		key_str[0] = '^';
1138 		key_str[1] = key + 0x60;
1139 		key_str[2] = 0;
1140 
1141 		return key_str;
1142 	}
1143 
1144 	/* Meta keys */
1145 	if (key & META_KEY_FLAG) {
1146 		strcpy (key_str, "M-");
1147 		key_str[2] = key & ~META_KEY_FLAG;
1148 		key_str[3] = 0;
1149 
1150 		return key_str;
1151 	}
1152 
1153 	/* Normal key */
1154 	key_str[0] = key;
1155 	key_str[1] = 0;
1156 
1157 	return key_str;
1158 }
1159 
1160 /* Check if keys for cmd1 and cmd2 are different, if not, issue an error. */
compare_keys(struct command * cmd1,struct command * cmd2)1161 static void compare_keys (struct command *cmd1, struct command *cmd2)
1162 {
1163 	int i = 0;
1164 
1165 	while (cmd1->keys[i] != -1) {
1166 		int j = 0;
1167 
1168 		while (cmd2->keys[j] != -1 && cmd2->keys[j] != cmd1->keys[i])
1169 			j++;
1170 		if (cmd2->keys[j] != -1)
1171 			fatal ("Key %s is defined for %s and %s!",
1172 					get_key_name(cmd2->keys[j]),
1173 					cmd1->name, cmd2->name);
1174 		i++;
1175 	}
1176 }
1177 
1178 /* Check that no key is defined more than once. */
check_keys()1179 static void check_keys ()
1180 {
1181 	size_t i, j;
1182 
1183 	for (i = 0; i < COMMANDS_NUM; i += 1) {
1184 		for (j = 0; j < COMMANDS_NUM; j += 1) {
1185 			if (j != i && commands[i].context == commands[j].context)
1186 				compare_keys (&commands[i], &commands[j]);
1187 		}
1188 	}
1189 }
1190 
1191 /* Return a string contains the list of keys used for command.
1192  * Returned memory is static. */
get_command_keys(const int idx)1193 static char *get_command_keys (const int idx)
1194 {
1195 	static char keys[64];
1196 	int i = 0;
1197 
1198 	keys[0] = 0;
1199 
1200 	while (commands[idx].keys[i] != -1) {
1201 		strncat (keys, get_key_name(commands[idx].keys[i]),
1202 				sizeof(keys) - strlen(keys) - 1);
1203 		strncat (keys, " ", sizeof(keys) - strlen(keys) - 1);
1204 		i++;
1205 	}
1206 
1207 	/* strip the last space */
1208 	if (keys[0] != 0)
1209 		keys[strlen (keys) - 1] = 0;
1210 
1211 	return keys;
1212 }
1213 
1214 /* Make the help message for keys. */
make_help()1215 static void make_help ()
1216 {
1217 	size_t i;
1218 	const char unassigned[] = " [unassigned]";
1219 
1220 	for (i = 0; i < COMMANDS_NUM; i += 1) {
1221 		size_t len;
1222 
1223 		len = HELP_INDENT + strlen (commands[i].help) + 1;
1224 		if (commands[i].keys[0] == -1)
1225 			len += strlen (unassigned);
1226 		help[i] = xcalloc (sizeof(char), len);
1227 		strncpy (help[i], get_command_keys(i), HELP_INDENT);
1228 		if (strlen(help[i]) < HELP_INDENT)
1229 			memset (help[i] + strlen(help[i]), ' ',
1230 					HELP_INDENT - strlen(help[i]));
1231 		strcpy (help[i] + HELP_INDENT, commands[i].help);
1232 		if (commands[i].keys[0] == -1)
1233 			strcat (help[i], unassigned);
1234 	}
1235 }
1236 
1237 /* Load key map. Set default keys if necessary. */
keys_init()1238 void keys_init ()
1239 {
1240 	char *file = find_keymap_file ();
1241 
1242 	if (file) {
1243 		load_key_map (file);
1244 		check_keys ();
1245 	}
1246 
1247 	make_help ();
1248 }
1249 
1250 /* Free the help message. */
keys_cleanup()1251 void keys_cleanup ()
1252 {
1253 	size_t i;
1254 
1255 	for (i = 0; i < COMMANDS_NUM; i += 1)
1256 		free (help[i]);
1257 }
1258 
1259 /* Return an array of strings with the keys help. The number of lines is put
1260  * in num. */
get_keys_help(int * num)1261 char **get_keys_help (int *num)
1262 {
1263 	*num = (int) COMMANDS_NUM;
1264 	return help;
1265 }
1266 
1267 /* Find command entry by key command; return COMMANDS_NUM if not found. */
find_command_cmd(const enum key_cmd cmd)1268 static size_t find_command_cmd (const enum key_cmd cmd)
1269 {
1270 	size_t result;
1271 
1272 	for (result = 0; result < COMMANDS_NUM; result += 1) {
1273 		if (commands[result].cmd == cmd)
1274 			break;
1275 	}
1276 
1277 	return result;
1278 }
1279 
1280 /* Return true iff the help key is still 'h'. */
is_help_still_h()1281 bool is_help_still_h ()
1282 {
1283 	size_t cmd_ix;
1284 
1285 	cmd_ix = find_command_cmd (KEY_CMD_HELP);
1286 
1287 	assert (cmd_ix < COMMANDS_NUM);
1288 
1289 	return commands[cmd_ix].keys[0] == 'h';
1290 }
1291