| @@ -273,6 +273,7 @@ int main(int argc, char* argv[]) { | |||||
| plugin::destroy(); | plugin::destroy(); | ||||
| INFO("Destroying network"); | INFO("Destroying network"); | ||||
| network::destroy(); | network::destroy(); | ||||
| settings::destroy(); | |||||
| INFO("Destroying logger"); | INFO("Destroying logger"); | ||||
| logger::destroy(); | logger::destroy(); | ||||
| @@ -13,6 +13,7 @@ namespace plugin { | |||||
| PRIVATE void init(); | PRIVATE void init(); | ||||
| PRIVATE void destroy(); | PRIVATE void destroy(); | ||||
| PRIVATE void settingsMergeJson(json_t* rootJ); | |||||
| /** Finds a loaded Plugin by slug. */ | /** Finds a loaded Plugin by slug. */ | ||||
| Plugin* getPlugin(const std::string& pluginSlug); | Plugin* getPlugin(const std::string& pluginSlug); | ||||
| @@ -16,3 +16,19 @@ Optional in plugins. | |||||
| */ | */ | ||||
| extern "C" | extern "C" | ||||
| void destroy(); | 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 BrowserSort browserSort; | ||||
| extern float browserZoom; | extern float browserZoom; | ||||
| extern json_t* pluginSettingsJ; | |||||
| struct ModuleInfo { | struct ModuleInfo { | ||||
| bool enabled = true; | bool enabled = true; | ||||
| @@ -110,6 +111,7 @@ extern std::map<std::string, PluginWhitelist> moduleWhitelist; | |||||
| bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug); | bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug); | ||||
| PRIVATE void init(); | PRIVATE void init(); | ||||
| PRIVATE void destroy(); | |||||
| PRIVATE json_t* toJson(); | PRIVATE json_t* toJson(); | ||||
| PRIVATE void fromJson(json_t* rootJ); | PRIVATE void fromJson(json_t* rootJ); | ||||
| PRIVATE void save(std::string path = ""); | PRIVATE void save(std::string path = ""); | ||||
| @@ -41,6 +41,18 @@ namespace plugin { | |||||
| // private API | // 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 */ | /** Returns library handle */ | ||||
| static void* loadLibrary(std::string libraryPath) { | static void* loadLibrary(std::string libraryPath) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| @@ -100,12 +112,7 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||||
| plugin->handle = loadLibrary(libraryPath); | plugin->handle = loadLibrary(libraryPath); | ||||
| // Get plugin's init() function | // 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) | if (!initCallback) | ||||
| throw Exception("Failed to read init() symbol in %s", libraryPath.c_str()); | 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); | Plugin* existingPlugin = getPlugin(plugin->slug); | ||||
| if (existingPlugin) | if (existingPlugin) | ||||
| throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()); | 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) { | catch (Exception& e) { | ||||
| WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | ||||
| @@ -265,11 +281,7 @@ static void destroyPlugin(Plugin* plugin) { | |||||
| typedef void (*DestroyCallback)(); | typedef void (*DestroyCallback)(); | ||||
| DestroyCallback destroyCallback = NULL; | DestroyCallback destroyCallback = NULL; | ||||
| if (handle) { | 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) { | if (destroyCallback) { | ||||
| try { | 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. | /** Given slug => fallback slug. | ||||
| Correctly handles bidirectional fallbacks. | Correctly handles bidirectional fallbacks. | ||||
| To request fallback slugs to be added to this list, open a GitHub issue. | To request fallback slugs to be added to this list, open a GitHub issue. | ||||
| @@ -61,6 +61,7 @@ int tipIndex = -1; | |||||
| bool discordUpdateActivity = true; | bool discordUpdateActivity = true; | ||||
| BrowserSort browserSort = BROWSER_SORT_UPDATED; | BrowserSort browserSort = BROWSER_SORT_UPDATED; | ||||
| float browserZoom = -1.f; | float browserZoom = -1.f; | ||||
| json_t* pluginSettingsJ = NULL; | |||||
| std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos; | std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos; | ||||
| std::map<std::string, PluginWhitelist> moduleWhitelist; | std::map<std::string, PluginWhitelist> moduleWhitelist; | ||||
| @@ -98,6 +99,12 @@ void init() { | |||||
| } | } | ||||
| void destroy() { | |||||
| if (pluginSettingsJ) | |||||
| json_decref(pluginSettingsJ); | |||||
| } | |||||
| json_t* toJson() { | json_t* toJson() { | ||||
| json_t* rootJ = json_object(); | json_t* rootJ = json_object(); | ||||
| @@ -176,6 +183,13 @@ json_t* toJson() { | |||||
| json_object_set_new(rootJ, "browserZoom", json_real(browserZoom)); | 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 | // moduleInfos | ||||
| json_t* moduleInfosJ = json_object(); | json_t* moduleInfosJ = json_object(); | ||||
| for (const auto& pluginPair : moduleInfos) { | for (const auto& pluginPair : moduleInfos) { | ||||
| @@ -375,6 +389,15 @@ void fromJson(json_t* rootJ) { | |||||
| if (browserZoomJ) | if (browserZoomJ) | ||||
| browserZoom = json_number_value(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(); | moduleInfos.clear(); | ||||
| json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos"); | json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos"); | ||||
| if (moduleInfosJ) { | if (moduleInfosJ) { | ||||