/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 Intel Corp. * * Author: Tomas Frydrych * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "compositor-clutter-plugin-manager.h" #include "compositor-clutter.h" #include "prefs.h" #include "errors.h" #include "workspace.h" #include #include static gboolean meta_compositor_clutter_plugin_manager_reload (MetaCompositorClutterPluginManager *mgr); struct MetaCompositorClutterPluginManager { MetaScreen *screen; ClutterActor *stage; GList *plugins; /* TODO -- maybe use hash table */ GList *unload; /* Plugins that are disabled and pending unload */ guint idle_unload_id; }; typedef struct MetaCompositorClutterPluginPrivate MetaCompositorClutterPluginPrivate; struct MetaCompositorClutterPluginPrivate { GModule *module; gboolean disabled : 1; }; /* * This function gets called when an effect completes. It is responsible for * any post-effect cleanup. */ static void meta_compositor_clutter_effect_completed (MetaCompositorClutterPlugin *plugin, ClutterActor *actor, unsigned long event) { if (!actor) { g_warning ("Plugin [%s] passed NULL for actor!", (plugin && plugin->name) ? plugin->name : "unknown"); } meta_compositor_clutter_window_effect_completed (actor, event); } static void free_plugin_workspaces (MetaCompositorClutterPlugin *plg) { GList *l; l = plg->work_areas; while (l) { g_free (l->data); l = l->next; } if (plg->work_areas) g_list_free (plg->work_areas); plg->work_areas = NULL; } /* * Gets work area geometry and stores it in list in the plugin. * * If the plg list is already populated, we simply replace it (we are dealing * with a small number of items in the list and unfrequent changes). */ static void update_plugin_workspaces (MetaScreen *screen, MetaCompositorClutterPlugin *plg) { GList *l, *l2 = NULL; l = meta_screen_get_workspaces (screen); while (l) { MetaWorkspace *w = l->data; PluginWorkspaceRectangle *r; r = g_new0 (PluginWorkspaceRectangle, 1); meta_workspace_get_work_area_all_xineramas (w, (MetaRectangle*)r); l2 = g_list_append (l2, r); l = l->next; } free_plugin_workspaces (plg); plg->work_areas = l2; } /* * Checks that the plugin is compatible with the WM and sets up the plugin * struct. */ static MetaCompositorClutterPlugin * meta_compositor_clutter_plugin_load (MetaScreen *screen, ClutterActor *stage, GModule *module, const gchar *params) { MetaCompositorClutterPlugin *plg; if (g_module_symbol (module, META_COMPOSITOR_CLUTTER_PLUGIN_STRUCT_NAME, (gpointer *)&plg)) { if (plg->version_api == METACITY_CLUTTER_PLUGIN_API_VERSION) { MetaCompositorClutterPluginPrivate *priv; gboolean (*init_func) (void); priv = g_new0 (MetaCompositorClutterPluginPrivate, 1); plg->params = g_strdup (params); plg->completed = meta_compositor_clutter_effect_completed; plg->stage = stage; plg->manager_private = priv; priv->module = module; meta_screen_get_size (screen, &plg->screen_width, &plg->screen_height); update_plugin_workspaces (screen, plg); /* * Check for and run the plugin init function. */ if (g_module_symbol (module, META_COMPOSITOR_CLUTTER_PLUGIN_INIT_FUNC_NAME, (gpointer *)&init_func) && !init_func()) { g_free (plg->params); g_free (priv); free_plugin_workspaces (plg); return NULL; } meta_verbose ("Loaded plugin [%s]\n", plg->name); return plg; } } return NULL; } /* * Attempst to unload a plugin; returns FALSE if plugin cannot be unloaded at * present (e.g., and effect is in progress) and should be scheduled for * removal later. */ static gboolean meta_compositor_clutter_plugin_unload (MetaCompositorClutterPlugin *plg) { MetaCompositorClutterPluginPrivate *priv; GModule *module; priv = plg->manager_private; module = priv->module; if (plg->running) { priv->disabled = TRUE; return FALSE; } g_free (plg->params); plg->params = NULL; g_free (priv); plg->manager_private = NULL; g_module_close (module); return TRUE; } /* * Iddle callback to remove plugins that could not be removed directly and are * pending for removal. */ static gboolean meta_compositor_clutter_plugin_manager_idle_unload (MetaCompositorClutterPluginManager *mgr) { GList *l = mgr->unload; gboolean dont_remove = TRUE; while (l) { MetaCompositorClutterPlugin *plg = l->data; if (meta_compositor_clutter_plugin_unload (plg)) { /* Remove from list */ GList *p = l->prev; GList *n = l->next; if (!p) mgr->unload = n; else p->next = n; if (n) n->prev = p; g_list_free_1 (l); l = n; } else l = l->next; } if (!mgr->unload) { /* If no more unloads are pending, remove the handler as well */ dont_remove = FALSE; mgr->idle_unload_id = 0; } return dont_remove; } /* * Unloads all plugins */ static void meta_compositor_clutter_plugin_manager_unload (MetaCompositorClutterPluginManager *mgr) { GList *plugins = mgr->plugins; while (plugins) { MetaCompositorClutterPlugin *plg = plugins->data; /* If the plugin could not be removed, move it to the unload list */ if (!meta_compositor_clutter_plugin_unload (plg)) { mgr->unload = g_list_prepend (mgr->unload, plg); if (!mgr->idle_unload_id) { mgr->idle_unload_id = g_idle_add ((GSourceFunc) meta_compositor_clutter_plugin_manager_idle_unload, mgr); } } plugins = plugins->next; } g_list_free (mgr->plugins); mgr->plugins = NULL; } static void prefs_changed_callback (MetaPreference pref, void *data) { MetaCompositorClutterPluginManager *mgr = data; if (pref == META_PREF_CLUTTER_PLUGINS) { meta_compositor_clutter_plugin_manager_reload (mgr); } else if (pref == META_PREF_NUM_WORKSPACES) { meta_compositor_clutter_plugin_manager_update_workspaces (mgr); } } /* * Loads all plugins listed in gconf registry. */ static gboolean meta_compositor_clutter_plugin_manager_load (MetaCompositorClutterPluginManager *mgr) { const gchar *dpath = METACITY_PKGLIBDIR "/plugins/clutter/"; GSList *plugins; plugins = meta_prefs_get_clutter_plugins (); while (plugins) { gchar *plg_string; gchar *params; plg_string = g_strdup (plugins->data); if (plg_string) { GModule *plg; gchar *path; params = strchr (plg_string, ':'); if (params) { *params = 0; ++params; } path = g_strconcat (dpath, plg_string, ".so", NULL); if ((plg = g_module_open (path, 0))) { MetaCompositorClutterPlugin *p; if ((p = meta_compositor_clutter_plugin_load (mgr->screen, mgr->stage, plg, params))) mgr->plugins = g_list_prepend (mgr->plugins, p); else g_module_close (plg); } g_free (path); g_free (plg_string); } plugins = plugins->next; } if (mgr->plugins != NULL) { meta_prefs_add_listener (prefs_changed_callback, mgr); return TRUE; } return FALSE; } /* * Reloads all plugins */ static gboolean meta_compositor_clutter_plugin_manager_reload (MetaCompositorClutterPluginManager *mgr) { /* TODO -- brute force; should we build a list of plugins to load and list of * plugins to unload? We are probably not going to have large numbers of * plugins loaded at the same time, so it might not be worth it. */ meta_compositor_clutter_plugin_manager_unload (mgr); return meta_compositor_clutter_plugin_manager_load (mgr); } static gboolean meta_compositor_clutter_plugin_manager_init (MetaCompositorClutterPluginManager *mgr) { return meta_compositor_clutter_plugin_manager_load (mgr); } void meta_compositor_clutter_plugin_manager_update_workspace (MetaCompositorClutterPluginManager *mgr, MetaWorkspace *w) { GList *l; gint n; n = meta_workspace_index (w); l = mgr->plugins; while (l) { MetaCompositorClutterPlugin *plg = l->data; PluginWorkspaceRectangle *r = g_list_nth_data (plg->work_areas, n); if (r) { meta_workspace_get_work_area_all_xineramas (w, (MetaRectangle*)r); } else { /* Something not entirely right; redo the whole thing */ update_plugin_workspaces (mgr->screen, plg); return; } l = l->next; } } void meta_compositor_clutter_plugin_manager_update_workspaces (MetaCompositorClutterPluginManager *mgr) { GList *l; l = mgr->plugins; while (l) { MetaCompositorClutterPlugin *plg = l->data; update_plugin_workspaces (mgr->screen, plg); l = l->next; } } MetaCompositorClutterPluginManager * meta_compositor_clutter_plugin_manager_new (MetaScreen *screen, ClutterActor *stage) { MetaCompositorClutterPluginManager *mgr; mgr = g_new0 (MetaCompositorClutterPluginManager, 1); mgr->screen = screen; mgr->stage = stage; if (!meta_compositor_clutter_plugin_manager_init (mgr)) { g_free (mgr); mgr = NULL; } return mgr; } void meta_compositor_clutter_plugin_manager_kill_effect (MetaCompositorClutterPluginManager *mgr, ClutterActor *actor, unsigned long events) { GList *l = mgr->plugins; while (l) { MetaCompositorClutterPlugin *plg = l->data; MetaCompositorClutterPluginPrivate *priv = plg->manager_private; if (!priv->disabled && (plg->features & events) && plg->kill_effect) plg->kill_effect (actor, events); l = l->next; } } #define ALL_BUT_SWITCH \ META_COMPOSITOR_CLUTTER_PLUGIN_ALL_EFFECTS & \ ~META_COMPOSITOR_CLUTTER_PLUGIN_SWITCH_WORKSPACE /* * Public method that the compositor hooks into for events that require * no additional parameters. * * Returns TRUE if at least one of the plugins handled the event type (i.e., * if the return value is FALSE, there will be no subsequent call to the * manager completed() callback, and the compositor must ensure that any * appropriate post-effect cleanup is carried out. */ gboolean meta_compositor_clutter_plugin_manager_event_0 (MetaCompositorClutterPluginManager *mgr, ClutterActor *actor, unsigned long event, MetaCompWindowType type, gint workspace) { GList *l = mgr->plugins; gboolean retval = FALSE; while (l) { MetaCompositorClutterPlugin *plg = l->data; MetaCompositorClutterPluginPrivate *priv = plg->manager_private; if (!priv->disabled && (plg->features & event)) { retval = TRUE; switch (event) { case META_COMPOSITOR_CLUTTER_PLUGIN_MINIMIZE: if (plg->minimize) { meta_compositor_clutter_plugin_manager_kill_effect (mgr, actor, ALL_BUT_SWITCH); plg->minimize (actor, type, workspace); } break; case META_COMPOSITOR_CLUTTER_PLUGIN_MAP: if (plg->map) { meta_compositor_clutter_plugin_manager_kill_effect (mgr, actor, ALL_BUT_SWITCH); plg->map (actor, type, workspace); } break; case META_COMPOSITOR_CLUTTER_PLUGIN_DESTROY: if (plg->destroy) { plg->destroy (actor, type, workspace); } break; default: g_warning ("Incorrect handler called for event %lu", event); } } l = l->next; } return retval; } /* * The public method that the compositor hooks into for events that require * up to 4 additional integer parameters. * * Returns TRUE if at least one of the plugins handled the event type (i.e., * if the return value is FALSE, there will be no subsequent call to the * manager completed() callback, and the compositor must ensure that any * appropriate post-effect cleanup is carried out. */ gboolean meta_compositor_clutter_plugin_manager_event_4i (MetaCompositorClutterPluginManager *mgr, ClutterActor *actor, unsigned long event, MetaCompWindowType type, gint workspace, gint i0, gint i1, gint i2, gint i3) { GList *l = mgr->plugins; gboolean retval = FALSE; while (l) { MetaCompositorClutterPlugin *plg = l->data; MetaCompositorClutterPluginPrivate *priv = plg->manager_private; if (!priv->disabled && (plg->features & event)) { retval = TRUE; switch (event) { case META_COMPOSITOR_CLUTTER_PLUGIN_MAXIMIZE: if (plg->maximize) { meta_compositor_clutter_plugin_manager_kill_effect (mgr, actor, ALL_BUT_SWITCH); plg->maximize (actor, type, workspace, i0, i1, i2, i3); } break; case META_COMPOSITOR_CLUTTER_PLUGIN_UNMAXIMIZE: if (plg->unmaximize) { meta_compositor_clutter_plugin_manager_kill_effect (mgr, actor, ALL_BUT_SWITCH); plg->unmaximize (actor, type, workspace, i0, i1, i2, i3); } break; default: g_warning ("Incorrect handler called for event %lu", event); } } l = l->next; } return retval; } /* * The public method that the compositor hooks into for desktop switching. * * Returns TRUE if at least one of the plugins handled the event type (i.e., * if the return value is FALSE, there will be no subsequent call to the * manager completed() callback, and the compositor must ensure that any * appropriate post-effect cleanup is carried out. */ gboolean meta_compositor_clutter_plugin_manager_switch_workspace (MetaCompositorClutterPluginManager *mgr, const GList **actors, gint from, gint to) { GList *l = mgr->plugins; gboolean retval = FALSE; while (l) { MetaCompositorClutterPlugin *plg = l->data; MetaCompositorClutterPluginPrivate *priv = plg->manager_private; if (!priv->disabled && (plg->features & META_COMPOSITOR_CLUTTER_PLUGIN_SWITCH_WORKSPACE) && (actors && *actors)) { if (plg->switch_workspace) { retval = TRUE; meta_compositor_clutter_plugin_manager_kill_effect (mgr, CLUTTER_ACTOR ((*actors)->data), META_COMPOSITOR_CLUTTER_PLUGIN_SWITCH_WORKSPACE); plg->switch_workspace (actors, from, to); } } l = l->next; } return retval; }