Commit 9b3a0d1a authored by Tomas Frydrych's avatar Tomas Frydrych

Basic plugin infastructure and a sample simple plugin.

parent d5090979
Makefile
Makefile.in
Makefile.in.in
aclocal.m4
autom4te.cache
compile
config.guess
config.h
config.h.in
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing
.deps
src/metacity-wm.desktop
*.o
*.a
*.lo
*.la
.libs
*.swp
tidy-enum-types.[ch]
tidy-marshal.[ch]
stamp-tidy-enum-types.h
stamp-tidy-marshal.h
stamp-h1
*.gmo
*.make
*~
stamp-it
.intltool-merge-cache
POTFILES
50-metacity-desktop-key.xml
50-metacity-key.xml
inlinepixbufs.h
libmetacity-private.pc
metacity
metacity-dialog
metacity-theme-viewer
metacity.desktop
metacity.schemas
testasyncgetprop
testboxes
testgradient
metacity-grayscale
metacity-mag
metacity-message
metacity-window-demo
focus-window
test-gravity
test-resizing
test-size-hints
wm-tester
INSTALL
mkinstalldirs
......@@ -8,6 +8,9 @@ m4_define([metacity_micro_version], [2])
m4_define([metacity_version],
[metacity_major_version.metacity_minor_version.metacity_micro_version])
m4_define([metacity_clutter_plugin_api_version], [1])
AC_INIT([metacity], [metacity_version],
[http://bugzilla.gnome.org/enter_bug.cgi?product=metacity])
......@@ -17,6 +20,15 @@ AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE
AM_MAINTAINER_MODE
METACITY_MAJOR_VERSION=metacity_major_version
METACITY_MINOR_VERSION=metacity_minor_version
METACITY_MICRO_VERSION=metacity_micro_version
METACITY_CLUTTER_PLUGIN_API_VERSION=metacity_clutter_plugin_api_version
AC_SUBST(METACITY_MAJOR_VERSION)
AC_SUBST(METACITY_MINOR_VERSION)
AC_SUBST(METACITY_MICRO_VERSION)
AC_SUBST(METACITY_CLUTTER_PLUGIN_API_VERSION)
# Honor aclocal flags
AC_SUBST(ACLOCAL_AMFLAGS, "\${ACLOCAL_FLAGS}")
......@@ -290,6 +302,7 @@ fi
if test x$have_clutter = xyes; then
CLUTTER_PACKAGE=clutter-0.8
METACITY_PC_MODULES="$METACITY_PC_MODULES $CLUTTER_PACKAGE "
PKG_CHECK_MODULES(CLUTTER, $CLUTTER_PACKAGE)
AC_DEFINE(WITH_CLUTTER, , [Building with Clutter compositor])
fi
......@@ -509,6 +522,7 @@ src/wm-tester/Makefile
src/libmetacity-private.pc
src/tools/Makefile
src/themes/Makefile
src/compositor/clutter-plugins/Makefile
po/Makefile.in
])
......
......@@ -2,7 +2,11 @@ lib_LTLIBRARIES = libmetacity-private.la
SUBDIRS=wm-tester tools themes
INCLUDES=@METACITY_CFLAGS@ -I $(srcdir)/include -DMETACITY_LIBEXECDIR=\"$(libexecdir)\" -DHOST_ALIAS=\"@HOST_ALIAS@\" -DMETACITY_LOCALEDIR=\"$(prefix)/@DATADIRNAME@/locale\" -DMETACITY_PKGDATADIR=\"$(pkgdatadir)\" -DMETACITY_DATADIR=\"$(datadir)\" -DG_LOG_DOMAIN=\"metacity\" -DSN_API_NOT_YET_FROZEN=1
if WITH_CLUTTER
SUBDIRS += compositor/clutter-plugins
endif
INCLUDES=@METACITY_CFLAGS@ -I $(srcdir)/include -DMETACITY_LIBEXECDIR=\"$(libexecdir)\" -DHOST_ALIAS=\"@HOST_ALIAS@\" -DMETACITY_LOCALEDIR=\"$(prefix)/@DATADIRNAME@/locale\" -DMETACITY_PKGDATADIR=\"$(pkgdatadir)\" -DMETACITY_DATADIR=\"$(datadir)\" -DG_LOG_DOMAIN=\"metacity\" -DSN_API_NOT_YET_FROZEN=1 -DMETACITY_MAJOR_VERSION=$(METACITY_MAJOR_VERSION) -DMETACITY_MINOR_VERSION=$(METACITY_MINOR_VERSION) -DMETACITY_MICRO_VERSION=$(METACITY_MICRO_VERSION) -DMETACITY_CLUTTER_PLUGIN_API_VERSION=$(METACITY_CLUTTER_PLUGIN_API_VERSION) -DMETACITY_PKGLIBDIR=\"$(pkglibdir)\"
metacity_SOURCES= \
core/async-getprop.c \
......@@ -99,8 +103,11 @@ metacity_SOURCES= \
ui/ui.c
if WITH_CLUTTER
metacity_SOURCES += compositor/compositor-clutter.c \
compositor/compositor-clutter.h
metacity_SOURCES += compositor/compositor-clutter.c \
compositor/compositor-clutter.h \
include/compositor-clutter-plugin.h \
compositor/compositor-clutter-plugin-manager.c \
compositor/compositor-clutter-plugin-manager.h
endif
# by setting libmetacity_private_la_CFLAGS, the files shared with
......
pkglibdir=$(libdir)/@PACKAGE@/plugins/clutter
if WITH_CLUTTER
INCLUDES=@METACITY_CFLAGS@ -I $(top_srcdir)/src/include -DMETACITY_LIBEXECDIR=\"$(libexecdir)\" -DHOST_ALIAS=\"@HOST_ALIAS@\" -DMETACITY_LOCALEDIR=\"$(prefix)/@DATADIRNAME@/locale\" -DMETACITY_PKGDATADIR=\"$(pkgdatadir)\" -DMETACITY_DATADIR=\"$(datadir)\" -DG_LOG_DOMAIN=\"metacity\" -DSN_API_NOT_YET_FROZEN=1 -DMETACITY_MAJOR_VERSION=$(METACITY_MAJOR_VERSION) -DMETACITY_MINOR_VERSION=$(METACITY_MINOR_VERSION) -DMETACITY_MICRO_VERSION=$(METACITY_MICRO_VERSION) -DMETACITY_CLUTTER_PLUGIN_API_VERSION=$(METACITY_CLUTTER_PLUGIN_API_VERSION) -DMETACITY_PKGLIBDIR=\"$(pkglibdir)\"
simple_la_CFLAGS = -fPIC
simple_la_SOURCES = simple.c
simple_la_LDFLAGS = -module -avoid-version
simple_la_LIBADD = @CLUTTER_LIBS@
pkglib_LTLIBRARIES = simple.la
# post-install hook to remove the .la and .a files we are not interested in
# (There is no way to stop libtool generating static libs locally, and we
# cannot do this globally because of libmetacity-private.so).
install-exec-hook:
rm $(pkglibdir)/*.a
rm $(pkglibdir)/*.la
endif
Plugins implement effects associated with WM events, such as window map,
minimizing, maximizing, unmaximizing, destruction and workspace switching. The
plugin API is documented in src/include/compositor-clutter-plugin.h; in
addition the simple plugin can be used as a reference implementation.
The API is intended to be generic, exposing no implementation details of the WM
to the plugins; this will facilitate reuse without modification with another WM
(there are plans to use the same plugin API with Matchbox 2).
Multiple plugins can implement the same effect and be loaded at the same time;
however, stacking arbitrary effects in this way might not work as expected;
this is particularly true of more complex effects, such as those for workspace
switching.
Plugins are installed in ${prefix}/lib/metacity/plugins/clutter; from there the
WM will load plugins listed in the clutter_plugins key in the Metacity gconf
general preferences group. Each entry in preferences has the format
'name: optional parameters'
where 'name' is the name of the library without the .so suffix.
As noted above, additional parameters can be passed to the plugin via the
preference key. In such case, the plugin name is immediately followed by a
colon, separating it from the parameters. Two common parameters should be
handled by all plugins:
'debug' indicates that the plugin is run in a debug mode (what exactly that
means is left to the plugin to determine).
'disable' parameter indicates which effects within the plugin should be
disabled; the format of the disable parameter is
'disable: effect1[, effect2];'
where effect1, etc., matches the effects listed in the
compositor-clutter-plugin.h file (currently one of 'map', 'destroy',
'maximize', 'unmaximize', 'switch-workspace'). Example 'disable:
minimize, maximize;'.
This diff is collapsed.
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (c) 2008 Intel Corp.
*
* Author: Tomas Frydrych <tf@linux.intel.com>
*
* 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 <gmodule.h>
#include <string.h>
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);
}
}