26be83f893
* module: Implement proof of concept action router Action implementation inside module becomes much cleaner because each module just registers action names together with a callback (pointer to member function) and the action router does the rest. * Make input function final This forces all modules to use the action router * modules: Catch exceptions in action handlers * Use action router for all modules * Use action_ prefix for function names The mpd module's 'stop' action overwrote the base module's stop function which caused difficult to debug behavior. To prevent this in the future we now prefix each function that is responsible for an action with 'action_' * Cleanup * actions: Throw exception when re-registering action Action names are unique inside modules. Unfortunately there is no way to ensure this statically, the next best thing is to crash the module and let the user know that this is a bug. * Formatting * actions: Ignore data for actions without data This is the same behavior as before. * action_router: Write tests
95 lines
2.2 KiB
C++
95 lines
2.2 KiB
C++
#pragma once
|
|
|
|
#include <cassert>
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
|
|
#include "common.hpp"
|
|
|
|
POLYBAR_NS
|
|
|
|
/**
|
|
* Maps action names to function pointers in this module and invokes them.
|
|
*
|
|
* Each module has one instance of this class and uses it to register action.
|
|
* For each action the module has to register the name, whether it can take
|
|
* additional data, and a pointer to the member function implementing that
|
|
* action.
|
|
*
|
|
* Ref: https://isocpp.org/wiki/faq/pointers-to-members
|
|
*
|
|
* The input() function in the base class uses this for invoking the actions
|
|
* of that module.
|
|
*
|
|
* Any module that does not reimplement that function will automatically use
|
|
* this class for action routing.
|
|
*/
|
|
template <typename Impl>
|
|
class action_router {
|
|
typedef void (Impl::*callback)();
|
|
typedef void (Impl::*callback_data)(const std::string&);
|
|
|
|
public:
|
|
explicit action_router(Impl* This) : m_this(This) {}
|
|
|
|
void register_action(const string& name, callback func) {
|
|
entry e;
|
|
e.with_data = false;
|
|
e.without = func;
|
|
register_entry(name, e);
|
|
}
|
|
|
|
void register_action_with_data(const string& name, callback_data func) {
|
|
entry e;
|
|
e.with_data = true;
|
|
e.with = func;
|
|
register_entry(name, e);
|
|
}
|
|
|
|
bool has_action(const string& name) {
|
|
return callbacks.find(name) != callbacks.end();
|
|
}
|
|
|
|
/**
|
|
* Invokes the given action name on the passed module pointer.
|
|
*
|
|
* The action must exist.
|
|
*/
|
|
void invoke(const string& name, const string& data) {
|
|
auto it = callbacks.find(name);
|
|
assert(it != callbacks.end());
|
|
|
|
entry e = it->second;
|
|
|
|
#define CALL_MEMBER_FN(object, ptrToMember) ((object).*(ptrToMember))
|
|
if (e.with_data) {
|
|
CALL_MEMBER_FN(*m_this, e.with)(data);
|
|
} else {
|
|
CALL_MEMBER_FN(*m_this, e.without)();
|
|
}
|
|
#undef CALL_MEMBER_FN
|
|
}
|
|
|
|
protected:
|
|
struct entry {
|
|
union {
|
|
callback without;
|
|
callback_data with;
|
|
};
|
|
bool with_data;
|
|
};
|
|
|
|
void register_entry(const string& name, const entry& e) {
|
|
if (has_action(name)) {
|
|
throw std::invalid_argument("Tried to register action '" + name + "' twice. THIS IS A BUG!");
|
|
}
|
|
callbacks[name] = e;
|
|
}
|
|
|
|
private:
|
|
std::unordered_map<string, entry> callbacks;
|
|
Impl* m_this;
|
|
};
|
|
|
|
POLYBAR_NS_END
|