/** * This file is a part of the Cairo-Dock project * * Copyright : (C) see the 'copyright' file. * E-mail : see the 'copyright' file. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "gldi-config.h" #include "gldi-module-config.h" #include "cairo-dock-dock-manager.h" #include "cairo-dock-themes-manager.h" // cairo_dock_add_conf_file #include "cairo-dock-file-manager.h" // cairo_dock_copy_file #include "cairo-dock-log.h" #include "cairo-dock-applet-manager.h" #include "cairo-dock-desktop-manager.h" // gldi_desktop_get_width #include "cairo-dock-desklet-manager.h" #include "cairo-dock-animations.h" #include "cairo-dock-config.h" #include "cairo-dock-module-instance-manager.h" #define _MANAGER_DEF_ #include "cairo-dock-module-manager.h" // public (manager, config, data) GldiModulesParam myModulesParam; GldiManager myModulesMgr; GldiObjectManager myModuleObjectMgr; GldiModuleInstance *g_pCurrentModule = NULL; // only used to trace a possible crash in one of the modules. // dependancies extern gchar *g_cConfFile; extern gchar *g_cCurrentThemePath; extern int g_iMajorVersion, g_iMinorVersion, g_iMicroVersion; extern gboolean g_bEasterEggs; // private static GHashTable *s_hModuleTable = NULL; static GList *s_AutoLoadedModules = NULL; static guint s_iSidWriteModules = 0; /////////////// /// MANAGER /// /////////////// GldiModule *gldi_module_get (const gchar *cModuleName) { g_return_val_if_fail (cModuleName != NULL, NULL); return g_hash_table_lookup (s_hModuleTable, cModuleName); } GldiModule *gldi_module_foreach (GHRFunc pCallback, gpointer user_data) { return g_hash_table_find (s_hModuleTable, (GHRFunc) pCallback, user_data); } static int _sort_module_by_alphabetical_order (GldiModule *m1, GldiModule *m2) { if (!m1 || !m1->pVisitCard || !m1->pVisitCard->cTitle) return 1; if (!m2 || !m2->pVisitCard || !m2->pVisitCard->cTitle) return -1; return g_ascii_strncasecmp (m1->pVisitCard->cTitle, m2->pVisitCard->cTitle, -1); } GldiModule *gldi_module_foreach_in_alphabetical_order (GCompareFunc pCallback, gpointer user_data) { GList *pModuleList = g_hash_table_get_values (s_hModuleTable); pModuleList = g_list_sort (pModuleList, (GCompareFunc) _sort_module_by_alphabetical_order); GldiModule *pModule = (GldiModule *)g_list_find_custom (pModuleList, user_data, pCallback); g_list_free (pModuleList); return pModule; } int gldi_module_get_nb (void) { return g_hash_table_size (s_hModuleTable); } static void _write_one_module_name (const gchar *cModuleName, GldiModule *pModule, GString *pString) { if (pModule->pInstancesList != NULL && ! gldi_module_is_auto_loaded (pModule)) { g_string_append_printf (pString, "%s;", cModuleName); } } static gchar *_gldi_module_list_active (void) { GString *pString = g_string_new (""); g_hash_table_foreach (s_hModuleTable, (GHFunc) _write_one_module_name, pString); if (pString->len > 0) pString->str[pString->len-1] = '\0'; gchar *cModuleNames = pString->str; g_string_free (pString, FALSE); return cModuleNames; } static gboolean _write_modules_idle (G_GNUC_UNUSED gpointer data) { gchar *cModuleNames = _gldi_module_list_active (); cd_debug ("%s", cModuleNames); cairo_dock_update_conf_file (g_cConfFile, G_TYPE_STRING, "System", "modules", cModuleNames, G_TYPE_INVALID); g_free (cModuleNames); s_iSidWriteModules = 0; return FALSE; } void gldi_modules_write_active (void) { if (s_iSidWriteModules == 0) s_iSidWriteModules = g_idle_add (_write_modules_idle, NULL); } ///////////////////// /// MODULE LOADER /// ///////////////////// GldiModule *gldi_module_new (GldiVisitCard *pVisitCard, GldiModuleInterface *pInterface) { g_return_val_if_fail (pVisitCard != NULL && pVisitCard->cModuleName != NULL, NULL); GldiModuleAttr attr = {pVisitCard, pInterface}; return (GldiModule*)gldi_object_new (&myModuleObjectMgr, &attr); } GldiModule *gldi_module_new_from_so_file (const gchar *cSoFilePath) { g_return_val_if_fail (cSoFilePath != NULL, NULL); GldiVisitCard *pVisitCard = NULL; GldiModuleInterface *pInterface = NULL; // open the .so file ///GModule *module = g_module_open (pGldiModule->cSoFilePath, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); gpointer handle = dlopen (cSoFilePath, RTLD_LAZY | RTLD_LOCAL); if (! handle) { cd_warning ("while opening module '%s' : (%s)", cSoFilePath, dlerror()); return NULL; } // find the pre-init entry point GldiModulePreInit function_pre_init = NULL; /**bSymbolFound = g_module_symbol (module, "pre_init", (gpointer) &function_pre_init); if (bSymbolFound && function_pre_init != NULL)*/ function_pre_init = dlsym (handle, "pre_init"); if (function_pre_init == NULL) { cd_warning ("this module ('%s') does not have the common entry point 'pre_init', it may be broken or icompatible with cairo-dock", cSoFilePath); goto discard; } // run the pre-init entry point to get the necessary info about the module pVisitCard = g_new0 (GldiVisitCard, 1); pInterface = g_new0 (GldiModuleInterface, 1); gboolean bModuleLoaded = function_pre_init (pVisitCard, pInterface); if (! bModuleLoaded) { cd_debug ("module '%s' has not been loaded", cSoFilePath); // can happen to xxx-integration or icon-effect for instance. goto discard; } // check module compatibility if (! g_bEasterEggs && (pVisitCard->iMajorVersionNeeded > g_iMajorVersion || (pVisitCard->iMajorVersionNeeded == g_iMajorVersion && pVisitCard->iMinorVersionNeeded > g_iMinorVersion) || (pVisitCard->iMajorVersionNeeded == g_iMajorVersion && pVisitCard->iMinorVersionNeeded == g_iMinorVersion && pVisitCard->iMicroVersionNeeded > g_iMicroVersion))) { cd_warning ("this module ('%s') needs at least Cairo-Dock v%d.%d.%d, but Cairo-Dock is in v%d.%d.%d (%s)\n It will be ignored", cSoFilePath, pVisitCard->iMajorVersionNeeded, pVisitCard->iMinorVersionNeeded, pVisitCard->iMicroVersionNeeded, g_iMajorVersion, g_iMinorVersion, g_iMicroVersion, GLDI_VERSION); goto discard; } if (! g_bEasterEggs && pVisitCard->cDockVersionOnCompilation != NULL && strcmp (pVisitCard->cDockVersionOnCompilation, GLDI_VERSION) != 0) // separation des versions en easter egg. { cd_warning ("this module ('%s') was compiled with Cairo-Dock v%s, but Cairo-Dock is in v%s\n It will be ignored", cSoFilePath, pVisitCard->cDockVersionOnCompilation, GLDI_VERSION); goto discard; } // create a new module with these info GldiModule *pModule = gldi_module_new (pVisitCard, pInterface); // takes ownership of pVisitCard and pInterface if (pModule) pModule->handle = handle; return pModule; discard: ///g_module_close (pModule); dlclose (handle); cairo_dock_free_visit_card (pVisitCard); g_free (pInterface); return NULL; } void gldi_modules_new_from_directory (const gchar *cModuleDirPath, GError **erreur) { if (cModuleDirPath == NULL) cModuleDirPath = GLDI_MODULES_DIR; cd_message ("%s (%s)", __func__, cModuleDirPath); GError *tmp_erreur = NULL; GDir *dir = g_dir_open (cModuleDirPath, 0, &tmp_erreur); if (tmp_erreur != NULL) { g_propagate_error (erreur, tmp_erreur); return ; } const gchar *cFileName; GString *sFilePath = g_string_new (""); do { cFileName = g_dir_read_name (dir); if (cFileName == NULL) break ; if (g_str_has_suffix (cFileName, ".so")) { g_string_printf (sFilePath, "%s/%s", cModuleDirPath, cFileName); (void)gldi_module_new_from_so_file (sFilePath->str); } } while (1); g_string_free (sFilePath, TRUE); g_dir_close (dir); } gchar *gldi_module_get_config_dir (GldiModule *pModule) { GldiVisitCard *pVisitCard = pModule->pVisitCard; if (pVisitCard->cConfFileName == NULL) return NULL; gchar *cUserDataDirPath = g_strdup_printf ("%s/plug-ins/%s", g_cCurrentThemePath, pVisitCard->cUserDataDir); if (! g_file_test (cUserDataDirPath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { cd_message ("directory %s doesn't exist, it will be added.", cUserDataDirPath); gchar *command = g_strdup_printf ("mkdir -p \"%s\"", cUserDataDirPath); int r = system (command); g_free (command); if (r != 0) { cd_warning ("couldn't create a directory for applet '%s' in '%s/plug-ins'\n check writing permissions", pVisitCard->cModuleName, g_cCurrentThemePath); g_free (cUserDataDirPath); g_free (pModule->cConfFilePath); pModule->cConfFilePath = NULL; return NULL; } } return cUserDataDirPath; } void cairo_dock_free_visit_card (GldiVisitCard *pVisitCard) { g_free (pVisitCard); // toutes les chaines sont statiques. } ///////////////////////// /// MODULES HIGH LEVEL/// ///////////////////////// void gldi_module_activate (GldiModule *module) { g_return_if_fail (module != NULL && module->pVisitCard != NULL); cd_debug ("%s (%s)", __func__, module->pVisitCard->cModuleName); if (module->pInstancesList != NULL) { cd_warning ("Module %s already active", module->pVisitCard->cModuleName); return ; } if (module->pVisitCard->cConfFileName != NULL) // the module has a conf file -> create an instance for each of them. { // check that the module's config dir exists or create it. gchar *cUserDataDirPath = gldi_module_get_config_dir (module); if (cUserDataDirPath == NULL) { cd_warning ("Unable to open the config folder of module %s\nCheck permissions", module->pVisitCard->cModuleName); return; } // look for conf files inside this folder, and create an instance for each of them. int n = 0; if (module->pVisitCard->bMultiInstance) // possibly several conf files. { // open it GError *tmp_erreur = NULL; GDir *dir = g_dir_open (cUserDataDirPath, 0, &tmp_erreur); if (tmp_erreur != NULL) { cd_warning ("couldn't open folder %s (%s)", cUserDataDirPath, tmp_erreur->message); g_error_free (tmp_erreur); g_free (cUserDataDirPath); return ; } // for each conf file inside, instanciate the module with it. const gchar *cFileName; gchar *cInstanceFilePath; while ((cFileName = g_dir_read_name (dir)) != NULL) { gchar *str = strstr (cFileName, ".conf"); if (!str) continue; if (*(str+5) != '-' && *(str+5) != '\0') // xxx.conf or xxx.conf-i continue; cInstanceFilePath = g_strdup_printf ("%s/%s", cUserDataDirPath, cFileName); gldi_module_instance_new (module, cInstanceFilePath); // takes ownership of 'cInstanceFilePath'. n ++; } g_dir_close (dir); } else // only 1 conf file possible. { gchar *cConfFilePath = g_strdup_printf ("%s/%s", cUserDataDirPath, module->pVisitCard->cConfFileName); if (g_file_test (cConfFilePath, G_FILE_TEST_EXISTS)) { gldi_module_instance_new (module, cConfFilePath); n = 1; } else { g_free (cConfFilePath); } } // if no conf file was present, copy the default one and instanciate the module with it. if (n == 0) // no conf file was present. { gchar *cConfFilePath = g_strdup_printf ("%s/%s", cUserDataDirPath, module->pVisitCard->cConfFileName); gboolean r = cairo_dock_copy_file (module->cConfFilePath, cConfFilePath); if (! r) // the copy failed. { cd_warning ("couldn't copy %s into %s; check permissions and file's existence", module->cConfFilePath, cUserDataDirPath); g_free (cConfFilePath); g_free (cUserDataDirPath); return; } else { gldi_module_instance_new (module, cConfFilePath); } } g_free (cUserDataDirPath); } else // the module has no conf file, just instanciate it once. { gldi_module_instance_new (module, NULL); } } void gldi_module_deactivate (GldiModule *module) // stop all instances of a module { g_return_if_fail (module != NULL); cd_debug ("%s (%s, %s)", __func__, module->pVisitCard->cModuleName, module->cConfFilePath); GList *pInstances = module->pInstancesList; module->pInstancesList = NULL; // set to NULL already so that notifications don't get fooled. This can probably be avoided... g_list_foreach (pInstances, (GFunc)gldi_object_unref, NULL); g_list_free (pInstances); gldi_object_notify (module, NOTIFICATION_MODULE_ACTIVATED, module->pVisitCard->cModuleName, FALSE); // throw it since the list was NULL when the instances were destroyed gldi_modules_write_active (); // same } void gldi_modules_activate_from_list (gchar **cActiveModuleList) { //\_______________ On active les modules auto-charges en premier. gchar *cModuleName; GldiModule *pModule; GList *m; for (m = s_AutoLoadedModules; m != NULL; m = m->next) { pModule = m->data; if (pModule->pInstancesList == NULL) // not yet active { gldi_module_activate (pModule); } } if (cActiveModuleList == NULL) return ; //\_______________ On active tous les autres. int i; for (i = 0; cActiveModuleList[i] != NULL; i ++) { cModuleName = cActiveModuleList[i]; pModule = g_hash_table_lookup (s_hModuleTable, cModuleName); if (pModule == NULL) { cd_debug ("No such module (%s)", cModuleName); continue ; } if (pModule->pInstancesList == NULL) // not yet active { gldi_module_activate (pModule); } } // don't write down if (s_iSidWriteModules != 0) { g_source_remove (s_iSidWriteModules); s_iSidWriteModules = 0; } } static void _deactivate_one_module (G_GNUC_UNUSED gchar *cModuleName, GldiModule *pModule, G_GNUC_UNUSED gpointer data) { if (! gldi_module_is_auto_loaded (pModule)) gldi_module_deactivate (pModule); } void gldi_modules_deactivate_all (void) { // first deactivate applets g_hash_table_foreach (s_hModuleTable, (GHFunc)_deactivate_one_module, NULL); // then deactivate auto-loaded modules (that have been loaded first) GldiModule *pModule; GList *m; for (m = s_AutoLoadedModules; m != NULL; m = m->next) { pModule = m->data; gldi_module_deactivate (pModule); } // don't write down if (s_iSidWriteModules != 0) { g_source_remove (s_iSidWriteModules); s_iSidWriteModules = 0; } } gchar *gldi_module_add_conf_file (GldiModule *pModule) { gchar *cUserDataDirPath = gldi_module_get_config_dir (pModule); if (cUserDataDirPath == NULL) return NULL; // find a name that doesn't exist yet in the config dir. gchar *cConfFilePath; int i = 0; do { if (i == 0) cConfFilePath = g_strdup_printf ("%s/%s", cUserDataDirPath, pModule->pVisitCard->cConfFileName); else cConfFilePath = g_strdup_printf ("%s/%s-%d", cUserDataDirPath, pModule->pVisitCard->cConfFileName, i); if (! g_file_test (cConfFilePath, G_FILE_TEST_EXISTS)) break; g_free (cConfFilePath); i ++; } while (1); // copy one of the instances conf file, or the default one. GldiModuleInstance *pFirstInstance = NULL; if (pModule->pInstancesList != NULL) { GList *last = g_list_last (pModule->pInstancesList); pFirstInstance = last->data; // instances are prepended. cairo_dock_add_conf_file (pFirstInstance->cConfFilePath, cConfFilePath); if (pFirstInstance->pDesklet) // prevent desklets from overlapping. { int iX2, iX = pFirstInstance->pContainer->iWindowPositionX; int iWidth = pFirstInstance->pContainer->iWidth; if (iX + iWidth/2 <= gldi_desktop_get_width()/2) // desklet on the left, we place the new one on its right. iX2 = iX + iWidth; else // desklet on the right, we place the new one on its left. iX2 = iX - iWidth; int iRelativePositionX = (iX2 + iWidth/2 <= gldi_desktop_get_width()/2 ? iX2 : iX2 - gldi_desktop_get_width()); cairo_dock_update_conf_file (cConfFilePath, G_TYPE_INT, "Desklet", "x position", iRelativePositionX, G_TYPE_BOOLEAN, "Desklet", "locked", FALSE, // we'll probably want to move it G_TYPE_BOOLEAN, "Desklet", "no input", FALSE, G_TYPE_INVALID); } } else // no instance yet, just copy the default conf file. { cairo_dock_add_conf_file (pModule->cConfFilePath, cConfFilePath); } g_free (cUserDataDirPath); return cConfFilePath; } void gldi_module_add_instance (GldiModule *pModule) { // check that the module is already active if (pModule->pInstancesList == NULL) { cd_warning ("This module has not been instanciated yet"); return ; } // check that the module can be multi-instanciated if (! pModule->pVisitCard->bMultiInstance) { cd_warning ("This module can't be instanciated more than once"); return ; } // add a conf file gchar *cInstanceFilePath = gldi_module_add_conf_file (pModule); // create an instance for it gldi_module_instance_new (pModule, cInstanceFilePath); // takes ownership of 'cInstanceFilePath'. } ////////////////// /// GET CONFIG /// ////////////////// static gboolean get_config (GKeyFile *pKeyFile, GldiModulesParam *pModules) { gboolean bFlushConfFileNeeded = FALSE; gsize length=0; pModules->cActiveModuleList = cairo_dock_get_string_list_key_value (pKeyFile, "System", "modules", &bFlushConfFileNeeded, &length, NULL, "Applets", "modules_0"); return bFlushConfFileNeeded; } //////////////////// /// RESET CONFIG /// //////////////////// static void reset_config (GldiModulesParam *pModules) { g_free (pModules->cActiveModuleList); } //////////// /// INIT /// //////////// static void init (void) { s_hModuleTable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, // module name (points directly on the 'cModuleName' field of the module). NULL); // module } /////////////// /// MANAGER /// /////////////// static void init_object (GldiObject *obj, gpointer attr) { GldiModule *pModule = (GldiModule*)obj; GldiModuleAttr *mattr = (GldiModuleAttr*)attr; // check everything is ok g_return_if_fail (mattr != NULL && mattr->pVisitCard != NULL && mattr->pVisitCard->cModuleName); if (g_hash_table_lookup (s_hModuleTable, mattr->pVisitCard->cModuleName) != NULL) { cd_warning ("a module with the name '%s' is already registered", mattr->pVisitCard->cModuleName); return; } // set params pModule->pVisitCard = mattr->pVisitCard; mattr->pVisitCard = NULL; pModule->pInterface = mattr->pInterface; mattr->pInterface = NULL; if (pModule->cConfFilePath == NULL && pModule->pVisitCard->cConfFileName) pModule->cConfFilePath = g_strdup_printf ("%s/%s", pModule->pVisitCard->cShareDataDir, pModule->pVisitCard->cConfFileName); // register the module g_hash_table_insert (s_hModuleTable, (gpointer)pModule->pVisitCard->cModuleName, pModule); if (gldi_module_is_auto_loaded (pModule)) // a module that doesn't have an init/stop entry point, or that extends a manager; we'll activate it automatically (and before the others). s_AutoLoadedModules = g_list_prepend (s_AutoLoadedModules, pModule); // notify everybody gldi_object_notify (&myModuleObjectMgr, NOTIFICATION_MODULE_REGISTERED, pModule->pVisitCard->cModuleName, TRUE); } static void reset_object (GldiObject *obj) { GldiModule *pModule = (GldiModule*)obj; if (pModule->pVisitCard == NULL) // we didn't register it, pass return; // deactivate the module, if it was active gldi_module_deactivate (pModule); // unregister the module g_hash_table_remove (s_hModuleTable, pModule->pVisitCard->cModuleName); // notify everybody gldi_object_notify (&myModuleObjectMgr, NOTIFICATION_MODULE_REGISTERED, pModule->pVisitCard->cModuleName, FALSE); // free data if (pModule->handle) dlclose (pModule->handle); g_free (pModule->pInterface); cairo_dock_free_visit_card (pModule->pVisitCard); } static GKeyFile* reload_object (GldiObject *obj, gboolean bReloadConf, G_GNUC_UNUSED GKeyFile *pKeyFile) { GldiModule *pModule = (GldiModule*)obj; GList *pElement; GldiModuleInstance *pInstance; for (pElement = pModule->pInstancesList; pElement != NULL; pElement = pElement->next) { pInstance = pElement->data; gldi_object_reload (GLDI_OBJECT(pInstance), bReloadConf); } return NULL; } void gldi_register_modules_manager (void) { // Manager memset (&myModulesMgr, 0, sizeof (GldiManager)); gldi_object_init (GLDI_OBJECT(&myModulesMgr), &myManagerObjectMgr, NULL); myModulesMgr.cModuleName = "Modules"; // interface myModulesMgr.init = init; myModulesMgr.load = NULL; myModulesMgr.unload = NULL; myModulesMgr.reload = (GldiManagerReloadFunc)NULL; myModulesMgr.get_config = (GldiManagerGetConfigFunc)get_config; myModulesMgr.reset_config = (GldiManagerResetConfigFunc)reset_config; // Config memset (&myModulesParam, 0, sizeof (GldiModulesParam)); myModulesMgr.pConfig = (GldiManagerConfigPtr)&myModulesParam; myModulesMgr.iSizeOfConfig = sizeof (GldiModulesParam); // data myModulesMgr.pData = (GldiManagerDataPtr)NULL; myModulesMgr.iSizeOfData = 0; // Object Manager memset (&myModuleObjectMgr, 0, sizeof (GldiObjectManager)); myModuleObjectMgr.cName = "Module"; myModuleObjectMgr.iObjectSize = sizeof (GldiModule); // interface myModuleObjectMgr.init_object = init_object; myModuleObjectMgr.reset_object = reset_object; myModuleObjectMgr.reload_object = reload_object; // signals gldi_object_install_notifications (GLDI_OBJECT(&myModuleObjectMgr), NB_NOTIFICATIONS_MODULES); }