openMSX
HotKey.cc
Go to the documentation of this file.
1 #include "HotKey.hh"
2 #include "InputEventFactory.hh"
4 #include "CommandException.hh"
5 #include "EventDistributor.hh"
6 #include "CliComm.hh"
7 #include "InputEvents.hh"
8 #include "XMLElement.hh"
9 #include "TclArgParser.hh"
10 #include "TclObject.hh"
11 #include "SettingsConfig.hh"
12 #include "outer.hh"
13 #include "ranges.hh"
14 #include "view.hh"
15 #include "build-info.hh"
16 #include <cassert>
17 #include <memory>
18 
19 using std::string;
20 using std::vector;
21 using std::make_shared;
22 
23 // This file implements all Tcl key bindings. These are the 'classical' hotkeys
24 // (e.g. F11 to (un)mute sound) and the more recent input layers. The idea
25 // behind an input layer is something like an OSD widget that (temporarily)
26 // takes semi-exclusive access to the input. So while the widget is active
27 // keyboard (and joystick) input is no longer passed to the emulated MSX.
28 // However the classical hotkeys or the openMSX console still receive input.
29 
30 namespace openmsx {
31 
32 const bool META_HOT_KEYS =
33 #ifdef __APPLE__
34  true;
35 #else
36  false;
37 #endif
38 
40  GlobalCommandController& commandController_,
41  EventDistributor& eventDistributor_)
42  : RTSchedulable(rtScheduler)
43  , bindCmd (commandController_, *this, false)
44  , bindDefaultCmd (commandController_, *this, true)
45  , unbindCmd (commandController_, *this, false)
46  , unbindDefaultCmd(commandController_, *this, true)
47  , activateCmd (commandController_)
48  , deactivateCmd (commandController_)
49  , commandController(commandController_)
50  , eventDistributor(eventDistributor_)
51 {
52  initDefaultBindings();
53 
54  eventDistributor.registerEventListener(
56  eventDistributor.registerEventListener(
58  eventDistributor.registerEventListener(
60  eventDistributor.registerEventListener(
62  eventDistributor.registerEventListener(
64  eventDistributor.registerEventListener(
66  eventDistributor.registerEventListener(
68  eventDistributor.registerEventListener(
70  eventDistributor.registerEventListener(
72  eventDistributor.registerEventListener(
74  eventDistributor.registerEventListener(
76  eventDistributor.registerEventListener(
78  eventDistributor.registerEventListener(
80 }
81 
83 {
86  eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this);
90  eventDistributor.unregisterEventListener(OPENMSX_JOY_HAT_EVENT, *this);
91  eventDistributor.unregisterEventListener(OPENMSX_MOUSE_WHEEL_EVENT, *this);
94  eventDistributor.unregisterEventListener(OPENMSX_MOUSE_MOTION_EVENT, *this);
95  eventDistributor.unregisterEventListener(OPENMSX_KEY_UP_EVENT, *this);
96  eventDistributor.unregisterEventListener(OPENMSX_KEY_DOWN_EVENT, *this);
97 }
98 
99 void HotKey::initDefaultBindings()
100 {
101  // TODO move to Tcl script?
102 
103  if (META_HOT_KEYS) {
104  // Hot key combos using Mac's Command key.
105  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
107  "screenshot -guess-name"));
108  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
110  "toggle pause"));
111  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
113  "toggle throttle"));
114  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
116  "toggle console"));
117  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
119  "toggle mute"));
120  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
122  "toggle fullscreen"));
123  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
125  "exit"));
126  } else {
127  // Hot key combos for typical PC keyboards.
128  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_PRINT),
129  "screenshot -guess-name"));
130  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_PAUSE),
131  "toggle pause"));
132  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_F9),
133  "toggle throttle"));
134  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_F10),
135  "toggle console"));
136  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_F11),
137  "toggle mute"));
138  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_F12),
139  "toggle fullscreen"));
140  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
142  "exit"));
143  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
145  "exit"));
146  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(
148  "toggle fullscreen"));
149  // and for Android
150  bindDefault(HotKeyInfo(make_shared<KeyDownEvent>(Keys::K_BACK),
151  "quitmenu::quit_menu"));
152  }
153 }
154 
155 static HotKey::EventPtr createEvent(const TclObject& obj, Interpreter& interp)
156 {
157  auto event = InputEventFactory::createInputEvent(obj, interp);
158  if (!dynamic_cast<const KeyEvent*> (event.get()) &&
159  !dynamic_cast<const MouseButtonEvent*> (event.get()) &&
160  !dynamic_cast<const GroupEvent*> (event.get()) &&
161  !dynamic_cast<const JoystickEvent*> (event.get()) &&
162  !dynamic_cast<const OsdControlEvent*> (event.get()) &&
163  !dynamic_cast<const FocusEvent*> (event.get())) {
164  throw CommandException("Unsupported event type");
165  }
166  return event;
167 }
168 static HotKey::EventPtr createEvent(const string& str, Interpreter& interp)
169 {
170  return createEvent(TclObject(str), interp);
171 }
172 
173 void HotKey::loadBindings(const XMLElement& config)
174 {
175  // restore default bindings
176  unboundKeys.clear();
177  boundKeys.clear();
178  cmdMap = defaultMap;
179 
180  // load bindings
181  auto* bindingsElement = config.findChild("bindings");
182  if (!bindingsElement) return;
183  auto copy = *bindingsElement; // dont iterate over changing container
184  for (auto& elem : copy.getChildren()) {
185  try {
186  auto& interp = commandController.getInterpreter();
187  if (elem.getName() == "bind") {
188  bind(HotKeyInfo(createEvent(elem.getAttribute("key"), interp),
189  elem.getData(),
190  elem.getAttributeAsBool("repeat", false),
191  elem.getAttributeAsBool("event", false)));
192  } else if (elem.getName() == "unbind") {
193  unbind(createEvent(elem.getAttribute("key"), interp));
194  }
195  } catch (MSXException& e) {
196  commandController.getCliComm().printWarning(
197  "Error while loading key bindings: ", e.getMessage());
198  }
199  }
200 }
201 
202 struct EqualEvent {
203  EqualEvent(const Event& event_) : event(event_) {}
204  bool operator()(const HotKey::EventPtr& e) const {
205  return event == *e;
206  }
207  bool operator()(const HotKey::HotKeyInfo& info) const {
208  return event == *info.event;
209  }
210  const Event& event;
211 };
212 
213 void HotKey::saveBindings(XMLElement& config) const
214 {
215  auto& bindingsElement = config.getCreateChild("bindings");
216  bindingsElement.removeAllChildren();
217 
218  // add explicit bind's
219  for (auto& k : boundKeys) {
220  auto& info = *find_if_unguarded(cmdMap, EqualEvent(*k));
221  auto& elem = bindingsElement.addChild("bind", info.command);
222  elem.addAttribute("key", k->toString());
223  if (info.repeat) {
224  elem.addAttribute("repeat", "true");
225  }
226  if (info.passEvent) {
227  elem.addAttribute("event", "true");
228  }
229  }
230  // add explicit unbind's
231  for (auto& k : unboundKeys) {
232  auto& elem = bindingsElement.addChild("unbind");
233  elem.addAttribute("key", k->toString());
234  }
235 }
236 
237 template<typename T>
238 static bool contains(const vector<T>& v, const Event& event)
239 {
240  return ranges::any_of(v, EqualEvent(event));
241 }
242 
243 template<typename T>
244 static void erase(vector<T>& v, const Event& event)
245 {
246  auto it = ranges::find_if(v, EqualEvent(event));
247  if (it != end(v)) move_pop_back(v, it);
248 }
249 
250 static void insert(HotKey::KeySet& set, HotKey::EventPtr event)
251 {
252  auto it = ranges::find_if(set, EqualEvent(*event));
253  if (it == end(set)) {
254  set.push_back(event);
255  } else {
256  *it = event;
257  }
258 }
259 
260 template<typename HotKeyInfo>
261 static void insert(HotKey::BindMap& map, HotKeyInfo&& info)
262 {
263  auto it = ranges::find_if(map, EqualEvent(*info.event));
264  if (it == end(map)) {
265  map.push_back(std::forward<HotKeyInfo>(info));
266  } else {
267  *it = std::forward<HotKeyInfo>(info);
268  }
269 }
270 
271 void HotKey::bind(HotKeyInfo&& info)
272 {
273  erase(unboundKeys, *info.event);
274  erase(defaultMap, *info.event);
275  insert(boundKeys, info.event);
276  insert(cmdMap, std::move(info));
277 
278  saveBindings(commandController.getSettingsConfig().getXMLElement());
279 }
280 
281 void HotKey::unbind(const EventPtr& event)
282 {
283  auto it1 = ranges::find_if(boundKeys, EqualEvent(*event));
284  if (it1 == end(boundKeys)) {
285  // only when not a regular bound event
286  insert(unboundKeys, event);
287  } else {
288  //erase(boundKeys, *event);
289  move_pop_back(boundKeys, it1);
290  }
291 
292  erase(defaultMap, *event);
293  erase(cmdMap, *event);
294 
295  saveBindings(commandController.getSettingsConfig().getXMLElement());
296 }
297 
298 void HotKey::bindDefault(HotKeyInfo&& info)
299 {
300  if (!contains( boundKeys, *info.event) &&
301  !contains(unboundKeys, *info.event)) {
302  // not explicity bound or unbound
303  insert(cmdMap, info);
304  }
305  insert(defaultMap, std::move(info));
306 }
307 
308 void HotKey::unbindDefault(const EventPtr& event)
309 {
310  if (!contains( boundKeys, *event) &&
311  !contains(unboundKeys, *event)) {
312  // not explicity bound or unbound
313  erase(cmdMap, *event);
314  }
315  erase(defaultMap, *event);
316 }
317 
318 void HotKey::bindLayer(HotKeyInfo&& info, const string& layer)
319 {
320  insert(layerMap[layer], std::move(info));
321 }
322 
323 void HotKey::unbindLayer(const EventPtr& event, const string& layer)
324 {
325  erase(layerMap[layer], *event);
326 }
327 
328 void HotKey::unbindFullLayer(const string& layer)
329 {
330  layerMap.erase(layer);
331 }
332 
333 void HotKey::activateLayer(std::string layer, bool blocking)
334 {
335  // Insert new activattion record at the end of the list.
336  // (it's not an error if the same layer was already active, in such
337  // as case it will now appear twice in the list of active layer,
338  // and it must also be deactivated twice).
339  activeLayers.push_back({std::move(layer), blocking});
340 }
341 
342 void HotKey::deactivateLayer(string_view layer)
343 {
344  // remove the first matching activation record from the end
345  // (it's not an error if there is no match at all)
346  auto it = ranges::find_if(view::reverse(activeLayers),
347  [&](auto& info) { return info.layer == layer; });
348  if (it != activeLayers.rend()) {
349  // 'reverse_iterator' -> 'iterator' conversion is a bit tricky
350  activeLayers.erase((it + 1).base());
351  }
352 }
353 
354 static HotKey::BindMap::const_iterator findMatch(
355  const HotKey::BindMap& map, const Event& event)
356 {
357  return ranges::find_if(map, [&](auto& p) {
358  return p.event->matches(event);
359  });
360 }
361 
362 void HotKey::executeRT()
363 {
364  if (lastEvent) executeEvent(lastEvent);
365 }
366 
367 int HotKey::signalEvent(const EventPtr& event)
368 {
369  if (lastEvent != event) {
370  // If the newly received event is different from the repeating
371  // event, we stop the repeat process.
372  // Except when we're repeating a OsdControlEvent and the
373  // received event was actually the 'generating' event for the
374  // Osd event. E.g. a cursor-keyboard-down event will generate
375  // a corresponding osd event (the osd event is send before the
376  // original event). Without this hack, key-repeat will not work
377  // for osd key bindings.
378  if (lastEvent && lastEvent->isRepeatStopper(*event)) {
379  stopRepeat();
380  }
381  }
382  return executeEvent(event);
383 }
384 
385 int HotKey::executeEvent(const EventPtr& event)
386 {
387  // First search in active layers (from back to front)
388  bool blocking = false;
389  for (auto& info : view::reverse(activeLayers)) {
390  auto& cmap = layerMap[info.layer]; // ok, if this entry doesn't exist yet
391  auto it = findMatch(cmap, *event);
392  if (it != end(cmap)) {
393  executeBinding(event, *it);
394  // Deny event to MSX listeners, also don't pass event
395  // to other layers (including the default layer).
396  return EventDistributor::MSX;
397  }
398  blocking = info.blocking;
399  if (blocking) break; // don't try lower layers
400  }
401 
402  // If the event was not yet handled, try the default layer.
403  auto it = findMatch(cmdMap, *event);
404  if (it != end(cmdMap)) {
405  executeBinding(event, *it);
406  return EventDistributor::MSX; // deny event to MSX listeners
407  }
408 
409  // Event is not handled, only let it pass to the MSX if there was no
410  // blocking layer active.
411  return blocking ? EventDistributor::MSX : 0;
412 }
413 
414 void HotKey::executeBinding(const EventPtr& event, const HotKeyInfo& info)
415 {
416  if (info.repeat) {
417  startRepeat(event);
418  }
419  try {
420  // Make a copy of the command string because executing the
421  // command could potentially execute (un)bind commands so
422  // that the original string becomes invalid.
423  // Valgrind complained about this in the following scenario:
424  // - open the OSD menu
425  // - activate the 'Exit openMSX' item
426  // The latter is triggered from e.g. a 'OSDControl A PRESS'
427  // event. The Tcl script bound to that event closes the main
428  // menu and reopens a new quit_menu. This will re-bind the
429  // action for the 'OSDControl A PRESS' event.
430  TclObject command(info.command);
431  if (info.passEvent) {
432  // Add event as the last argument to the command.
433  // (If needed) the current command string is first
434  // converted to a list (thus split in a command name
435  // and arguments).
436  command.addListElement(event->toTclList());
437  }
438 
439  // ignore return value
440  command.executeCommand(commandController.getInterpreter());
441  } catch (CommandException& e) {
442  commandController.getCliComm().printWarning(
443  "Error executing hot key command: ", e.getMessage());
444  }
445 }
446 
447 void HotKey::startRepeat(const EventPtr& event)
448 {
449  // I initially thought about using the builtin SDL key-repeat feature,
450  // but that won't work for example on joystick buttons. So we have to
451  // code it ourselves.
452 
453  // On android, because of the sensitivity of the touch screen it's
454  // very hard to have touches of short durations. So half a second is
455  // too short for the key-repeat-delay. A full second should be fine.
456  static const unsigned DELAY = PLATFORM_ANDROID ? 1000 : 500;
457  // Repeat period.
458  static const unsigned PERIOD = 30;
459 
460  unsigned delay = (lastEvent ? PERIOD : DELAY) * 1000;
461  lastEvent = event;
462  scheduleRT(delay);
463 }
464 
465 void HotKey::stopRepeat()
466 {
467  lastEvent.reset();
468  cancelRT();
469 }
470 
471 
472 // class BindCmd
473 
474 static string_view getBindCmdName(bool defaultCmd)
475 {
476  return defaultCmd ? "bind_default" : "bind";
477 }
478 
479 HotKey::BindCmd::BindCmd(CommandController& commandController_, HotKey& hotKey_,
480  bool defaultCmd_)
481  : Command(commandController_, getBindCmdName(defaultCmd_))
482  , hotKey(hotKey_)
483  , defaultCmd(defaultCmd_)
484 {
485 }
486 
487 string HotKey::BindCmd::formatBinding(const HotKey::HotKeyInfo& info)
488 {
489  return strCat(info.event->toString(), (info.repeat ? " [repeat]" : ""),
490  (info.passEvent ? " [event]" : ""), ": ", info.command, '\n');
491 }
492 
493 void HotKey::BindCmd::execute(span<const TclObject> tokens, TclObject& result)
494 {
495  string layer;
496  bool layers = false;
497  bool repeat = false;
498  bool passEvent = false;
499  ArgsInfo parserInfo[] = {
500  valueArg("-layer", layer),
501  flagArg("-layers", layers),
502  flagArg("-repeat", repeat),
503  flagArg("-event", passEvent),
504  };
505  auto arguments = parseTclArgs(getInterpreter(), tokens.subspan<1>(), parserInfo);
506  if (defaultCmd && !layer.empty()) {
507  throw CommandException("Layers are not supported for default bindings");
508  }
509 
510  auto& cMap = defaultCmd
511  ? hotKey.defaultMap
512  : layer.empty() ? hotKey.cmdMap
513  : hotKey.layerMap[layer];
514 
515  if (layers) {
516  for (auto& p : hotKey.layerMap) {
517  // An alternative for this test is to always properly
518  // prune layerMap. ATM this approach seems simpler.
519  if (!p.second.empty()) {
520  result.addListElement(p.first);
521  }
522  }
523  return;
524  }
525 
526  switch (arguments.size()) {
527  case 0: {
528  // show all bounded keys (for this layer)
529  string r;
530  for (auto& p : cMap) {
531  r += formatBinding(p);
532  }
533  result = r;
534  break;
535  }
536  case 1: {
537  // show bindings for this key (in this layer)
538  auto it = ranges::find_if(cMap,
539  EqualEvent(*createEvent(arguments[0], getInterpreter())));
540  if (it == end(cMap)) {
541  throw CommandException("Key not bound");
542  }
543  result = formatBinding(*it);
544  break;
545  }
546  default: {
547  // make a new binding
548  string command = arguments[1].getString().str();
549  for (const auto& arg : view::drop(arguments, 2)) {
550  strAppend(command, ' ', arg.getString());
551  }
552  HotKey::HotKeyInfo info(
553  createEvent(arguments[0], getInterpreter()),
554  command, repeat, passEvent);
555  if (defaultCmd) {
556  hotKey.bindDefault(std::move(info));
557  } else if (layer.empty()) {
558  hotKey.bind(std::move(info));
559  } else {
560  hotKey.bindLayer(std::move(info), layer);
561  }
562  break;
563  }
564  }
565 }
566 string HotKey::BindCmd::help(const vector<string>& /*tokens*/) const
567 {
568  auto cmd = getBindCmdName(defaultCmd);
569  return strCat(
570  cmd, " : show all bounded keys\n",
571  cmd, " <key> : show binding for this key\n",
572  cmd, " <key> [-repeat] [-event] <cmd> : bind key to command, optionally "
573  "repeat command while key remains pressed and also optionally "
574  "give back the event as argument (a list) to <cmd>\n"
575  "These 3 take an optional '-layer <layername>' option, "
576  "see activate_input_layer.\n",
577  cmd, " -layers : show a list of layers with bound keys\n");
578 }
579 
580 
581 // class UnbindCmd
582 
583 static string getUnbindCmdName(bool defaultCmd)
584 {
585  return defaultCmd ? "unbind_default" : "unbind";
586 }
587 
588 HotKey::UnbindCmd::UnbindCmd(CommandController& commandController_,
589  HotKey& hotKey_, bool defaultCmd_)
590  : Command(commandController_, getUnbindCmdName(defaultCmd_))
591  , hotKey(hotKey_)
592  , defaultCmd(defaultCmd_)
593 {
594 }
595 
596 void HotKey::UnbindCmd::execute(span<const TclObject> tokens, TclObject& /*result*/)
597 {
598  string layer;
599  ArgsInfo info[] = { valueArg("-layer", layer) };
600  auto arguments = parseTclArgs(getInterpreter(), tokens.subspan<1>(), info);
601  if (defaultCmd && !layer.empty()) {
602  throw CommandException("Layers are not supported for default bindings");
603  }
604 
605  if ((arguments.size() > 1) || (layer.empty() && (arguments.size() != 1))) {
606  throw SyntaxError();
607  }
608 
609  HotKey::EventPtr event;
610  if (arguments.size() == 1) {
611  event = createEvent(arguments[0], getInterpreter());
612  }
613 
614  if (defaultCmd) {
615  assert(event);
616  hotKey.unbindDefault(event);
617  } else if (layer.empty()) {
618  assert(event);
619  hotKey.unbind(event);
620  } else {
621  if (event) {
622  hotKey.unbindLayer(event, layer);
623  } else {
624  hotKey.unbindFullLayer(layer);
625  }
626  }
627 }
628 string HotKey::UnbindCmd::help(const vector<string>& /*tokens*/) const
629 {
630  auto cmd = getUnbindCmdName(defaultCmd);
631  return strCat(
632  cmd, " <key> : unbind this key\n",
633  cmd, " -layer <layername> <key> : unbind key in a specific layer\n",
634  cmd, " -layer <layername> : unbind all keys in this layer\n");
635 }
636 
637 
638 // class ActivateCmd
639 
640 HotKey::ActivateCmd::ActivateCmd(CommandController& commandController_)
641  : Command(commandController_, "activate_input_layer")
642 {
643 }
644 
645 void HotKey::ActivateCmd::execute(span<const TclObject> tokens, TclObject& result)
646 {
647  bool blocking = false;
648  ArgsInfo info[] = { flagArg("-blocking", blocking) };
649  auto args = parseTclArgs(getInterpreter(), tokens.subspan(1), info);
650 
651  auto& hotKey = OUTER(HotKey, activateCmd);
652  switch (args.size()) {
653  case 0: {
654  string r;
655  for (auto& layerInfo : view::reverse(hotKey.activeLayers)) {
656  r += layerInfo.layer;
657  if (layerInfo.blocking) {
658  r += " -blocking";
659  }
660  r += '\n';
661  }
662  result = r;
663  break;
664  }
665  case 1: {
666  string_view layer = args[0].getString();
667  hotKey.activateLayer(layer.str(), blocking);
668  break;
669  }
670  default:
671  throw SyntaxError();
672  }
673 }
674 
675 string HotKey::ActivateCmd::help(const vector<string>& /*tokens*/) const
676 {
677  return "activate_input_layer "
678  ": show list of active layers (most recent on top)\n"
679  "activate_input_layer [-blocking] <layername> "
680  ": activate new layer, optionally in blocking mode\n";
681 }
682 
683 
684 // class DeactivateCmd
685 
686 HotKey::DeactivateCmd::DeactivateCmd(CommandController& commandController_)
687  : Command(commandController_, "deactivate_input_layer")
688 {
689 }
690 
691 void HotKey::DeactivateCmd::execute(span<const TclObject> tokens, TclObject& /*result*/)
692 {
693  checkNumArgs(tokens, 2, "layer");
694  auto& hotKey = OUTER(HotKey, deactivateCmd);
695  hotKey.deactivateLayer(tokens[1].getString());
696 }
697 
698 string HotKey::DeactivateCmd::help(const vector<string>& /*tokens*/) const
699 {
700  return "deactivate_input_layer <layername> : deactive the given input layer";
701 }
702 
703 
704 } // namespace openmsx
KeyCode combine(KeyCode key, KeyCode modifier)
Convenience method to create key combinations (hides ugly casts).
Definition: Keys.hh:232
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:149
std::vector< HotKeyInfo > BindMap
Definition: HotKey.hh:36
const std::string & getMessage() const &
Definition: MSXException.hh:23
bool operator()(const HotKey::HotKeyInfo &info) const
Definition: HotKey.cc:207
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
bool operator()(const HotKey::EventPtr &e) const
Definition: HotKey.cc:204
Definition: span.hh:34
const XMLElement * findChild(string_view name) const
Definition: XMLElement.cc:94
ArgsInfo flagArg(string_view name, bool &flag)
Definition: TclArgParser.hh:72
ArgsInfo valueArg(string_view name, T &value)
Definition: TclArgParser.hh:85
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:191
const bool META_HOT_KEYS
Definition: HotKey.cc:32
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:648
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
void loadBindings(const XMLElement &config)
Definition: HotKey.cc:173
auto reverse(Range &&range)
Definition: view.hh:306
HotKey(RTScheduler &rtScheduler, GlobalCommandController &commandController, EventDistributor &eventDistributor)
Definition: HotKey.cc:39
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:125
EqualEvent(const Event &event_)
Definition: HotKey.cc:203
std::shared_ptr< const Event > EventPtr
Definition: HotKey.hh:24
void scheduleRT(uint64_t delta)
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
EventPtr createInputEvent(const TclObject &str, Interpreter &interp)
void addListElement(T t)
Definition: TclObject.hh:121
std::string str() const
Definition: string_view.cc:12
XMLElement & getCreateChild(string_view name, string_view defaultValue={})
Definition: XMLElement.cc:148
std::vector< EventPtr > KeySet
Definition: HotKey.hh:37
void saveBindings(XMLElement &config) const
Definition: HotKey.cc:213
ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to &#39;find_if&#39; when it&#39;s guaranteed that the predicate will be true for at least one...
Definition: stl.hh:145
auto drop(Range &&range, size_t n)
Definition: view.hh:294
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
const Event & event
Definition: HotKey.cc:210
#define OUTER(type, member)
Definition: outer.hh:38
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition: TclObject.cc:172
void printWarning(string_view message)
Definition: CliComm.cc:20
auto end(const string_view &x)
Definition: string_view.hh:152
std::vector< TclObject > parseTclArgs(Interpreter &interp, span< const TclObject > inArgs, span< const ArgsInfo > table)