@@ -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) { | ||||