LCOV - code coverage report
Current view: top level - source/ps - Hotkey.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 142 200 71.0 %
Date: 2022-06-14 00:41:00 Functions: 10 16 62.5 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : #include "Hotkey.h"
      20             : 
      21             : #include <boost/tokenizer.hpp>
      22             : 
      23             : #include "lib/external_libraries/libsdl.h"
      24             : #include "ps/CConsole.h"
      25             : #include "ps/CLogger.h"
      26             : #include "ps/CStr.h"
      27             : #include "ps/ConfigDB.h"
      28             : #include "ps/Globals.h"
      29             : #include "ps/KeyName.h"
      30             : 
      31             : static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
      32             : 
      33             : std::unordered_map<int, KeyMapping> g_HotkeyMap;
      34             : 
      35             : namespace {
      36             :     std::unordered_map<std::string, bool> g_HotkeyStatus;
      37             : 
      38             :     struct PressedHotkey
      39             :     {
      40          40 :         PressedHotkey(const SHotkeyMapping* m, bool t) : mapping(m), retriggered(t) {};
      41             :         // NB: this points to one of g_HotkeyMap's mappings. It works because that std::unordered_map is stable once constructed.
      42             :         const SHotkeyMapping* mapping;
      43             :         // Whether the hotkey was triggered by a key release (silences "press" and "up" events).
      44             :         bool retriggered;
      45             :     };
      46             : 
      47             :     struct ReleasedHotkey
      48             :     {
      49          19 :         ReleasedHotkey(const char* n, bool t) : name(n), wasRetriggered(t) {};
      50             :         const char* name;
      51             :         bool wasRetriggered;
      52             :     };
      53             : 
      54             :     // 'In-flight' state used because the hotkey triggering process is split in two phase.
      55             :     // These hotkeys may still be stopped if the event responsible for triggering them is handled
      56             :     // before it can be used to generate the hotkeys.
      57             :     std::vector<PressedHotkey> newPressedHotkeys;
      58             :     // Stores the 'specificity' of the newly pressed hotkeys.
      59             :     size_t closestMapMatch = 0;
      60             :     // This is merely used to ensure consistency in EventWillFireHotkey.
      61             :     const SDL_Event_* currentEvent;
      62             : 
      63             :     // List of currently pressed hotkeys. This is used to quickly reset hotkeys.
      64             :     // This is an unsorted vector because there will generally be very few elements,
      65             :     // so it's presumably faster than std::set.
      66             :     std::vector<PressedHotkey> pressedHotkeys;
      67             : 
      68             :     // List of active keys relevant for hotkeys.
      69             :     std::vector<SDL_Scancode_> activeScancodes;
      70             : }
      71             : 
      72             : static_assert(std::is_integral<std::underlying_type<SDL_Scancode>::type>::value, "SDL_Scancode is not an integral enum.");
      73             : static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT");
      74             : static_assert(UNUSED_HOTKEY_CODE == SDL_SCANCODE_UNKNOWN);
      75             : 
      76             : // Look up each key binding in the config file and set the mappings for
      77             : // all key combinations that trigger it.
      78           4 : static void LoadConfigBindings(CConfigDB& configDB)
      79             : {
      80          16 :     for (const std::pair<const CStr, CConfigValueSet>& configPair : configDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."))
      81             :     {
      82          24 :         std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix
      83             : 
      84             :         // "unused" is kept or the A23->24 migration, this can likely be removed in A25.
      85          24 :         if (configPair.second.empty() || (configPair.second.size() == 1 && configPair.second.front() == "unused"))
      86             :         {
      87             :             // Unused hotkeys must still be registered in the map to appear in the hotkey editor.
      88           0 :             SHotkeyMapping unusedCode;
      89           0 :             unusedCode.name = hotkeyName;
      90           0 :             unusedCode.primary = SKey{ UNUSED_HOTKEY_CODE };
      91           0 :             g_HotkeyMap[UNUSED_HOTKEY_CODE].push_back(unusedCode);
      92           0 :             continue;
      93             :         }
      94             : 
      95          54 :         for (const CStr& hotkey : configPair.second)
      96             :         {
      97          30 :             std::vector<SKey> keyCombination;
      98             : 
      99             :             // Iterate through multiple-key bindings (e.g. Ctrl+I)
     100          30 :             boost::char_separator<char> sep("+");
     101          15 :             typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
     102          30 :             tokenizer tok(hotkey, sep);
     103         105 :             for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
     104             :             {
     105             :                 // Attempt decode as key name
     106          75 :                 SDL_Scancode scancode = FindScancode(it->c_str());
     107          25 :                 if (!scancode)
     108             :                 {
     109           0 :                     LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
     110           0 :                     continue;
     111             :                 }
     112             : 
     113          25 :                 SKey key = { scancode };
     114          25 :                 keyCombination.push_back(key);
     115             :             }
     116             : 
     117          15 :             std::vector<SKey>::iterator itKey, itKey2;
     118          55 :             for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey)
     119             :             {
     120          50 :                 SHotkeyMapping bindCode;
     121             : 
     122          25 :                 bindCode.name = hotkeyName;
     123          25 :                 bindCode.primary = SKey{ itKey->code };
     124             : 
     125          99 :                 for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2)
     126          98 :                     if (itKey != itKey2) // Push any auxiliary keys
     127          24 :                         bindCode.requires.push_back(*itKey2);
     128             : 
     129          25 :                 g_HotkeyMap[itKey->code].push_back(bindCode);
     130             :             }
     131             :         }
     132             :     }
     133           4 : }
     134             : 
     135           4 : void LoadHotkeys(CConfigDB& configDB)
     136             : {
     137           4 :     pressedHotkeys.clear();
     138           4 :     LoadConfigBindings(configDB);
     139           4 : }
     140             : 
     141           5 : void UnloadHotkeys()
     142             : {
     143           5 :     pressedHotkeys.clear();
     144           5 :     g_HotkeyMap.clear();
     145           5 :     g_HotkeyStatus.clear();
     146           5 : }
     147             : 
     148          85 : bool isPressed(const SKey& key)
     149             : {
     150             :     // Normal keycodes are below EXTRA_KEYS_BASE
     151          85 :     if ((int)key.code < EXTRA_KEYS_BASE)
     152          85 :         return g_scancodes[key.code];
     153             :     // Mouse 'keycodes' are after the modifier keys
     154           0 :     else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE)
     155           0 :         return g_mouse_buttons[key.code - MOUSE_BASE];
     156             :     // Modifier keycodes are between the normal keys and the mouse 'keys'
     157           0 :     else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES)
     158           0 :         return unified[key.code - UNIFIED_SHIFT];
     159             :     // This codepath shouldn't be taken, but not having it triggers warnings.
     160             :     else
     161             :         return false;
     162             : }
     163             : 
     164          78 : InReaction HotkeyStateChange(const SDL_Event_* ev)
     165             : {
     166          78 :     if (ev->ev.type == SDL_HOTKEYPRESS || ev->ev.type == SDL_HOTKEYPRESS_SILENT)
     167          66 :         g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true;
     168          45 :     else if (ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYUP_SILENT)
     169          38 :         g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false;
     170          78 :     return IN_PASS;
     171             : }
     172             : 
     173          44 : InReaction HotkeyInputPrepHandler(const SDL_Event_* ev)
     174             : {
     175          44 :     int scancode = SDL_SCANCODE_UNKNOWN;
     176             : 
     177             :     // Restore default state.
     178          44 :     newPressedHotkeys.clear();
     179          44 :     currentEvent = nullptr;
     180             : 
     181          44 :     switch(ev->ev.type)
     182             :     {
     183          40 :     case SDL_KEYDOWN:
     184          40 :     case SDL_KEYUP:
     185          40 :         scancode = ev->ev.key.keysym.scancode;
     186          40 :         break;
     187             : 
     188           0 :     case SDL_MOUSEBUTTONDOWN:
     189           0 :     case SDL_MOUSEBUTTONUP:
     190             :         // Mousewheel events are no longer buttons, but we want to maintain the order
     191             :         // expected by g_mouse_buttons for compatibility
     192           0 :         if (ev->ev.button.button >= SDL_BUTTON_X1)
     193           0 :             scancode = MOUSE_BASE + (int)ev->ev.button.button + 2;
     194             :         else
     195           0 :             scancode = MOUSE_BASE + (int)ev->ev.button.button;
     196             :         break;
     197             : 
     198           0 :     case SDL_MOUSEWHEEL:
     199           0 :         if (ev->ev.wheel.y > 0)
     200             :         {
     201           0 :             scancode = MOUSE_WHEELUP;
     202           0 :             break;
     203             :         }
     204           0 :         else if (ev->ev.wheel.y < 0)
     205             :         {
     206           0 :             scancode = MOUSE_WHEELDOWN;
     207           0 :             break;
     208             :         }
     209           0 :         else if (ev->ev.wheel.x > 0)
     210             :         {
     211           0 :             scancode = MOUSE_X2;
     212           0 :             break;
     213             :         }
     214           0 :         else if (ev->ev.wheel.x < 0)
     215             :         {
     216           0 :             scancode = MOUSE_X1;
     217           0 :             break;
     218             :         }
     219             :         return IN_PASS;
     220             : 
     221             : 
     222             :     default:
     223             :         return IN_PASS;
     224             :     }
     225             : 
     226             :     // Somewhat hackish:
     227             :     // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed
     228             :     // Just send them to this handler; don't let the imaginary event codes leak back to real SDL.
     229             : 
     230          40 :     SDL_Event_ phantom;
     231          40 :     phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP;
     232          40 :     if (phantom.ev.type == SDL_KEYDOWN)
     233          22 :         phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0;
     234             : 
     235          40 :     if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
     236             :     {
     237           0 :         phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SHIFT);
     238           0 :         unified[0] = (phantom.ev.type == SDL_KEYDOWN);
     239           0 :         return HotkeyInputPrepHandler(&phantom);
     240             :     }
     241          40 :     else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
     242             :     {
     243           0 :         phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_CTRL);
     244           0 :         unified[1] = (phantom.ev.type == SDL_KEYDOWN);
     245           0 :         return HotkeyInputPrepHandler(&phantom);
     246             :     }
     247          40 :     else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
     248             :     {
     249           0 :         phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_ALT);
     250           0 :         unified[2] = (phantom.ev.type == SDL_KEYDOWN);
     251           0 :         return HotkeyInputPrepHandler(&phantom);
     252             :     }
     253          40 :     else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
     254             :     {
     255           0 :         phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SUPER);
     256           0 :         unified[3] = (phantom.ev.type == SDL_KEYDOWN);
     257           0 :         return HotkeyInputPrepHandler(&phantom);
     258             :     }
     259             : 
     260             :     // Check whether we have any hotkeys registered that include this scancode.
     261         120 :     if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end())
     262             :         return IN_PASS;
     263             : 
     264          40 :     currentEvent = ev;
     265             : 
     266             :     /**
     267             :      * Hotkey behaviour spec (see also tests):
     268             :      *  - If both 'F' and 'Ctrl+F' are hotkeys, and Ctrl & F keys are down, then the more specific one only is fired ('Ctrl+F' here).
     269             :      *  - If 'Ctrl+F' and 'Ctrl+A' are both hotkeys, both may fire simulatenously (respectively without Ctrl).
     270             :      *    - However, per the first point, 'Ctrl+Shift+F' would fire alone in that situation.
     271             :      *  - "Press" is sent once, when the hotkey is initially triggered.
     272             :      *  - "Up" is sent once, when the hotkey is released or superseded by a more specific hotkey.
     273             :      *  - "Down" is sent repeatedly, and is also sent alongside the inital "Press".
     274             :      *    - As a special case (see below), "Down" is not sent alongside "PressSilent".
     275             :      *  - If 'Ctrl+F' is active, and 'Ctrl' is released, 'F' must become active again.
     276             :      *    - However, the "Press" event is _not_ fired. Instead, "PressSilent" is.
     277             :      *    - Likewise, once 'F' is released, the "Up" event will be a "UpSilent".
     278             :      *      (the reason is that it is unexpected to trigger a press on key release).
     279             :      *  - Hotkeys are allowed to fire with extra keys (e.g. Ctrl+F+A still triggers 'Ctrl+F').
     280             :      *  - If 'F' and 'Ctrl+F' trigger the same hotkey, adding 'Ctrl' _and_ releasing 'Ctrl' will trigger new 'Press' events.
     281             :      *    The "Up" event is only sent when both Ctrl & F are released.
     282             :      *    - This is somewhat unexpected/buggy, but it makes the implementation easier and is easily avoidable for players.
     283             :      *  - Wheel scrolling is 'instantaneous' behaviour and is essentially entirely separate from the above.
     284             :      *    - It won't untrigger other hotkeys, and fires/releases on the same 'key event'.
     285             :      * Note that mouse buttons/wheel inputs can fire hotkeys, in combinations with keys.
     286             :      * ...Yes, this is all surprisingly complex.
     287             :      */
     288             : 
     289          40 :     bool isReleasedKey = ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_MOUSEBUTTONUP;
     290             :     // Wheel events are pressed & released in the same go.
     291          40 :     bool isInstantaneous = ev->ev.type == SDL_MOUSEWHEEL;
     292             : 
     293          40 :     if (!isInstantaneous)
     294             :     {
     295         120 :         std::vector<SDL_Scancode_>::iterator it = std::find(activeScancodes.begin(), activeScancodes.end(), scancode);
     296             :         // This prevents duplicates, assuming we might end up in a weird state - feels safer with input.
     297          40 :         if (isReleasedKey && it != activeScancodes.end())
     298          54 :             activeScancodes.erase(it);
     299          22 :         else if (!isReleasedKey && it == activeScancodes.end())
     300          19 :             activeScancodes.emplace_back(scancode);
     301             :     }
     302             : 
     303          40 :     std::vector<SDL_Scancode_> triggers;
     304          40 :     if (!isReleasedKey || isInstantaneous)
     305          22 :         triggers.push_back(scancode);
     306             :     else
     307             :         // If the key is released, we need to check all less precise hotkeys again, to see if we should retrigger some.
     308          76 :         for (SDL_Scancode_ code : activeScancodes)
     309          22 :             triggers.push_back(code);
     310             : 
     311             :     // Now check if we need to trigger new hotkeys / retrigger hotkeys.
     312             :     // We'll need the match-level and the keys in play to release currently pressed hotkeys.
     313          40 :     closestMapMatch = 0;
     314         164 :     for (SDL_Scancode_ code : triggers)
     315         123 :         for (const SHotkeyMapping& hotkey : g_HotkeyMap[code])
     316             :         {
     317             :             // Ensure no duplications in the new list.
     318         158 :             if (std::find_if(newPressedHotkeys.begin(), newPressedHotkeys.end(),
     319         363 :                              [&hotkey](const PressedHotkey& v){ return v.mapping->name == hotkey.name; }) != newPressedHotkeys.end())
     320             :                 continue;
     321             : 
     322          72 :             bool accept = true;
     323         233 :             for (const SKey& k : hotkey.requires)
     324             :             {
     325          54 :                 accept = isPressed(k);
     326          54 :                 if (!accept)
     327             :                     break;
     328             :             }
     329          72 :             if (!accept)
     330             :                 continue;
     331             : 
     332             :             // Check if this is an equally precise or more precise match
     333          70 :             if (hotkey.requires.size() + 1 >= closestMapMatch)
     334             :             {
     335             :                 // Check if more precise
     336          35 :                 if (hotkey.requires.size() + 1 > closestMapMatch)
     337             :                 {
     338             :                     // Throw away the old less-precise matches
     339          31 :                     newPressedHotkeys.clear();
     340          31 :                     closestMapMatch = hotkey.requires.size() + 1;
     341             :                 }
     342          35 :                 newPressedHotkeys.emplace_back(&hotkey, isReleasedKey);
     343             :             }
     344             :         }
     345             : 
     346          40 :     return IN_PASS;
     347             : }
     348             : 
     349          44 : InReaction HotkeyInputActualHandler(const SDL_Event_* ev)
     350             : {
     351          44 :     if (!currentEvent)
     352             :         return IN_PASS;
     353             : 
     354          40 :     bool isInstantaneous = ev->ev.type == SDL_MOUSEWHEEL;
     355             : 
     356             :     // TODO: it's probably possible to break hotkeys somewhat if the "Up" event that would release a hotkey is handled
     357             :     // by a priori handler - it might be safer to do that in the 'Prep' phase.
     358          40 :     std::vector<ReleasedHotkey> releasedHotkeys;
     359             : 
     360             :     // For instantaneous events, we don't update the pressedHotkeys (i.e. currently active hotkeys),
     361             :     // we just fire/release the triggered hotkeys transiently.
     362             :     // Therefore, skip the whole 'check pressedHotkeys & swap with newPressedHotkeys' logic.
     363          40 :     if (!isInstantaneous)
     364             :     {
     365         153 :         for (PressedHotkey& hotkey : pressedHotkeys)
     366             :         {
     367          33 :             bool addingAnew = std::find_if(newPressedHotkeys.begin(), newPressedHotkeys.end(),
     368         159 :                                            [&hotkey](const PressedHotkey& v){ return v.mapping->name == hotkey.mapping->name; }) != newPressedHotkeys.end();
     369             : 
     370             :             // Update the triggered status to match our current state.
     371          33 :             if (addingAnew)
     372           9 :                 std::find_if(newPressedHotkeys.begin(), newPressedHotkeys.end(),
     373          37 :                              [&hotkey](const PressedHotkey& v){ return v.mapping->name == hotkey.mapping->name; })->retriggered = hotkey.retriggered;
     374             :             // If the already-pressed hotkey has a lower specificity than the new hotkey(s), de-activate it.
     375          48 :             else if (hotkey.mapping->requires.size() + 1 < closestMapMatch)
     376             :             {
     377          10 :                 releasedHotkeys.emplace_back(hotkey.mapping->name.c_str(), hotkey.retriggered);
     378           5 :                 continue;
     379             :             }
     380             : 
     381             :             // Check that the hotkey still matches all active keys.
     382          28 :             bool accept = isPressed(hotkey.mapping->primary);
     383          28 :             if (accept)
     384          41 :                 for (const SKey& k : hotkey.mapping->requires)
     385             :                 {
     386           3 :                     accept = isPressed(k);
     387           3 :                     if (!accept)
     388             :                         break;
     389             :                 }
     390          28 :             if (!accept && !addingAnew)
     391          28 :                 releasedHotkeys.emplace_back(hotkey.mapping->name.c_str(), hotkey.retriggered);
     392          14 :             else if (accept)
     393             :             {
     394             :                 // If this hotkey has higher specificity than the new hotkeys we wanted to trigger/retrigger,
     395             :                 // then discard this new addition(s). This works because at any given time, all hotkeys
     396             :                 // active must have the same specificity.
     397          24 :                 if (hotkey.mapping->requires.size() + 1 > closestMapMatch)
     398             :                 {
     399           3 :                     closestMapMatch = hotkey.mapping->requires.size() + 1;
     400           3 :                     newPressedHotkeys.clear();
     401           3 :                     newPressedHotkeys.emplace_back(hotkey.mapping, hotkey.retriggered);
     402             :                 }
     403           9 :                 else if (!addingAnew)
     404           2 :                     newPressedHotkeys.emplace_back(hotkey.mapping, hotkey.retriggered);
     405             :             }
     406             :         }
     407             : 
     408          40 :         pressedHotkeys.swap(newPressedHotkeys);
     409             :     }
     410             : 
     411         194 :     for (const PressedHotkey& hotkey : isInstantaneous ? newPressedHotkeys : pressedHotkeys)
     412             :     {
     413             :         // Send a KeyPress event when a hotkey is pressed initially and on mouseButton and mouseWheel events.
     414          34 :         if (ev->ev.type != SDL_KEYDOWN || ev->ev.key.repeat == 0)
     415             :         {
     416          33 :             SDL_Event_ hotkeyPressNotification;
     417          33 :             hotkeyPressNotification.ev.type = hotkey.retriggered ? SDL_HOTKEYPRESS_SILENT : SDL_HOTKEYPRESS;
     418          33 :             hotkeyPressNotification.ev.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
     419          33 :             in_push_priority_event(&hotkeyPressNotification);
     420             :         }
     421             : 
     422             :         // Send a HotkeyDown event on every key, mouseButton and mouseWheel event.
     423             :         // The exception is on the first retriggering: hotkeys may fire transiently
     424             :         // while a user lifts fingers off multi-key hotkeys, and listeners to "hotkeydown"
     425             :         // generally don't expect that to trigger then.
     426             :         // (It might be better to check for HotkeyIsPressed, however).
     427             :         // For keys the event is repeated depending on hardware and OS configured interval.
     428             :         // On linux, modifier keys (shift, alt, ctrl) are not repeated, see https://github.com/SFML/SFML/issues/122.
     429          34 :         if (ev->ev.key.repeat == 0 && hotkey.retriggered)
     430          11 :             continue;
     431          23 :         SDL_Event_ hotkeyDownNotification;
     432          23 :         hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN;
     433          23 :         hotkeyDownNotification.ev.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
     434          23 :         in_push_priority_event(&hotkeyDownNotification);
     435             :     }
     436             : 
     437             :     // Release instantaneous events (e.g. mouse wheel) right away.
     438          40 :     if (isInstantaneous)
     439           0 :         for (const PressedHotkey& hotkey : newPressedHotkeys)
     440           0 :             releasedHotkeys.emplace_back(hotkey.mapping->name.c_str(), false);
     441             : 
     442         139 :     for (const ReleasedHotkey& hotkey : releasedHotkeys)
     443             :     {
     444          19 :         SDL_Event_ hotkeyNotification;
     445          19 :         hotkeyNotification.ev.type = hotkey.wasRetriggered ? SDL_HOTKEYUP_SILENT : SDL_HOTKEYUP;
     446          19 :         hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkey.name);
     447          19 :         in_push_priority_event(&hotkeyNotification);
     448             :     }
     449             : 
     450          40 :     return IN_PASS;
     451             : }
     452             : 
     453           0 : bool EventWillFireHotkey(const SDL_Event_* ev, const CStr& keyname)
     454             : {
     455             :     // Sanity check of sort. This parameter mostly exists because it looks right from the caller's perspective.
     456           0 :     if (ev != currentEvent || !currentEvent)
     457             :         return false;
     458             : 
     459           0 :     return std::find_if(newPressedHotkeys.begin(), newPressedHotkeys.end(),
     460           0 :         [&keyname](const PressedHotkey& v){ return v.mapping->name == keyname; }) != newPressedHotkeys.end();
     461             : }
     462             : 
     463           0 : void ResetActiveHotkeys()
     464             : {
     465           0 :     newPressedHotkeys.clear();
     466           0 :     for (const PressedHotkey& hotkey : pressedHotkeys)
     467             :     {
     468           0 :         SDL_Event_ hotkeyNotification;
     469           0 :         hotkeyNotification.ev.type = hotkey.retriggered ? SDL_HOTKEYUP_SILENT : SDL_HOTKEYUP;
     470           0 :         hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
     471           0 :         in_push_priority_event(&hotkeyNotification);
     472             :     }
     473           0 :     pressedHotkeys.clear();
     474           0 :     activeScancodes.clear();
     475           0 :     currentEvent = nullptr;
     476           0 : }
     477             : 
     478          92 : bool HotkeyIsPressed(const CStr& keyname)
     479             : {
     480          92 :     return g_HotkeyStatus[keyname];
     481             : }

Generated by: LCOV version 1.13