From 53346328e8cc12dc2bb800534aefcb4eabb15502 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Dec 2011 18:12:15 +0400 Subject: [PATCH 1/4] Notify plugins about game being loaded or unloaded. As a test, make seadwatch deactivate on these events. --- library/Core.cpp | 19 +++++++++++++++++++ library/PluginManager.cpp | 24 ++++++++++++++++++++++++ library/include/dfhack/Core.h | 3 +++ library/include/dfhack/PluginManager.h | 8 ++++++++ plugins/seedwatch.cpp | 24 +++++++++++++++++++++++- 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 04b885fa7..41d3b5bd9 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -51,6 +51,9 @@ using namespace DFHack; #include "dfhack/SDL_fakes/events.h" +#include "dfhack/df/world.h" +#include "dfhack/df/world_data.h" + #include #include #include @@ -439,6 +442,7 @@ Core::Core() HotkeyMutex = 0; HotkeyCond = 0; misc_data_mutex=0; + last_world_data_ptr = NULL; }; void Core::fatal (std::string output, bool deactivate) @@ -631,8 +635,23 @@ int Core::Update() if(errorstate) return -1; + // detect if the game was loaded or unloaded in the meantime + void *new_wdata = NULL; + if (df::global::world) { + df::world_data *wdata = df::global::world->world_data; + // when the game is unloaded, world_data isn't deleted, but its contents are + if (wdata && !wdata->sites.empty()) + new_wdata = wdata; + } + + if (new_wdata != last_world_data_ptr) { + last_world_data_ptr = new_wdata; + plug_mgr->OnStateChange(new_wdata ? SC_GAME_LOADED : SC_GAME_UNLOADED); + } + // notify all the plugins that a game tick is finished plug_mgr->OnUpdate(); + // wake waiting tools // do not allow more tools to join in while we process stuff here StackMutex->lock(); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d37c3fbf0..997b8e59f 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -135,6 +135,7 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f plugin_shutdown = 0; plugin_status = 0; plugin_onupdate = 0; + plugin_onstatechange = 0; state = PS_UNLOADED; access = new RefLock(); } @@ -192,6 +193,7 @@ bool Plugin::load() plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status"); plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate"); plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown"); + plugin_onstatechange = (command_result (*)(Core *, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); //name = _PlugName(); plugin_lib = plug; if(plugin_init(&c,commands) == CR_OK) @@ -299,6 +301,19 @@ command_result Plugin::on_update() return cr; } +command_result Plugin::on_state_change(state_change_event event) +{ + Core & c = Core::getInstance(); + command_result cr = CR_NOT_IMPLEMENTED; + access->lock_add(); + if(state == PS_LOADED && plugin_onstatechange) + { + cr = plugin_onstatechange(&c, event); + } + access->lock_sub(); + return cr; +} + Plugin::plugin_state Plugin::getState() const { return state; @@ -370,6 +385,15 @@ void PluginManager::OnUpdate( void ) all_plugins[i]->on_update(); } } + +void PluginManager::OnStateChange( state_change_event event ) +{ + for(int i = 0; i < all_plugins.size(); i++) + { + all_plugins[i]->on_state_change(event); + } +} + // FIXME: doesn't check name collisions! void PluginManager::registerCommands( Plugin * p ) { diff --git a/library/include/dfhack/Core.h b/library/include/dfhack/Core.h index 64eae5d3e..e91dae390 100644 --- a/library/include/dfhack/Core.h +++ b/library/include/dfhack/Core.h @@ -134,6 +134,8 @@ namespace DFHack /// returns a named pointer. void *GetData(std::string key); + bool isWorldLoaded() { return (last_world_data_ptr != NULL); } + DFHack::Process * p; DFHack::VersionInfo * vinfo; DFHack::Console con; @@ -184,6 +186,7 @@ namespace DFHack bool hotkey_set; tthread::mutex * HotkeyMutex; tthread::condition_variable * HotkeyCond; + void *last_world_data_ptr; // for state change tracking // Very important! bool started; diff --git a/library/include/dfhack/PluginManager.h b/library/include/dfhack/PluginManager.h index fef1aea50..0ac84f219 100644 --- a/library/include/dfhack/PluginManager.h +++ b/library/include/dfhack/PluginManager.h @@ -46,6 +46,11 @@ namespace DFHack CR_FAILURE = 0, CR_OK = 1 }; + enum state_change_event + { + SC_GAME_LOADED, + SC_GAME_UNLOADED + }; struct PluginCommand { /// create a command with a name, description, function pointer to its code @@ -87,6 +92,7 @@ namespace DFHack Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); ~Plugin(); command_result on_update(); + command_result on_state_change(state_change_event event); public: bool load(); bool unload(); @@ -117,6 +123,7 @@ namespace DFHack command_result (*plugin_status)(Core *, std::string &); command_result (*plugin_shutdown)(Core *); command_result (*plugin_onupdate)(Core *); + command_result (*plugin_onstatechange)(Core *, state_change_event); }; class DFHACK_EXPORT PluginManager { @@ -126,6 +133,7 @@ namespace DFHack PluginManager(Core * core); ~PluginManager(); void OnUpdate( void ); + void OnStateChange( state_change_event event ); void registerCommands( Plugin * p ); void unregisterCommands( Plugin * p ); // PUBLIC METHODS diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 418398e3d..90175470c 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -288,10 +288,32 @@ DFhackCExport DFHack::command_result plugin_init(DFHack::Core* pCore, std::vecto return DFHack::CR_OK; } +DFhackCExport DFHack::command_result plugin_onstatechange(DFHack::Core* pCore, DFHack::state_change_event event) +{ + switch (event) { + case DFHack::SC_GAME_LOADED: + case DFHack::SC_GAME_UNLOADED: + if (running) + pCore->con.printerr("seedwatch deactivated due to game load/unload\n"); + running = false; + break; + default: + break; + } + + return DFHack::CR_OK; +} + DFhackCExport DFHack::command_result plugin_onupdate(DFHack::Core* pCore) { - if(running) + if (running) { + // reduce processing rate + static int counter = 0; + if (++counter < 500) + return DFHack::CR_OK; + counter = 0; + DFHack::Core& core = *pCore; DFHack::World *w = core.getWorld(); DFHack::t_gamemodes gm; From b71e577771fc8ee5994d79af422bccfd665ad8c8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Dec 2011 18:27:55 +0400 Subject: [PATCH 2/4] Allow specifying parameters in hotkey commands. Now that hotkeys can be set to arbitrary strings, tokenize them. --- library/Core.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 41d3b5bd9..e8fc63d03 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -148,8 +148,17 @@ void fHKthread(void * iodata) std::string stuff = core->getHotkeyCmd(); // waits on mutex! if(!stuff.empty()) { - vector crap; - command_result cr = plug_mgr->InvokeCommand(stuff, crap, false); + vector args; + cheap_tokenise(stuff, args); + if (args.empty()) { + core->con.printerr("Empty hotkey command.\n"); + continue; + } + + string first = args[0]; + args.erase(args.begin()); + command_result cr = plug_mgr->InvokeCommand(first, args, false); + if(cr == CR_WOULD_BREAK) { core->con.printerr("It isn't possible to run an interactive command outside the console.\n"); From 4aa77f55308236189f157f65ad9ce82c35b9dabc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Dec 2011 23:11:34 +0400 Subject: [PATCH 3/4] Execute commands from the dfhack.init file on startup. --- library/Core.cpp | 106 +++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index e8fc63d03..c6d204f03 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -167,53 +167,25 @@ void fHKthread(void * iodata) } } -// A thread function... for the interactive console. -void fIOthread(void * iodata) +static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command) { - IODATA * iod = ((IODATA*) iodata); - Core * core = iod->core; - PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; - CommandHistory main_history; - main_history.load("dfhack.history"); Console & con = core->con; - if(plug_mgr == 0 || core == 0) - { - con.printerr("Something horrible happened in Core's constructor...\n"); - return; - } - con.print("DFHack is ready. Have a nice day!\n" - "Type in '?' or 'help' for general help, 'ls' to see all commands.\n"); - int clueless_counter = 0; - while (true) + + if (!command.empty()) { - string command = ""; - int ret = con.lineedit("[DFHack]# ",command, main_history); - if(ret == -2) - { - cerr << "Console is shutting down properly." << endl; - return; - } - else if(ret == -1) - { - cerr << "Console caught an unspecified error." << endl; - continue; - } - else if(ret) - { - // a proper, non-empty command was entered - main_history.add(command); - main_history.save("dfhack.history"); - } // cut the input into parts vector parts; cheap_tokenise(command,parts); if(parts.size() == 0) { clueless_counter ++; - continue; + return; } string first = parts[0]; parts.erase(parts.begin()); + + if (first[0] == '#') return; + cerr << "Invoking: " << command << endl; // let's see what we actually got @@ -422,6 +394,70 @@ void fIOthread(void * iodata) */ } } + } +} + +static void loadInitFile(Core *core, PluginManager *plug_mgr, string fname) +{ + ifstream init(fname); + if (init.bad()) + return; + + int tmp = 0; + string command; + while (getline(init, command)) + { + if (!command.empty()) + runInteractiveCommand(core, plug_mgr, tmp, command); + } +} + +// A thread function... for the interactive console. +void fIOthread(void * iodata) +{ + IODATA * iod = ((IODATA*) iodata); + Core * core = iod->core; + PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; + + CommandHistory main_history; + main_history.load("dfhack.history"); + + Console & con = core->con; + if(plug_mgr == 0 || core == 0) + { + con.printerr("Something horrible happened in Core's constructor...\n"); + return; + } + + loadInitFile(core, plug_mgr, "dfhack.init"); + + con.print("DFHack is ready. Have a nice day!\n" + "Type in '?' or 'help' for general help, 'ls' to see all commands.\n"); + + int clueless_counter = 0; + while (true) + { + string command = ""; + int ret = con.lineedit("[DFHack]# ",command, main_history); + if(ret == -2) + { + cerr << "Console is shutting down properly." << endl; + return; + } + else if(ret == -1) + { + cerr << "Console caught an unspecified error." << endl; + continue; + } + else if(ret) + { + // a proper, non-empty command was entered + main_history.add(command); + main_history.save("dfhack.history"); + } + + runInteractiveCommand(core, plug_mgr, clueless_counter, command); + if(clueless_counter == 3) { con.print("Do 'help' or '?' for the list of available commands.\n"); From 2222757e77d4c7f2a337c8b6a0f663414702d111 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Dec 2011 23:25:50 +0400 Subject: [PATCH 4/4] Implement context-sensitive keybinding support. Allow defining commands with guard conditions, and binding one or more commands to alphabetic and function keys. When the relevant key is pressed, the first listed command with successfully evaluated guard is chosen. For consistency, the guard is also checked when the command is invoked from the console; this requires suspending the core inside PluginManager, before invoking plugin code. --- library/Core.cpp | 214 ++++++++++++++++++++++--- library/PluginManager.cpp | 96 +++++++++-- library/include/dfhack/Core.h | 23 ++- library/include/dfhack/PluginManager.h | 47 ++++-- library/xml/df.viewscreen.xml | 5 + plugins/stockpiles.cpp | 33 ++-- 6 files changed, 351 insertions(+), 67 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index c6d204f03..938b875e6 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -51,16 +51,20 @@ using namespace DFHack; #include "dfhack/SDL_fakes/events.h" +#include "dfhack/df/ui.h" #include "dfhack/df/world.h" #include "dfhack/df/world_data.h" +#include "dfhack/df/interface.h" +#include "dfhack/df/viewscreen_dwarfmodest.h" #include #include #include #include #include "tinythread.h" -using namespace tthread; +using namespace tthread; +using namespace df::enums; struct Core::Cond { @@ -204,6 +208,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue " cls - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" + " keybinding - Modify bindings of commands to keys\n" "Plugin management (useful for developers):\n" //" belongs COMMAND - Tell which plugin a command belongs to.\n" " plug [PLUGIN|v] - List plugin state and description.\n" @@ -354,6 +359,40 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue con.print("%s\n", plug->getName().c_str()); } } + else if(first == "keybinding") + { + if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) + { + std::string keystr = parts[1]; + if (parts[0] == "set") + core->ClearKeyBindings(keystr); + for (int i = parts.size()-1; i >= 2; i--) + { + if (!core->AddKeyBinding(keystr, parts[i])) { + con.printerr("Invalid key spec: %s\n", keystr.c_str()); + break; + } + } + } + else if (parts.size() >= 2 && parts[0] == "clear") + { + for (unsigned i = 1; i < parts.size(); i++) + { + if (!core->ClearKeyBindings(parts[i])) { + con.printerr("Invalid key spec: %s\n", parts[i].c_str()); + break; + } + } + } + else + { + con << "Usage:" << endl + << " keybinding clear ..." << endl + << " keybinding set \"cmdline\" \"cmdline\"..." << endl + << " keybinding add \"cmdline\" \"cmdline\"..." << endl + << "Later adds, and earlier items within one command have priority." << endl; + } + } else if(first == "fpause") { World * w = core->getWorld(); @@ -482,12 +521,12 @@ Core::Core() StackMutex = 0; core_cond = 0; // set up hotkey capture - memset(hotkey_states,0,sizeof(hotkey_states)); hotkey_set = false; HotkeyMutex = 0; HotkeyCond = 0; misc_data_mutex=0; last_world_data_ptr = NULL; + top_viewscreen = NULL; }; void Core::fatal (std::string output, bool deactivate) @@ -694,6 +733,19 @@ int Core::Update() plug_mgr->OnStateChange(new_wdata ? SC_GAME_LOADED : SC_GAME_UNLOADED); } + // detect if the viewscreen changed + if (df::global::gview) + { + df::viewscreen *screen = &df::global::gview->view; + while (screen->child) + screen = screen->child; + if (screen != top_viewscreen) + { + top_viewscreen = screen; + plug_mgr->OnStateChange(SC_VIEWSCREEN_CHANGED); + } + } + // notify all the plugins that a game tick is finished plug_mgr->OnUpdate(); @@ -785,39 +837,151 @@ int Core::SDL_Event(SDL::Event* ev, int orig_return) if(ev && ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP) { SDL::KeyboardEvent * ke = (SDL::KeyboardEvent *)ev; - bool shift = ke->ksym.mod & SDL::KMOD_SHIFT; - // consuming F1 .. F8 - int idx = ke->ksym.sym - SDL::K_F1; - if(idx < 0 || idx > 7) - return orig_return; - idx += 8*shift; - // now we have the real index... - if(ke->state == SDL::BTN_PRESSED && !hotkey_states[idx]) + + if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym]) { - hotkey_states[idx] = 1; - Gui * g = getGui(); - if(g->hotkeys && g->df_interface && g->df_menu_state) - { - t_viewscreen * ws = g->GetCurrentScreen(); - // FIXME: put hardcoded values into memory.xml - if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23) - return orig_return; - else - { - t_hotkey & hotkey = (*g->hotkeys)[idx]; - setHotkeyCmd(hotkey.name); - } - } + hotkey_states[ke->ksym.sym] = true; + + int mod = 0; + if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1; + if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2; + if (ke->ksym.mod & SDL::KMOD_ALT) mod |= 4; + + SelectHotkey(ke->ksym.sym, mod); } else if(ke->state == SDL::BTN_RELEASED) { - hotkey_states[idx] = 0; + hotkey_states[ke->ksym.sym] = false; } } return orig_return; // do stuff with the events... } +bool Core::SelectHotkey(int sym, int modifiers) +{ + // Find the topmost viewscreen + if (!df::global::gview || !df::global::ui) + return false; + + df::viewscreen *screen = &df::global::gview->view; + while (screen->child) + screen = screen->child; + + std::string cmd; + + { + tthread::lock_guard lock(*HotkeyMutex); + + // Check the internal keybindings + std::vector &bindings = key_bindings[sym]; + for (int i = bindings.size()-1; i >= 0; --i) { + if (bindings[i].modifiers != modifiers) + continue; + if (!plug_mgr->CanInvokeHotkey(bindings[i].command[0], screen)) + continue; + cmd = bindings[i].cmdline; + break; + } + + if (cmd.empty()) { + // Check the hotkey keybindings + int idx = sym - SDL::K_F1; + if(idx >= 0 && idx < 8) + { + if (modifiers & 1) + idx += 8; + + if (!strict_virtual_cast(screen) || + df::global::ui->main.mode != ui_sidebar_mode::Hotkeys) + { + cmd = df::global::ui->main.hotkeys[idx].name; + } + } + } + } + + if (!cmd.empty()) { + setHotkeyCmd(cmd); + return true; + } + else + return false; +} + +static bool parseKeySpec(std::string keyspec, int *psym, int *pmod) +{ + *pmod = 0; + + // ugh, ugly + for (;;) { + if (keyspec.size() > 6 && keyspec.substr(0, 6) == "Shift-") { + *pmod |= 1; + keyspec = keyspec.substr(6); + } else if (keyspec.size() > 5 && keyspec.substr(0, 5) == "Ctrl-") { + *pmod |= 2; + keyspec = keyspec.substr(5); + } else if (keyspec.size() > 4 && keyspec.substr(0, 4) == "Alt-") { + *pmod |= 4; + keyspec = keyspec.substr(4); + } else + break; + } + + if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { + *psym = SDL::K_a + (keyspec[0]-'A'); + return true; + } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') { + *psym = SDL::K_F1 + (keyspec[1]-'1'); + return true; + } else + return false; +} + +bool Core::ClearKeyBindings(std::string keyspec) +{ + int sym, mod; + if (!parseKeySpec(keyspec, &sym, &mod)) + return false; + + tthread::lock_guard lock(*HotkeyMutex); + + std::vector &bindings = key_bindings[sym]; + for (int i = bindings.size()-1; i >= 0; --i) { + if (bindings[i].modifiers == mod) + bindings.erase(bindings.begin()+i); + } + + return true; +} + +bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) +{ + int sym; + KeyBinding binding; + if (!parseKeySpec(keyspec, &sym, &binding.modifiers)) + return false; + + cheap_tokenise(cmdline, binding.command); + if (binding.command.empty()) + return false; + + tthread::lock_guard lock(*HotkeyMutex); + + binding.cmdline = cmdline; + key_bindings[sym].push_back(binding); + return true; +} + +bool DFHack::default_hotkey(Core *, df::viewscreen *top) +{ + // Default hotkey guard function + for (;top ;top = top->parent) + if (strict_virtual_cast(top)) + return true; + return false; +} + //////////////// // ClassNamCheck //////////////// diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 997b8e59f..a520575d8 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -27,6 +27,10 @@ distribution. #include "dfhack/Process.h" #include "dfhack/PluginManager.h" #include "dfhack/Console.h" + +#include "dfhack/DataDefs.h" +#include "dfhack/df/viewscreen.h" + using namespace DFHack; #include @@ -273,13 +277,69 @@ command_result Plugin::invoke( std::string & command, std::vector { for (int i = 0; i < commands.size();i++) { - if(commands[i].name == command) + PluginCommand &cmd = commands[i]; + + if(cmd.name == command) { // running interactive things from some other source than the console would break it - if(!interactive_ && commands[i].interactive) + if(!interactive_ && cmd.interactive) cr = CR_WOULD_BREAK; - else - cr = commands[i].function(&c, parameters); + else if (cmd.guard) + { + // Execute hotkey commands in a way where they can + // expect their guard conditions to be matched, + // so as to avoid duplicating checks. + // This means suspending the core beforehand. + CoreSuspender suspend(&c); + df::viewscreen *top = c.getTopViewscreen(); + + if ((cmd.viewscreen_type && !cmd.viewscreen_type->is_instance(top)) + || !cmd.guard(&c, top)) + { + c.con.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str()); + cr = CR_FAILURE; + } + else + { + cr = cmd.function(&c, parameters); + } + } + else + { + cr = cmd.function(&c, parameters); + } + break; + } + } + } + access->lock_sub(); + return cr; +} + +bool Plugin::can_invoke_hotkey( std::string & command, df::viewscreen *top ) +{ + Core & c = Core::getInstance(); + bool cr = false; + access->lock_add(); + if(state == PS_LOADED) + { + for (int i = 0; i < commands.size();i++) + { + PluginCommand &cmd = commands[i]; + + if(cmd.name == command) + { + if (cmd.interactive) + cr = false; + else if (cmd.guard) + { + cr = (!cmd.viewscreen_type || cmd.viewscreen_type->is_instance(top)) + && cmd.guard(&c, top); + } + else + { + cr = default_hotkey(&c, top); + } break; } } @@ -363,19 +423,27 @@ Plugin *PluginManager::getPluginByName (const std::string & name) return 0; } +Plugin *PluginManager::getPluginByCommand(const std::string &command) +{ + tthread::lock_guard lock(*cmdlist_mutex); + map ::iterator iter = belongs.find(command); + if (iter != belongs.end()) + return iter->second; + else + return NULL; +} + // FIXME: handle name collisions... command_result PluginManager::InvokeCommand( std::string & command, std::vector & parameters, bool interactive) { - command_result cr = CR_NOT_IMPLEMENTED; - Core * c = &Core::getInstance(); - cmdlist_mutex->lock(); - map ::iterator iter = belongs.find(command); - if(iter != belongs.end()) - { - cr = iter->second->invoke(command, parameters, interactive); - } - cmdlist_mutex->unlock(); - return cr; + Plugin *plugin = getPluginByCommand(command); + return plugin ? plugin->invoke(command, parameters, interactive) : CR_NOT_IMPLEMENTED; +} + +bool PluginManager::CanInvokeHotkey(std::string &command, df::viewscreen *top) +{ + Plugin *plugin = getPluginByCommand(command); + return plugin ? plugin->can_invoke_hotkey(command, top) : false; } void PluginManager::OnUpdate( void ) diff --git a/library/include/dfhack/Core.h b/library/include/dfhack/Core.h index e91dae390..427cdf048 100644 --- a/library/include/dfhack/Core.h +++ b/library/include/dfhack/Core.h @@ -43,6 +43,11 @@ namespace tthread class thread; } +namespace df +{ + struct viewscreen; +} + namespace DFHack { class Process; @@ -134,7 +139,11 @@ namespace DFHack /// returns a named pointer. void *GetData(std::string key); + bool ClearKeyBindings(std::string keyspec); + bool AddKeyBinding(std::string keyspec, std::string cmdline); + bool isWorldLoaded() { return (last_world_data_ptr != NULL); } + df::viewscreen *getTopViewscreen() { return top_viewscreen; } DFHack::Process * p; DFHack::VersionInfo * vinfo; @@ -180,13 +189,25 @@ namespace DFHack } s_mods; std::vector allModules; DFHack::PluginManager * plug_mgr; + // hotkey-related stuff - int hotkey_states[16]; + struct KeyBinding { + int modifiers; + std::vector command; + std::string cmdline; + }; + + std::map > key_bindings; + std::map hotkey_states; std::string hotkey_cmd; bool hotkey_set; tthread::mutex * HotkeyMutex; tthread::condition_variable * HotkeyCond; + + bool SelectHotkey(int key, int modifiers); + void *last_world_data_ptr; // for state change tracking + df::viewscreen *top_viewscreen; // Very important! bool started; diff --git a/library/include/dfhack/PluginManager.h b/library/include/dfhack/PluginManager.h index 0ac84f219..3bea5977e 100644 --- a/library/include/dfhack/PluginManager.h +++ b/library/include/dfhack/PluginManager.h @@ -35,10 +35,16 @@ namespace tthread class mutex; class condition_variable; } +namespace df +{ + struct viewscreen; +} namespace DFHack { class Core; class PluginManager; + struct virtual_identity; + enum command_result { CR_WOULD_BREAK = -2, @@ -49,35 +55,45 @@ namespace DFHack enum state_change_event { SC_GAME_LOADED, - SC_GAME_UNLOADED + SC_GAME_UNLOADED, + SC_VIEWSCREEN_CHANGED }; - struct PluginCommand + struct DFHACK_EXPORT PluginCommand { + typedef command_result (*command_function)(Core *, std::vector &); + typedef bool (*command_hotkey_guard)(Core *, df::viewscreen *); + /// create a command with a name, description, function pointer to its code /// and saying if it needs an interactive terminal /// Most commands shouldn't require an interactive terminal! PluginCommand(const char * _name, const char * _description, - command_result (*function_)(Core *, std::vector &), + command_function function_, bool interactive_ = false ) + : name(_name), description(_description), + function(function_), interactive(interactive_), + guard(NULL), viewscreen_type(NULL) { - name = _name; - description = _description; - function = function_; - interactive = interactive_; } - PluginCommand (const PluginCommand & rhs) + + PluginCommand(const char * _name, + const char * _description, + command_function function_, + command_hotkey_guard guard_, + virtual_identity *viewscreen_type_ = NULL) + : name(_name), description(_description), + function(function_), interactive(false), + guard(guard_), viewscreen_type(viewscreen_type_) { - name = rhs.name; - description = rhs.description; - function = rhs.function; - interactive = rhs.interactive; } + std::string name; std::string description; - command_result (*function)(Core *, std::vector &); + command_function function; bool interactive; + command_hotkey_guard guard; + virtual_identity *viewscreen_type; }; class Plugin { @@ -98,6 +114,7 @@ namespace DFHack bool unload(); bool reload(); command_result invoke( std::string & command, std::vector & parameters, bool interactive ); + bool can_invoke_hotkey( std::string & command, df::viewscreen *top ); plugin_state getState () const; const PluginCommand& operator[] (std::size_t index) const { @@ -139,7 +156,9 @@ namespace DFHack // PUBLIC METHODS public: Plugin *getPluginByName (const std::string & name); + Plugin *getPluginByCommand (const std::string &command); command_result InvokeCommand( std::string & command, std::vector & parameters, bool interactive = true ); + bool CanInvokeHotkey(std::string &command, df::viewscreen *top); Plugin* operator[] (std::size_t index) { if(index >= all_plugins.size()) @@ -157,5 +176,7 @@ namespace DFHack std::vector all_plugins; std::string plugin_path; }; + + DFHACK_EXPORT bool default_hotkey(Core *, df::viewscreen *); } diff --git a/library/xml/df.viewscreen.xml b/library/xml/df.viewscreen.xml index cb76db09b..e5816a194 100644 --- a/library/xml/df.viewscreen.xml +++ b/library/xml/df.viewscreen.xml @@ -91,6 +91,11 @@ + + + todo + +