Index: ps/trunk/binaries/data/mods/public/gui/common/OverlayCounter.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/OverlayCounter.js +++ ps/trunk/binaries/data/mods/public/gui/common/OverlayCounter.js @@ -12,7 +12,7 @@ registerConfigChangeHandler(this.onConfigChange.bind(this)); if (this.Hotkey) - Engine.SetGlobalHotkey(this.Hotkey, this.toggle.bind(this)); + Engine.SetGlobalHotkey(this.Hotkey, "Press", this.toggle.bind(this)); } onConfigChange(changes) Index: ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.xml +++ ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.xml @@ -1,11 +1,11 @@ - selectNextTab(1); + selectNextTab(1); - selectNextTab(-1); + selectNextTab(-1); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/Buttons/CivInfoButton.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/Buttons/CivInfoButton.js +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/Buttons/CivInfoButton.js @@ -15,8 +15,8 @@ "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree") }); - Engine.SetGlobalHotkey("structree", this.openPage.bind(this, "page_structree.xml")); - Engine.SetGlobalHotkey("civinfo", this.openPage.bind(this, "page_civinfo.xml")); + Engine.SetGlobalHotkey("structree", "Press", this.openPage.bind(this, "page_structree.xml")); + Engine.SetGlobalHotkey("civinfo", "Press", this.openPage.bind(this, "page_civinfo.xml")); } onPress() Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsTabs.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsTabs.js +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsTabs.js @@ -16,7 +16,7 @@ colorizeHotkey("\n" + this.HotkeyUpTooltip, this.ConfigNameHotkeyUp); setupWindow.registerLoadHandler(this.onLoad.bind(this)); - Engine.SetGlobalHotkey("cancel", selectPanel); + Engine.SetGlobalHotkey("cancel", "Press", selectPanel); } registerTabsResizeHandler(handler) Index: ps/trunk/binaries/data/mods/public/gui/lobby/LeaderboardPage/LeaderboardPage.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/LeaderboardPage/LeaderboardPage.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/LeaderboardPage/LeaderboardPage.js @@ -29,7 +29,7 @@ openPage() { this.leaderboardPage.hidden = false; - Engine.SetGlobalHotkey("cancel", this.onPressClose.bind(this)); + Engine.SetGlobalHotkey("cancel", "Press", this.onPressClose.bind(this)); Engine.SendGetBoardList(); let playerName = this.leaderboardList.selectedPlayer(); Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/QuitButton.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/QuitButton.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/QuitButton.js @@ -17,10 +17,10 @@ if (dialog) { - Engine.SetGlobalHotkey("lobby", onPress); - Engine.SetGlobalHotkey("cancel", onPress); + Engine.SetGlobalHotkey("lobby", "Press", onPress); + Engine.SetGlobalHotkey("cancel", "Press", onPress); - let cancelHotkey = Engine.SetGlobalHotkey.bind(Engine, "cancel", onPress); + let cancelHotkey = Engine.SetGlobalHotkey.bind(Engine, "cancel", "Press", onPress); leaderboardPage.registerClosePageHandler(cancelHotkey); profilePage.registerClosePageHandler(cancelHotkey); } Index: ps/trunk/binaries/data/mods/public/gui/lobby/ProfilePage/ProfilePage.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/ProfilePage/ProfilePage.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/ProfilePage/ProfilePage.js @@ -37,7 +37,7 @@ openPage() { this.profilePage.hidden = false; - Engine.SetGlobalHotkey("cancel", this.onPressClose.bind(this)); + Engine.SetGlobalHotkey("cancel", "Press", this.onPressClose.bind(this)); } onPressLookup() Index: ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItemHandler.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItemHandler.js +++ ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItemHandler.js @@ -85,7 +85,7 @@ { let item = menuItems[i]; if (item.onPress && item.hotkey) - Engine.SetGlobalHotkey(item.hotkey, () => { + Engine.SetGlobalHotkey(item.hotkey, "Press", () => { this.closeSubmenu(); item.onPress(); }); Index: ps/trunk/binaries/data/mods/public/gui/session/RangeOverlayManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/RangeOverlayManager.js +++ ps/trunk/binaries/data/mods/public/gui/session/RangeOverlayManager.js @@ -12,7 +12,7 @@ for (let type of this.Types) { this.setEnabled(type, this.isEnabled(type)); - Engine.SetGlobalHotkey(type.hotkey, this.toggle.bind(this, type)); + Engine.SetGlobalHotkey(type.hotkey, "Press", this.toggle.bind(this, type)); } registerConfigChangeHandler(this.onConfigChange.bind(this)); Index: ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js +++ ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js @@ -40,9 +40,9 @@ registerPlayerAssignmentsChangeHandler(updater); playerViewControl.registerViewedPlayerChangeHandler(updater); - Engine.SetGlobalHotkey("chat", this.openPage.bind(this)); - Engine.SetGlobalHotkey("privatechat", this.openPage.bind(this)); - Engine.SetGlobalHotkey("teamchat", () => { this.openPage(g_IsObserver ? "/observers" : "/allies"); }); + Engine.SetGlobalHotkey("chat", "Press", this.openPage.bind(this)); + Engine.SetGlobalHotkey("privatechat", "Press", this.openPage.bind(this)); + Engine.SetGlobalHotkey("teamchat", "Press", () => { this.openPage(g_IsObserver ? "/observers" : "/allies"); }); } /** @@ -81,7 +81,7 @@ submitChat(text, command = "") { if (command.startsWith("/msg ")) - Engine.SetGlobalHotkey("privatechat", () => { this.openPage(command); }); + Engine.SetGlobalHotkey("privatechat", "Press", () => { this.openPage(command); }); let msg = command ? command + " " + text : text; Index: ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml +++ ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml @@ -32,19 +32,19 @@ - performCommand(g_Selection.toList().map(ent => GetEntityState(ent)), "delete"); + performCommand(g_Selection.toList().map(ent => GetEntityState(ent)), "delete"); - unloadAll(); + unloadAll(); - stopUnits(g_Selection.toList()); + stopUnits(g_Selection.toList()); - backToWork(); + backToWork(); @@ -54,15 +54,15 @@ - findIdleUnit(g_MilitaryTypes); + findIdleUnit(g_MilitaryTypes); - findIdleUnit(["!Domestic"]); + findIdleUnit(["!Domestic"]); - clearSelection(); + clearSelection(); Index: ps/trunk/binaries/data/mods/public/gui/session/hotkeys/training.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/hotkeys/training.xml +++ ps/trunk/binaries/data/mods/public/gui/session/hotkeys/training.xml @@ -2,36 +2,36 @@ - addTrainingByPosition(0); + addTrainingByPosition(0); - addTrainingByPosition(1); + addTrainingByPosition(1); - addTrainingByPosition(2); + addTrainingByPosition(2); - addTrainingByPosition(3); + addTrainingByPosition(3); - addTrainingByPosition(4); + addTrainingByPosition(4); - addTrainingByPosition(5); + addTrainingByPosition(5); - addTrainingByPosition(6); + addTrainingByPosition(6); Index: ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js +++ ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js @@ -6,7 +6,8 @@ constructor(playerViewControl, idleClasses) { this.idleWorkerButton = Engine.GetGUIObjectByName("idleWorkerButton"); - this.idleWorkerButton.onPress = this.onPress.bind(this); + this.idleWorkerButton.onKeyDown = this.onKeyDown.bind(this); + this.idleWorkerButton.onMouseLeftPress = this.onKeyDown.bind(this); this.idleClasses = idleClasses; registerHotkeyChangeHandler(this.onHotkeyChange.bind(this)); @@ -30,7 +31,7 @@ }); } - onPress() + onKeyDown() { findIdleUnit(this.idleClasses); } Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js +++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js @@ -18,8 +18,8 @@ playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this)); registerHotkeyChangeHandler(this.rebuild.bind(this)); - Engine.SetGlobalHotkey("civinfo", () => this.openPage("page_civinfo.xml")); - Engine.SetGlobalHotkey("structree", () => this.openPage("page_structree.xml")); + Engine.SetGlobalHotkey("structree", "Press", this.openPage.bind(this, "page_structree.xml")); + Engine.SetGlobalHotkey("civinfo", "Press", this.openPage.bind(this, "page_civinfo.xml")); } onPress() Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/summary/summary.xml +++ ps/trunk/binaries/data/mods/public/gui/summary/summary.xml @@ -17,11 +17,11 @@ - selectNextTab(1); + selectNextTab(1); - selectNextTab(-1); + selectNextTab(-1); Index: ps/trunk/source/graphics/CameraController.cpp =================================================================== --- ps/trunk/source/graphics/CameraController.cpp +++ ps/trunk/source/graphics/CameraController.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -640,7 +640,19 @@ { switch (ev->ev.type) { + case SDL_HOTKEYPRESS: + { + std::string hotkey = static_cast(ev->ev.user.data1); + if (hotkey == "camera.reset") + { + ResetCameraAngleZoom(); + return IN_HANDLED; + } + return IN_PASS; + } + case SDL_HOTKEYDOWN: + { std::string hotkey = static_cast(ev->ev.user.data1); // Mouse wheel must be treated using events instead of polling, @@ -698,11 +710,8 @@ m_ViewZoomSpeed /= m_ViewZoomSpeedModifier; return IN_HANDLED; } - else if (hotkey == "camera.reset") - { - ResetCameraAngleZoom(); - return IN_HANDLED; - } + return IN_PASS; + } } return IN_PASS; Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp +++ ps/trunk/source/graphics/GameView.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -378,10 +378,9 @@ { switch(ev->ev.type) { - - case SDL_HOTKEYDOWN: + case SDL_HOTKEYPRESS: + { std::string hotkey = static_cast(ev->ev.user.data1); - if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) @@ -407,6 +406,7 @@ return IN_HANDLED; } } + } return m->CameraController->HandleEvent(ev); } Index: ps/trunk/source/gui/CGUI.h =================================================================== --- ps/trunk/source/gui/CGUI.h +++ ps/trunk/source/gui/CGUI.h @@ -136,8 +136,8 @@ /** * Allows the JS side to add or remove global hotkeys. */ - void SetGlobalHotkey(const CStr& hotkeyTag, JS::HandleValue function); - void UnsetGlobalHotkey(const CStr& hotkeyTag); + void SetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName, JS::HandleValue function); + void UnsetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName); /** * Return the object which is an ancestor of every other GUI object. @@ -606,10 +606,11 @@ std::map > m_HotkeyObjects; /** - * Map from hotkey names to functions that are triggered if the hotkey is pressed. - * Contrary to object hotkeys, this allows for only one global function per hotkey name. + * Map from hotkey names to maps of eventNames to functions that are triggered + * when the hotkey goes through the event. Contrary to object hotkeys, this + * allows for only one global function per hotkey name per event type. */ - std::map m_GlobalHotkeys; + std::map> m_GlobalHotkeys; /** * XML and JS can subscribe handlers to events identified by these names. @@ -619,6 +620,7 @@ static const CStr EventNameLoad; static const CStr EventNameTick; static const CStr EventNamePress; + static const CStr EventNameKeyDown; static const CStr EventNameRelease; static const CStr EventNameMouseRightPress; static const CStr EventNameMouseLeftPress; Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp +++ ps/trunk/source/gui/CGUI.cpp @@ -50,6 +50,7 @@ const CStr CGUI::EventNameLoad = "Load"; const CStr CGUI::EventNameTick = "Tick"; const CStr CGUI::EventNamePress = "Press"; +const CStr CGUI::EventNameKeyDown = "KeyDown"; const CStr CGUI::EventNameRelease = "Release"; const CStr CGUI::EventNameMouseRightPress = "MouseRightPress"; const CStr CGUI::EventNameMouseLeftPress = "MouseLeftPress"; @@ -86,11 +87,13 @@ { InReaction ret = IN_PASS; - if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP) + if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYPRESS || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); - if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && ev->ev.type == SDL_HOTKEYDOWN) + const CStr& eventName = ev->ev.type == SDL_HOTKEYPRESS ? EventNamePress : ev->ev.type == SDL_HOTKEYDOWN ? EventNameKeyDown : EventNameRelease; + + if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && m_GlobalHotkeys[hotkey].find(eventName) != m_GlobalHotkeys[hotkey].end()) { ret = IN_HANDLED; @@ -98,15 +101,17 @@ JSAutoRequest rq(cx); JS::RootedObject globalObj(cx, &GetGlobalObject().toObject()); JS::RootedValue result(cx); - JS_CallFunctionValue(cx, globalObj, m_GlobalHotkeys[hotkey], JS::HandleValueArray::empty(), &result); + JS_CallFunctionValue(cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result); } std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { - if (ev->ev.type == SDL_HOTKEYDOWN) + if (ev->ev.type == SDL_HOTKEYPRESS) ret = obj->SendEvent(GUIM_PRESSED, EventNamePress); + else if (ev->ev.type == SDL_HOTKEYDOWN) + ret = obj->SendEvent(GUIM_KEYDOWN, EventNameKeyDown); else ret = obj->SendEvent(GUIM_RELEASED, EventNameRelease); } @@ -400,7 +405,7 @@ assignment.end()); } -void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, JS::HandleValue function) +void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName, JS::HandleValue function) { JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); @@ -411,19 +416,33 @@ return; } + // Only support "Press", "Keydown" and "Release" events. + if (eventName != EventNamePress && eventName != EventNameKeyDown && eventName != EventNameRelease) + { + JS_ReportError(cx, "Cannot assign a function to an unsupported event!"); + return; + } + if (!function.isObject() || !JS_ObjectIsFunction(cx, &function.toObject())) { JS_ReportError(cx, "Cannot assign non-function value to global hotkey '%s'", hotkeyTag.c_str()); return; } - UnsetGlobalHotkey(hotkeyTag); - m_GlobalHotkeys[hotkeyTag].init(cx, function); + UnsetGlobalHotkey(hotkeyTag, eventName); + m_GlobalHotkeys[hotkeyTag][eventName].init(cx, function); } -void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag) +void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName) { - m_GlobalHotkeys.erase(hotkeyTag); + std::map>::iterator it = m_GlobalHotkeys.find(hotkeyTag); + if (it == m_GlobalHotkeys.end()) + return; + + m_GlobalHotkeys[hotkeyTag].erase(eventName); + + if (m_GlobalHotkeys.count(hotkeyTag) == 0) + m_GlobalHotkeys.erase(it); } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const Index: ps/trunk/source/gui/SGUIMessage.h =================================================================== --- ps/trunk/source/gui/SGUIMessage.h +++ ps/trunk/source/gui/SGUIMessage.h @@ -43,6 +43,7 @@ GUIM_MOUSE_WHEEL_DOWN, GUIM_SETTINGS_UPDATED, // SGUIMessage.m_Value = name of setting GUIM_PRESSED, + GUIM_KEYDOWN, GUIM_RELEASED, GUIM_DOUBLE_PRESSED, GUIM_MOUSE_MOTION, Index: ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h +++ ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,8 +27,8 @@ void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData); void PopGuiPage(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args); JS::Value GetGUIObjectByName(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); - void SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function); - void UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag); + void SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, const std::string& eventName, JS::HandleValue function); + void UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, const std::string& eventName); std::wstring SetCursor(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name); void ResetCursor(ScriptInterface::CxPrivate* pCxPrivate); bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); Index: ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp +++ ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -61,16 +61,16 @@ return JS::ObjectValue(*guiObj->GetJSObject()); } -void JSI_GUIManager::SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function) +void JSI_GUIManager::SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, const std::string& eventName, JS::HandleValue function) { CGUI* guiPage = static_cast(pCxPrivate->pCBData); - guiPage->SetGlobalHotkey(hotkeyTag, function); + guiPage->SetGlobalHotkey(hotkeyTag, eventName, function); } -void JSI_GUIManager::UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag) +void JSI_GUIManager::UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, const std::string& eventName) { CGUI* guiPage = static_cast(pCxPrivate->pCBData); - guiPage->UnsetGlobalHotkey(hotkeyTag); + guiPage->UnsetGlobalHotkey(hotkeyTag, eventName); } std::wstring JSI_GUIManager::SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name) @@ -99,8 +99,8 @@ { scriptInterface.RegisterFunction("PushGuiPage"); scriptInterface.RegisterFunction("SwitchGuiPage"); - scriptInterface.RegisterFunction("SetGlobalHotkey"); - scriptInterface.RegisterFunction("UnsetGlobalHotkey"); + scriptInterface.RegisterFunction("SetGlobalHotkey"); + scriptInterface.RegisterFunction("UnsetGlobalHotkey"); scriptInterface.RegisterFunction("PopGuiPage"); scriptInterface.RegisterFunction("GetGUIObjectByName"); scriptInterface.RegisterFunction("SetCursor"); Index: ps/trunk/source/gui/tests/test_GuiManager.h =================================================================== --- ps/trunk/source/gui/tests/test_GuiManager.h +++ ps/trunk/source/gui/tests/test_GuiManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -74,6 +74,7 @@ SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_KEYDOWN; hotkeyNotification.ev.key.keysym.sym = SDLK_a; + hotkeyNotification.ev.key.repeat = 0; // Init input and poll the event. InitInput(); @@ -100,6 +101,22 @@ ScriptInterface::FromJSVal(pcx, js_hotkey_pressed_value, hotkey_pressed_value); TS_ASSERT_EQUALS(hotkey_pressed_value, true); + // We are listening to KeyDown events, so repeat shouldn't matter. + hotkeyNotification.ev.key.repeat = 1; + in_push_priority_event(&hotkeyNotification); + while (in_poll_event(&ev)) + in_dispatch_event(&ev); + + hotkey_pressed_value = false; + pageScriptInterface.GetProperty(global, "state_before", &js_hotkey_pressed_value); + ScriptInterface::FromJSVal(pcx, js_hotkey_pressed_value, hotkey_pressed_value); + TS_ASSERT_EQUALS(hotkey_pressed_value, true); + + hotkey_pressed_value = false; + pageScriptInterface.GetProperty(global, "state_after", &js_hotkey_pressed_value); + ScriptInterface::FromJSVal(pcx, js_hotkey_pressed_value, hotkey_pressed_value); + TS_ASSERT_EQUALS(hotkey_pressed_value, true); + hotkeyNotification.ev.type = SDL_KEYUP; in_push_priority_event(&hotkeyNotification); while (in_poll_event(&ev)) Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp +++ ps/trunk/source/main.cpp @@ -171,7 +171,7 @@ QuitEngine(); break; - case SDL_HOTKEYDOWN: + case SDL_HOTKEYPRESS: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "exit") { Index: ps/trunk/source/ps/CConsole.cpp =================================================================== --- ps/trunk/source/ps/CConsole.cpp +++ ps/trunk/source/ps/CConsole.cpp @@ -637,7 +637,7 @@ if (!g_Console) return IN_PASS; - if ((int)ev->ev.type == SDL_HOTKEYDOWN) + if (static_cast(ev->ev.type) == SDL_HOTKEYPRESS) { std::string hotkey = static_cast(ev->ev.user.data1); Index: ps/trunk/source/ps/Hotkey.h =================================================================== --- ps/trunk/source/ps/Hotkey.h +++ ps/trunk/source/ps/Hotkey.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,9 +25,10 @@ * Hotkeys consist of a name (an arbitrary string), and a key mapping. * The names and mappings are loaded from the config system (any * config setting with the name prefix "hotkey."). - * When a hotkey is pressed or released, SDL_HOTKEYDOWN and SDL_HOTKEYUP - * events are triggered, with the hotkey name stored in ev.user.data1 - * as a const char*. + * When a hotkey is pressed one SDL_HOTKEYPRESS is triggered. While the key is + * kept down repeated SDL_HOTKEYDOWN events are triggered at an interval + * determined by the OS. When a hotkey is released an SDL_HOTKEYUP event is + * triggered. All with the hotkey name stored in ev.user.data1 as a const char*. */ #include "CStr.h" @@ -38,8 +39,9 @@ // required for our HOTKEY event type definition. this is OK since // hotkey.h is not included from any headers. -const int SDL_HOTKEYDOWN = SDL_USEREVENT; -const int SDL_HOTKEYUP = SDL_USEREVENT + 1; +const uint SDL_HOTKEYPRESS = SDL_USEREVENT; +const uint SDL_HOTKEYDOWN = SDL_USEREVENT + 1; +const uint SDL_HOTKEYUP = SDL_USEREVENT + 2; extern void LoadHotkeys(); extern void UnloadHotkeys(); Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp +++ ps/trunk/source/ps/Hotkey.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -154,7 +154,7 @@ InReaction HotkeyStateChange(const SDL_Event_* ev) { - if (ev->ev.type == SDL_HOTKEYDOWN) + if (ev->ev.type == SDL_HOTKEYPRESS) g_HotkeyStatus[static_cast(ev->ev.user.data1)] = true; else if (ev->ev.type == SDL_HOTKEYUP) g_HotkeyStatus[static_cast(ev->ev.user.data1)] = false; @@ -216,6 +216,9 @@ SDL_Event_ phantom; phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP; + if (phantom.ev.type == SDL_KEYDOWN) + phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0; + if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT)) { phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT; @@ -259,7 +262,7 @@ // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) - // but only send a HotkeyDown event for the event with bindings most precisely + // but only send a HotkeyPress/HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) @@ -306,10 +309,22 @@ for (size_t i = 0; i < closestMapNames.size(); ++i) { - SDL_Event_ hotkeyNotification; - hotkeyNotification.ev.type = SDL_HOTKEYDOWN; - hotkeyNotification.ev.user.data1 = const_cast(closestMapNames[i]); - in_push_priority_event(&hotkeyNotification); + // Send a KeyPress event when a key is pressed initially and on mouseButton and mouseWheel events. + if (ev->ev.type != SDL_KEYDOWN || ev->ev.key.repeat == 0) + { + SDL_Event_ hotkeyPressNotification; + hotkeyPressNotification.ev.type = SDL_HOTKEYPRESS; + hotkeyPressNotification.ev.user.data1 = const_cast(closestMapNames[i]); + in_push_priority_event(&hotkeyPressNotification); + } + + // Send a HotkeyDown event on every key, mouseButton and mouseWheel event. + // For keys the event is repeated depending on hardware and OS configured interval. + // On linux, modifier keys (shift, alt, ctrl) are not repeated, see https://github.com/SFML/SFML/issues/122. + SDL_Event_ hotkeyDownNotification; + hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN; + hotkeyDownNotification.ev.user.data1 = const_cast(closestMapNames[i]); + in_push_priority_event(&hotkeyDownNotification); } // -- KEYUP SECTION -- Index: ps/trunk/source/ps/ProfileViewer.cpp =================================================================== --- ps/trunk/source/ps/ProfileViewer.cpp +++ ps/trunk/source/ps/ProfileViewer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -336,7 +336,7 @@ } break; } - case SDL_HOTKEYDOWN: + case SDL_HOTKEYPRESS: std::string hotkey = static_cast(ev->ev.user.data1); if( hotkey == "profile.toggle" )