Skip to content

Commit b5d8899

Browse files
committed
Lua api for hotkeys and library support for future keybinding gui
1 parent 3c2dc3c commit b5d8899

File tree

4 files changed

+182
-44
lines changed

4 files changed

+182
-44
lines changed

library/LuaApi.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ distribution.
4747
#include "modules/EventManager.h"
4848
#include "modules/Filesystem.h"
4949
#include "modules/Gui.h"
50+
#include "modules/Hotkey.h"
5051
#include "modules/Items.h"
5152
#include "modules/Job.h"
5253
#include "modules/Kitchen.h"
@@ -1866,6 +1867,77 @@ static const luaL_Reg dfhack_gui_funcs[] = {
18661867
{ NULL, NULL }
18671868
};
18681869

1870+
/***** Hotkey module *****/
1871+
static bool hotkey_addKeybind(const std::string spec, const std::string cmd) {
1872+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1873+
if (!hotkey_mgr) return false;
1874+
return hotkey_mgr->addKeybind(spec, cmd);
1875+
}
1876+
1877+
static bool hotkey_clearKeybind(const std::string spec, bool any_focus, std::string cmd) {
1878+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1879+
if (!hotkey_mgr) return false;
1880+
return hotkey_mgr->clearKeybind(spec, any_focus, cmd);
1881+
}
1882+
1883+
static void hotkey_requestKeybindingInput() {
1884+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1885+
if (hotkey_mgr) hotkey_mgr->requestKeybindInput();
1886+
}
1887+
1888+
static std::string hotkey_readKeybindInput() {
1889+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1890+
if (!hotkey_mgr) return "";
1891+
return hotkey_mgr->readKeybindInput();
1892+
}
1893+
1894+
void hotkey_pushBindArray(lua_State *L, const std::vector<Hotkey::KeyBinding>& binds) {
1895+
lua_createtable(L, binds.size(), 0);
1896+
int i = 1;
1897+
for (const auto& bind : binds) {
1898+
lua_createtable(L, 0, 2);
1899+
1900+
lua_pushstring(L, "spec");
1901+
lua_pushstring(L, Hotkey::keyspec_to_string(bind.spec, true).c_str());
1902+
lua_settable(L, -3);
1903+
1904+
lua_pushstring(L, "command");
1905+
lua_pushstring(L, bind.cmdline.c_str());
1906+
lua_settable(L, -3);
1907+
lua_rawseti(L, -2, i++);
1908+
}
1909+
}
1910+
1911+
static int hotkey_listActiveKeybinds(lua_State *L) {
1912+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1913+
auto binds = hotkey_mgr->listActiveKeybinds();
1914+
1915+
hotkey_pushBindArray(L, binds);
1916+
return 1;
1917+
}
1918+
1919+
static int hotkey_listAllKeybinds(lua_State *L) {
1920+
auto hotkey_mgr = Core::getInstance().getHotkeyManager();
1921+
auto binds = hotkey_mgr->listAllKeybinds();
1922+
1923+
hotkey_pushBindArray(L, binds);
1924+
return 1;
1925+
}
1926+
1927+
static const luaL_Reg dfhack_hotkey_funcs[] = {
1928+
{ "listActiveKeybinds", hotkey_listActiveKeybinds },
1929+
{ "listAllKeybinds", hotkey_listAllKeybinds },
1930+
{ NULL, NULL }
1931+
};
1932+
1933+
static const LuaWrapper::FunctionReg dfhack_hotkey_module[] = {
1934+
WRAPN(addKeybind, hotkey_addKeybind),
1935+
WRAPN(clearKeybind, hotkey_clearKeybind),
1936+
WRAPN(requestKeybindInput, hotkey_requestKeybindingInput),
1937+
WRAPN(readKeybindInput, hotkey_readKeybindInput),
1938+
{ NULL, NULL }
1939+
};
1940+
18691941
/***** Job module *****/
18701942

18711943
static bool jobEqual(const df::job *job1, const df::job *job2)
@@ -4304,6 +4376,7 @@ void OpenDFHackApi(lua_State *state)
43044376
luaL_setfuncs(state, dfhack_funcs, 0);
43054377
OpenModule(state, "translation", dfhack_translation_module);
43064378
OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs);
4379+
OpenModule(state, "hotkey", dfhack_hotkey_module, dfhack_hotkey_funcs);
43074380
OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs);
43084381
OpenModule(state, "textures", dfhack_textures_funcs);
43094382
OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs);

library/include/modules/Hotkey.h

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,49 +10,63 @@
1010
#include <vector>
1111

1212
namespace DFHack {
13+
namespace Hotkey {
14+
struct KeySpec {
15+
int modifiers = 0;
16+
// Negative numbers denote mouse buttons
17+
int sym = 0;
18+
std::vector<std::string> focus;
19+
};
20+
21+
struct KeyBinding {
22+
KeySpec spec;
23+
std::string command;
24+
std::string cmdline;
25+
};
26+
27+
DFHACK_EXPORT std::string keyspec_to_string(const KeySpec& spec, bool include_focus=false);
28+
}
1329
class DFHACK_EXPORT HotkeyManager {
1430
friend class Core;
1531
public:
1632
HotkeyManager();
1733
~HotkeyManager();
1834

19-
struct KeySpec {
20-
int modifiers = 0;
21-
// Negative numbers denote mouse buttons
22-
int sym = 0;
23-
std::vector<std::string> focus;
24-
};
25-
26-
struct KeyBinding {
27-
HotkeyManager::KeySpec spec;
28-
std::string command;
29-
std::string cmdline;
30-
};
3135

3236
bool addKeybind(std::string keyspec, std::string cmd);
33-
bool addKeybind(KeySpec spec, std::string cmd);
34-
bool clearKeybind(std::string keyspec, bool any_focus=false);
35-
bool clearKeybind(const KeySpec& spec, bool any_focus=false);
37+
bool addKeybind(Hotkey::KeySpec spec, std::string cmd);
38+
// Clear a keybind with the given keyspec, optionally for any focus, or with a specific command
39+
bool clearKeybind(std::string keyspec, bool any_focus=false, std::string cmdline="");
40+
bool clearKeybind(const Hotkey::KeySpec& spec, bool any_focus=false, std::string cmdline="");
3641

3742
std::vector<std::string> listKeybinds(std::string keyspec);
38-
std::vector<std::string> listKeybinds(const KeySpec& spec);
43+
std::vector<std::string> listKeybinds(const Hotkey::KeySpec& spec);
3944

40-
std::vector<KeyBinding> listActiveKeybinds();
45+
std::vector<Hotkey::KeyBinding> listActiveKeybinds();
46+
std::vector<Hotkey::KeyBinding> listAllKeybinds();
4147

4248
bool handleKeybind(int sym, int modifiers);
43-
4449
void setHotkeyCommand(std::string cmd);
4550

46-
std::optional<KeySpec> parseKeySpec(std::string spec);
51+
// Used to request the next keybind input is saved.
52+
// This is to allow for graphical keybinding menus.
53+
void requestKeybindInput();
54+
// Returns the latest requested keybind input
55+
std::string readKeybindInput();
56+
57+
std::optional<Hotkey::KeySpec> parseKeySpec(std::string spec);
4758
private:
4859
std::thread hotkey_thread;
4960
std::mutex lock {};
5061
std::condition_variable cond {};
5162

63+
bool keybind_save_requested = false;
64+
std::string requested_keybind;
65+
5266
int hotkey_sig = 0;
5367
std::string queued_command = "";
5468

55-
std::map<int, std::vector<KeyBinding>> bindings;
69+
std::map<int, std::vector<Hotkey::KeyBinding>> bindings;
5670

5771
void hotkey_thread_fn();
5872
void handleKeybindingCommand(color_ostream& out, const std::vector<std::string>& parts);

library/modules/Hotkey.cpp

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,58 @@
1616
#include <SDL_keycode.h>
1717

1818
using namespace DFHack;
19+
using Hotkey::KeySpec;
20+
using Hotkey::KeyBinding;
1921

2022
enum HotkeySignal {
2123
None = 0,
2224
CmdReady,
2325
Shutdown,
2426
};
2527

26-
bool operator==(const HotkeyManager::KeySpec& a, const HotkeyManager::KeySpec& b) {
28+
bool operator==(const KeySpec& a, const KeySpec& b) {
2729
return a.modifiers == b.modifiers && a.sym == b.sym &&
2830
a.focus.size() == b.focus.size() &&
2931
std::equal(a.focus.begin(), a.focus.end(), b.focus.begin());
3032
}
3133

3234
// Equality operator for key bindings
33-
bool operator==(const HotkeyManager::KeyBinding& a, const HotkeyManager::KeyBinding& b) {
35+
bool operator==(const KeyBinding& a, const KeyBinding& b) {
3436
return a.spec == b.spec &&
3537
a.command == b.command &&
3638
a.cmdline == b.cmdline;
3739
}
3840

41+
std::string Hotkey::keyspec_to_string(const KeySpec &spec, bool include_focus) {
42+
std::string sym;
43+
if (spec.modifiers & DFH_MOD_CTRL) sym += "Ctrl-";
44+
if (spec.modifiers & DFH_MOD_ALT) sym += "Alt-";
45+
if (spec.modifiers & DFH_MOD_SHIFT) sym += "Shift-";
46+
47+
std::string key_name;
48+
if (spec.sym < 0) {
49+
key_name = "MOUSE" + std::to_string(-spec.sym);
50+
} else {
51+
key_name = DFSDL::DFSDL_GetKeyName(spec.sym);
52+
}
53+
sym += key_name;
54+
55+
if (include_focus && !spec.focus.empty()) {
56+
sym += "@";
57+
bool first = true;
58+
for (const auto& focus : spec.focus) {
59+
if (first) {
60+
first = false;
61+
sym += focus;
62+
} else {
63+
sym += "|" + focus;
64+
}
65+
}
66+
}
67+
68+
return sym;
69+
}
70+
3971
// Hotkeys actions are executed from an external thread to avoid deadlocks
4072
// that may occur if running commands from the render or simulation threads.
4173
void HotkeyManager::hotkey_thread_fn() {
@@ -64,7 +96,7 @@ void HotkeyManager::hotkey_thread_fn() {
6496
}
6597
}
6698

67-
std::optional<HotkeyManager::KeySpec> HotkeyManager::parseKeySpec(std::string spec) {
99+
std::optional<KeySpec> HotkeyManager::parseKeySpec(std::string spec) {
68100
KeySpec out;
69101

70102
// Determine focus string, if present
@@ -138,16 +170,16 @@ bool HotkeyManager::addKeybind(std::string keyspec, std::string cmd) {
138170
return this->addKeybind(spec_opt.value(), cmd);
139171
}
140172

141-
bool HotkeyManager::clearKeybind(const KeySpec& spec, bool any_focus) {
173+
bool HotkeyManager::clearKeybind(const KeySpec& spec, bool any_focus, std::string cmdline) {
142174
std::lock_guard<std::mutex> l(lock);
143175
if (!bindings.contains(spec.sym))
144176
return false;
145177
auto& binds = bindings[spec.sym];
146178

147-
auto new_end = std::remove_if(binds.begin(), binds.end(), [any_focus, spec](const auto& v) {
148-
return any_focus
179+
auto new_end = std::remove_if(binds.begin(), binds.end(), [any_focus, spec, &cmdline](const auto& v) {
180+
return (any_focus
149181
? v.spec.sym == spec.sym && v.spec.modifiers == spec.modifiers
150-
: v.spec == spec;
182+
: v.spec == spec) && (cmdline.empty() || v.cmdline == cmdline);
151183
});
152184
if (new_end == binds.end())
153185
return false; // No bindings removed
@@ -156,11 +188,11 @@ bool HotkeyManager::clearKeybind(const KeySpec& spec, bool any_focus) {
156188
return true;
157189
}
158190

159-
bool HotkeyManager::clearKeybind(std::string keyspec, bool any_focus) {
191+
bool HotkeyManager::clearKeybind(std::string keyspec, bool any_focus, std::string cmdline) {
160192
std::optional<KeySpec> spec_opt = parseKeySpec(keyspec);
161193
if (!spec_opt.has_value())
162194
return false;
163-
return this->clearKeybind(spec_opt.value(), any_focus);
195+
return this->clearKeybind(spec_opt.value(), any_focus, cmdline);
164196
}
165197

166198
std::vector<std::string> HotkeyManager::listKeybinds(const KeySpec& spec) {
@@ -200,7 +232,7 @@ std::vector<std::string> HotkeyManager::listKeybinds(std::string keyspec) {
200232
return this->listKeybinds(spec_opt.value());
201233
}
202234

203-
std::vector<HotkeyManager::KeyBinding> HotkeyManager::listActiveKeybinds() {
235+
std::vector<KeyBinding> HotkeyManager::listActiveKeybinds() {
204236
std::vector<KeyBinding> out;
205237

206238
for(const auto& [_, bind_set] : bindings) {
@@ -223,6 +255,17 @@ std::vector<HotkeyManager::KeyBinding> HotkeyManager::listActiveKeybinds() {
223255
return out;
224256
}
225257

258+
std::vector<KeyBinding> HotkeyManager::listAllKeybinds() {
259+
std::vector<KeyBinding> out;
260+
261+
for (const auto& [_, bind_set] : bindings) {
262+
for (const auto& bind : bind_set) {
263+
out.emplace_back(bind);
264+
}
265+
}
266+
return out;
267+
}
268+
226269
bool HotkeyManager::handleKeybind(int sym, int modifiers) {
227270
// Ensure gamestate is ready
228271
if (!df::global::gview || !df::global::plotinfo)
@@ -237,6 +280,17 @@ bool HotkeyManager::handleKeybind(int sym, int modifiers) {
237280
sym = SDLK_RETURN;
238281

239282
std::unique_lock<std::mutex> l(lock);
283+
284+
// If reading input for a keybinding screen, save the input and exit early
285+
if (keybind_save_requested) {
286+
KeySpec spec;
287+
spec.sym = sym;
288+
spec.modifiers = modifiers;
289+
requested_keybind = Hotkey::keyspec_to_string(spec);
290+
keybind_save_requested = false;
291+
return true;
292+
}
293+
240294
if (!bindings.contains(sym))
241295
return false;
242296
auto& binds = bindings[sym];
@@ -286,6 +340,16 @@ void HotkeyManager::setHotkeyCommand(std::string cmd) {
286340
cond.notify_all();
287341
}
288342

343+
void HotkeyManager::requestKeybindInput() {
344+
std::lock_guard<std::mutex> l(lock);
345+
keybind_save_requested = true;
346+
requested_keybind = "";
347+
}
348+
349+
std::string HotkeyManager::readKeybindInput() {
350+
std::lock_guard<std::mutex> l(lock);
351+
return requested_keybind;
352+
}
289353

290354
void HotkeyManager::handleKeybindingCommand(color_ostream &con, const std::vector<std::string>& parts) {
291355
if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) {

plugins/hotkeys.cpp

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,7 @@ static void find_active_keybindings(color_ostream &out, df::viewscreen *screen,
9494

9595
auto active_binds = Core::getInstance().getHotkeyManager()->listActiveKeybinds();
9696
for (const auto& bind : active_binds) {
97-
string sym;
98-
if (bind.spec.modifiers & DFH_MOD_CTRL) sym += "Ctrl-";
99-
if (bind.spec.modifiers & DFH_MOD_ALT) sym += "Alt-";
100-
if (bind.spec.modifiers & DFH_MOD_SHIFT) sym += "Shift-";
101-
102-
std::string key_name;
103-
if (bind.spec.sym < 0) {
104-
key_name = "MOUSE" + std::to_string(-bind.spec.sym);
105-
} else {
106-
key_name = DFSDL::DFSDL_GetKeyName(bind.spec.sym);
107-
}
108-
if (key_name.empty()) continue;
109-
sym += key_name;
110-
97+
string sym = Hotkey::keyspec_to_string(bind.spec);
11198
add_binding_if_valid(out, sym, bind.cmdline, screen, filtermenu);
11299
}
113100

0 commit comments

Comments
 (0)