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