/*- * 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); }