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