1 /**
2  * @file
3  * @brief Crude macro-capability
4 **/
5 
6 /*
7  * The macro-implementation works like this:
8  *   - getchm() works by reading characters from an internal
9  *     buffer. If none are available, new characters are read into
10  *     the buffer with _getch_mul().
11  *   - _getch_mul() reads at least one character, but will read more
12  *     if available (determined using kbhit(), which should be defined
13  *     in the platform specific libraries).
14  *   - Before adding the characters read into the buffer, any keybindings
15  *     in the sequence are replaced (see macro_add_buf_long for the
16  *     details).
17  *
18  * (When the above text mentions characters, it actually means int).
19  */
20 
21 #include "AppHdr.h"
22 
23 #include "macro.h"
24 
25 #include <cctype>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <deque>
29 #include <map>
30 #include <sstream>
31 #include <string>
32 #include <vector>
33 
34 #ifdef USE_TILE_LOCAL
35 #include <SDL.h>
36 #include <SDL_keycode.h>
37 #endif
38 
39 #include "cio.h"
40 #include "command.h"
41 #include "files.h"
42 #include "initfile.h"
43 #include "libutil.h"
44 #include "menu.h"
45 #include "message.h"
46 #include "misc.h" // erase_val
47 #include "options.h"
48 #include "output.h"
49 #include "prompt.h"
50 #include "state.h"
51 #include "state.h"
52 #include "stringutil.h"
53 #include "syscalls.h"
54 #include "unicode.h"
55 #include "version.h"
56 
57 typedef deque<int> keybuf;
58 typedef map<keyseq,keyseq> macromap;
59 
60 static macromap Keymaps[KMC_CONTEXT_COUNT];
61 static macromap Macros;
62 
63 static macromap *all_maps[] =
64 {
65     &Keymaps[KMC_DEFAULT],
66     &Keymaps[KMC_LEVELMAP],
67     &Keymaps[KMC_TARGETING],
68     &Keymaps[KMC_CONFIRM],
69 
70     &Macros,
71 };
72 
73 static keybuf Buffer;
74 static keybuf SendKeysBuffer;
75 
76 #define USERFUNCBASE -10000
77 static vector<string> userfunctions;
78 
79 static vector<key_recorder*> recorders;
80 
81 typedef map<string, int> name_to_cmd_map;
82 typedef map<int, string> cmd_to_name_map;
83 
84 struct command_name
85 {
86     command_type cmd;
87     const char*  name;
88 };
89 
90 static command_name _command_name_list[] =
91 {
92 #include "cmd-name.h"
93 };
94 
95 static name_to_cmd_map _names_to_cmds;
96 static cmd_to_name_map _cmds_to_names;
97 
98 struct default_binding
99 {
100     int           key;
101     command_type  cmd;
102 };
103 
104 static default_binding _default_binding_list[] =
105 {
106 #include "cmd-keys.h"
107 };
108 
109 typedef map<int, int> key_to_cmd_map;
110 typedef map<int, int> cmd_to_key_map;
111 
112 static key_to_cmd_map _keys_to_cmds[KMC_CONTEXT_COUNT];
113 static cmd_to_key_map _cmds_to_keys[KMC_CONTEXT_COUNT];
114 
userfunc_index(int key)115 static inline int userfunc_index(int key)
116 {
117     int index = (key <= USERFUNCBASE ? USERFUNCBASE - key : -1);
118     return index < 0 || index >= (int) userfunctions.size()? -1 : index;
119 }
120 
userfunc_index(const keyseq & seq)121 static int userfunc_index(const keyseq &seq)
122 {
123     if (seq.empty())
124         return -1;
125 
126     return userfunc_index(seq.front());
127 }
128 
is_userfunction(int key)129 bool is_userfunction(int key)
130 {
131     return userfunc_index(key) != -1;
132 }
133 
is_userfunction(const keyseq & seq)134 static bool is_userfunction(const keyseq &seq)
135 {
136     return userfunc_index(seq) != -1;
137 }
138 
get_userfunction(int key)139 string get_userfunction(int key)
140 {
141     int index = userfunc_index(key);
142     return index == -1 ? nullptr : userfunctions[index];
143 }
144 
get_userfunction(const keyseq & seq)145 static string get_userfunction(const keyseq &seq)
146 {
147     int index = userfunc_index(seq);
148     return index == -1 ? nullptr : userfunctions[index];
149 }
150 
userfunc_referenced(int index,const macromap & mm)151 static bool userfunc_referenced(int index, const macromap &mm)
152 {
153     for (const auto &entry : mm)
154         if (userfunc_index(entry.second) == index)
155             return true;
156 
157     return false;
158 }
159 
userfunc_referenced(int index)160 static bool userfunc_referenced(int index)
161 {
162     for (auto m : all_maps)
163         if (userfunc_referenced(index, *m))
164             return true;
165 
166     return false;
167 }
168 
169 // Expensive function to discard unused function names
userfunc_collectgarbage()170 static void userfunc_collectgarbage()
171 {
172     for (int i = userfunctions.size() - 1; i >= 0; --i)
173     {
174         if (!userfunctions.empty() && !userfunc_referenced(i))
175             userfunctions[i].clear();
176     }
177 }
178 
userfunc_getindex(const string & fname)179 static int userfunc_getindex(const string &fname)
180 {
181     if (fname.length() == 0)
182         return -1;
183 
184     userfunc_collectgarbage();
185 
186     // Pass 1 to see if we have this string already
187     for (int i = 0, count = userfunctions.size(); i < count; ++i)
188     {
189         if (userfunctions[i] == fname)
190             return i;
191     }
192 
193     // Pass 2 to hunt for gaps.
194     for (int i = 0, count = userfunctions.size(); i < count; ++i)
195     {
196         if (userfunctions[i].empty())
197         {
198             userfunctions[i] = fname;
199             return i;
200         }
201     }
202 
203     userfunctions.push_back(fname);
204     return userfunctions.size() - 1;
205 }
206 
207 // Returns the name of the file that contains macros.
get_macro_file()208 static string get_macro_file()
209 {
210     string dir = !Options.macro_dir.empty() ? Options.macro_dir :
211                  !SysEnv.crawl_dir.empty()  ? SysEnv.crawl_dir : "";
212 
213     if (!dir.empty())
214     {
215 #ifndef DGL_MACRO_ABSOLUTE_PATH
216         if (dir[dir.length() - 1] != FILE_SEPARATOR)
217             dir += FILE_SEPARATOR;
218 #endif
219     }
220 
221 #if defined(DGL_MACRO_ABSOLUTE_PATH)
222     return dir.empty()? "macro.txt" : dir;
223 #endif
224 
225     check_mkdir("Macro directory", &dir, true);
226 
227 #if defined(DGL_NAMED_MACRO_FILE)
228     return dir + strip_filename_unsafe_chars(you.your_name) + "-macro.txt";
229 #else
230     return dir + "macro.txt";
231 #endif
232 }
233 
buf2keyseq(const char * buff,keyseq & k)234 static void buf2keyseq(const char *buff, keyseq &k)
235 {
236     // Sanity check
237     if (!buff)
238         return;
239 
240     // convert c_str to keyseq
241 
242     // Check for user functions
243     if (*buff == '=' && buff[1] == '=' && buff[2] == '=' && buff[3])
244     {
245         int index = userfunc_getindex(buff + 3);
246         if (index != -1)
247             k.push_back(USERFUNCBASE - index);
248     }
249     else
250     {
251         const int len = strlen(buff);
252         for (int i = 0; i < len; i++)
253             k.push_back(buff[i]);
254     }
255 }
256 
function_keycode_fixup(int keycode)257 int function_keycode_fixup(int keycode)
258 {
259     // is this harmless on windows console?
260 #if !defined(USE_TILE_LOCAL) && TAG_MAJOR_VERSION == 34
261     // For many years, dcss has (accidentally, it seems) used these keycodes
262     // for function keys, because of a patch from 2009 that mapped some common
263     // terminal escape codes for F1-F4 to 1011-1014 under the belief (??) that
264     // these were used for some numpad keys. In webtiles code, these keycodes
265     // are even hardcoded in to the non-versioned part of the js code, so it's
266     // extremely hard to change. So we do this somewhat horrible fixup to deal
267     // with the complicated history. TODO: remove some day
268     switch (keycode)
269     {
270     case -1011: return CK_F1;
271     case -1012: return CK_F2;
272     case -1013: return CK_F3;
273     case -1014: return CK_F4;
274     case -1015: return CK_F5;
275     case -1016: return CK_F6;
276     case -1017: return CK_F7;
277     case -1018: return CK_F8;
278     case -1019: return CK_F9;
279     case -1020: return CK_F10;
280     default:
281         return keycode;
282     }
283 #else
284     return keycode;
285 #endif
286 }
287 
read_key_code(string s)288 static int read_key_code(string s)
289 {
290     if (s.empty())
291         return 0;
292 
293     int base = 10;
294     if (s[0] == 'x')
295     {
296         s = s.substr(1);
297         base = 16;
298     }
299     else if (s[0] == '^')
300     {
301         // ^A = 1, etc.
302         return 1 + toupper_safe(s[1]) - 'A';
303     }
304 
305     char *tail;
306     return strtol(s.c_str(), &tail, base);
307 }
308 
309 /*
310  * Takes as argument a string, and returns a sequence of keys described
311  * by the string. Most characters produce their own ASCII code. These
312  * are the cases:
313  *   \\ produces the ASCII code of a single \
314  *   \{123} produces 123 (decimal)
315  *   \{^A}  produces 1 (Ctrl-A)
316  *   \{x40} produces 64 (hexadecimal code)
317  *   \{!more} or \{!m} disables -more- prompt until the end of the macro.
318  */
parse_keyseq(string s)319 keyseq parse_keyseq(string s)
320 {
321     // TODO parse readable descriptions of special keys, e.g. \{F1} or something
322     int state = 0;
323     keyseq v;
324 
325     if (starts_with(s, "==="))
326     {
327         buf2keyseq(s.c_str(), v);
328         return v;
329     }
330 
331     bool more_reset = false;
332     for (int i = 0, size = s.length(); i < size; ++i)
333     {
334         char c = s[i];
335 
336         switch (state)
337         {
338         case 0: // Normal state
339             if (c == '\\')
340                 state = 1;
341             else
342                 v.push_back(c);
343             break;
344 
345         case 1: // Last char is a '\'
346             if (c == '\\')
347             {
348                 state = 0;
349                 v.push_back(c);
350             }
351             else if (c == '{')
352                 state = 2;
353             // XXX Error handling
354             break;
355 
356         case 2: // Inside \{}
357         {
358             const string::size_type clb = s.find('}', i);
359             if (clb == string::npos)
360                 break;
361 
362             const string arg = s.substr(i, clb - i);
363             if (!more_reset && (arg == "!more" || arg == "!m"))
364             {
365                 more_reset = true;
366                 v.push_back(KEY_MACRO_MORE_PROTECT);
367             }
368             else
369             {
370                 const int key = function_keycode_fixup(read_key_code(arg));
371                 v.push_back(key);
372             }
373 
374             state = 0;
375             i = clb;
376             break;
377         }
378         }
379     }
380 
381     return v;
382 }
383 
384 /*
385  * Serialises a key sequence into a string of the format described
386  * above.
387  */
vtostr(const keyseq & seq)388 static string vtostr(const keyseq &seq)
389 {
390     ostringstream s;
391 
392     const keyseq *v = &seq;
393     keyseq dummy;
394     if (is_userfunction(seq))
395     {
396         // Laboriously reconstruct the original user input
397         string newseq = string("==") + get_userfunction(seq);
398         buf2keyseq(newseq.c_str(), dummy);
399         dummy.push_front('=');
400 
401         v = &dummy;
402     }
403 
404     for (auto key : *v)
405     {
406         if (key <= 32 || key > 127)
407         {
408             if (key == KEY_MACRO_MORE_PROTECT)
409                 s << "\\{!more}";
410             else
411             {
412                 char buff[20];
413                 // TODO: get rid of negative values in user-facing uses of
414                 // keycodes
415                 snprintf(buff, sizeof(buff), "\\{%d}", key);
416                 s << buff;
417             }
418         }
419         else if (key == '\\')
420             s << "\\\\";
421         else
422             s << static_cast<char>(key);
423     }
424 
425     return s.str();
426 }
427 
428 /*
429  * Add a macro (surprise, surprise).
430  */
macro_add(macromap & mapref,keyseq key,keyseq action)431 static void macro_add(macromap &mapref, keyseq key, keyseq action)
432 {
433     mapref[key] = action;
434 }
435 
436 /*
437  * Remove a macro.
438  */
macro_del(macromap & mapref,keyseq key)439 static bool macro_del(macromap &mapref, keyseq key)
440 {
441     return mapref.erase(key) != 0;
442 }
443 
444 static void _register_expanded_keys(int num, bool reverse);
445 
446 // Safely add macro-expanded keystrokes to the end of Crawl's keyboard
447 // buffer. Macro-expanded keystrokes will be held in a separate
448 // keybuffer until the primary keybuffer is empty, at which point
449 // they'll be added to the primary keybuffer.
macro_sendkeys_end_add_expanded(int key)450 void macro_sendkeys_end_add_expanded(int key)
451 {
452     SendKeysBuffer.push_back(key);
453 }
454 
_macro_inject_sent_keys()455 static void _macro_inject_sent_keys()
456 {
457     if (Buffer.empty() && !SendKeysBuffer.empty())
458     {
459         // Transfer keys from SendKeysBuffer to the main Buffer as
460         // expanded keystrokes and empty SendKeysBuffer.
461         macro_buf_add(SendKeysBuffer, false, true);
462         SendKeysBuffer.clear();
463     }
464 }
465 
466 /*
467  * Safely add a command to the end of the sendkeys keybuffer.
468  */
macro_sendkeys_end_add_cmd(command_type cmd)469 void macro_sendkeys_end_add_cmd(command_type cmd)
470 {
471     ASSERT_RANGE(cmd, CMD_NO_CMD + 1, CMD_MIN_SYNTHETIC);
472 
473     // There should be plenty of room between the synthetic keys
474     // (KEY_MACRO_MORE_PROTECT == -10) and USERFUNCBASE (-10000) for
475     // command_type to fit (currently 1000 through 2069).
476     macro_sendkeys_end_add_expanded(-((int) cmd));
477 }
478 
479 /*
480  * Adds keypresses from a sequence into the internal keybuffer. Ignores
481  * macros.
482  */
macro_buf_add(const keyseq & actions,bool reverse,bool expanded)483 void macro_buf_add(const keyseq &actions, bool reverse, bool expanded)
484 {
485     keyseq act;
486     bool need_more_reset = false;
487     for (auto key : actions)
488     {
489         if (key == KEY_MACRO_MORE_PROTECT)
490         {
491             key = KEY_MACRO_DISABLE_MORE;
492             need_more_reset = true;
493         }
494         act.push_back(key);
495     }
496     if (need_more_reset)
497         act.push_back(KEY_MACRO_ENABLE_MORE);
498 
499     Buffer.insert(reverse? Buffer.begin() : Buffer.end(),
500                    act.begin(), act.end());
501 
502     if (expanded)
503         _register_expanded_keys(act.size(), reverse);
504 }
505 
506 /*
507  * Adds a single keypress into the internal keybuffer.
508  */
macro_buf_add(int key,bool reverse,bool expanded)509 void macro_buf_add(int key, bool reverse, bool expanded)
510 {
511     if (reverse)
512         Buffer.push_front(key);
513     else
514         Buffer.push_back(key);
515     if (expanded)
516         _register_expanded_keys(1, reverse);
517 }
518 
519 /*
520  * Add a command into the internal keybuffer.
521  */
macro_buf_add_cmd(command_type cmd,bool reverse)522 void macro_buf_add_cmd(command_type cmd, bool reverse)
523 {
524     ASSERT_RANGE(cmd, CMD_NO_CMD + 1, CMD_MIN_SYNTHETIC);
525 
526     // There should be plenty of room between the synthetic keys
527     // (KEY_MACRO_MORE_PROTECT == -10) and USERFUNCBASE (-10000) for
528     // command_type to fit (currently 1000 through 2069).
529     macro_buf_add(-((int) cmd), reverse, true);
530 }
531 
532 /*
533  * Adds keypresses from a sequence into the internal keybuffer. Does some
534  * O(N^2) analysis to the sequence to apply keymaps.
535  */
macro_buf_add_long(keyseq actions,macromap & keymap=Keymaps[KMC_DEFAULT])536 static void macro_buf_add_long(keyseq actions,
537                                macromap &keymap = Keymaps[KMC_DEFAULT])
538 {
539     keyseq tmp;
540 
541     // debug << "Adding: " << vtostr(actions) << endl;
542     // debug.flush();
543 
544     // Check whether any subsequences of the sequence are macros.
545     // The matching starts from as early as possible, and is
546     // as long as possible given the first constraint. I.e from
547     // the sequence "abcdef" and macros "ab", "bcde" and "de"
548     // "ab" and "de" are recognised as macros.
549 
550     while (!actions.empty())
551     {
552         tmp = actions;
553 
554         while (!tmp.empty())
555         {
556             auto subst = keymap.find(tmp);
557             // Found a macro. Add the expansion (action) of the
558             // macro into the buffer.
559             if (subst != keymap.end() && !subst->second.empty())
560             {
561                 macro_buf_add(subst->second, false, false);
562                 break;
563             }
564 
565             // Didn't find a macro. Remove a key from the end
566             // of the sequence, and try again.
567             tmp.pop_back();
568         }
569 
570         if (tmp.empty())
571         {
572             // Didn't find a macro. Add the first keypress of the sequence
573             // into the buffer, remove it from the sequence, and try again.
574             macro_buf_add(actions.front(), false, false);
575             actions.pop_front();
576         }
577         else
578         {
579             // Found a macro, which has already been added above. Now just
580             // remove the macroed keys from the sequence.
581             for (unsigned int i = 0; i < tmp.size(); i++)
582                 actions.pop_front();
583         }
584     }
585 }
586 
587 // Number of keys from start of buffer that have already gone through
588 // macro expansion.
589 static int expanded_keys_left = 0;
_register_expanded_keys(int num,bool reverse)590 static void _register_expanded_keys(int num, bool reverse)
591 {
592     expanded_keys_left += num;
593     if (!reverse)
594     {
595         // We added at the end of the buffer, so the whole buffer had
596         // better be full of expanded keys.
597         ASSERT((int)Buffer.size() == expanded_keys_left);
598         expanded_keys_left = Buffer.size();
599     }
600 }
601 
602 static int macro_keys_left = -1;
603 
macro_clear_buffers()604 void macro_clear_buffers()
605 {
606     crawl_state.cancel_cmd_repeat();
607     crawl_state.cancel_cmd_again();
608 
609     Buffer.clear();
610     SendKeysBuffer.clear();
611     expanded_keys_left = 0;
612     macro_keys_left = -1;
613 
614     crawl_state.show_more_prompt = true;
615 }
616 
is_processing_macro()617 bool is_processing_macro()
618 {
619     return macro_keys_left >= 0;
620 }
621 
has_pending_input()622 bool has_pending_input()
623 {
624     return !Buffer.empty() || !SendKeysBuffer.empty();
625 }
626 
627 /*
628  * Command macros are only applied from the immediate front of the
629  * buffer, and only when the game is expecting a command.
630  */
macro_buf_apply_command_macro()631 static void macro_buf_apply_command_macro()
632 {
633     if (macro_keys_left > 0 || expanded_keys_left > 0)
634         return;
635 
636     keyseq tmp = Buffer;
637 
638     // find the longest match from the start of the buffer and replace it
639     while (!tmp.empty())
640     {
641         auto expansion = Macros.find(tmp);
642 
643         if (expansion != Macros.end() && !expansion->second.empty())
644         {
645             const keyseq &result = expansion->second;
646 
647             // Found macro, remove match from front:
648             for (unsigned int i = 0; i < tmp.size(); i++)
649                 Buffer.pop_front();
650 
651             macro_keys_left = result.size();
652 
653             macro_buf_add(result, true, true);
654 
655             break;
656         }
657 
658         tmp.pop_back();
659     }
660 }
661 
662 /*
663  * Removes the earliest keypress from the keybuffer, and returns its
664  * value. If buffer was empty, returns -1;
665  */
macro_buf_get()666 int macro_buf_get()
667 {
668     ASSERT(macro_keys_left <= (int)Buffer.size()
669            && expanded_keys_left <= (int)Buffer.size());
670 
671     _macro_inject_sent_keys();
672 
673     if (Buffer.empty())
674     {
675         // If we're trying to fetch a new keystroke, then the processing
676         // of the previous keystroke is complete.
677         if (macro_keys_left == 0)
678             macro_keys_left = -1;
679 
680         return -1;
681     }
682 
683     int key = Buffer.front();
684     Buffer.pop_front();
685 
686     if (macro_keys_left >= 0)
687         macro_keys_left--;
688     if (expanded_keys_left > 0)
689         expanded_keys_left--;
690 
691     for (key_recorder *recorder : recorders)
692         recorder->add_key(key);
693 
694     return key;
695 }
696 
process_command_on_record(command_type cmd)697 void process_command_on_record(command_type cmd)
698 {
699     const int key = command_to_key(cmd);
700     if (key != '\0')
701         for (key_recorder *recorder : recorders)
702             recorder->add_key(key);
703     process_command(cmd);
704 }
705 
write_map(FILE * f,const macromap & mp,const char * key)706 static void write_map(FILE *f, const macromap &mp, const char *key)
707 {
708     for (const auto &entry : mp)
709     {
710         // Need this check, since empty values are added into the
711         // macro struct for all used keyboard commands.
712         if (entry.second.size())
713         {
714             fprintf(f, "%s%s\nA:%s\n\n", OUTS(key),
715                 OUTS(vtostr(entry.first)), OUTS(vtostr(entry.second)));
716         }
717     }
718 }
719 
720 /*
721  * Saves macros into the macrofile, overwriting the old one.
722  */
macro_save()723 void macro_save()
724 {
725     FILE *f;
726     const string macrofile = get_macro_file();
727     f = fopen_u(macrofile.c_str(), "w");
728     if (!f)
729     {
730         mprf(MSGCH_ERROR, "Couldn't open %s for writing!", macrofile.c_str());
731         return;
732     }
733 
734     fprintf(f, "# %s %s macro file\n"
735                "# WARNING: This file is entirely auto-generated.\n"
736                "\n"
737                "# Key Mappings:\n",
738             OUTS(CRAWL), // ok, localizing the game name is not likely
739             OUTS(Version::Long)); // nor the version string
740     for (int mc = KMC_DEFAULT; mc < KMC_CONTEXT_COUNT; ++mc)
741     {
742         char buf[30] = "K:";
743         if (mc)
744             snprintf(buf, sizeof buf, "K%d:", mc);
745         write_map(f, Keymaps[mc], buf);
746     }
747 
748     fprintf(f, "# Command Macros:\n");
749     write_map(f, Macros, "M:");
750 
751     crawl_state.unsaved_macros = false;
752     fclose(f);
753 }
754 
755 /*
756  * Reads as many keypresses as are available (waiting for at least one),
757  * and returns them as a single keyseq.
758  */
_getch_mul()759 static keyseq _getch_mul()
760 {
761     keyseq keys;
762     int    a;
763 
764     // Something's gone wrong with replaying keys if crawl needs to
765     // get new keys from the user.
766     if (crawl_state.is_replaying_keys())
767     {
768         mprf(MSGCH_ERROR, "(Key replay ran out of keys)");
769         crawl_state.cancel_cmd_repeat();
770         crawl_state.cancel_cmd_again();
771     }
772 
773     // The a == 0 test is legacy code that I don't dare to remove. I
774     // have a vague recollection of it being a kludge for conio support.
775     do
776     {
777         a = getch_ck();
778         if (a != CK_NO_KEY)
779             keys.push_back(a);
780     }
781     while (keys.size() == 0 || ((kbhit() || a == 0) && a != CK_REDRAW));
782 
783     return keys;
784 }
785 
786 /*
787  * Replacement for getch(). Returns keys from the key buffer if available.
788  * If not, adds some content to the buffer, and returns some of it.
789  */
getchm(KeymapContext mc)790 int getchm(KeymapContext mc)
791 {
792     int a;
793 
794     // Got data from buffer.
795     if ((a = macro_buf_get()) != -1)
796         return a;
797 
798     // Read some keys...
799     keyseq keys = _getch_mul();
800     macro_buf_add_with_keymap(keys, mc);
801     return macro_buf_get();
802 }
803 
macro_buf_add_with_keymap(keyseq keys,KeymapContext mc)804 void macro_buf_add_with_keymap(keyseq keys, KeymapContext mc)
805 {
806     if (mc == KMC_NONE)
807         macro_buf_add(keys, false, false);
808     else
809         macro_buf_add_long(keys, Keymaps[mc]);
810 }
811 
812 /**
813  * Get a character?
814  */
get_ch()815 int get_ch()
816 {
817     mouse_control mc(MOUSE_MODE_PROMPT);
818     int gotched = getchm();
819 
820     if (gotched == 0)
821         gotched = getchm();
822 
823     return gotched;
824 }
825 
826 /*
827  * Replacement for getch(). Returns keys from the key buffer if available.
828  * If not, adds some content to the buffer, and returns some of it.
829  */
getch_with_command_macros()830 int getch_with_command_macros()
831 {
832     _macro_inject_sent_keys();
833 
834     if (Buffer.empty())
835     {
836         keyseq keys = _getch_mul();
837         macro_buf_add_with_keymap(keys, KMC_DEFAULT);
838     }
839 
840     // Apply longest matching macro at front of buffer:
841     macro_buf_apply_command_macro();
842 
843     return macro_buf_get();
844 }
845 
_buffer_to_string()846 static string _buffer_to_string()
847 {
848     string s;
849     for (const int k : Buffer)
850     {
851         if (k > 0 && k <= numeric_limits<unsigned char>::max())
852         {
853             char c = static_cast<unsigned char>(k);
854             if (c == '[' || c == ']')
855                 s += "\\";
856             s += c;
857         }
858         else
859             s += make_stringf("[%d]", k);
860     }
861     return s;
862 }
863 
864 /*
865  * Flush the buffer. Later we'll probably want to give the player options
866  * as to when this happens (ex. always before command input, casting failed).
867  */
flush_input_buffer(int reason)868 void flush_input_buffer(int reason)
869 {
870     ASSERT(reason != FLUSH_KEY_REPLAY_CANCEL
871            || crawl_state.is_replaying_keys() || crawl_state.cmd_repeat_start);
872 
873     ASSERT(reason != FLUSH_ABORT_MACRO || is_processing_macro());
874 
875     // Any attempt to flush means that the processing of the previously
876     // fetched keystroke is complete.
877     if (macro_keys_left == 0)
878         macro_keys_left = -1;
879 
880     if (crawl_state.is_replaying_keys() && reason != FLUSH_ABORT_MACRO
881         && reason != FLUSH_KEY_REPLAY_CANCEL
882         && reason != FLUSH_REPLAY_SETUP_FAILURE
883         && reason != FLUSH_ON_FAILURE)
884     {
885         return;
886     }
887 
888     if (Options.flush_input[ reason ] || reason == FLUSH_ABORT_MACRO
889         || reason == FLUSH_KEY_REPLAY_CANCEL
890         || reason == FLUSH_REPLAY_SETUP_FAILURE
891         || reason == FLUSH_REPEAT_SETUP_DONE)
892     {
893         if (crawl_state.nonempty_buffer_flush_errors && !Buffer.empty())
894         {
895             if (you.wizard) // crash -- intended for tests
896             {
897                 mprf(MSGCH_ERROR,
898                     "Flushing non-empty key buffer (Buffer is '%s')",
899                     _buffer_to_string().c_str());
900                 ASSERT(Buffer.empty());
901             }
902             else
903                 mprf(MSGCH_ERROR, "Flushing non-empty key buffer");
904         }
905         while (!Buffer.empty())
906         {
907             const int key = Buffer.front();
908             Buffer.pop_front();
909             if (key == KEY_MACRO_ENABLE_MORE)
910                 crawl_state.show_more_prompt = true;
911         }
912         macro_keys_left = -1;
913         expanded_keys_left = 0;
914     }
915 }
916 
_keyseq_desc(const keyseq & key)917 static string _keyseq_desc(const keyseq &key)
918 {
919     string r = keycode_is_printable(key[0])
920                 ? keycode_to_name(key[0]).c_str()
921                 : make_stringf("%s (%s)",
922                     vtostr(key).c_str(), keycode_to_name(key[0]).c_str());
923     r = replace_all(r, "<", "<<");
924     return r;
925 }
926 
_keyseq_action_desc(keyseq & action)927 static string _keyseq_action_desc(keyseq &action)
928 {
929     if (action.empty())
930         return "<red>[none]</red>";
931 
932     string action_str = vtostr(action);
933     action_str = replace_all(action_str, "<", "<<");
934     return action_str;
935 }
936 
_keyseq_action_desc(const keyseq & key,macromap & mapref)937 static string _keyseq_action_desc(const keyseq &key, macromap &mapref)
938 {
939     if (!mapref.count(key))
940     {
941         keyseq empty;
942         return _keyseq_action_desc(empty);
943     }
944     else
945         return _keyseq_action_desc(mapref[key]);
946 }
947 
948 class MacroEditMenu : public Menu
949 {
950 private:
951     const vector<pair<KeymapContext,string>> modes =
952         { { KMC_NONE, "macros" }, // not actually what KMC_NONE means
953           { KMC_DEFAULT, "default" },
954           { KMC_MENU, "menu" },
955           { KMC_LEVELMAP, "level" },
956           { KMC_TARGETING, "targeting" },
957           { KMC_CONFIRM, "confirmation" }
958         };
959 
960 public:
MacroEditMenu()961     MacroEditMenu()
962         : Menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING | MF_ALWAYS_SHOW_MORE
963                 | MF_ARROWS_SELECT | MF_WRAP),
964           selected_new_key(false), keymc(KMC_NONE), edited_keymaps(false)
965     {
966         set_tag("macros");
967 #ifdef USE_TILE_LOCAL
968         set_min_col_width(80);
969 #endif
970         action_cycle = Menu::CYCLE_NONE;
971         menu_action  = Menu::ACT_EXECUTE;
972         set_title(new MenuEntry("", MEL_TITLE));
973         on_single_selection = [this](const MenuEntry& item)
974         {
975             status_msg = "";
976             update_macro_more();
977             if (item.data)
978                 edit_mapping(*static_cast<keyseq *>(item.data));
979             else if (item.hotkeys.size() && item.hotkeys[0] == '~')
980                 edit_mapping(keyseq());
981             else if (item.hotkeys.size() && item.hotkeys[0] == '-')
982             {
983                 if (item_count() > 0)
984                     clear_all();
985             }
986 
987             return true;
988         };
989     }
990 
fill_entries(int set_hover_keycode=0)991     void fill_entries(int set_hover_keycode=0)
992     {
993         // TODO: this seems like somehow it should involve ui::Switcher, but I
994         // have no idea how to use that class with a Menu
995         clear();
996         add_entry(new MenuEntry("Create/edit " + mode_name() + " from key",
997             MEL_ITEM, 1, '~'));
998         if (get_map().size())
999         {
1000             add_entry(new MenuEntry("Clear all " + mode_name() + "s",
1001                 MEL_ITEM, 1, '-'));
1002             add_entry(new MenuEntry("Current " + mode_name() + "s", MEL_SUBTITLE));
1003             for (auto &mapping : get_map())
1004             {
1005                 // TODO: indicate if macro is from rc file somehow?
1006                 string action_str = vtostr(mapping.second);
1007                 action_str = replace_all(action_str, "<", "<<");
1008                 MenuEntry *me = new MenuEntry(action_str,
1009                                                         MEL_ITEM, 1,
1010                                                         (int) mapping.first[0]);
1011                 me->data = (void *) &mapping.first;
1012                 add_entry(me);
1013                 if (set_hover_keycode == mapping.first[0])
1014                     last_hovered = item_count() - 1;
1015             }
1016         }
1017         // update more in case menu changes between empty and non-empty
1018         update_macro_more();
1019         update_menu(true); // necessary to update webtiles menu
1020         if (last_hovered == -1)
1021             cycle_hover();
1022     }
1023 
cycle_mode()1024     void cycle_mode()
1025     {
1026         auto it = modes.begin();
1027         for (; it != modes.end(); it++)
1028             if (it->first == keymc)
1029                 break;
1030         if (it == modes.end())
1031             keymc = KMC_NONE;
1032         else
1033         {
1034             it++;
1035             if (it == modes.end())
1036                 keymc = KMC_NONE;
1037             else
1038                 keymc = it->first;
1039         }
1040         status_msg = "";
1041         update_title();
1042         fill_entries();
1043     }
1044 
get_mode() const1045     KeymapContext get_mode() const
1046     {
1047         return keymc;
1048     }
1049 
update_macro_more()1050     void update_macro_more()
1051     {
1052         vector<string> result;
1053         for (auto m : modes)
1054             if (keymc == m.first)
1055                 result.push_back("<w>" + m.second + "</w>");
1056             else
1057                 result.push_back(m.second);
1058 
1059         string hint;
1060         if (keymc != KMC_NONE)
1061             edited_keymaps = true;
1062 
1063         // there's much less use-case for editing keymaps in-game, so hide the
1064         // details by default
1065         string mode_hint = edited_keymaps
1066             ? "cycle mode: " + join_strings(result.begin(), result.end(), "|")
1067             : "edit keymaps";
1068 
1069         set_more(formatted_string::parse_string(
1070             status_msg + "\n"
1071             "Arrows/[<w>enter</w>] to select, [<w>bksp</w>] to clear selected, [<w>?</w>] for help\n"
1072             "[<w>!</w>"
1073 #ifdef USE_TILE_LOCAL
1074             "/<w>Right-click</w>"
1075 #endif
1076             "] " + mode_hint));
1077 
1078     }
1079 
mode_name()1080     string mode_name()
1081     {
1082         // XX less stupid implementation
1083         switch (keymc)
1084         {
1085         case KMC_NONE: return "macro";
1086         case KMC_DEFAULT: return "regular keymap";
1087         case KMC_MENU: return "menu keymap";
1088         case KMC_TARGETING: return "targeting keymap";
1089         case KMC_LEVELMAP: return "level map keymap";
1090         case KMC_CONFIRM: return "confirmation keymap";
1091         default: return "buggy keymap";
1092         }
1093     }
1094 
calc_title()1095     virtual formatted_string calc_title() override
1096     {
1097         return formatted_string::parse_string(
1098             "Editing <w>" + mode_name() + "s</w>.");
1099     }
1100 
clear_all()1101     void clear_all()
1102     {
1103         const string clear_prompt = make_stringf("Really clear all %ss?",
1104                 mode_name().c_str());
1105         if (yesno(clear_prompt.c_str(), true, 'N'))
1106         {
1107             get_map() = macromap();
1108             status_msg = "All " + mode_name() + "s cleared!";
1109             crawl_state.unsaved_macros = true;
1110             fill_entries();
1111         }
1112     }
1113 
get_map()1114     macromap &get_map()
1115     {
1116         return keymc != KMC_NONE ? Keymaps[keymc] : Macros;
1117     }
1118 
clear_mapping(keyseq key)1119     void clear_mapping(keyseq key)
1120     {
1121         macromap &mapref = get_map();
1122         if (!mapref.count(key))
1123             return;
1124 
1125         const int keycode = key[0];
1126         string key_str = keycode_is_printable(keycode)
1127             ? keycode_to_name(keycode).c_str()
1128             : make_stringf("%s (%s)",
1129                     vtostr(key).c_str(), keycode_to_name(keycode).c_str());
1130         string action_str = vtostr(mapref[key]);
1131 
1132         action_str = replace_all(action_str, "<", "<<");
1133         key_str = replace_all(key_str, "<", "<<");
1134 
1135         status_msg = make_stringf("Cleared %s '%s' => '%s'.",
1136                     mode_name().c_str(),
1137                     key_str.c_str(),
1138                  action_str.c_str());
1139 
1140         macro_del(mapref, key);
1141         crawl_state.unsaved_macros = true;
1142         fill_entries();
1143     }
1144 
clear_hovered()1145     void clear_hovered()
1146     {
1147         if (last_hovered < 0)
1148             return;
1149         keyseq *_key_chosen = static_cast<keyseq *>(items[last_hovered]->data);
1150         if (!_key_chosen)
1151             return;
1152 
1153         // TODO: add a quick undo key?
1154         clear_mapping(*_key_chosen);
1155     }
1156 
1157     class MappingEditMenu : public Menu
1158     {
1159     public:
MappingEditMenu(keyseq _key,keyseq _action,MacroEditMenu & _parent)1160         MappingEditMenu(keyseq _key, keyseq _action, MacroEditMenu &_parent)
1161             : Menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING | MF_ARROWS_SELECT
1162                     | MF_ALWAYS_SHOW_MORE, "", KMC_MENU),
1163               key(_key), action(_action), abort(false),
1164               parent(_parent),
1165               doing_key_input(false), doing_raw_action_input(false)
1166         {
1167             set_tag("macro_mapping");
1168 #ifdef USE_TILE_LOCAL
1169             set_min_col_width(62); // based on `r` more width
1170 #endif
1171             set_more(string(""));
1172             if (key.size() == 0)
1173                 initialize_needs_key();
1174             else if (action.size() == 0)
1175             {
1176                 reset_key_prompt();
1177                 on_show = [this]()
1178                 {
1179                     if (edit_action())
1180                         return false;
1181                     initialize_with_key();
1182                     update_menu(true);
1183                     return true;
1184                 };
1185             }
1186             else
1187                 initialize_with_key();
1188         }
1189 
1190         /// show the menu and edit a mapping
1191         /// @return whether a mapping was set
input_mapping()1192         bool input_mapping()
1193         {
1194             show();
1195             return !abort;
1196         }
1197 
1198         /// Initialize the menu to accept key input immediately on show
initialize_needs_key()1199         void initialize_needs_key()
1200         {
1201             clear();
1202             key.clear();
1203             set_more(string(""));
1204             prompt = make_stringf(
1205                 "Input trigger key to edit or create a %s:",
1206                 parent.mode_name().c_str());
1207             set_title(new MenuEntry(prompt, MEL_TITLE));
1208             set_more("<lightgray>([<w>~</w>] to enter by keycode)</lightgray>");
1209             doing_key_input = true;
1210         }
1211 
reset_key_prompt()1212         void reset_key_prompt()
1213         {
1214             prompt = make_stringf("Current %s for %s: %s",
1215                         parent.mode_name().c_str(),
1216                         _keyseq_desc(key).c_str(),
1217                         _keyseq_action_desc(action).c_str());
1218 
1219             set_title(new MenuEntry(prompt + "\n", MEL_TITLE));
1220         }
1221 
1222         /// Initialize the menu for key editing, given some key to edit
initialize_with_key()1223         void initialize_with_key()
1224         {
1225             ASSERT(key.size());
1226             clear();
1227             reset_key_prompt();
1228             set_more(string(""));
1229 
1230             add_entry(new MenuEntry("redefine", MEL_ITEM, 1, 'r'));
1231             add_entry(new MenuEntry("redefine with raw key entry", MEL_ITEM, 1, 'R'));
1232             if (!action.empty())
1233                 add_entry(new MenuEntry("clear", MEL_ITEM, 1, 'c'));
1234             add_entry(new MenuEntry("abort", MEL_ITEM, 1, 'a'));
1235 
1236             on_single_selection = [this](const MenuEntry& item)
1237             {
1238                 set_more("");
1239                 if (!item.hotkeys.size())
1240                     return true;
1241                 if (item.hotkeys[0] == 'r')
1242                     return !edit_action();
1243                 else if (item.hotkeys[0] == 'R')
1244                 {
1245                     edit_action_raw();
1246                     return true;
1247                 }
1248                 else if (item.hotkeys[0] == 'c')
1249                     action.clear();
1250                 else if (item.hotkeys[0] == 'a')
1251                     abort = true;
1252                 return false;
1253             };
1254             if (last_hovered == -1)
1255                 cycle_hover();
1256         }
1257 
1258         /// Enter raw input mode for keymaps -- allows mapping to any key
1259         /// except enter and esc
edit_action_raw()1260         void edit_action_raw()
1261         {
1262             prompt = make_stringf(
1263                 "<w>%s</w>\nInput (raw) new %s for %s: ",
1264                         prompt.c_str(),
1265                         parent.mode_name().c_str(),
1266                         _keyseq_desc(key).c_str());
1267             set_title(new MenuEntry(prompt, MEL_TITLE));
1268             set_more("Raw input: [<w>esc</w>] to abort, [<w>enter</w>] to accept.");
1269             update_menu(true);
1270             doing_raw_action_input = true;
1271         }
1272 
1273         /// edit an action, using title_prompt for text entry
1274         /// @return true if an action was fully set
edit_action()1275         bool edit_action()
1276         {
1277             char buff[1024];
1278             const string edit_prompt = make_stringf("<w>%s</w>\nInput new %s for %s:",
1279                         prompt.c_str(),
1280                         parent.mode_name().c_str(),
1281                         _keyseq_desc(key).c_str());
1282 
1283             int old_last_hovered = last_hovered;
1284             set_hovered(-1);
1285             set_more("Input a key sequence. Use <w>\\{n}</w> to enter keycode <w>n</w>. [<w>esc</w>] for menu");
1286             if (!title_prompt(buff, sizeof(buff), edit_prompt.c_str()))
1287             {
1288                 set_hovered(old_last_hovered);
1289                 set_more("");
1290                 // line reader success code is 0
1291                 return lastch == 0;
1292             }
1293 
1294             keyseq new_action = parse_keyseq(buff);
1295             // it's still possible to get a blank keyseq by having parsing
1296             // issues with backslashes, for example
1297             if (!new_action.size())
1298             {
1299                 set_more(make_stringf("Parsing error in key sequence '%s'", buff));
1300                 return true;
1301             }
1302             set_hovered(old_last_hovered);
1303             action = new_action;
1304             reset_key_prompt();
1305             update_menu(true);
1306             return true;
1307         }
1308 
process_key(int keyin)1309         bool process_key(int keyin)
1310         {
1311             // stateful key processing:
1312             // * in raw action input mode, fill keys into raw_tmp
1313             // * in key input mode, fill exactly one key into `key`, either
1314             //   by key entry or keycode
1315             // * otherwise, use normal menu key handling
1316             //
1317             // regular action input as well as keycode entry use title_prompt,
1318             // and so are handled in the superclass
1319             if (doing_raw_action_input)
1320             {
1321                 if (keyin == ESCAPE || keyin == CONTROL('G'))
1322                 {
1323                     doing_raw_action_input = false;
1324                     raw_tmp.clear();
1325                     set_more("");
1326                     reset_key_prompt();
1327                     return true;
1328                 }
1329                 else if (keyin == '\r' || keyin == '\n')
1330                 {
1331                     doing_raw_action_input = false;
1332                     if (raw_tmp.size() && raw_tmp[0] != 0)
1333                         action = raw_tmp;
1334                     set_more("");
1335                     reset_key_prompt();
1336                     return true;
1337                 }
1338                 raw_tmp.push_back(keyin);
1339                 set_title(new MenuEntry(prompt + vtostr(raw_tmp)));
1340                 return true;
1341             }
1342             else if (doing_key_input)
1343             {
1344                 doing_key_input = false;
1345                 if (keyin == ESCAPE || keyin == CONTROL('G') || keyin == CK_MOUSE_B2)
1346                 {
1347                     abort = true;
1348                     return false;
1349                 }
1350                 else if (keyin == '~')
1351                 {
1352                     char buff[10];
1353                     set_more("Quick reference: 8: [<w>bksp</w>], 9: [<w>tab</w>], 13: [<w>enter</w>], 27: [<w>esc</w>]");
1354                     if (!title_prompt(buff, sizeof(buff), "Enter keycode by number:"))
1355                     {
1356                         abort = true;
1357                         return false;
1358                     }
1359                     keyin = read_key_code(string(buff));
1360                     if (keyin == 0)
1361                     {
1362                         abort = true;
1363                         return false;
1364                     }
1365                 }
1366 
1367                 // intercept one key, and store it in `key`
1368                 key.push_back(keyin); // TODO: vs _getch_mul?
1369                 // switch to editing state and reinit the menu
1370                 macromap &mapref = parent.get_map();
1371                 if (!mapref.count(key))
1372                     action.clear();
1373                 else
1374                     action = mapref[key];
1375                 // if the mapping is new, edit immediately
1376                 if (action.empty())
1377                 {
1378                     prompt = make_stringf("%s %s", prompt.c_str(),
1379                                                     _keyseq_desc(key).c_str());
1380                     if (edit_action())
1381                         return false;
1382                 }
1383                 // TODO: this drops to the mapping edit menu at this point. It
1384                 // would be faster to go back to the main macro menu, but this
1385                 // allows the player to cancel. Which is better?
1386                 initialize_with_key();
1387                 update_menu(true);
1388                 return true;
1389             }
1390 
1391             if (keyin == '?')
1392                 show_specific_help("macro-menu");
1393             else if (keyin == 'a')
1394                 return false; // legacy key
1395             return Menu::process_key(keyin);
1396         }
1397 
1398         keyseq key;
1399         keyseq action;
1400         bool abort;
1401     protected:
1402         MacroEditMenu &parent;
1403         keyseq raw_tmp;
1404         bool doing_key_input;
1405         bool doing_raw_action_input;
1406         string prompt;
1407     };
1408 
edit_mapping(keyseq key)1409     bool edit_mapping(keyseq key)
1410     {
1411         const bool existed = get_map().count(key);
1412 
1413         MappingEditMenu pop = MappingEditMenu(key,
1414             existed ? get_map()[key] : keyseq(), *this);
1415         if (pop.input_mapping())
1416         {
1417             if (pop.action.size()
1418                 && (!get_map().count(pop.key) || get_map()[pop.key] != pop.action))
1419             {
1420                 macro_add(get_map(), pop.key, pop.action);
1421                 status_msg = make_stringf("%s %s '%s' => '%s'.",
1422                     existed ? "Redefined" : "Created",
1423                     mode_name().c_str(),
1424                     _keyseq_desc(pop.key).c_str(),
1425                     _keyseq_action_desc(pop.key, get_map()).c_str());
1426                 crawl_state.unsaved_macros = true;
1427             }
1428             else if (!pop.action.size())
1429                 clear_mapping(pop.key);
1430             if (pop.key.size())
1431                 fill_entries(pop.key[0]);
1432             // else, we aborted
1433         }
1434 
1435         return false;
1436     }
1437 
add_mapping_from_last()1438     void add_mapping_from_last()
1439     {
1440         keyseq key;
1441         key.push_back(lastch);
1442         // XX could this jump right into editing?
1443         edit_mapping(key);
1444     }
1445 
process_key(int keyin)1446     bool process_key(int keyin) override
1447     {
1448         switch (keyin)
1449         {
1450         case CK_REDRAW:
1451         case ' ': case CK_PGDN: case '>': case '+':
1452         case CK_MOUSE_CLICK:
1453         case CK_MOUSE_B1:
1454         case CK_PGUP: case '<':
1455         case CK_UP:
1456         case CK_DOWN:
1457         case CK_HOME:
1458         case CK_END:
1459         case CK_ENTER:
1460             if (item_count() == 0)
1461                 return true; // override weird superclass behavior
1462             //fallthrough
1463         case CK_MOUSE_B2:
1464         CASE_ESCAPE
1465         case '-':
1466         case '~':
1467             return Menu::process_key(keyin);
1468         case '?':
1469             show_specific_help("macro-menu");
1470             return true;
1471         case CK_MOUSE_CMD:
1472         case '!':
1473             cycle_mode();
1474             return true;
1475         case CK_DELETE:
1476         case CK_BKSP:
1477             clear_hovered();
1478             return true;
1479         default: // any other key -- no menu item yet
1480             lastch = keyin;
1481             add_mapping_from_last();
1482             return true;
1483         }
1484     }
1485     bool selected_new_key;
1486     string status_msg;
1487 protected:
1488     KeymapContext keymc;
1489     bool edited_keymaps;
1490 };
1491 
macro_quick_add()1492 void macro_quick_add()
1493 {
1494     MacroEditMenu menu;
1495     keyseq empty;
1496     menu.edit_mapping(empty);
1497     if (menu.status_msg.size())
1498         mpr(menu.status_msg);
1499     else
1500         canned_msg(MSG_OK);
1501 }
1502 
macro_menu()1503 void macro_menu()
1504 {
1505     MacroEditMenu menu;
1506     menu.fill_entries();
1507 
1508     menu.show();
1509 
1510     redraw_screen();
1511     update_screen();
1512 }
1513 
1514 /*
1515  * Initialises the macros.
1516  */
_read_macros_from(const char * filename)1517 static void _read_macros_from(const char* filename)
1518 {
1519     if (!file_exists(filename))
1520         return;
1521 
1522     string s;
1523     FileLineInput f(filename);
1524     keyseq key, action;
1525     bool keymap = false;
1526     KeymapContext keymc = KMC_DEFAULT;
1527 
1528     while (!f.eof())
1529     {
1530         s = f.get_line();
1531         trim_string(s);  // remove white space from ends
1532 
1533         if (s.empty() || s[0] == '#')
1534             continue;    // skip comments
1535         else if (s.substr(0, 2) == "K:")
1536         {
1537             key = parse_keyseq(s.substr(2));
1538             keymap = true;
1539             keymc  = KMC_DEFAULT;
1540         }
1541         else if (s.length() >= 3 && s[0] == 'K' && s[2] == ':')
1542         {
1543             const KeymapContext ctx = KeymapContext(KMC_DEFAULT + s[1] - '0');
1544             if (ctx >= KMC_DEFAULT && ctx < KMC_CONTEXT_COUNT)
1545             {
1546                 key    = parse_keyseq(s.substr(3));
1547                 keymap = true;
1548                 keymc  = ctx;
1549             }
1550         }
1551         else if (s.substr(0, 2) == "M:")
1552         {
1553             key = parse_keyseq(s.substr(2));
1554             keymap = false;
1555         }
1556         else if (s.substr(0, 2) == "A:")
1557         {
1558             action = parse_keyseq(s.substr(2));
1559             macro_add((keymap ? Keymaps[keymc] : Macros), key, action);
1560         }
1561     }
1562 }
1563 
1564 /*
1565  * Based on _read_macros_from(), reads macros or keymaps from rc files.
1566  *
1567  * @param field The orig_field (not lowercased) of the macro option (the
1568  * bit after "macros +=")
1569  *
1570  * @return The string of any errors which occurred, or "" if no error.
1571  * %s is the field argument.
1572  */
1573 
read_rc_file_macro(const string & field)1574 string read_rc_file_macro(const string& field)
1575 {
1576     const int first_space = field.find(' ');
1577 
1578     if (first_space < 0)
1579         return "Cannot parse marcos += %s , there is only one argument";
1580 
1581     // Start by deciding what context the macro/keymap is in
1582     const string context = field.substr(0, first_space);
1583 
1584     bool keymap = false;
1585     KeymapContext keymc = KMC_DEFAULT;
1586 
1587     if (context == "K")
1588     {
1589         keymap = true;
1590         keymc  = KMC_DEFAULT;
1591     }
1592     else if (context.length() >= 2 && context[0] == 'K')
1593     {
1594         const KeymapContext ctx =
1595               KeymapContext(KMC_DEFAULT + context[1] - '0');
1596 
1597         if (ctx >= KMC_DEFAULT && ctx < KMC_CONTEXT_COUNT)
1598         {
1599             keymap = true;
1600             keymc  = ctx;
1601         }
1602     }
1603     else if (context == "M")
1604         keymap = false;
1605     else
1606         return "'" + context
1607                    + "' is not a valid macro or keymap context (macros += %s)";
1608 
1609     // Now grab the key and action to be performed
1610     const string key_and_action = field.substr((first_space + 1));
1611 
1612     const int second_space = key_and_action.find(' ');
1613 
1614     if (second_space < 0)
1615         return "Cannot parse macros += %s , there are only two arguments";
1616 
1617     const string macro_key_string = key_and_action.substr(0, second_space);
1618     const string action_string = key_and_action.substr((second_space + 1));
1619 
1620 
1621     keyseq key = parse_keyseq(macro_key_string);
1622 
1623     keyseq action = parse_keyseq(action_string);
1624 
1625     macro_add((keymap ? Keymaps[keymc] : Macros), key, action);
1626 
1627     // If we didn't save here, macros in rc files would be saved iff you also
1628     // changed another macro with cntrl-D and saved at the exit prompt.
1629     // Consistent behavior works better.
1630     macro_save();
1631 
1632     return "";
1633 }
1634 
1635 // useful for debugging
keyseq_to_str(const keyseq & seq)1636 string keyseq_to_str(const keyseq &seq)
1637 {
1638     string s = "";
1639     for (auto k : seq)
1640     {
1641         if (k == '\n' || k == '\r')
1642             s += "newline";
1643         else if (k == '\t')
1644             s += "tab";
1645         else
1646             s += (char) k;
1647         s += ", ";
1648     }
1649     return s.size() == 0 ? s : s.substr(0, s.size() - 2);
1650 
1651 }
1652 
keycode_is_printable(int keycode)1653 bool keycode_is_printable(int keycode)
1654 {
1655     switch (keycode)
1656     {
1657     case  0:
1658     case  8:
1659     case  9:
1660     case 27:
1661     case '\n':
1662     case '\r':
1663         return false;
1664     default:
1665 #ifdef USE_TILE_LOCAL
1666         // the upper bound here is based on a comment in
1667         // windowmanager-sdl.cc:_translate_keysym. It could potentially be
1668         // applied more generally but I'm concerned about non-US keyboard
1669         // layouts etc. I'm also not sure how accurate it is for sdl...
1670         return keycode >= 32 && keycode < 256;
1671 #elif defined(USE_TILE_WEB)
1672         return keycode >= 32 && keycode < 128;
1673 #else
1674         return keycode >= 32;
1675 #endif
1676     }
1677 }
1678 
keycode_to_name(int keycode)1679 string keycode_to_name(int keycode)
1680 {
1681     // this is printable, but it's very confusing to try to use ' ' to print it
1682     // in circumstances where a name is called for
1683     if (keycode == ' ')
1684         return "Space";
1685     // TODO: handling of alt keys in SDL is generally a mess, including here
1686     // (they are basically just ignored)
1687     if (keycode_is_printable(keycode))
1688         return string(1, keycode);
1689 
1690     // placeholder
1691     switch (keycode)
1692     {
1693     case  0: return "NULL";
1694     case  8: return "Backspace"; // CK_BKSP
1695     case  9: return "Tab";
1696     case 27: return "Esc";
1697     case '\n':
1698     case '\r': // CK_ENTER
1699         return "Enter";
1700     case CK_DELETE: return "Del";
1701     case CK_UP:     return "Up";
1702     case CK_DOWN:   return "Down";
1703     case CK_LEFT:   return "Left";
1704     case CK_RIGHT:  return "Right";
1705     case CK_INSERT: return "Ins";
1706     case CK_HOME:   return "Home";
1707     case CK_CLEAR:  return "Clear";
1708     case CK_PGUP:   return "PgUp";
1709     case CK_PGDN:   return "PgDn";
1710     // shift/ctrl-modified keys aside from shift-tab don't seem to work on mac
1711     // console, and are somewhat spotty on webtiles.
1712     case CK_SHIFT_UP:     return "Shift-Up";
1713     case CK_SHIFT_DOWN:   return "Shift-Down";
1714     case CK_SHIFT_LEFT:   return "Shift-Left";
1715     case CK_SHIFT_RIGHT:  return "Shift-Right";
1716     case CK_SHIFT_INSERT: return "Shift-Ins";
1717     case CK_SHIFT_HOME:   return "Shift-Home";
1718     case CK_SHIFT_CLEAR:  return "Shift-Clear";
1719     case CK_SHIFT_PGUP:   return "Shift-PgUp";
1720     case CK_SHIFT_PGDN:   return "Shift-PgDn";
1721     case CK_SHIFT_TAB:    return "Shift-Tab";
1722     case CK_CTRL_UP:      return "^Up";
1723     case CK_CTRL_DOWN:    return "^Down";
1724     case CK_CTRL_LEFT:    return "^Left";
1725     case CK_CTRL_RIGHT:   return "^Right";
1726     case CK_CTRL_INSERT:  return "^Ins";
1727     case CK_CTRL_HOME:    return "^Home";
1728     case CK_CTRL_CLEAR:   return "^Clear";
1729     case CK_CTRL_PGUP:    return "^PgUp";
1730     case CK_CTRL_PGDN:    return "^PgDn";
1731     case CK_CTRL_TAB:     return "^Tab";
1732     case CK_F0:     return "F0";
1733     case CK_F1:     return "F1";
1734     case CK_F2:     return "F2";
1735     case CK_F3:     return "F3";
1736     case CK_F4:     return "F4";
1737     case CK_F5:     return "F5";
1738     case CK_F6:     return "F6";
1739     case CK_F7:     return "F7";
1740     case CK_F8:     return "F8";
1741     case CK_F9:     return "F9";
1742     case CK_F10:    return "F10";
1743     case CK_F11:    return "F11";
1744     case CK_F12:    return "F12";
1745     default:
1746 #ifdef USE_TILE_LOCAL
1747         // SDL uses 1 << 30 to indicate non-printable keys, crawl uses negative
1748         // numbers; convert back to plain SDL form
1749         if (keycode < 0)
1750             keycode = -keycode;
1751         // SDL_GetKeyName strips capitalization, so we don't want to use it for
1752         // printable keys.
1753         return string(SDL_GetKeyName(keycode));
1754 #else
1755     {
1756         if (keycode >= CONTROL('A') && keycode <= CONTROL('Z'))
1757             return make_stringf("^%c", UNCONTROL(keycode));
1758 
1759         keyseq v;
1760         v.push_back(keycode);
1761         return vtostr(v);
1762     }
1763 #endif
1764     }
1765 }
1766 
macro_init()1767 void macro_init()
1768 {
1769     for (const auto &fn : Options.additional_macro_files)
1770         _read_macros_from(fn.c_str());
1771 
1772     _read_macros_from(get_macro_file().c_str());
1773 }
1774 
macro_userfn(const char * keys,const char * regname)1775 void macro_userfn(const char *keys, const char *regname)
1776 {
1777     UNUSED(keys, regname);
1778     // TODO: Implement.
1779     // Converting 'keys' to a key sequence is the difficulty. Doing it portably
1780     // requires a mapping of key names to whatever getch() spits back, unlikely
1781     // to happen in a hurry.
1782 }
1783 
is_synthetic_key(int key)1784 bool is_synthetic_key(int key)
1785 {
1786     switch (key)
1787     {
1788     case KEY_MACRO_ENABLE_MORE:
1789     case KEY_MACRO_DISABLE_MORE:
1790     case KEY_MACRO_MORE_PROTECT:
1791 #ifdef USE_TILE
1792     case CK_MOUSE_CMD:
1793     case CK_REDRAW:
1794 #endif
1795         return true;
1796     default:
1797         return false;
1798     }
1799 }
1800 
key_recorder()1801 key_recorder::key_recorder()
1802     : paused(false)
1803 {
1804     keys.clear();
1805 }
1806 
add_key(int key,bool reverse)1807 void key_recorder::add_key(int key, bool reverse)
1808 {
1809     if (paused)
1810         return;
1811 
1812     if (reverse)
1813         keys.push_front(key);
1814     else
1815         keys.push_back(key);
1816 }
1817 
clear()1818 void key_recorder::clear()
1819 {
1820     keys.clear();
1821 }
1822 
pause_all_key_recorders()1823 pause_all_key_recorders::pause_all_key_recorders()
1824 {
1825     for (key_recorder *rec : recorders)
1826     {
1827         prev_pause_status.push_back(rec->paused);
1828         rec->paused = true;
1829     }
1830 }
1831 
~pause_all_key_recorders()1832 pause_all_key_recorders::~pause_all_key_recorders()
1833 {
1834     for (unsigned int i = 0; i < recorders.size(); i++)
1835         recorders[i]->paused = prev_pause_status[i];
1836 }
1837 
add_key_recorder(key_recorder * recorder)1838 void add_key_recorder(key_recorder* recorder)
1839 {
1840     ASSERT(find(begin(recorders), end(recorders), recorder) == end(recorders));
1841     recorders.push_back(recorder);
1842 }
1843 
remove_key_recorder(key_recorder * recorder)1844 void remove_key_recorder(key_recorder* recorder)
1845 {
1846     erase_val(recorders, recorder);
1847 }
1848 
get_macro_buf_size()1849 int get_macro_buf_size()
1850 {
1851     return Buffer.size();
1852 }
1853 
1854 ///////////////////////////////////////////////////////////////
1855 // Keybinding stuff
1856 
1857 #define VALID_BIND_COMMAND(cmd) (cmd > CMD_NO_CMD && cmd < CMD_MIN_SYNTHETIC)
1858 
init_keybindings()1859 void init_keybindings()
1860 {
1861     int i;
1862 
1863     for (i = 0; _command_name_list[i].cmd != CMD_NO_CMD
1864                 && _command_name_list[i].name != nullptr; i++)
1865     {
1866         command_name &data = _command_name_list[i];
1867 
1868         ASSERT(VALID_BIND_COMMAND(data.cmd));
1869         ASSERT(!_names_to_cmds.count(data.name));
1870         ASSERT(_cmds_to_names.find(data.cmd)  == _cmds_to_names.end());
1871 
1872         _names_to_cmds[data.name] = data.cmd;
1873         _cmds_to_names[data.cmd]  = data.name;
1874     }
1875 
1876     ASSERT(i >= 130);
1877 
1878     for (i = 0; _default_binding_list[i].cmd != CMD_NO_CMD
1879                 && _default_binding_list[i].key != '\0'; i++)
1880     {
1881         default_binding &data = _default_binding_list[i];
1882         ASSERT(VALID_BIND_COMMAND(data.cmd));
1883 
1884         KeymapContext context = context_for_command(data.cmd);
1885 
1886         ASSERT(context < KMC_CONTEXT_COUNT);
1887 
1888         key_to_cmd_map &key_map = _keys_to_cmds[context];
1889         cmd_to_key_map &cmd_map = _cmds_to_keys[context];
1890 
1891         // Only one command per key, but it's okay to have several
1892         // keys map to the same command.
1893         ASSERTM(!key_map.count(data.key), "bad mapping for key '%c'", data.key);
1894 
1895         key_map[data.key] = data.cmd;
1896         cmd_map[data.cmd] = data.key;
1897     }
1898 
1899     ASSERT(i >= 130);
1900 }
1901 
name_to_command(string name)1902 command_type name_to_command(string name)
1903 {
1904     return static_cast<command_type>(lookup(_names_to_cmds, name, CMD_NO_CMD));
1905 }
1906 
command_to_name(command_type cmd)1907 string command_to_name(command_type cmd)
1908 {
1909     return lookup(_cmds_to_names, cmd, "CMD_NO_CMD");
1910 }
1911 
key_to_command(int key,KeymapContext context)1912 command_type key_to_command(int key, KeymapContext context)
1913 {
1914     if (CMD_NO_CMD < -key && -key < CMD_MIN_SYNTHETIC)
1915     {
1916         const auto cmd = static_cast<command_type>(-key);
1917         const auto cmd_context = context_for_command(cmd);
1918 
1919         if (cmd == CMD_NO_CMD)
1920             return CMD_NO_CMD;
1921 
1922         if (cmd_context != context)
1923         {
1924             mprf(MSGCH_ERROR,
1925                  "key_to_command(): command '%s' (%d:%d) wrong for desired "
1926                  "context %d",
1927                  command_to_name(cmd).c_str(), -key - CMD_NO_CMD,
1928                  CMD_MAX_CMD + key, (int) context);
1929             if (is_processing_macro())
1930                 flush_input_buffer(FLUSH_ABORT_MACRO);
1931             if (crawl_state.is_replaying_keys()
1932                 || crawl_state.cmd_repeat_start)
1933             {
1934                 flush_input_buffer(FLUSH_KEY_REPLAY_CANCEL);
1935             }
1936             flush_input_buffer(FLUSH_BEFORE_COMMAND);
1937             return CMD_NO_CMD;
1938         }
1939         return cmd;
1940     }
1941 
1942     const auto cmd = static_cast<command_type>(lookup(_keys_to_cmds[context],
1943                                                       key, CMD_NO_CMD));
1944 
1945     ASSERT(cmd == CMD_NO_CMD || context_for_command(cmd) == context);
1946 
1947     return cmd;
1948 }
1949 
command_to_key(command_type cmd)1950 int command_to_key(command_type cmd)
1951 {
1952     KeymapContext context = context_for_command(cmd);
1953 
1954     if (context == KMC_NONE)
1955         return '\0';
1956 
1957     return lookup(_cmds_to_keys[context], cmd, '\0');
1958 }
1959 
context_for_command(command_type cmd)1960 KeymapContext context_for_command(command_type cmd)
1961 {
1962     if (cmd > CMD_NO_CMD && cmd <= CMD_MAX_NORMAL)
1963         return KMC_DEFAULT;
1964 
1965     if (cmd >= CMD_MIN_OVERMAP && cmd <= CMD_MAX_OVERMAP)
1966         return KMC_LEVELMAP;
1967 
1968     if (cmd >= CMD_MIN_TARGET && cmd <= CMD_MAX_TARGET)
1969         return KMC_TARGETING;
1970 
1971 #ifdef USE_TILE
1972     if (cmd >= CMD_MIN_DOLL && cmd <= CMD_MAX_DOLL)
1973         return KMC_DOLL;
1974 #endif
1975 
1976     return KMC_NONE;
1977 }
1978 
bind_command_to_key(command_type cmd,int key)1979 void bind_command_to_key(command_type cmd, int key)
1980 {
1981     KeymapContext context = context_for_command(cmd);
1982     string   command_name = command_to_name(cmd);
1983 
1984     if (context == KMC_NONE || command_name == "CMD_NO_CMD"
1985         || !VALID_BIND_COMMAND(cmd))
1986     {
1987         if (command_name == "CMD_NO_CMD")
1988         {
1989             mprf(MSGCH_ERROR, "Cannot bind command #%d to a key.",
1990                  (int) cmd);
1991             return;
1992         }
1993 
1994         mprf(MSGCH_ERROR, "Cannot bind command '%s' to a key.",
1995              command_name.c_str());
1996         return;
1997     }
1998 
1999     if (is_userfunction(key))
2000     {
2001         mprf(MSGCH_ERROR, "Cannot bind user function keys to a command.");
2002         return;
2003     }
2004 
2005     if (is_synthetic_key(key))
2006     {
2007         mprf(MSGCH_ERROR, "Cannot bind synthetic keys to a command.");
2008         return;
2009     }
2010 
2011     // We're good.
2012     key_to_cmd_map &key_map = _keys_to_cmds[context];
2013     cmd_to_key_map &cmd_map = _cmds_to_keys[context];
2014 
2015     key_map[key] = cmd;
2016     cmd_map[cmd] = key;
2017 }
2018 
_special_keys_to_string(int key)2019 static string _special_keys_to_string(int key)
2020 {
2021     const bool shift = (key >= CK_SHIFT_UP && key <= CK_SHIFT_PGDN);
2022     const bool ctrl  = (key >= CK_CTRL_UP && key <= CK_CTRL_PGDN);
2023 
2024     string cmd = "";
2025 
2026     if (shift)
2027     {
2028         key -= (CK_SHIFT_UP - CK_UP);
2029         cmd = "Shift-";
2030     }
2031     else if (ctrl)
2032     {
2033         key -= (CK_CTRL_UP - CK_UP);
2034         cmd = "Ctrl-";
2035     }
2036 
2037     switch (key)
2038     {
2039     case CK_ENTER:  cmd += "Enter"; break;
2040     case CK_BKSP:   cmd += "Backspace"; break;
2041     CASE_ESCAPE     cmd += "Esc"; break;
2042     case CK_DELETE: cmd += "Del"; break;
2043     case CK_UP:     cmd += "Up"; break;
2044     case CK_DOWN:   cmd += "Down"; break;
2045     case CK_LEFT:   cmd += "Left"; break;
2046     case CK_RIGHT:  cmd += "Right"; break;
2047     case CK_INSERT: cmd += "Ins"; break;
2048     case CK_HOME:   cmd += "Home"; break;
2049     case CK_END:    cmd += "End"; break;
2050     case CK_CLEAR:  cmd += "Clear"; break;
2051     case CK_PGUP:   cmd += "PgUp"; break;
2052     case CK_PGDN:   cmd += "PgDn"; break;
2053     case CK_F0:     return "F0";
2054     case CK_F1:     return "F1";
2055     case CK_F2:     return "F2";
2056     case CK_F3:     return "F3";
2057     case CK_F4:     return "F4";
2058     case CK_F5:     return "F5";
2059     case CK_F6:     return "F6";
2060     case CK_F7:     return "F7";
2061     case CK_F8:     return "F8";
2062     case CK_F9:     return "F9";
2063     case CK_F10:    return "F10";
2064     case CK_F11:    return "F11";
2065     case CK_F12:    return "F12";
2066     }
2067 
2068     return cmd;
2069 }
2070 
command_to_string(command_type cmd,bool tutorial)2071 string command_to_string(command_type cmd, bool tutorial)
2072 {
2073     const int key = command_to_key(cmd);
2074 
2075     const string desc = _special_keys_to_string(key);
2076     if (!desc.empty())
2077         return desc;
2078 
2079     string result;
2080     if (key >= 32 && key < 256)
2081     {
2082         if (tutorial && key >= 'A' && key <= 'Z')
2083             result = make_stringf("uppercase %c", (char) key);
2084         else
2085             result = string(1, (char) key);
2086     }
2087     else if (key > 1000 && key <= 1009)
2088     {
2089         const int numpad = (key - 1000);
2090         result = make_stringf("Numpad %d", numpad);
2091     }
2092 #ifdef USE_TILE_LOCAL
2093     // SDL allows control modifiers for some extra punctuation
2094     else if (key < 0 && key > SDLK_EXCLAIM - SDLK_a + 1)
2095         result = make_stringf("Ctrl-%c", (char) (key + SDLK_a - 1));
2096 #endif
2097     else
2098     {
2099         const int ch = key + 'A' - 1;
2100         if (ch >= 'A' && ch <= 'Z')
2101             result = make_stringf("Ctrl-%c", (char) ch);
2102         else
2103             result = to_string(key);
2104     }
2105 
2106     return result;
2107 }
2108 
insert_commands(string & desc,const vector<command_type> & cmds,bool formatted)2109 void insert_commands(string &desc, const vector<command_type> &cmds, bool formatted)
2110 {
2111     desc = untag_tiles_console(desc);
2112     for (command_type cmd : cmds)
2113     {
2114         const string::size_type found = desc.find("%");
2115         if (found == string::npos)
2116             break;
2117 
2118         string command_name = command_to_string(cmd);
2119         if (formatted && command_name == "<")
2120             command_name += "<";
2121         else if (command_name == "%")
2122             command_name = "percent";
2123 
2124         desc.replace(found, 1, command_name);
2125     }
2126     desc = replace_all(desc, "percent", "%");
2127 }
2128