/*-
* Copyright (c) 2001 Jordan DeLong
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "wm.h"
/* the list of loaded plugins */
pluginlist_t plugin_list = LIST_HEAD_INITIALIZER(plugin_list);
/*
* when calling into a plugin this is set to it's plugin_t,
* all calls into plugins must save the previous value of
* this and restore it after the call.
*/
plugin_t *plugin_this = NULL;
/*
* lists for all plugin callbacks; when events generating
* callbacks occur, the corresponding list is traversed and all
* callout routines are called.
*/
static callbacklist_t callbacklist[PCALL_COUNT];
/*
* plugin initialization, just set up all the plugin
* callback lists.
*/
void plugin_init() {
int i;
for (i = 0; i < PCALL_COUNT; i++)
LIST_INIT(&callbacklist[i]);
}
/* unload all plugins */
void plugin_shutdown() {
plugin_t *plugin;
while (!LIST_EMPTY(&plugin_list)) {
plugin = LIST_FIRST(&plugin_list);
plugin_unload(plugin);
}
}
/*
* find a plugin on the plugin path, and attempt to load it. return
* a null if unsuccessful, or the dlopen hnd if successful.
*/
static __inline void *loadplugin(char *name) {
char path[MAXPATHLEN];
char *pluginpath, *tmp, *c, *plugindir;
char *usedplugdir = NULL;
void *hnd;
/* make a local copy of pluginpath for strsep */
pluginpath = strdup(PLUGINPATH);
if (!pluginpath)
return NULL;
tmp = pluginpath;
/*
* loop through the : deliminated plugin path and look for
* a file at each node. if the file exists, a dlopen is
* attempted.
*/
hnd = NULL;
while ((plugindir = strsep(&tmp, ":")) != NULL) {
/*
* replace ~ with the path to the user's
* home directory
*/
if ((c = strchr(plugindir, '~')) != NULL) {
char *newdir;
int olen, hplen;
olen = strlen(plugindir);
newdir = malloc(olen + 2);
if (!newdir)
continue;
*c = '\0';
strcpy(newdir, plugindir);
strcat(&newdir[c - plugindir], "%s");
strcat(&newdir[c - plugindir + 2], &c[1]);
hplen = strlen(homedir_path);
usedplugdir = malloc(hplen + olen + 1);
if (!usedplugdir) {
free(newdir);
continue;
}
snprintf(usedplugdir, hplen + olen + 1,
newdir, homedir_path);
free(newdir);
plugindir = usedplugdir;
}
/* build the full path */
if (snprintf(path, MAXPATHLEN, "%s/%s.so",
plugindir, name) >= MAXPATHLEN) {
if (usedplugdir) {
free(usedplugdir);
usedplugdir = NULL;
}
warnx("path to plugin exceeds MAXPATHLEN");
continue;
}
/*
* free usedplugdir if we allocated it when translating
* ~ to $HOME, and try to access the file.
*/
if (usedplugdir) {
free(usedplugdir);
usedplugdir = NULL;
}
if (access(path, R_OK) != 0)
continue;
/*
* the access worked, so try to dlopen it.
*/
hnd = dlopen(path, RTLD_LAZY);
if (hnd != NULL)
break;
warnx("failed dlopen for %s: %s", path, dlerror());
}
free(pluginpath);
return hnd;
}
/* load a plugin, call it's init */
plugin_t *plugin_load(char *name, subparams_t *params, int doinit) {
plugin_t *plugin;
void *hnd;
/* attempt to load the plugin */
hnd = loadplugin(name);
if (!hnd) {
warnx("unable to load plugin %s", name);
return NULL;
}
/* get memory for the plugin structure */
plugin = malloc(sizeof(plugin_t));
if (!plugin)
return NULL;
warnx("%s: loaded", name);
/* fill in the plugin structure */
plugin->hnd = hnd;
plugin->name = name;
plugin->params.count = params ? params->count : 0;
plugin->params.params = params ? params->params : NULL;
plugin->init = dlsym(hnd, SYM_PREFIX "init");
plugin->shutdown = dlsym(hnd, SYM_PREFIX "shutdown");
plugin->start = dlsym(hnd, SYM_PREFIX "start");
plugin->xevent_handler = dlsym(hnd, SYM_PREFIX "xevent_handler");
LIST_INIT(&plugin->callback_list);
LIST_INSERT_HEAD(&plugin_list, plugin, p_list);
/*
* call the plugin init function if we're supposed to
* and it exists. we may be instructed not to call init
* because if a plugin forplug exists for this plugin,
* the call to init will be delayed until after the
* forplug is parsed.
*/
if (doinit && plugin->init) {
plugin_t *oldthis = plugin_this;
plugin_this = plugin;
if (plugin->init() == PLUGIN_UNLOAD)
plugin_unload(plugin);
else
plugin_subparams_free(&plugin->params);
plugin_this = oldthis;
}
return plugin;
}
/* unload a plugin, call it's shutdown */
void plugin_unload(plugin_t *plugin) {
plugin_t *oldthis;
/* call shutdown if it's there */
if (plugin->shutdown) {
oldthis = plugin_this;
plugin_this = plugin;
plugin->shutdown();
plugin_this = oldthis;
}
/*
* deallocate all callbacks that the plugin
* registered.
*/
while (!LIST_EMPTY(&plugin->callback_list))
plugin_callback_rm(LIST_FIRST(&plugin->callback_list));
/*
* get rid of the params (they still exist if init()
* unloaded the plugin)
*/
plugin_subparams_free(&plugin->params);
#ifndef USE_DMALLOC
/* don't do this while dmallocing or dmalloc freaks out */
if (dlclose(plugin->hnd))
warnx("failed dlclose: %s", dlerror());
#endif
warnx("%s: unloaded", plugin->name);
LIST_REMOVE(plugin, p_list);
free(plugin->name);
free(plugin);
}
/* start all the loaded plugins */
void plugin_start() {
plugin_t *plugin, *oldthis, *next;
oldthis = plugin_this;
plugin = LIST_FIRST(&plugin_list);
while (plugin) {
next = LIST_NEXT(plugin, p_list);
if (plugin->start) {
plugin_this = plugin;
if (plugin->start() == PLUGIN_UNLOAD)
plugin_unload(plugin);
}
plugin = next;
}
plugin_this = oldthis;
}
/* call the xevent handler for a plugin */
void plugin_handle_event(plugin_t *plugin, XEvent *e) {
plugin_t *oldthis;
if (!plugin->xevent_handler)
return;
oldthis = plugin_this;
plugin_this = plugin;
if (plugin->xevent_handler(e) == PLUGIN_UNLOAD)
plugin_unload(plugin);
plugin_this = oldthis;
}
/* add a callback handler for a plugin */
callback_t *plugin_callback_add(plugin_t *plugin, int pcall, void *handler) {
callback_t *callback;
/* no bogus callback numbers */
assert(pcall < PCALL_COUNT && pcall >= 0);
/* create the callback */
callback = malloc(sizeof(callback_t));
if (!callback)
return NULL;
callback->handler = handler;
callback->plugin = plugin;
callback->pcall = pcall;
LIST_INSERT_HEAD(&plugin->callback_list, callback, cb_plug);
LIST_INSERT_HEAD(&callbacklist[pcall], callback, cb_list);
return callback;
}
/* remove a callback entry */
void plugin_callback_rm(callback_t *callback) {
LIST_REMOVE(callback, cb_plug);
LIST_REMOVE(callback, cb_list);
free(callback);
}
/*
* call a callback handler function with the specified
* arguments; also provide the implicit pcall argument to the
* callback handler.
*
* XXX: the translation here of va_list into real
* parameters is somewhat hackish...
*/
static int callhandler(callback_t *callback, int argc, va_list ap) {
intptr_t args[argc];
plugin_t *oldthis;
int i, ret;
/*
* save old plugin_this and set the new one for
* the entry into the plugin callback
*/
oldthis = plugin_this;
plugin_this = callback->plugin;
/*
* build argument list for the call into
* the plugin.
*/
for (i = 0; i < argc; i++)
args[i] = va_arg(ap, intptr_t);
/*
* call the plugin routine passing the appropriate
* number of parameters.
*/
switch (argc) {
case 0:
ret = callback->handler(callback->pcall);
break;
case 1:
ret = callback->handler(callback->pcall, args[0]);
break;
case 2:
ret = callback->handler(callback->pcall, args[0], args[1]);
break;
case 3:
ret = callback->handler(callback->pcall, args[0], args[1], args[2]);
break;
default:
trace("%d arguments is currently too many...", argc);
assert(0);
ret = PLUGIN_OK;
}
/*
* handle possibility of a plugin unloading
* itself, restore plugin_this.
*/
if (ret == PLUGIN_UNLOAD)
plugin_unload(callback->plugin);
plugin_this = oldthis;
return ret;
}
/*
* call into all the callback handlers of type pcall that
* are registered by this plugin.
*/
int plugin_callback(plugin_t *plugin, int pcall, int argc, ...) {
callback_t *callback, *next;
va_list ap;
int ret;
/*
* it is neccesary to get the next pointer before
* calling the callback, because the callback could
* cause unloading of the plugin.
*/
callback = LIST_FIRST(&plugin->callback_list);
while (callback) {
next = LIST_NEXT(callback, cb_plug);
if (callback->pcall == pcall) {
va_start(ap, argc);
ret = callhandler(callback, argc, ap);
va_end(ap);
if (ret == PLUGIN_USING)
return PLUGIN_USING;
}
callback = next;
}
return PLUGIN_OK;
}
/*
* call into all the callback handlers of type pcall that
* are registered by any plugin.
*/
int plugin_callback_all(int pcall, int argc, ...) {
callback_t *callback, *next;
va_list ap;
int ret;
callback = LIST_FIRST(&callbacklist[pcall]);
while (callback) {
next = LIST_NEXT(callback, cb_list);
va_start(ap, argc);
ret = callhandler(callback, argc, ap);
va_end(ap);
if (ret == PLUGIN_USING)
return PLUGIN_USING;
callback = next;
}
return PLUGIN_OK;
}
/* make a new plugin parameter */
param_t *plugin_param(char *name, char *value) {
param_t *param;
param = malloc(sizeof(param_t));
if (!param)
return NULL;
param->name = name;
param->value = value;
return param;
}
/* add a parameter to a subparams */
int plugin_subparams_add(subparams_t *subparams, param_t *param) {
param_t **tmp;
tmp = realloc(subparams->params, (subparams->count + 1) * sizeof(param_t *));
if (!tmp)
return -1;
subparams->params = tmp;
subparams->params[subparams->count++] = param;
return 0;
}
/* merge the second param of subparams onto the first */
int plugin_subparams_merge(subparams_t *subparams, subparams_t *addition) {
param_t **tmp;
tmp = realloc(subparams->params,
(subparams->count + addition->count) * sizeof(param_t *));
if (!tmp)
return -1;
subparams->params = tmp;
memcpy(&subparams->params[subparams->count], addition->params, addition->count * sizeof(param_t *));
subparams->count += addition->count;
free(addition->params);
return 0;
}
/* free up a tree of subparams */
void plugin_subparams_free(subparams_t *subparams) {
int i;
if (subparams->count) {
for (i = 0; i < subparams->count; i++)
plugin_param_free(subparams->params[i]);
free(subparams->params);
subparams->count = 0;
}
}
/* free up a param and it's subparams */
void plugin_param_free(param_t *param) {
plugin_subparams_free(¶m->subparams);
free(param->name);
free(param->value);
free(param);
}
/* try to find a plugin parameter in the first depth of a tree */
param_t *plugin_find_param(subparams_t *subparams, char *name) {
int i;
for (i = 0; i < subparams->count; i++)
if (strcmp(subparams->params[i]->name, name) == 0)
return subparams->params[i];
return NULL;
}
/* string parameters */
int plugin_string_param(subparams_t *subparams, char *name, char **ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
*ret = strdup(param->value);
return *ret ? 0 : -1;
}
/* color parameters; return an array of Pixel: one for each screen */
int plugin_color_param(subparams_t *subparams, char *name, Pixel **ret) {
XColor clr;
param_t *param;
int i;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
/*
* because differnt screens can have differnt bit depths
* we need to return a Pixel for each screen on the display.
*/
*ret = malloc(sizeof(Pixel) * ScreenCount(display));
if (!*ret)
return -1;
for (i = 0; i < ScreenCount(display); i++)
if (XParseColor(display, DefaultColormap(display, i),
param->value, &clr)) {
XAllocColor(display, DefaultColormap(display, i), &clr);
(*ret)[i] = clr.pixel;
} else
return free(*ret), -1;
return 0;
}
/* pixmap parameters */
int plugin_pixmap_param(subparams_t *subparams, char *name, pixmap_t **ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
*ret = pixmap_ident(param->value);
return *ret ? 0 : -1;
}
/* dgroup parameters */
int plugin_dgroup_param(subparams_t *subparams, char *name, dgroup_t **ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
*ret = dgroup_ident(param->value);
return *ret ? 0 : -1;
}
/* integer parameters */
int plugin_int_param(subparams_t *subparams, char *name, int *ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
*ret = atoi(param->value);
return 0;
}
/* decimal parameters */
int plugin_double_param(subparams_t *subparams, char *name, double *ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
*ret = atof(param->value);
return 0;
}
/* boolean parameters */
int plugin_bool_param(subparams_t *subparams, char *name, int *ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
/* "true", "false", or just ret -1 */
if (strcmp(param->value, "true") == 0)
return *ret = 1, 0;
else if (strcmp(param->value, "false") == 0)
return *ret = 0, 0;
return -1;
}
/* stacking layer parameters */
int plugin_stacklayer_param(subparams_t *subparams, char *name, int *ret) {
param_t *param;
param = plugin_find_param(subparams, name);
if (!param)
return -1;
/* find out the layer */
if (strcmp(param->value, "bottom") == 0)
*ret = STACKLAYER_BOTTOM;
else if (strcmp(param->value, "below") == 0)
*ret = STACKLAYER_BELOW;
else if (strcmp(param->value, "normal") == 0)
*ret = STACKLAYER_NORMAL;
else if (strcmp(param->value, "above") == 0)
*ret = STACKLAYER_ABOVE;
else if (strcmp(param->value, "top") == 0)
*ret = STACKLAYER_TOP;
else if (strcmp(param->value, "tiptop") == 0)
*ret = STACKLAYER_TIPTOP;
else
return -1;
return 0;
}
/* set the plugin_context of a window so plugin will get events */
void plugin_setcontext(plugin_t *plugin, Window wnd) {
XSaveContext(display, wnd, plugin_context, (XPointer) plugin);
}
/*
* this must be done for all windows that a plugin called
* plugin_setcontext on before the plugin exits, otherwise
* an event may cause an attempted xevent_handler call on an
* already-unloaded plugin, which is bad.
*/
void plugin_rmcontext(Window wnd) {
XDeleteContext(display, wnd, plugin_context);
}
syntax highlighted by Code2HTML, v. 0.9.1