OpenTTD Source 20260911-master-gee2b2ac12a
hotkeys.cpp
Go to the documentation of this file.
1/*
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
11#include "core/backup_type.hpp"
12#include "gfx_func.h"
13#include "openttd.h"
14#include "hotkeys.h"
15#include "ini_type.h"
16#include "string_func.h"
17#include "window_gui.h"
19
20#include "safeguards.h"
21
22std::string _hotkeys_file;
23
28static std::vector<HotkeyList*> *_hotkey_lists = nullptr;
29
32 const std::string_view name;
34};
35
37static const std::initializer_list<KeycodeNames> _keycode_to_name = {
38 {"SHIFT", WKC_SHIFT},
39 {"CTRL", WKC_CTRL},
40 {"ALT", WKC_ALT},
41 {"META", WKC_META},
42 {"GLOBAL", WKC_GLOBAL_HOTKEY},
43 {"ESC", WKC_ESC},
44 {"BACKSPACE", WKC_BACKSPACE},
45 {"INS", WKC_INSERT},
46 {"DEL", WKC_DELETE},
47 {"PAGEUP", WKC_PAGEUP},
48 {"PAGEDOWN", WKC_PAGEDOWN},
49 {"END", WKC_END},
50 {"HOME", WKC_HOME},
51 {"RETURN", WKC_RETURN},
52 {"SPACE", WKC_SPACE},
53 {"F1", WKC_F1},
54 {"F2", WKC_F2},
55 {"F3", WKC_F3},
56 {"F4", WKC_F4},
57 {"F5", WKC_F5},
58 {"F6", WKC_F6},
59 {"F7", WKC_F7},
60 {"F8", WKC_F8},
61 {"F9", WKC_F9},
62 {"F10", WKC_F10},
63 {"F11", WKC_F11},
64 {"F12", WKC_F12},
65 {"BACKQUOTE", WKC_BACKQUOTE},
66 {"PAUSE", WKC_PAUSE},
67 {"NUM_DIV", WKC_NUM_DIV},
68 {"NUM_MUL", WKC_NUM_MUL},
69 {"NUM_MINUS", WKC_NUM_MINUS},
70 {"NUM_PLUS", WKC_NUM_PLUS},
71 {"NUM_ENTER", WKC_NUM_ENTER},
72 {"NUM_DOT", WKC_NUM_DECIMAL},
73 {"SLASH", WKC_SLASH},
74 {"/", WKC_SLASH}, /* deprecated, use SLASH */
75 {"SEMICOLON", WKC_SEMICOLON},
76 {";", WKC_SEMICOLON}, /* deprecated, use SEMICOLON */
77 {"EQUALS", WKC_EQUALS},
78 {"=", WKC_EQUALS}, /* deprecated, use EQUALS */
79 {"L_BRACKET", WKC_L_BRACKET},
80 {"[", WKC_L_BRACKET}, /* deprecated, use L_BRACKET */
81 {"BACKSLASH", WKC_BACKSLASH},
82 {"\\", WKC_BACKSLASH}, /* deprecated, use BACKSLASH */
83 {"R_BRACKET", WKC_R_BRACKET},
84 {"]", WKC_R_BRACKET}, /* deprecated, use R_BRACKET */
85 {"SINGLEQUOTE", WKC_SINGLEQUOTE},
86 {"'", WKC_SINGLEQUOTE}, /* deprecated, use SINGLEQUOTE */
87 {"COMMA", WKC_COMMA},
88 {"PERIOD", WKC_PERIOD},
89 {".", WKC_PERIOD}, /* deprecated, use PERIOD */
90 {"MINUS", WKC_MINUS},
91 {"-", WKC_MINUS}, /* deprecated, use MINUS */
92};
93
99static std::optional<uint16_t> ParseCode(std::string_view keystr)
100{
101 keystr = StrTrimView(keystr, StringConsumer::WHITESPACE_NO_NEWLINE);
102 for (const auto &kn : _keycode_to_name) {
103 if (StrEqualsIgnoreCase(keystr, kn.name)) {
104 return kn.keycode;
105 }
106 }
107 if (keystr.size() == 1) {
108 auto c = keystr[0];
109 if (c >= 'a' && c <= 'z') return c - ('a'-'A');
110 /* Ignore invalid keycodes */
111 if (static_cast<uint8_t>(c) < 128) return c;
112 }
113 return std::nullopt;
114}
115
121static std::optional<uint16_t> ParseKeycode(std::string_view keystr)
122{
123 if (keystr.empty()) return std::nullopt;
124 StringConsumer consumer{keystr};
125 uint16_t keycode = 0;
126 while (consumer.AnyBytesLeft()) {
127 auto cur = consumer.ReadUntilChar('+', StringConsumer::SKIP_ONE_SEPARATOR);
128 auto code = ParseCode(cur);
129 if (!code.has_value()) return std::nullopt;
130 if (*code & WKC_SPECIAL_KEYS) {
131 /* Some completely wrong keycode we don't support. */
132 if (*code & ~WKC_SPECIAL_KEYS) return std::nullopt;
133 keycode |= *code;
134 } else {
135 /* Ignore the code if it has more then 1 letter. */
136 if (keycode & ~WKC_SPECIAL_KEYS) return std::nullopt;
137 keycode |= *code;
138 }
139 }
140 return keycode;
141}
142
148static void ParseHotkeys(Hotkey &hotkey, std::string_view value)
149{
150 StringConsumer consumer{value};
151 while (consumer.AnyBytesLeft()) {
152 auto keystr = consumer.ReadUntilChar(',', StringConsumer::SKIP_ONE_SEPARATOR);
153 auto keycode = ParseKeycode(keystr);
154 if (keycode.has_value()) hotkey.AddKeycode(*keycode);
155 }
156}
157
165static std::string KeycodeToString(uint16_t keycode)
166{
167 std::string str;
168 if (keycode & WKC_GLOBAL_HOTKEY) {
169 str += "GLOBAL";
170 }
171 if (keycode & WKC_SHIFT) {
172 if (!str.empty()) str += "+";
173 str += "SHIFT";
174 }
175 if (keycode & WKC_CTRL) {
176 if (!str.empty()) str += "+";
177 str += "CTRL";
178 }
179 if (keycode & WKC_ALT) {
180 if (!str.empty()) str += "+";
181 str += "ALT";
182 }
183 if (keycode & WKC_META) {
184 if (!str.empty()) str += "+";
185 str += "META";
186 }
187 if (!str.empty()) str += "+";
188 keycode = keycode & ~WKC_SPECIAL_KEYS;
189
190 for (const auto &kn : _keycode_to_name) {
191 if (kn.keycode == keycode) {
192 str += kn.name;
193 return str;
194 }
195 }
196 assert(keycode < 128);
197 str.push_back(keycode);
198 return str;
199}
200
207std::string SaveKeycodes(const Hotkey &hotkey)
208{
209 std::string str;
210 for (auto keycode : hotkey.keycodes) {
211 if (!str.empty()) str += ",";
212 str += KeycodeToString(keycode);
213 }
214 return str;
215}
216
223Hotkey::Hotkey(uint16_t default_keycode, const std::string &name, int num) :
224 name(name),
225 num(num)
226{
227 if (default_keycode != 0) this->AddKeycode(default_keycode);
228}
229
236Hotkey::Hotkey(const std::vector<uint16_t> &default_keycodes, const std::string &name, int num) :
237 name(name),
238 num(num)
239{
240 for (uint16_t keycode : default_keycodes) {
241 this->AddKeycode(keycode);
242 }
243}
244
250void Hotkey::AddKeycode(uint16_t keycode)
251{
252 this->keycodes.insert(keycode);
253}
254
255HotkeyList::HotkeyList(const std::string &ini_group, const std::vector<Hotkey> &items, GlobalHotkeyHandlerFunc global_hotkey_handler) :
256 global_hotkey_handler(global_hotkey_handler), ini_group(ini_group), items(items)
257{
258 if (_hotkey_lists == nullptr) _hotkey_lists = new std::vector<HotkeyList*>();
259 _hotkey_lists->push_back(this);
260}
261
264{
265 _hotkey_lists->erase(std::ranges::find(*_hotkey_lists, this));
266}
267
272void HotkeyList::Load(const IniFile &ini)
273{
274 const IniGroup *group = ini.GetGroup(this->ini_group);
275 if (group == nullptr) return;
276 for (Hotkey &hotkey : this->items) {
277 const IniItem *item = group->GetItem(hotkey.name);
278 if (item != nullptr) {
279 hotkey.keycodes.clear();
280 if (item->value.has_value()) ParseHotkeys(hotkey, *item->value);
281 }
282 }
283}
284
289void HotkeyList::Save(IniFile &ini) const
290{
291 IniGroup &group = ini.GetOrCreateGroup(this->ini_group);
292 for (const Hotkey &hotkey : this->items) {
293 IniItem &item = group.GetOrCreateItem(hotkey.name);
294 item.SetValue(SaveKeycodes(hotkey));
295 }
296}
297
304int HotkeyList::CheckMatch(uint16_t keycode, bool global_only) const
305{
306 for (const Hotkey &hotkey : this->items) {
307 auto begin = hotkey.keycodes.begin();
308 auto end = hotkey.keycodes.end();
309 if (std::find(begin, end, keycode | WKC_GLOBAL_HOTKEY) != end || (!global_only && std::find(begin, end, keycode) != end)) {
310 return hotkey.num;
311 }
312 }
313 return -1;
314}
315
316
317static void SaveLoadHotkeys(bool save)
318{
319 IniFile ini{};
320 ini.LoadFromDisk(_hotkeys_file, Subdirectory::None);
321
322 for (HotkeyList *list : *_hotkey_lists) {
323 if (save) {
324 list->Save(ini);
325 } else {
326 list->Load(ini);
327 }
328 }
329
330 if (save) ini.SaveToDisk(_hotkeys_file);
331}
332
333
336{
337 SaveLoadHotkeys(false);
338}
339
342{
343 SaveLoadHotkeys(true);
344}
345
353static EventState HandleGlobalHotkey(HotkeyList::GlobalHotkeyHandlerFunc func, int hotkey)
354{
355 /* Clear global modifier keys so they do not interfere with modifiers assigned to hotkeys. */
356 AutoRestoreBackup _shift_backup{_shift_pressed, false};
357 AutoRestoreBackup _ctrl_backup{_ctrl_pressed, false};
358
359 return func(hotkey);
360}
361
362void HandleGlobalHotkeys([[maybe_unused]] char32_t key, uint16_t keycode)
363{
364 for (HotkeyList *list : *_hotkey_lists) {
365 if (list->global_hotkey_handler == nullptr) continue;
366
367 int hotkey = list->CheckMatch(keycode, true);
368 if (hotkey >= 0 && HandleGlobalHotkey(list->global_hotkey_handler, hotkey) == EventState::Handled) return;
369 }
370}
371
Class for backupping variables and making sure they are restored later.
Parse data from a string / buffer.
std::string_view ReadUntilChar(char c, SeparatorUsage sep)
Read data until the first occurrence of 8-bit char 'c', and advance reader.
@ SKIP_ONE_SEPARATOR
Read and discard one separator, do not include it in the result.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
static const std::string_view WHITESPACE_NO_NEWLINE
ASCII whitespace characters, excluding new-line.
@ None
A path without any base directory.
bool _shift_pressed
Is Shift pressed?
Definition gfx.cpp:40
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:39
Functions related to the gfx engine.
WindowKeyCodes
Definition gfx_type.h:29
@ WKC_BACKSLASH
\ Backslash
Definition gfx_type.h:101
@ WKC_MINUS
Definition gfx_type.h:106
@ WKC_COMMA
, Comma
Definition gfx_type.h:104
@ WKC_PERIOD
. Period
Definition gfx_type.h:105
@ WKC_EQUALS
= Equals
Definition gfx_type.h:99
@ WKC_SLASH
/ Forward slash
Definition gfx_type.h:97
@ WKC_SINGLEQUOTE
' Single quote
Definition gfx_type.h:103
@ WKC_R_BRACKET
] Right square bracket
Definition gfx_type.h:102
@ WKC_L_BRACKET
[ Left square bracket
Definition gfx_type.h:100
@ WKC_GLOBAL_HOTKEY
Fake keycode bit to indicate global hotkeys.
Definition gfx_type.h:35
@ WKC_SEMICOLON
; Semicolon
Definition gfx_type.h:98
static EventState HandleGlobalHotkey(HotkeyList::GlobalHotkeyHandlerFunc func, int hotkey)
Call the global handler for a hotkey.
Definition hotkeys.cpp:353
static std::vector< HotkeyList * > * _hotkey_lists
List of all HotkeyLists.
Definition hotkeys.cpp:28
void LoadHotkeysFromConfig()
Load the hotkeys from the config file.
Definition hotkeys.cpp:335
static std::optional< uint16_t > ParseCode(std::string_view keystr)
Try to parse a single part of a keycode.
Definition hotkeys.cpp:99
static void ParseHotkeys(Hotkey &hotkey, std::string_view value)
Parse a string to the keycodes it represents.
Definition hotkeys.cpp:148
std::string SaveKeycodes(const Hotkey &hotkey)
Convert all keycodes attached to a hotkey to a single string.
Definition hotkeys.cpp:207
void SaveHotkeysToConfig()
Save the hotkeys to the config file.
Definition hotkeys.cpp:341
static std::optional< uint16_t > ParseKeycode(std::string_view keystr)
Parse a string representation of a keycode.
Definition hotkeys.cpp:121
static std::string KeycodeToString(uint16_t keycode)
Convert a hotkey to it's string representation so it can be written to the config file.
Definition hotkeys.cpp:165
static const std::initializer_list< KeycodeNames > _keycode_to_name
Array of non-standard keycodes that can be used in the hotkeys config file.
Definition hotkeys.cpp:37
Hotkey related functions.
Types related to reading/writing '*.ini' files.
Some generic types.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:325
Parse strings.
Functions related to low-level strings.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
List of hotkeys for a window.
Definition hotkeys.h:46
void Save(IniFile &ini) const
Save HotkeyList to IniFile.
Definition hotkeys.cpp:289
int CheckMatch(uint16_t keycode, bool global_only=false) const
Check if a keycode is bound to something.
Definition hotkeys.cpp:304
void Load(const IniFile &ini)
Load HotkeyList from IniFile.
Definition hotkeys.cpp:272
~HotkeyList()
Remove ourselves from the global hotkey list.
Definition hotkeys.cpp:263
All data for a single hotkey.
Definition hotkeys.h:22
Hotkey(uint16_t default_keycode, const std::string &name, int num)
Create a new Hotkey object with a single default keycode.
Definition hotkeys.cpp:223
void AddKeycode(uint16_t keycode)
Add a keycode to this hotkey, from now that keycode will be matched in addition to any previously add...
Definition hotkeys.cpp:250
Ini file that supports both loading and saving.
Definition ini_type.h:87
bool SaveToDisk(const std::string &filename)
Save the Ini file's data to the disk.
Definition ini.cpp:42
A group within an ini file.
Definition ini_type.h:34
const IniItem * GetItem(std::string_view name) const
Get the item with the given name.
Definition ini_load.cpp:50
IniItem & GetOrCreateItem(std::string_view name)
Get the item with the given name, and if it doesn't exist create a new item.
Definition ini_load.cpp:64
A single "line" in an ini file.
Definition ini_type.h:23
std::optional< std::string > value
The value of this item.
Definition ini_type.h:25
void SetValue(std::string_view value)
Replace the current value with another value.
Definition ini_load.cpp:30
const IniGroup * GetGroup(std::string_view name) const
Get the group with the given name.
Definition ini_load.cpp:117
void LoadFromDisk(std::string_view filename, Subdirectory subdir)
Load the Ini file's data from the disk.
Definition ini_load.cpp:184
IniGroup & GetOrCreateGroup(std::string_view name)
Get the group with the given name, and if it doesn't exist create a new group.
Definition ini_load.cpp:145
String representation of a keycode.
Definition hotkeys.cpp:31
WindowKeyCodes keycode
The keycode.
Definition hotkeys.cpp:33
const std::string_view name
Name of the keycode.
Definition hotkeys.cpp:32
Functions, definitions and such used only by the GUI.
EventState
State of handling an event.
@ Handled
The passed event is handled.