/*-
 * 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.
 */
#ifndef PLUGIN_H
#define PLUGIN_H

/*
 * return values for plugin callbacks.  if any plugin callback
 * returns PLUGIN_UNLOAD that plugin is immediatly unloaded.
 * certain plugin callbacks can return plugin using, which means
 * that they are using the event that caused the callback,
 * and the main code shouldn't call any more plugin callbacks
 * for it, or do any normal processing on it.
 */
#define PLUGIN_OK		0
#define PLUGIN_UNLOAD		1
#define PLUGIN_USING		2

/*
 * callbacks that may be registered by plugins, and macros to
 * call all the registered handlers for a callback.  all callbacks
 * take parameters that depend on the type of the callback; all
 * plugin callback handlers also take a first parameter of the
 * type of the callback; this allows using one handler for multiple
 * callbacks who's handling is nearly identical.
 */
enum {
	/*
	 * init_hints(int pcall, client_t *client, dgroup_t *dgroup);
	 *
	 *	initialize windowmanager hints; the client passed in
	 * is a newly-arriving client.  the plugin may modify client
	 * flags or set client->dgroup to affect how the client is
	 * initially created.
	 */
	PCALL_INIT_HINTS	= 0,
#define plugin_init_hints(client)	\
	plugin_callback_all(PCALL_INIT_HINTS, 1, client)

	/*
	 * window_birth(int pcall, client_t *client);
	 *
	 *	signals arrival of a new client.  this is called right
	 * after the client's window is mapped for the first time.
	 */
	PCALL_WINDOW_BIRTH,
#define plugin_window_birth(client)	\
	plugin_callback_all(PCALL_WINDOW_BIRTH, 1, client)

	/*
	 * window_death(int pcall, client_t *client);
	 *
	 *	signals the departure of a client.  called immediatly
	 * before the client_rm call, allowing the plugin to reference
	 * client_t internals for one last time.
	 */
	PCALL_WINDOW_DEATH,
#define plugin_window_death(client)	\
	plugin_callback_all(PCALL_WINDOW_DEATH, 1, client)

	/*
	 * focus_change(int pcall, client_t *client);
	 *
	 *	the X focus was changed to a different managed client
	 * window; the client paramater is the client that just got
	 * focused (also accessable as the client_focused global).
	 */
	PCALL_FOCUS_CHANGE,
#define plugin_focus_change(client)	\
	plugin_callback_all(PCALL_FOCUS_CHANGE, 1, client)

	/*
	 * geometry_change(int pcall, client_t *client);
	 *
	 *	the client was resized or moved; a seperate callback
	 * is used for zooming.
	 */
	PCALL_GEOMETRY_CHANGE,
#define plugin_geometry_change(client)	\
	plugin_callback_all(PCALL_GEOMETRY_CHANGE, 1, client)

	/*
	 * iconify_notify(int pcall, client_t *client);
	 *
	 *	the client was iconified.
	 */
	PCALL_ICONIFY_NOTIFY,
#define plugin_iconify_notify(client)	\
	plugin_callback_all(PCALL_ICONIFY_NOTIFY, 1, client)

	/*
	 * restore_notify(int pcall, client_t *client);
	 *
	 *	the client was restored (uniconified).
	 */
	PCALL_RESTORE_NOTIFY,
#define plugin_restore_notify(client)	\
	plugin_callback_all(PCALL_RESTORE_NOTIFY, 1, client)

	/*
	 * zoom_notify(int pcall, client_t *client);
	 *
	 *	the client was zoomed (maximized).
	 */
	PCALL_ZOOM_NOTIFY,
#define plugin_zoom_notify(client)	\
	plugin_callback_all(PCALL_ZOOM_NOTIFY, 1, client)

	/*
	 * unzoom_notify(int pcall, client_t *client);
	 *
	 *	the client was unzoomed.
	 */
	PCALL_UNZOOM_NOTIFY,
#define plugin_unzoom_notify(client)	\
	plugin_callback_all(PCALL_UNZOOM_NOTIFY, 1, client)

	/*
	 * raise_notify(int pcall, client_t *client, client_t *lowest);
	 *
	 *	client was raised to the top of it's stacking layer.  the
	 * lowest parameter is set to lowest client that is above the
	 * the one that was raised, or null if there is no such client.
	 */
	PCALL_RAISE_NOTIFY,
#define plugin_raise_notify(client, lowest)	\
	plugin_callback_all(PCALL_RAISE_NOTIFY, 2, client, lowest)

	/*
	 * lower_notify(int pcall, client_t *client, client_t *lowest);
	 *
	 *	client was lowered to the bottom of it's stacking layer.
	 * the lowest parameter is the same as in raise_notify().
	 */
	PCALL_LOWER_NOTIFY,
#define plugin_lower_notify(client, lowest)	\
	plugin_callback_all(PCALL_LOWER_NOTIFY, 2, client, lowest)

	/*
	 * workspace_change(int pcall, screen_t *screen, desktop_t *desktop);
	 *
	 *	the current workspace changed for desktop, on screen.
	 * desktop is not neccesarily the current desktop for the screen.
	 */
	PCALL_WORKSPACE_CHANGE,
#define plugin_workspace_change(screen, desktop)	\
	plugin_callback_all(PCALL_WORKSPACE_CHANGE, 2, screen, desktop)

	/*
	 * desktop_change(int pcall, screen_t *screen, desktop_t *olddesk);
	 *
	 *	the current desktop changed on screen.  screen->desktop
	 * contains the new current desktop, and a pointer to the old
	 * desktop is given in olddesk.
	 */
	PCALL_DESKTOP_CHANGE,
#define plugin_desktop_change(screen, olddesk)	\
	plugin_callback_all(PCALL_DESKTOP_CHANGE, 2, screen, olddesk)

	/*
	 * anim_birth(int pcall, client_t *client);
	 *
	 *	somewhat hackish callback that occurs before the window
	 * gets mapped and signals new window arrival.  the callback is
	 * located at an appropriate time to do animations for newly
	 * ariving windows.
	 */
	PCALL_ANIM_BIRTH,
#define plugin_anim_birth(client)	\
	plugin_callback_all(PCALL_ANIM_BIRTH, 1, client)

	/*
	 * root_button(int pcall, screen_t *screen, XButtonEvent *e);
	 *
	 *	because clients can't plugin_setcontext on the root window,
	 * this callback is provided to notify clients of root window
	 * button presses and releases. the X button event and the screen
	 * on which it occured are passed to the client.
	 */
	PCALL_ROOT_BUTTON,
#define	plugin_root_button(screen, e)	\
	plugin_callback_all(PCALL_ROOT_BUTTON, 2, screen, e)

	/*
	 * property_notify(int pcall, client_t *client, XPropertyEvent *e);
	 *
	 *	plugins can't plugin_setcontext on a client window to
	 * handle window property changes because other plugins may
	 * be interested in those changes as well; this callback is
	 * provided to allow them to recieve property information without
	 * doing a setcontext.
	 */
	PCALL_PROPERTY_NOTIFY,
#define plugin_property_notify(client, e)	\
	plugin_callback_all(PCALL_PROPERTY_NOTIFY, 2, client, e)

	/*
	 * map_request(int pcall, screen_t *screen, XMapRequestEvent *e);
	 *
	 *	called when a new toplevel window attempts to map
	 * itself.  screen is the screen on which the request occured,
	 * and e is the actual X event that cause the map request.
	 * if the callback returns PLUGIN_USING, no client_t will
	 * be created for this window.
	 */
	PCALL_MAP_REQUEST,
#define plugin_map_request(screen, e)	\
	plugin_callback_all(PCALL_MAP_REQUEST, 2, screen, e)

	/* total number of plugin callback types */
	PCALL_COUNT
};

/*
 * an array of subparamaters; placed in the plugin structure
 * and in each parameter structure.
 */
struct subparams {
	int	count;		/* number of sub params */
	param_t	**params;	/* the sub parameters */
};

/*
 * a tree of plugin parameters that are passed in for use by the plugin from
 * the user's rcfile.  there is a pointer to one of these in the plugin_t
 * structure who's varname will
 */
struct param {
	char	*name;		/* name of the parameter */
	char	*value;		/* value of the parameter */

	subparams_t subparams;
};

/*
 * plugin callout list entry structure; plugins register callbacks
 * by passing in an address of a handler function.  the handler
 * is stored here as an (int *)(), but the handler routines do actually
 * take arguments who's type depends on the callback.  this entries
 * are hung off the plugin structure, for calling callbacks for
 * particular plugins, as well as being hung off internal lists
 * based on callback type to save from traversing the full list
 * of all plugins.
 *
 * plugins may add or remove callback handlers at any time during
 * their operation; however most plugins are expected to add a few
 * callbacks in their start() routine and leave them alone from
 * then on.
 */
struct callback {
	LIST_ENTRY(callback) cb_list;	/* callback list entry */
	LIST_ENTRY(callback) cb_plug;	/* plugin registered callbacks list */
	plugin_t	*plugin;	/* plugin requesting the callback */
	int		(*handler)();	/* address of handler function */
	int		pcall;		/* callback type */
};
typedef LIST_HEAD(, callback) callbacklist_t;

/* 
 * plugin structure; plugins are dlopened shared objects that
 * export set of routines that are called into by the main code.
 * the plugin may, during the calls into those routines, organize
 * other calls into the plugin text for notification or action on
 * certain events.  all callbacks a plugin adds are automatically
 * removed for it when it is unloaded.
 */
struct plugin {
	void	*hnd;			/* handle from dlopen */
	char	*name;			/* name the plugin was loaded as */

	/*
	 * these are functions that are called to alert
	 * the plugin of the stages during startup/shutdown.
	 * init is called during the rcfile parsing and
	 * as such it shouldn't use any internal structures.
	 * shutdown can be called at any time and should be
	 * prepared to release memory; it shouldn't use any
	 * of the internal structures unless it's sure that it
	 * is safe to do so (due to setting some global or
	 * some such in a handler for another callback).
	 * start gets called immediatly after rcfile parsing
	 * and indicates that it is time for the plugin to
	 * begin normal operation.
	 */
	int	(*init)();		/* plugin initialization routine */
	void	(*shutdown)();		/* plugin death routine */
	int	(*start)();		/* post-init plugin starting */

	/*
	 * all x events for windows that have a plugin_context
	 * with this plugin (set by plugin_setcontext) will call
	 * into the plugin for handling through this routine.
	 */
	int	(*xevent_handler)(XEvent *);

	/*
	 * list of callback handlers this plugin has
	 * registered.
	 */
	callbacklist_t	callback_list;

	/*
	 * parameters for the plugin; all plugins can recieve
	 * a tree of parameters from the rcfile.  these parameters
	 * are freed after rcfile parsing, so they should not be
	 * used after (or during) the call to start.
	 */
	subparams_t	params;

	LIST_ENTRY(plugin) p_list;
};

typedef LIST_HEAD(, plugin) pluginlist_t;
extern pluginlist_t plugin_list;
extern plugin_t *plugin_this;

void plugin_init();
void plugin_shutdown();
plugin_t *plugin_load(char *name, subparams_t *params, int doinit);
void plugin_unload(plugin_t *plugin);
void plugin_start();
void plugin_handle_event(plugin_t *plugin, XEvent *e);

/* plugin callback management */
callback_t *plugin_callback_add(plugin_t *plugin, int pcall, void *handler);
void plugin_callback_rm(callback_t *callback);
int plugin_callback(plugin_t *plugin, int pcall, int argc, ...);
int plugin_callback_all(int pcall, int argc, ...);

/* parameter structure routines*/
param_t	*plugin_param(char *name, char *value);
int plugin_subparams_add(subparams_t *subparams, param_t *param);
int plugin_subparams_merge(subparams_t *subparams, subparams_t *addition);
void plugin_subparams_free(subparams_t *subparams);
void plugin_param_free(param_t *param);

/* 
 * these are called from plugins to get param values.  Some of
 * them need to make memory for the returned values; i.e. string
 * and color.
 */
param_t	*plugin_find_param(subparams_t *subparams, char *name);
int plugin_string_param(subparams_t *subparams, char *name, char **ret);
int plugin_color_param(subparams_t *subparams, char *name, Pixel **ret);
int plugin_pixmap_param(subparams_t *subparams, char *name, pixmap_t **ret);
int plugin_dgroup_param(subparams_t *subparams, char *name, dgroup_t **ret);
int plugin_int_param(subparams_t *subparams, char *name, int *ret);
int plugin_double_param(subparams_t *subparams, char *name, double *ret);
int plugin_bool_param(subparams_t *subparams, char *name, int *ret);
int plugin_stacklayer_param(subparams_t *subparams, char *name, int *ret);

/*
 * plugins call these to associate a non-managed window with it;
 * the purpose being to cause the main event code to call out to
 * the plugin to handle the event.
 */
void plugin_setcontext(plugin_t *plugin, Window wnd);
void plugin_rmcontext(Window wnd);

#endif
