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