@@ -273,6 +273,7 @@ int main(int argc, char* argv[]) { | |||
plugin::destroy(); | |||
INFO("Destroying network"); | |||
network::destroy(); | |||
settings::destroy(); | |||
INFO("Destroying logger"); | |||
logger::destroy(); | |||
@@ -13,6 +13,7 @@ namespace plugin { | |||
PRIVATE void init(); | |||
PRIVATE void destroy(); | |||
PRIVATE void settingsMergeJson(json_t* rootJ); | |||
/** Finds a loaded Plugin by slug. */ | |||
Plugin* getPlugin(const std::string& pluginSlug); | |||
@@ -16,3 +16,19 @@ Optional in plugins. | |||
*/ | |||
extern "C" | |||
void destroy(); | |||
/** Called when saving user settings. | |||
Stored in `settings["pluginSettings"][pluginSlug]`. | |||
Useful for persisting plugin-wide settings. | |||
Optional in plugins. | |||
*/ | |||
extern "C" | |||
json_t* settingsToJson(); | |||
/** Called after initializing plugin if user plugin settings property is defined. | |||
Optional in plugins. | |||
*/ | |||
extern "C" | |||
void settingsFromJson(json_t* rootJ); |
@@ -83,6 +83,7 @@ enum BrowserSort { | |||
}; | |||
extern BrowserSort browserSort; | |||
extern float browserZoom; | |||
extern json_t* pluginSettingsJ; | |||
struct ModuleInfo { | |||
bool enabled = true; | |||
@@ -110,6 +111,7 @@ extern std::map<std::string, PluginWhitelist> moduleWhitelist; | |||
bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug); | |||
PRIVATE void init(); | |||
PRIVATE void destroy(); | |||
PRIVATE json_t* toJson(); | |||
PRIVATE void fromJson(json_t* rootJ); | |||
PRIVATE void save(std::string path = ""); | |||
@@ -41,6 +41,18 @@ namespace plugin { | |||
// private API | |||
//////////////////// | |||
static void* getSymbol(void* handle, const char* name) { | |||
if (!handle) | |||
return NULL; | |||
#if defined ARCH_WIN | |||
return GetProcAddress((HMODULE) handle, name); | |||
#else | |||
return dlsym(handle, name); | |||
#endif | |||
} | |||
/** Returns library handle */ | |||
static void* loadLibrary(std::string libraryPath) { | |||
#if defined ARCH_WIN | |||
@@ -100,12 +112,7 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||
plugin->handle = loadLibrary(libraryPath); | |||
// Get plugin's init() function | |||
InitCallback initCallback; | |||
#if defined ARCH_WIN | |||
initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init"); | |||
#else | |||
initCallback = (InitCallback) dlsym(plugin->handle, "init"); | |||
#endif | |||
InitCallback initCallback = (InitCallback) getSymbol(plugin->handle, "init"); | |||
if (!initCallback) | |||
throw Exception("Failed to read init() symbol in %s", libraryPath.c_str()); | |||
@@ -169,6 +176,15 @@ static Plugin* loadPlugin(std::string path) { | |||
Plugin* existingPlugin = getPlugin(plugin->slug); | |||
if (existingPlugin) | |||
throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()); | |||
// Call settingsFromJson() if exists | |||
// Returns NULL for Core. | |||
auto settingsFromJson = (decltype(&::settingsFromJson)) getSymbol(plugin->handle, "settingsFromJson"); | |||
if (settingsFromJson) { | |||
json_t* settingsJ = json_object_get(settings::pluginSettingsJ, plugin->slug.c_str()); | |||
if (settingsJ) | |||
settingsFromJson(settingsJ); | |||
} | |||
} | |||
catch (Exception& e) { | |||
WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | |||
@@ -265,11 +281,7 @@ static void destroyPlugin(Plugin* plugin) { | |||
typedef void (*DestroyCallback)(); | |||
DestroyCallback destroyCallback = NULL; | |||
if (handle) { | |||
#if defined ARCH_WIN | |||
destroyCallback = (DestroyCallback) GetProcAddress((HMODULE) handle, "destroy"); | |||
#else | |||
destroyCallback = (DestroyCallback) dlsym(handle, "destroy"); | |||
#endif | |||
destroyCallback = (DestroyCallback) getSymbol(handle, "destroy"); | |||
} | |||
if (destroyCallback) { | |||
try { | |||
@@ -303,6 +315,20 @@ void destroy() { | |||
} | |||
void settingsMergeJson(json_t* rootJ) { | |||
for (Plugin* plugin : plugins) { | |||
auto settingsToJson = (decltype(&::settingsToJson)) getSymbol(plugin->handle, "settingsToJson"); | |||
if (settingsToJson) { | |||
json_t* settingsJ = settingsToJson(); | |||
json_object_set_new(rootJ, plugin->slug.c_str(), settingsJ); | |||
} | |||
else { | |||
json_object_del(rootJ, plugin->slug.c_str()); | |||
} | |||
} | |||
} | |||
/** Given slug => fallback slug. | |||
Correctly handles bidirectional fallbacks. | |||
To request fallback slugs to be added to this list, open a GitHub issue. | |||
@@ -61,6 +61,7 @@ int tipIndex = -1; | |||
bool discordUpdateActivity = true; | |||
BrowserSort browserSort = BROWSER_SORT_UPDATED; | |||
float browserZoom = -1.f; | |||
json_t* pluginSettingsJ = NULL; | |||
std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos; | |||
std::map<std::string, PluginWhitelist> moduleWhitelist; | |||
@@ -98,6 +99,12 @@ void init() { | |||
} | |||
void destroy() { | |||
if (pluginSettingsJ) | |||
json_decref(pluginSettingsJ); | |||
} | |||
json_t* toJson() { | |||
json_t* rootJ = json_object(); | |||
@@ -176,6 +183,13 @@ json_t* toJson() { | |||
json_object_set_new(rootJ, "browserZoom", json_real(browserZoom)); | |||
// Merge pluginSettings instead of replace so plugins that fail to load don't cause their settings to be deleted. | |||
if (!pluginSettingsJ) | |||
pluginSettingsJ = json_object(); | |||
plugin::settingsMergeJson(pluginSettingsJ); | |||
// Don't use *_set_new() here because we need to keep the reference to pluginSettingsJ. | |||
json_object_set(rootJ, "pluginSettings", pluginSettingsJ); | |||
// moduleInfos | |||
json_t* moduleInfosJ = json_object(); | |||
for (const auto& pluginPair : moduleInfos) { | |||
@@ -375,6 +389,15 @@ void fromJson(json_t* rootJ) { | |||
if (browserZoomJ) | |||
browserZoom = json_number_value(browserZoomJ); | |||
// Delete previous pluginSettings object | |||
if (pluginSettingsJ) { | |||
json_decref(pluginSettingsJ); | |||
pluginSettingsJ = NULL; | |||
} | |||
pluginSettingsJ = json_object_get(rootJ, "pluginSettings"); | |||
if (pluginSettingsJ) | |||
json_incref(pluginSettingsJ); | |||
moduleInfos.clear(); | |||
json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos"); | |||
if (moduleInfosJ) { | |||