You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
8.0 KiB
459 lines
8.0 KiB
#include "Plugin.h" |
|
|
|
#include <config.h> |
|
#include <i18n.h> |
|
|
|
#ifdef ENABLE_PLUGINS |
|
|
|
extern "C" { |
|
#include <lua.h> |
|
#include <lauxlib.h> |
|
#include <lualib.h> |
|
#include <lauxlib.h> |
|
} |
|
|
|
#include "luapi_application.h" |
|
|
|
#define LOAD_FROM_INI(target, group, key) \ |
|
{ \ |
|
char* value = g_key_file_get_string(config, group, key, NULL); \ |
|
if (value != NULL) \ |
|
{ \ |
|
target = value; \ |
|
g_free(value); \ |
|
} \ |
|
} |
|
|
|
/* |
|
** these libs are loaded by lua.c and are readily available to any Lua |
|
** program |
|
*/ |
|
static const luaL_Reg loadedlibs[] = { |
|
{ "app", luaopen_app }, |
|
{ NULL, NULL } |
|
}; |
|
|
|
Plugin::Plugin(Control* control, string name, string path) |
|
: control(control), |
|
name(name), |
|
path(path) |
|
{ |
|
XOJ_INIT_TYPE(Plugin); |
|
|
|
loadIni(); |
|
} |
|
|
|
Plugin::~Plugin() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
if (lua) |
|
{ |
|
// Clean up, free the Lua state var |
|
lua_close(lua); |
|
lua = NULL; |
|
} |
|
|
|
for (MenuEntry* m : menuEntries) |
|
{ |
|
delete m; |
|
} |
|
menuEntries.clear(); |
|
|
|
XOJ_RELEASE_TYPE(Plugin); |
|
} |
|
|
|
/** |
|
* Get Plugin from lua engine |
|
*/ |
|
Plugin* Plugin::getPluginFromLua(lua_State* lua) |
|
{ |
|
lua_getfield(lua, LUA_REGISTRYINDEX, "Xournalpp_Plugin"); |
|
|
|
if (lua_islightuserdata(lua, -1)) |
|
{ |
|
Plugin* data = (Plugin*)lua_touserdata(lua, -1); |
|
lua_pop(lua, 1); |
|
|
|
XOJ_CHECK_TYPE_OBJ(data, Plugin); |
|
|
|
return data; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
/** |
|
* Register toolbar item and all other UI stuff |
|
*/ |
|
void Plugin::registerToolbar() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
if (!this->valid || !this->enabled) |
|
{ |
|
return; |
|
} |
|
|
|
inInitUi = true; |
|
|
|
lua_getglobal(lua, "initUi"); |
|
if (lua_isfunction (lua, -1) == 1) |
|
{ |
|
if (callFunction("initUi")) |
|
{ |
|
g_message("Plugin «%s» UI initialized", name.c_str()); |
|
} |
|
else |
|
{ |
|
g_warning("Plugin «%s» init failed!", name.c_str()); |
|
} |
|
} |
|
else |
|
{ |
|
g_message("Plugin «%s» has no UI init", name.c_str()); |
|
} |
|
|
|
inInitUi = false; |
|
} |
|
|
|
|
|
/** |
|
* Register all menu entries to the menu |
|
*/ |
|
void Plugin::registerMenu(GtkWindow* mainWindow, GtkWidget* menu) |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
if (menuEntries.empty() || !this->enabled) |
|
{ |
|
// No entries - nothing to do |
|
return; |
|
} |
|
|
|
gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); |
|
|
|
GtkAccelGroup* accelGroup = gtk_accel_group_new(); |
|
|
|
for (MenuEntry* m : menuEntries) |
|
{ |
|
GtkWidget* mi = gtk_menu_item_new_with_label(m->menu.c_str()); |
|
m->widget = mi; |
|
gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); |
|
|
|
if (m->accelerator != "") |
|
{ |
|
guint acceleratorKey = 0; |
|
GdkModifierType mods = (GdkModifierType)0; |
|
gtk_accelerator_parse(m->accelerator.c_str(), &acceleratorKey, &mods); |
|
|
|
gtk_widget_add_accelerator(mi, "activate", accelGroup, acceleratorKey, mods, GTK_ACCEL_VISIBLE); |
|
} |
|
|
|
g_signal_connect(mi, "activate", G_CALLBACK( |
|
+[](GtkWidget* bt, MenuEntry* me) |
|
{ |
|
XOJ_CHECK_TYPE_OBJ(me, MenuEntry); |
|
me->plugin->executeMenuEntry(me); |
|
}), m); |
|
} |
|
|
|
gtk_window_add_accel_group(GTK_WINDOW(mainWindow), accelGroup); |
|
} |
|
|
|
/** |
|
* Execute menu entry |
|
*/ |
|
void Plugin::executeMenuEntry(MenuEntry* entry) |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
callFunction(entry->callback); |
|
} |
|
|
|
/** |
|
* @return the Plugin name |
|
*/ |
|
string Plugin::getName() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return name; |
|
} |
|
|
|
/** |
|
* @return Description of the plugin |
|
*/ |
|
string Plugin::getDescription() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return description; |
|
} |
|
|
|
/** |
|
* Author of the plugin |
|
*/ |
|
string Plugin::getAuthor() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return author; |
|
} |
|
|
|
/** |
|
* Plugin version |
|
*/ |
|
string Plugin::getVersion() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return version; |
|
} |
|
|
|
/** |
|
* The plugin is enabled |
|
*/ |
|
bool Plugin::isEnabled() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return enabled; |
|
} |
|
|
|
/** |
|
* The plugin is enabled |
|
*/ |
|
void Plugin::setEnabled(bool enabled) |
|
{ |
|
this->enabled = enabled; |
|
} |
|
|
|
/** |
|
* The plugin is default enabled |
|
*/ |
|
bool Plugin::isDefaultEnabled() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return defaultEnabled; |
|
} |
|
|
|
/** |
|
* @return Flag to check if init ui is currently running |
|
*/ |
|
bool Plugin::isInInitUi() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return inInitUi; |
|
} |
|
|
|
/** |
|
* Register a menu item |
|
* |
|
* @return Internal ID, can e.g. be used to disable the menu |
|
*/ |
|
int Plugin::registerMenu(string menu, string callback, string accelerator) |
|
{ |
|
MenuEntry* m = new MenuEntry(this); |
|
m->menu = menu; |
|
m->callback = callback; |
|
m->accelerator = accelerator; |
|
menuEntries.push_back(m); |
|
|
|
return menuEntries.size() - 1; |
|
} |
|
|
|
/** |
|
* @return The main controller |
|
*/ |
|
Control* Plugin::getControl() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return control; |
|
} |
|
|
|
/** |
|
* Load ini file |
|
*/ |
|
void Plugin::loadIni() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
GKeyFile* config = g_key_file_new(); |
|
g_key_file_set_list_separator(config, ','); |
|
|
|
string filename = path + "/plugin.ini"; |
|
if (!g_key_file_load_from_file(config, filename.c_str(), G_KEY_FILE_NONE, NULL)) |
|
{ |
|
g_key_file_free(config); |
|
return; |
|
} |
|
|
|
LOAD_FROM_INI(author, "about", "author"); |
|
LOAD_FROM_INI(version, "about", "version"); |
|
LOAD_FROM_INI(description, "about", "description"); |
|
|
|
if (version == "<xournalpp>") |
|
{ |
|
version = PROJECT_VERSION; |
|
} |
|
|
|
LOAD_FROM_INI(mainfile, "plugin", "mainfile"); |
|
|
|
string defaultEnabledStr; |
|
LOAD_FROM_INI(defaultEnabledStr, "default", "enabled"); |
|
|
|
defaultEnabled = defaultEnabledStr == "true"; |
|
enabled = defaultEnabled; |
|
|
|
g_key_file_free(config); |
|
|
|
this->valid = true; |
|
} |
|
|
|
/** |
|
* Load custom Lua Libraries |
|
*/ |
|
void Plugin::registerXournalppLibs(lua_State* lua) |
|
{ |
|
for (const luaL_Reg* lib = loadedlibs; lib->func; lib++) |
|
{ |
|
luaL_requiref(lua, lib->name, lib->func, 1); |
|
|
|
// remove lib |
|
lua_pop(lua, 1); |
|
} |
|
} |
|
|
|
/** |
|
* Add the plugin folder to the lua path |
|
*/ |
|
void Plugin::addPluginToLuaPath() |
|
{ |
|
lua_getglobal(lua, "package"); |
|
|
|
// get field "path" from table at top of stack (-1) |
|
lua_getfield(lua, -1, "path"); |
|
|
|
// For now: limit the include path to the current plugin folder, for security and compatibility reasons |
|
// grab path string from top of stack |
|
// string curPath = lua_tostring(lua, -1); |
|
// curPath.append(";"); |
|
string curPath; |
|
curPath.append(path); |
|
curPath.append("/?.lua"); |
|
|
|
// get rid of the string on the stack we just pushed on line 5 |
|
lua_pop(lua, 1); |
|
|
|
// push the new one |
|
lua_pushstring(lua, curPath.c_str()); |
|
|
|
// set the field "path" in table at -2 with value at top of stack |
|
lua_setfield(lua, -2, "path"); |
|
|
|
// get rid of package table from top of stack |
|
lua_pop(lua, 1); |
|
} |
|
|
|
/** |
|
* Load the plugin script |
|
*/ |
|
void Plugin::loadScript() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
if (mainfile == "") |
|
{ |
|
this->valid = false; |
|
return; |
|
} |
|
|
|
if (mainfile.find("..") != std::string::npos) |
|
{ |
|
g_warning("Plugin «%s» contains unsupported path «%s»", name.c_str(), mainfile.c_str()); |
|
this->valid = false; |
|
return; |
|
} |
|
|
|
if (!this->enabled) |
|
{ |
|
return; |
|
} |
|
|
|
|
|
// Create Lua state variable |
|
lua = luaL_newstate(); |
|
|
|
// Load Lua libraries |
|
luaL_openlibs(lua); |
|
|
|
// Load but don't run the Lua script |
|
string luafile = path + "/" + mainfile; |
|
if (luaL_loadfile(lua, luafile.c_str())) |
|
{ |
|
// Error out if file can't be read |
|
g_warning("Could not run plugin Lua file: «%s»", luafile.c_str()); |
|
this->valid = false; |
|
return; |
|
} |
|
|
|
// Register Plugin object to Lua instance |
|
lua_pushlightuserdata(lua, this); |
|
lua_setfield(lua, LUA_REGISTRYINDEX, "Xournalpp_Plugin"); |
|
|
|
registerXournalppLibs(lua); |
|
|
|
addPluginToLuaPath(); |
|
|
|
// Run the loaded Lua script |
|
if (lua_pcall(lua, 0, 0, 0) != LUA_OK) |
|
{ |
|
const char* errMsg = lua_tostring(lua, -1); |
|
map<int, string> button; |
|
button.insert(std::pair<int, string>(0, _("OK"))); |
|
XojMsgBox::showPluginMessage(name, errMsg, button, true); |
|
|
|
g_warning("Could not run plugin Lua file: «%s», error: «%s»", luafile.c_str(), errMsg); |
|
this->valid = false; |
|
return; |
|
} |
|
} |
|
|
|
/** |
|
* Execute lua function |
|
*/ |
|
bool Plugin::callFunction(string fnc) |
|
{ |
|
lua_getglobal(lua, fnc.c_str()); |
|
|
|
// Run the function |
|
if (lua_pcall(lua, 0, 0, 0)) |
|
{ |
|
const char* errMsg = lua_tostring(lua, -1); |
|
map<int, string> button; |
|
button.insert(std::pair<int, string>(0, _("OK"))); |
|
XojMsgBox::showPluginMessage(name, errMsg, button, true); |
|
|
|
g_warning("Error in Plugin: «%s», error: «%s»", name.c_str(), errMsg); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* Check if this plugin is valid |
|
*/ |
|
bool Plugin::isValid() |
|
{ |
|
XOJ_CHECK_TYPE(Plugin); |
|
|
|
return valid; |
|
} |
|
|
|
#endif |
|
|
|
|