@@ -68,35 +68,8 @@ std::string Hotkey::keyspec_to_string(const KeySpec &spec, bool include_focus) {
6868 return sym;
6969}
7070
71- // Hotkeys actions are executed from an external thread to avoid deadlocks
72- // that may occur if running commands from the render or simulation threads.
73- void HotkeyManager::hotkey_thread_fn () {
74- auto & core = DFHack::Core::getInstance ();
75-
76- std::unique_lock<std::mutex> l (lock);
77- while (true ) {
78- cond.wait (l, [this ]() { return this ->hotkey_sig != HotkeySignal::None; });
79- if (hotkey_sig == HotkeySignal::Shutdown)
80- return ;
81- if (hotkey_sig != HotkeySignal::CmdReady)
82- continue ;
8371
84- // Copy and reset important data, then release the lock
85- this ->hotkey_sig = HotkeySignal::None;
86- std::string cmd = this ->queued_command ;
87- this ->queued_command .clear ();
88- l.unlock ();
89-
90- // Attempt execution of command
91- DFHack::color_ostream_proxy out (core.getConsole ());
92- auto res = core.runCommand (out, cmd);
93- if (res == DFHack::CR_NOT_IMPLEMENTED)
94- out.printerr (" Invalid hotkey command: '%s'\n " , cmd.c_str ());
95- l.lock ();
96- }
97- }
98-
99- std::optional<KeySpec> HotkeyManager::parseKeySpec (std::string spec) {
72+ std::optional<KeySpec> Hotkey::parseKeySpec (std::string spec, std::string* err) {
10073 KeySpec out;
10174
10275 // Determine focus string, if present
@@ -136,10 +109,42 @@ std::optional<KeySpec> HotkeyManager::parseKeySpec(std::string spec) {
136109 }
137110 }
138111
112+ if (err)
113+ *err = " Unknown key '" + spec + " '" ;
114+
139115 // Invalid key binding
140116 return std::nullopt ;
141117}
142118
119+ // Hotkeys actions are executed from an external thread to avoid deadlocks
120+ // that may occur if running commands from the render or simulation threads.
121+ void HotkeyManager::hotkey_thread_fn () {
122+ auto & core = DFHack::Core::getInstance ();
123+
124+ std::unique_lock<std::mutex> l (lock);
125+ while (true ) {
126+ cond.wait (l, [this ]() { return this ->hotkey_sig != HotkeySignal::None; });
127+ if (hotkey_sig == HotkeySignal::Shutdown)
128+ return ;
129+ if (hotkey_sig != HotkeySignal::CmdReady)
130+ continue ;
131+
132+ // Copy and reset important data, then release the lock
133+ this ->hotkey_sig = HotkeySignal::None;
134+ std::string cmd = this ->queued_command ;
135+ this ->queued_command .clear ();
136+ l.unlock ();
137+
138+ // Attempt execution of command
139+ DFHack::color_ostream_proxy out (core.getConsole ());
140+ auto res = core.runCommand (out, cmd);
141+ if (res == DFHack::CR_NOT_IMPLEMENTED)
142+ out.printerr (" Invalid hotkey command: '%s'\n " , cmd.c_str ());
143+ l.lock ();
144+ }
145+ }
146+
147+
143148bool HotkeyManager::addKeybind (KeySpec spec, std::string cmd) {
144149 // No point in a hotkey with no action
145150 if (cmd.empty ())
@@ -164,7 +169,7 @@ bool HotkeyManager::addKeybind(KeySpec spec, std::string cmd) {
164169}
165170
166171bool HotkeyManager::addKeybind (std::string keyspec, std::string cmd) {
167- std::optional<KeySpec> spec_opt = parseKeySpec (keyspec);
172+ std::optional<KeySpec> spec_opt = Hotkey:: parseKeySpec (keyspec);
168173 if (!spec_opt.has_value ())
169174 return false ;
170175 return this ->addKeybind (spec_opt.value (), cmd);
@@ -189,7 +194,7 @@ bool HotkeyManager::clearKeybind(const KeySpec& spec, bool any_focus, std::strin
189194}
190195
191196bool HotkeyManager::clearKeybind (std::string keyspec, bool any_focus, std::string cmdline) {
192- std::optional<KeySpec> spec_opt = parseKeySpec (keyspec);
197+ std::optional<KeySpec> spec_opt = Hotkey:: parseKeySpec (keyspec);
193198 if (!spec_opt.has_value ())
194199 return false ;
195200 return this ->clearKeybind (spec_opt.value (), any_focus, cmdline);
@@ -226,7 +231,7 @@ std::vector<std::string> HotkeyManager::listKeybinds(const KeySpec& spec) {
226231}
227232
228233std::vector<std::string> HotkeyManager::listKeybinds (std::string keyspec) {
229- std::optional<KeySpec> spec_opt = parseKeySpec (keyspec);
234+ std::optional<KeySpec> spec_opt = Hotkey:: parseKeySpec (keyspec);
230235 if (!spec_opt.has_value ())
231236 return {};
232237 return this ->listKeybinds (spec_opt.value ());
@@ -352,27 +357,42 @@ std::string HotkeyManager::readKeybindInput() {
352357}
353358
354359void HotkeyManager::handleKeybindingCommand (color_ostream &con, const std::vector<std::string>& parts) {
360+ std::string parse_error;
355361 if (parts.size () >= 3 && (parts[0 ] == " set" || parts[0 ] == " add" )) {
356362 std::string keystr = parts[1 ];
357363 if (parts[0 ] == " set" )
358364 clearKeybind (keystr);
359365 for (const auto & part : parts | std::views::drop (2 ) | std::views::reverse) {
360- if (!addKeybind (keystr, part)) {
361- con.printerr (" Invalid key spec: %s\n " , keystr.c_str ());
366+ auto spec = Hotkey::parseKeySpec (keystr, &parse_error);
367+ if (!spec.has_value ()) {
368+ con.printerr (" %s\n " , parse_error.c_str ());
369+ break ;
370+ }
371+ if (!addKeybind (spec.value (), part)) {
372+ con.printerr (" Invalid command: '%s'\n " , part.c_str ());
362373 break ;
363374 }
364375 }
365376 }
366377 else if (parts.size () >= 2 && parts[0 ] == " clear" ) {
367378 for (const auto & part : parts | std::views::drop (1 )) {
368- if (!clearKeybind (part)) {
369- con.printerr (" Invalid key spec: %s\n " , part.c_str ());
379+ auto spec = Hotkey::parseKeySpec (part, &parse_error);
380+ if (!spec.has_value ()) {
381+ con.printerr (" %s\n " , parse_error.c_str ());
382+ }
383+ if (!clearKeybind (spec.value ())) {
384+ con.printerr (" No matching keybinds to remove\n " );
370385 break ;
371386 }
372387 }
373388 }
374389 else if (parts.size () == 2 && parts[0 ] == " list" ) {
375- std::vector<std::string> list = listKeybinds (parts[1 ]);
390+ auto spec = Hotkey::parseKeySpec (parts[1 ], &parse_error);
391+ if (!spec.has_value ()) {
392+ con.printerr (" %s\n " , parse_error.c_str ());
393+ return ;
394+ }
395+ std::vector<std::string> list = listKeybinds (spec.value ());
376396 if (list.empty ())
377397 con << " No bindings." << std::endl;
378398 for (const auto & kb : list)
0 commit comments