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