2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file hotkeys.cpp Implementation of hotkey related functions */
14 #include "string_func.h"
15 #include "window_gui.h"
17 #include "safeguards.h"
19 std::string _hotkeys_file
;
22 * List of all HotkeyLists.
23 * This is a pointer to ensure initialisation order with the various static HotkeyList instances.
25 static std::vector
<HotkeyList
*> *_hotkey_lists
= nullptr;
27 /** String representation of a keycode */
29 const std::string_view name
; ///< Name of the keycode
30 WindowKeyCodes keycode
; ///< The keycode
33 /** Array of non-standard keycodes that can be used in the hotkeys config file. */
34 static const std::initializer_list
<KeycodeNames
> _keycode_to_name
= {
39 {"GLOBAL", WKC_GLOBAL_HOTKEY
},
41 {"BACKSPACE", WKC_BACKSPACE
},
44 {"PAGEUP", WKC_PAGEUP
},
45 {"PAGEDOWN", WKC_PAGEDOWN
},
48 {"RETURN", WKC_RETURN
},
62 {"BACKQUOTE", WKC_BACKQUOTE
},
64 {"NUM_DIV", WKC_NUM_DIV
},
65 {"NUM_MUL", WKC_NUM_MUL
},
66 {"NUM_MINUS", WKC_NUM_MINUS
},
67 {"NUM_PLUS", WKC_NUM_PLUS
},
68 {"NUM_ENTER", WKC_NUM_ENTER
},
69 {"NUM_DOT", WKC_NUM_DECIMAL
},
71 {"/", WKC_SLASH
}, /* deprecated, use SLASH */
72 {"SEMICOLON", WKC_SEMICOLON
},
73 {";", WKC_SEMICOLON
}, /* deprecated, use SEMICOLON */
74 {"EQUALS", WKC_EQUALS
},
75 {"=", WKC_EQUALS
}, /* deprecated, use EQUALS */
76 {"L_BRACKET", WKC_L_BRACKET
},
77 {"[", WKC_L_BRACKET
}, /* deprecated, use L_BRACKET */
78 {"BACKSLASH", WKC_BACKSLASH
},
79 {"\\", WKC_BACKSLASH
}, /* deprecated, use BACKSLASH */
80 {"R_BRACKET", WKC_R_BRACKET
},
81 {"]", WKC_R_BRACKET
}, /* deprecated, use R_BRACKET */
82 {"SINGLEQUOTE", WKC_SINGLEQUOTE
},
83 {"'", WKC_SINGLEQUOTE
}, /* deprecated, use SINGLEQUOTE */
85 {"PERIOD", WKC_PERIOD
},
86 {".", WKC_PERIOD
}, /* deprecated, use PERIOD */
88 {"-", WKC_MINUS
}, /* deprecated, use MINUS */
92 * Try to parse a single part of a keycode.
93 * @param start Start of the string to parse.
94 * @param end End of the string to parse.
95 * @return A keycode if a match is found or 0.
97 static uint16_t ParseCode(const char *start
, const char *end
)
100 while (start
< end
&& *start
== ' ') start
++;
101 while (end
> start
&& *end
== ' ') end
--;
102 std::string_view str
{start
, end
};
103 for (const auto &kn
: _keycode_to_name
) {
104 if (StrEqualsIgnoreCase(str
, kn
.name
)) {
108 if (end
- start
== 1) {
109 if (*start
>= 'a' && *start
<= 'z') return *start
- ('a'-'A');
110 /* Ignore invalid keycodes */
111 if (*(const uint8_t *)start
< 128) return *start
;
117 * Parse a string representation of a keycode.
118 * @param start Start of the input.
119 * @param end End of the input.
120 * @return A valid keycode or 0.
122 static uint16_t ParseKeycode(const char *start
, const char *end
)
124 assert(start
<= end
);
125 uint16_t keycode
= 0;
127 const char *cur
= start
;
128 while (*cur
!= '+' && cur
!= end
) cur
++;
129 uint16_t code
= ParseCode(start
, cur
);
130 if (code
== 0) return 0;
131 if (code
& WKC_SPECIAL_KEYS
) {
132 /* Some completely wrong keycode we don't support. */
133 if (code
& ~WKC_SPECIAL_KEYS
) return 0;
136 /* Ignore the code if it has more then 1 letter. */
137 if (keycode
& ~WKC_SPECIAL_KEYS
) return 0;
140 if (cur
== end
) break;
148 * Parse a string to the keycodes it represents
149 * @param hotkey The hotkey object to add the keycodes to
150 * @param value The string to parse
152 static void ParseHotkeys(Hotkey
&hotkey
, const char *value
)
154 const char *start
= value
;
155 while (*start
!= '\0') {
156 const char *end
= start
;
157 while (*end
!= '\0' && *end
!= ',') end
++;
158 uint16_t keycode
= ParseKeycode(start
, end
);
159 if (keycode
!= 0) hotkey
.AddKeycode(keycode
);
160 start
= (*end
== ',') ? end
+ 1: end
;
165 * Convert a hotkey to it's string representation so it can be written to the
166 * config file. Separate parts of the keycode (like "CTRL" and "F1" are split
168 * @param keycode The keycode to convert to a string.
169 * @return A string representation of this keycode.
171 static std::string
KeycodeToString(uint16_t keycode
)
174 if (keycode
& WKC_GLOBAL_HOTKEY
) {
177 if (keycode
& WKC_SHIFT
) {
178 if (!str
.empty()) str
+= "+";
181 if (keycode
& WKC_CTRL
) {
182 if (!str
.empty()) str
+= "+";
185 if (keycode
& WKC_ALT
) {
186 if (!str
.empty()) str
+= "+";
189 if (keycode
& WKC_META
) {
190 if (!str
.empty()) str
+= "+";
193 if (!str
.empty()) str
+= "+";
194 keycode
= keycode
& ~WKC_SPECIAL_KEYS
;
196 for (const auto &kn
: _keycode_to_name
) {
197 if (kn
.keycode
== keycode
) {
202 assert(keycode
< 128);
203 str
.push_back(keycode
);
208 * Convert all keycodes attached to a hotkey to a single string. If multiple
209 * keycodes are attached to the hotkey they are split by a comma.
210 * @param hotkey The keycodes of this hotkey need to be converted to a string.
211 * @return A string representation of all keycodes.
213 std::string
SaveKeycodes(const Hotkey
&hotkey
)
216 for (auto keycode
: hotkey
.keycodes
) {
217 if (!str
.empty()) str
+= ",";
218 str
+= KeycodeToString(keycode
);
224 * Create a new Hotkey object with a single default keycode.
225 * @param default_keycode The default keycode for this hotkey.
226 * @param name The name of this hotkey.
227 * @param num Number of this hotkey, should be unique within the hotkey list.
229 Hotkey::Hotkey(uint16_t default_keycode
, const std::string
&name
, int num
) :
233 if (default_keycode
!= 0) this->AddKeycode(default_keycode
);
237 * Create a new Hotkey object with multiple default keycodes.
238 * @param default_keycodes An array of default keycodes terminated with 0.
239 * @param name The name of this hotkey.
240 * @param num Number of this hotkey, should be unique within the hotkey list.
242 Hotkey::Hotkey(const std::vector
<uint16_t> &default_keycodes
, const std::string
&name
, int num
) :
246 for (uint16_t keycode
: default_keycodes
) {
247 this->AddKeycode(keycode
);
252 * Add a keycode to this hotkey, from now that keycode will be matched
253 * in addition to any previously added keycodes.
254 * @param keycode The keycode to add.
256 void Hotkey::AddKeycode(uint16_t keycode
)
258 this->keycodes
.insert(keycode
);
261 HotkeyList::HotkeyList(const std::string
&ini_group
, const std::vector
<Hotkey
> &items
, GlobalHotkeyHandlerFunc global_hotkey_handler
) :
262 global_hotkey_handler(global_hotkey_handler
), ini_group(ini_group
), items(items
)
264 if (_hotkey_lists
== nullptr) _hotkey_lists
= new std::vector
<HotkeyList
*>();
265 _hotkey_lists
->push_back(this);
268 HotkeyList::~HotkeyList()
270 _hotkey_lists
->erase(std::ranges::find(*_hotkey_lists
, this));
274 * Load HotkeyList from IniFile.
275 * @param ini IniFile to load from.
277 void HotkeyList::Load(const IniFile
&ini
)
279 const IniGroup
*group
= ini
.GetGroup(this->ini_group
);
280 if (group
== nullptr) return;
281 for (Hotkey
&hotkey
: this->items
) {
282 const IniItem
*item
= group
->GetItem(hotkey
.name
);
283 if (item
!= nullptr) {
284 hotkey
.keycodes
.clear();
285 if (item
->value
.has_value()) ParseHotkeys(hotkey
, item
->value
->c_str());
291 * Save HotkeyList to IniFile.
292 * @param ini IniFile to save to.
294 void HotkeyList::Save(IniFile
&ini
) const
296 IniGroup
&group
= ini
.GetOrCreateGroup(this->ini_group
);
297 for (const Hotkey
&hotkey
: this->items
) {
298 IniItem
&item
= group
.GetOrCreateItem(hotkey
.name
);
299 item
.SetValue(SaveKeycodes(hotkey
));
304 * Check if a keycode is bound to something.
305 * @param keycode The keycode that was pressed
306 * @param global_only Limit the search to hotkeys defined as 'global'.
307 * @return The number of the matching hotkey or -1.
309 int HotkeyList::CheckMatch(uint16_t keycode
, bool global_only
) const
311 for (const Hotkey
&hotkey
: this->items
) {
312 auto begin
= hotkey
.keycodes
.begin();
313 auto end
= hotkey
.keycodes
.end();
314 if (std::find(begin
, end
, keycode
| WKC_GLOBAL_HOTKEY
) != end
|| (!global_only
&& std::find(begin
, end
, keycode
) != end
)) {
322 static void SaveLoadHotkeys(bool save
)
325 ini
.LoadFromDisk(_hotkeys_file
, NO_DIRECTORY
);
327 for (HotkeyList
*list
: *_hotkey_lists
) {
335 if (save
) ini
.SaveToDisk(_hotkeys_file
);
339 /** Load the hotkeys from the config file */
340 void LoadHotkeysFromConfig()
342 SaveLoadHotkeys(false);
345 /** Save the hotkeys to the config file */
346 void SaveHotkeysToConfig()
348 SaveLoadHotkeys(true);
351 void HandleGlobalHotkeys([[maybe_unused
]] char32_t key
, uint16_t keycode
)
353 for (HotkeyList
*list
: *_hotkey_lists
) {
354 if (list
->global_hotkey_handler
== nullptr) continue;
356 int hotkey
= list
->CheckMatch(keycode
, true);
357 if (hotkey
>= 0 && (list
->global_hotkey_handler(hotkey
) == ES_HANDLED
)) return;