Line data Source code
1 : /* Copyright (C) 2022 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 :
20 : #include "JSInterface_Hotkey.h"
21 :
22 : #include "lib/external_libraries/libsdl.h"
23 : #include "ps/CLogger.h"
24 : #include "ps/ConfigDB.h"
25 : #include "ps/Hotkey.h"
26 : #include "ps/KeyName.h"
27 : #include "scriptinterface/FunctionWrapper.h"
28 : #include "scriptinterface/ScriptConversions.h"
29 :
30 : #include <unordered_map>
31 : #include <vector>
32 : #include <set>
33 :
34 : /**
35 : * Convert an unordered map to a JS object, mapping keys to values.
36 : * Assumes T to have a c_str() method that returns a const char*
37 : * NB: this is unordered since no particular effort is made to preserve order.
38 : * TODO: this could be moved to ScriptConversions.cpp if the need arises.
39 : */
40 : template<typename T, typename U>
41 0 : static void ToJSVal_unordered_map(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<T, U>& val)
42 : {
43 0 : JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx));
44 0 : if (!obj)
45 : {
46 0 : ret.setUndefined();
47 0 : return;
48 : }
49 0 : for (const std::pair<const T, U>& item : val)
50 : {
51 0 : JS::RootedValue el(rq.cx);
52 0 : Script::ToJSVal<U>(rq, &el, item.second);
53 0 : JS_SetProperty(rq.cx, obj, item.first.c_str(), el);
54 : }
55 0 : ret.setObject(*obj);
56 : }
57 :
58 : template<>
59 0 : void Script::ToJSVal<std::unordered_map<std::string, std::vector<std::vector<std::string>>>>(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<std::string, std::vector<std::vector<std::string>>>& val)
60 : {
61 0 : ToJSVal_unordered_map(rq, ret, val);
62 0 : }
63 :
64 : template<>
65 0 : void Script::ToJSVal<std::unordered_map<std::string, std::string>>(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::unordered_map<std::string, std::string>& val)
66 : {
67 0 : ToJSVal_unordered_map(rq, ret, val);
68 0 : }
69 :
70 : namespace
71 : {
72 : /**
73 : * @return a (js) object mapping hotkey name (from cfg files) to a list ofscancode names
74 : */
75 0 : JS::Value GetHotkeyMap(const ScriptRequest& rq)
76 : {
77 0 : JS::RootedValue hotkeyMap(rq.cx);
78 :
79 0 : std::unordered_map<std::string, std::vector<std::vector<std::string>>> hotkeys;
80 0 : for (const std::pair<const SDL_Scancode_, KeyMapping>& key : g_HotkeyMap)
81 0 : for (const SHotkeyMapping& mapping : key.second)
82 : {
83 0 : std::vector<std::string> keymap;
84 0 : if (key.first != UNUSED_HOTKEY_CODE)
85 0 : keymap.push_back(FindScancodeName(static_cast<SDL_Scancode>(key.first)));
86 0 : for (const SKey& secondary_key : mapping.requires)
87 0 : keymap.push_back(FindScancodeName(static_cast<SDL_Scancode>(secondary_key.code)));
88 : // If keymap is empty (== unused) or size 1, push the combination.
89 : // Otherwise, all permutations of the combination will exist, so pick one using an arbitrary order.
90 0 : if (keymap.size() < 2 || keymap[0] < keymap[1])
91 0 : hotkeys[mapping.name].emplace_back(keymap);
92 : }
93 0 : Script::ToJSVal(rq, &hotkeyMap, hotkeys);
94 :
95 0 : return hotkeyMap;
96 : }
97 :
98 : /**
99 : * @return a (js) object mapping scancode names to their locale-dependent name.
100 : */
101 0 : JS::Value GetScancodeKeyNames(const ScriptRequest& rq)
102 : {
103 0 : JS::RootedValue obj(rq.cx);
104 0 : std::unordered_map<std::string, std::string> map;
105 :
106 : // Get the name of all scancodes.
107 : // This is slightly wasteful but should be fine overall, they are dense.
108 0 : for (int i = 0; i < MOUSE_LAST; ++i)
109 0 : map[FindScancodeName(static_cast<SDL_Scancode>(i))] = FindKeyName(static_cast<SDL_Scancode>(i));
110 0 : Script::ToJSVal(rq, &obj, map);
111 :
112 0 : return obj;
113 : }
114 :
115 0 : void ReloadHotkeys()
116 : {
117 0 : UnloadHotkeys();
118 0 : LoadHotkeys(g_ConfigDB);
119 0 : }
120 :
121 0 : JS::Value GetConflicts(const ScriptRequest& rq, JS::HandleValue combination)
122 : {
123 0 : std::vector<std::string> keys;
124 0 : if (!Script::FromJSVal(rq, combination, keys))
125 : {
126 0 : LOGERROR("Invalid hotkey combination");
127 0 : return JS::NullValue();
128 : }
129 :
130 0 : if (keys.empty())
131 0 : return JS::NullValue();
132 :
133 : // Pick a random code as a starting point of the hotkeys (they are all equivalent).
134 0 : SDL_Scancode_ startCode = FindScancode(keys.back());
135 :
136 0 : std::unordered_map<SDL_Scancode_, KeyMapping>::const_iterator it = g_HotkeyMap.find(startCode);
137 0 : if (it == g_HotkeyMap.end())
138 0 : return JS::NullValue();
139 :
140 : // Create a sorted vector with the remaining keys.
141 0 : keys.pop_back();
142 :
143 0 : std::set<SKey> codes;
144 0 : for (const std::string& key : keys)
145 0 : codes.insert(SKey{ FindScancode(key) });
146 :
147 0 : std::vector<CStr> conflicts;
148 : // This isn't very efficient, but we shouldn't iterate too many hotkeys
149 : // since we at least have one matching key.
150 0 : for (const SHotkeyMapping& keymap : it->second)
151 : {
152 0 : std::set<SKey> match(keymap.requires.begin(), keymap.requires.end());
153 0 : if (codes == match)
154 0 : conflicts.emplace_back(keymap.name);
155 : }
156 0 : if (conflicts.empty())
157 0 : return JS::NullValue();
158 :
159 0 : JS::RootedValue ret(rq.cx);
160 0 : Script::ToJSVal(rq, &ret, conflicts);
161 0 : return ret;
162 : }
163 : }
164 :
165 12 : void JSI_Hotkey::RegisterScriptFunctions(const ScriptRequest& rq)
166 : {
167 24 : ScriptFunction::Register<&HotkeyIsPressed>(rq, "HotkeyIsPressed");
168 12 : ScriptFunction::Register<&GetHotkeyMap>(rq, "GetHotkeyMap");
169 12 : ScriptFunction::Register<&GetScancodeKeyNames>(rq, "GetScancodeKeyNames");
170 12 : ScriptFunction::Register<&ReloadHotkeys>(rq, "ReloadHotkeys");
171 12 : ScriptFunction::Register<&GetConflicts>(rq, "GetConflicts");
172 15 : }
|