/*
 * Copyright © 2006 Novell, Inc.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include <string.h>
#include <stdlib.h>
#include <poll.h>

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>

#include <beryl.h>

#define BERYL_DBUS_SERVICE_NAME	            "org.freedesktop.beryl"

#define BERYL_DBUS_ACTIVATE_MEMBER_NAME     "activate"
#define BERYL_DBUS_DEACTIVATE_MEMBER_NAME   "deactivate"
#define BERYL_DBUS_SET_MEMBER_NAME          "set"
#define BERYL_DBUS_GET_MEMBER_NAME          "get"
#define BERYL_DBUS_GET_METADATA_MEMBER_NAME "getMetadata"
#define BERYL_DBUS_LIST_MEMBER_NAME	        "list"
#define BERYL_DBUS_GET_PLUGINS_MEMBER_NAME	"getPlugins"

#define BERYL_DBUS_CHANGED_SIGNAL_NAME      "changed"
typedef enum
{
	DbusActionIndexKeyBinding = 0,
	DbusActionIndexButtonBinding = 1,
	DbusActionIndexBell = 2,
	DbusActionIndexEdge = 3,
	DbusActionIndexEdgeButton = 4
} DbusActionIndex;

static int displayPrivateIndex;

typedef struct _DbusDisplay
{
	int screenPrivateIndex;

	DBusConnection *connection;
	CompWatchFdHandle watchFdHandle;

	SetDisplayOptionProc setDisplayOption;
	SetDisplayOptionForPluginProc setDisplayOptionForPlugin;
} DbusDisplay;

typedef struct _DbusScreen
{
	SetScreenOptionProc setScreenOption;
	SetScreenOptionForPluginProc setScreenOptionForPlugin;
} DbusScreen;

#define GET_DBUS_DISPLAY(d)				     \
    ((DbusDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define DBUS_DISPLAY(d)			   \
    DbusDisplay *dd = GET_DBUS_DISPLAY (d)

#define GET_DBUS_SCREEN(s, dd)				     \
    ((DbusScreen *) (s)->privates[(dd)->screenPrivateIndex].ptr)

#define DBUS_SCREEN(s) \
    DbusScreen *ds = GET_DBUS_SCREEN (s, GET_DBUS_DISPLAY (s->display))


static CompOption *dbusGetOptionsFromPath(CompDisplay * d,
										  char **path,
										  CompScreen ** return_screen,
										  int *nOption)
{
	CompScreen *s = NULL;

	if (strcmp(path[1], "allscreens"))
	{
		int screenNum;

		if (sscanf(path[1], "screen%d", &screenNum) != 1)
			return FALSE;

		for (s = d->screens; s; s = s->next)
			if (s->screenNum == screenNum)
				break;

		if (!s)
			return NULL;
	}

	if (return_screen)
		*return_screen = s;

	if (strcmp(path[0], "core") == 0)
	{
		if (s)
			return compGetScreenOptions(s, nOption);
		else
			return compGetDisplayOptions(d, nOption);
	}
	else
	{
		CompPlugin *p;

		for (p = getPlugins(); p; p = p->next)
			if (strcmp(p->vTable->name, path[0]) == 0)
				break;

		if (!p)
			return NULL;

		if (s)
		{
			if (p->vTable->getScreenOptions)
				return (*p->vTable->getScreenOptions) (s, nOption);
		}
		else
		{
			if (p->vTable->getDisplayOptions)
				return (*p->vTable->getDisplayOptions) (d, nOption);
		}
	}

	return NULL;
}

/*
 * Activate can be used to trigger any existing action. Arguments
 * should be a pair of { string, bool|int32|double|string }.
 *
 * Example (rotate to face 1):
 *
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/rotate/allscreens/rotate_to	      \
 * org.freedesktop.beryl.activate			      \
 * string:'root'                                              \
 * int32:`xwininfo -root | grep id: | awk '{ print $4 }'`     \
 * string:'face' int32:1
 *
 *
 * You can also call the terminate function
 *
 * Example unfold and refold cube:
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/cube/allscreens/unfold             \
 * org.freedesktop.beryl.activate                            \
 * string:'root'                                              \
 * int32:`xwininfo -root | grep id: | awk '{ print $4 }'`     \
 *
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/cube/allscreens/unfold             \
 * org.freedesktop.beryl.deactivate                          \
 * string:'root'                                              \
 * int32:`xwininfo -root | grep id: | awk '{ print $4 }'`     \
 */
static Bool
dbusHandleActionMessage(DBusConnection * connection,
						DBusMessage * message,
						CompDisplay * d, char **path, Bool activate)
{
	CompOption *option;
	int nOption;

	option = dbusGetOptionsFromPath(d, path, NULL, &nOption);
	if (!option)
		return FALSE;

	while (nOption--)
	{
		if (strcmp(option->name, path[2]) == 0)
		{
			CompOption *argument = NULL;
			int nArgument = 0;
			DBusMessageIter iter;

			if (option->type != CompOptionTypeAction)
				return FALSE;

			if (activate)
			{
				if (!option->value.action.initiate)
					return FALSE;
			}
			else
			{
				if (!option->value.action.terminate)
					return FALSE;
			}

			if (dbus_message_iter_init(message, &iter))
			{
				CompOptionValue value;
				CompOptionType type = 0;
				char *name;
				Bool hasValue;

				do
				{
					name = NULL;
					hasValue = FALSE;

					while (!name)
					{
						switch (dbus_message_iter_get_arg_type(&iter))
						{
						case DBUS_TYPE_STRING:
							dbus_message_iter_get_basic(&iter, &name);
						default:
							break;
						}

						if (!dbus_message_iter_next(&iter))
							break;
					}

					while (!hasValue)
					{
						double tmp;

						switch (dbus_message_iter_get_arg_type(&iter))
						{
						case DBUS_TYPE_BOOLEAN:
							hasValue = TRUE;
							type = CompOptionTypeBool;

							dbus_message_iter_get_basic(&iter, &value.b);
							break;
						case DBUS_TYPE_INT32:
							hasValue = TRUE;
							type = CompOptionTypeInt;

							dbus_message_iter_get_basic(&iter, &value.i);
							break;
						case DBUS_TYPE_DOUBLE:
							hasValue = TRUE;
							type = CompOptionTypeFloat;

							dbus_message_iter_get_basic(&iter, &tmp);

							value.f = tmp;
							break;
						case DBUS_TYPE_STRING:
							hasValue = TRUE;
							type = CompOptionTypeString;

							dbus_message_iter_get_basic(&iter, &value.s);
						default:
							break;
						}

						if (!dbus_message_iter_next(&iter))
							break;
					}

					if (name && hasValue)
					{
						CompOption *a;

						a = realloc(argument,
									sizeof(CompOption) * (nArgument + 1));
						if (a)
						{
							argument = a;

							argument[nArgument].name = name;
							argument[nArgument].type = type;
							argument[nArgument].value = value;

							nArgument++;
						}
					}
				}
				while (dbus_message_iter_has_next(&iter));
			}

			if (activate)
			{
				(*option->value.action.initiate) (d,
												  &option->value.action,
												  0, argument, nArgument);
			}
			else
			{
				(*option->value.action.terminate) (d,
												   &option->value.action,
												   0, argument, nArgument);
			}

			if (argument)
				free(argument);
	
			if (!dbus_message_get_no_reply(message))
			{	
				DBusMessage * reply;

				reply = dbus_message_new_method_return (message);
				
				dbus_connection_send(connection,reply,NULL);
				dbus_connection_flush(connection);	

				dbus_message_unref(reply);
			}	

			return TRUE;
		}

		option++;
	}

	return FALSE;
}

static Bool
dbusTryGetValueWithType(DBusMessageIter * iter, int type, void *value)
{
	if (dbus_message_iter_get_arg_type(iter) == type)
	{
		dbus_message_iter_get_basic(iter, value);

		return TRUE;
	}

	return FALSE;
}

static Bool
dbusGetOptionValue(DBusMessageIter * iter,
				   CompOptionType type, CompOptionValue * value)
{
	double d;
	char *s;

	switch (type)
	{
	case CompOptionTypeBool:
		return dbusTryGetValueWithType(iter, DBUS_TYPE_BOOLEAN, &value->b);
		break;
	case CompOptionTypeInt:
		return dbusTryGetValueWithType(iter, DBUS_TYPE_INT32, &value->i);
		break;
	case CompOptionTypeFloat:
		if (dbusTryGetValueWithType(iter, DBUS_TYPE_DOUBLE, &d))
		{
			value->f = d;
			return TRUE;
		}
		break;
	case CompOptionTypeString:
		return dbusTryGetValueWithType(iter, DBUS_TYPE_STRING, &value->s);
		break;
	case CompOptionTypeColor:
		if (dbusTryGetValueWithType(iter, DBUS_TYPE_STRING, &s))
		{
			if (stringToColor(s, value->c))
				return TRUE;
		}
	default:
		break;
	}

	return FALSE;
}

/*
 * 'Set' can be used to change any existing option. Argument
 * should be the new value for the option.
 *
 * Example (will set command0 option to firefox):
 *
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/core/allscreens/command0	      \
 * org.freedesktop.beryl.set				      \
 * string:'firefox'
 *
 * List and action options can be changed using more than one
 * argument.
 *
 * Example (will set active_plugins option to
 * [dbus,decoration,place]):
 *
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/core/allscreens/active_plugins     \
 * org.freedesktop.beryl.set				      \
 * string:'dbus' string:'decoration' string:'place'
 *
 * Example (will set run_command0 option to trigger on key
 * binding <Control><Alt>Return and not trigger on any button
 * bindings, screen edges or bell notifications):
 *
 * dbus-send --type=method_call --dest=org.freedesktop.beryl \
 * /org/beryl-project/beryl/core/allscreens/run_command0	      \
 * org.freedesktop.beryl.set				      \
 * string:'<Control><Alt>Return'			      \
 * string:'Disabled'					      \
 * boolean:'false'                        \
 * string:''						      \
 * int:'0'
 */
static Bool
dbusHandleSetOptionMessage(DBusConnection * connection,
						   DBusMessage * message,
						   CompDisplay * d, char **path)
{
	CompScreen *s;
	CompOption *option;
	int nOption;

	option = dbusGetOptionsFromPath(d, path, &s, &nOption);
	if (!option)
		return FALSE;

	while (nOption--)
	{
		if (strcmp(option->name, path[2]) == 0)
		{
			DBusMessageIter iter;

			if (dbus_message_iter_init(message, &iter))
			{
				CompOptionValue value, tmpValue;
				DbusActionIndex actionIndex = DbusActionIndexKeyBinding;
				Bool status = FALSE;

				memset(&value, 0, sizeof(value));

				do
				{
					if (option->type == CompOptionTypeList)
					{
						if (dbusGetOptionValue
							(&iter, option->type, &tmpValue))
						{
							CompOptionValue *v;

							v = realloc(value.list.value,
										sizeof(CompOptionValue) *
										(value.list.nValue + 1));
							if (v)
							{
								v[value.list.nValue++] = tmpValue;
								value.list.value = v;
								status |= TRUE;
							}
						}
					}
					else if (option->type == CompOptionTypeAction)
					{
						CompAction *a = &value.action;
						char *str;

						status = TRUE;

						switch (actionIndex)
						{
						case DbusActionIndexKeyBinding:
							if (dbusTryGetValueWithType(&iter,
														DBUS_TYPE_STRING,
														&str))
							{
								if (stringToKeyBinding(d, str, &a->key))
									a->type |= CompBindingTypeKey;
							}
							break;
						case DbusActionIndexButtonBinding:
							if (dbusTryGetValueWithType(&iter,
														DBUS_TYPE_STRING,
														&str))
							{
								if (stringToButtonBinding(d, str, &a->button))
									a->type |= CompBindingTypeButton;
							}
							break;
						case DbusActionIndexBell:
							dbusTryGetValueWithType(&iter,
													DBUS_TYPE_BOOLEAN,
													&a->bell);
							break;
						case DbusActionIndexEdge:
							if (dbusTryGetValueWithType(&iter,
														DBUS_TYPE_STRING,
														&str))
							{
								status |= TRUE;

								while (strlen(str))
								{
									char *edge;
									int len, i = SCREEN_EDGE_NUM;

									for (;;)
									{
										edge = edgeToString(--i);
										len = strlen(edge);

										if (strncasecmp(str, edge, len) == 0)
										{
											a->edgeMask |= 1 << i;

											str += len;
											break;
										}

										if (!i)
										{
											str++;
											break;
										}
									}
								}
							}
						default:
							break;
						}

						actionIndex++;
					}
					else if (dbusGetOptionValue(&iter, option->type, &value))
					{
						status |= TRUE;
					}
				}
				while (dbus_message_iter_next(&iter));

				if (status)
				{
					if (s)
					{
						if (strcmp(path[0], "core"))
							status = (*s->setScreenOptionForPlugin) (s,
																	 path[0],
																	 option->
																	 name,
																	 &value);
						else
							status = (*s->setScreenOption) (s, option->name,
															&value);
					}
					else
					{
						if (strcmp(path[0], "core"))
							status = (*d->setDisplayOptionForPlugin) (d,
																	  path[0],
																	  option->
																	  name,
																	  &value);
						else
							status = (*d->setDisplayOption) (d, option->name,
															 &value);
					}

					return status;
				}
				else
				{
					return FALSE;
				}
			}
		}

		option++;
	}

	return FALSE;
}

static void
dbusAppendSimpleOptionValue(DBusMessage * message,
							CompOptionType type, CompOptionValue * value)
{
	double d;
	char *s;

	switch (type)
	{
	case CompOptionTypeBool:
		dbus_message_append_args(message,
								 DBUS_TYPE_BOOLEAN, &value->b,
								 DBUS_TYPE_INVALID);
		break;
	case CompOptionTypeInt:
		dbus_message_append_args(message,
								 DBUS_TYPE_INT32, &value->i,
								 DBUS_TYPE_INVALID);
		break;
	case CompOptionTypeFloat:
		d = value->f;

		dbus_message_append_args(message,
								 DBUS_TYPE_DOUBLE, &d, DBUS_TYPE_INVALID);
		break;
	case CompOptionTypeString:
		dbus_message_append_args(message,
								 DBUS_TYPE_STRING, &value->s,
								 DBUS_TYPE_INVALID);
		break;
	case CompOptionTypeColor:
		s = colorToString(value->c);
		if (s)
		{
			dbus_message_append_args(message,
									 DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID);
			free(s);
		}
	default:
		break;
	}
}

static void
dbusAppendOptionValue(CompDisplay * d,
					  DBusMessage * message,
					  CompOptionType type, CompOptionValue * value)
{
	int i;

	if (type == CompOptionTypeList)
	{
		for (i = 0; i < value->list.nValue; i++)
			dbusAppendSimpleOptionValue(message, value->list.type,
										&value->list.value[i]);
	}
	else if (type == CompOptionTypeAction)
	{
		CompAction *a = &value->action;
		char *key = "Disabled";
		char *button = "Disabled";
		char *edge = "Disabled";
		int edgeButton = 0;

		if (a->type & CompBindingTypeKey)
			key = keyBindingToString(d, &a->key);

		if (a->type & CompBindingTypeButton)
			button = buttonBindingToString(d, &a->button);

		for (i = 0; i < SCREEN_EDGE_NUM; i++)
			if (a->edgeMask & (1 << i))
				edge = edgeToString(i);

		dbus_message_append_args(message,
								 DBUS_TYPE_STRING, &key,
								 DBUS_TYPE_STRING, &button,
								 DBUS_TYPE_BOOLEAN, &a->bell,
								 DBUS_TYPE_STRING, &edge,
								 DBUS_TYPE_INT32, &edgeButton,
								 DBUS_TYPE_INVALID);
	}
	else
	{
		dbusAppendSimpleOptionValue(message, type, value);
	}

}

/*
 * 'Get' can be used to retrieve the value of any existing option.
 *
 * Example (will retrieve the current value of command0 option):
 *
 * dbus-send --print-reply --type=method_call	    \
 * --dest=org.freedesktop.beryl		    \
 * /org/beryl-project/beryl/core/allscreens/command0 \
 * org.freedesktop.beryl.get
 */
static Bool
dbusHandleGetOptionMessage(DBusConnection * connection,
						   DBusMessage * message,
						   CompDisplay * d, char **path)
{
	CompScreen *s;
	CompOption *option;
	int nOption = 0;
	DBusMessage *reply = NULL;

	option = dbusGetOptionsFromPath(d, path, &s, &nOption);

	while (nOption--)
	{
		if (strcmp(option->name, path[2]) == 0)
		{
			reply = dbus_message_new_method_return(message);
			dbusAppendOptionValue(d, reply, option->type, &option->value);
			break;
		}

		option++;
	}

	if (!reply)
		reply = dbus_message_new_error(message,
									   DBUS_ERROR_FAILED, "No such option");

	dbus_connection_send(connection, reply, NULL);
	dbus_connection_flush(connection);

	dbus_message_unref(reply);

	return TRUE;
}

static Bool
dbusHandleListMessage(DBusConnection * connection,
					  DBusMessage * message, CompDisplay * d, char **path)
{
	CompScreen *s;
	CompOption *option;
	int nOption = 0;
	DBusMessage *reply;

	option = dbusGetOptionsFromPath(d, path, &s, &nOption);

	reply = dbus_message_new_method_return(message);

	while (nOption--)
	{
		dbus_message_append_args(reply,
								 DBUS_TYPE_STRING, &option->name,
								 DBUS_TYPE_INVALID);
		option++;
	}

	dbus_connection_send(connection, reply, NULL);
	dbus_connection_flush(connection);

	dbus_message_unref(reply);

	return TRUE;
}

static Bool
dbusHandleGetMetadataMessage(DBusConnection * connection,
							 DBusMessage * message,
							 CompDisplay * d, char **path)
{
	CompScreen *s;
	CompOption *option;
	int nOption = 0;
	DBusMessage *reply = NULL;

	reply = dbus_message_new_method_return(message);

	//check to see if we've been called on a plugin
	if (!path[1])
	{
		if (!strcmp(path[0], "core"))
		{
			char *shortDesc = "General Options";
			char *longDesc = "Options for Beryl as a whole";

			dbus_message_append_args(reply,
									 DBUS_TYPE_STRING, &shortDesc,
									 DBUS_TYPE_STRING, &longDesc,
									 DBUS_TYPE_INVALID);
		}
		else
		{
			CompPlugin *p;

			p = findActivePlugin(path[0]);
			if (p)
			{
				dbus_message_append_args(reply,
										 DBUS_TYPE_STRING,
										 &p->vTable->shortDesc,
										 DBUS_TYPE_STRING,
										 &p->vTable->longDesc,
										 DBUS_TYPE_INVALID);
			}
		}
	}
	else
	{

		option = dbusGetOptionsFromPath(d, path, &s, &nOption);

		while (nOption--)
		{
			if (strcmp(option->name, path[2]) == 0)
			{
				CompOptionType restrictionType = option->type;
				char *type;

				type = optionTypeToString(option->type);

				dbus_message_append_args(reply,
										 DBUS_TYPE_STRING, &option->shortDesc,
										 DBUS_TYPE_STRING, &option->longDesc,
										 DBUS_TYPE_STRING, &type,
										 DBUS_TYPE_INVALID);

				if (restrictionType == CompOptionTypeList)
				{
					type = optionTypeToString(option->value.list.type);
					restrictionType = option->value.list.type;

					dbus_message_append_args(reply,
											 DBUS_TYPE_STRING, &type,
											 DBUS_TYPE_INVALID);
				}

				switch (restrictionType)
				{
				case CompOptionTypeInt:
					dbus_message_append_args(reply,
											 DBUS_TYPE_INT32,
											 &option->rest.i.min,
											 DBUS_TYPE_INT32,
											 &option->rest.i.max,
											 DBUS_TYPE_INVALID);
					break;
				case CompOptionTypeFloat:
				{
					double min, max, precision;

					min = option->rest.f.min;
					max = option->rest.f.max;
					precision = option->rest.f.precision;

					dbus_message_append_args(reply,
											 DBUS_TYPE_DOUBLE, &min,
											 DBUS_TYPE_DOUBLE, &max,
											 DBUS_TYPE_DOUBLE, &precision,
											 DBUS_TYPE_INVALID);
				} break;
				case CompOptionTypeString:
				{
				

	       		         	DBusMessageIter iter;
		       	         	DBusMessageIter listIter;
		               	 	char            sig[2];

	       		         	sig[0] = DBUS_TYPE_STRING;
					sig[1] = '\0';

					dbus_message_iter_init_append (reply, &iter);
			                dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY,
                                                  sig, &listIter);
					if (option->rest.s.nString)
					{
						char *possible;
						int i;

						for (i = 0; i < option->rest.s.nString; i++)
						{
							possible = option->rest.s.string[i];

							dbus_message_iter_append_basic(&listIter,DBUS_TYPE_STRING,&possible);
						}
						
					}
					dbus_message_iter_close_container(&iter,&listIter);
				}
				default:
					break;
				}
				
				break;
				
				}

			option++;
		}
	}

	if (!reply)
		reply = dbus_message_new_error(message,
									   DBUS_ERROR_FAILED, "No such option");

	dbus_connection_send(connection, reply, NULL);
	dbus_connection_flush(connection);

	dbus_message_unref(reply);

	return TRUE;

}

static Bool
dbusHandleGetPluginsMessage( DBusConnection *connection, DBusMessage *message, CompDisplay *d)
{
	DBusMessage *reply;
	char **plugins, **p;
	int n;
	
	reply = dbus_message_new_method_return ( message );
	
	plugins = availablePlugins(&n);
	if (plugins)
	{
		p=plugins;
	
		while (n--)
		{
			dbus_message_append_args(reply,DBUS_TYPE_STRING,p,DBUS_TYPE_INVALID);
			free(*p);
			p++;
		}
		free (plugins);
	}
	dbus_connection_send(connection,reply,NULL);
	dbus_connection_flush(connection);
	return TRUE;
}

static DBusHandlerResult
dbusHandleMessage(DBusConnection * connection,
				  DBusMessage * message, void *userData)
{
	CompDisplay *d = (CompDisplay *) userData;
	Bool status = FALSE;
	char **path;
	const char *service, *interface, *member;

	service = dbus_message_get_destination(message);
	interface = dbus_message_get_interface(message);
	member = dbus_message_get_member(message);

	if (!service || !interface || !member)
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (!dbus_message_is_method_call(message, interface, member))
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (!dbus_message_has_destination(message, BERYL_DBUS_SERVICE_NAME))
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (!dbus_message_get_path_decomposed(message, &path))
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (!path[0] || !path[1] || !path[2])
	{
		dbus_free_string_array (path);
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}
	
	if (strcmp(path[0], "org") ||
		strcmp(path[1], "freedesktop") || strcmp(path[2], "beryl"))
	{
		dbus_free_string_array (path);
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	if (dbus_message_has_member (message, BERYL_DBUS_GET_PLUGINS_MEMBER_NAME))
	{
		if (dbusHandleGetPluginsMessage(connection,message,d))
		{	
			dbus_free_string_array(path);
			return DBUS_HANDLER_RESULT_HANDLED;
		}
	}
	
	if (!path[3] || !path[4])
	{
		dbus_free_string_array(path);
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}
	
	if (dbus_message_has_member (message, BERYL_DBUS_LIST_MEMBER_NAME))
	{
		if (dbusHandleListMessage(connection,message,d, &path[4]))
		{
			dbus_free_string_array(path);
			return DBUS_HANDLER_RESULT_HANDLED;
		}
	}

	if (!path[5])
	{
		dbus_free_string_array(path);
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	if (dbus_message_has_member(message, BERYL_DBUS_ACTIVATE_MEMBER_NAME) )
	{
		status = dbusHandleActionMessage(connection, message, d, &path[3],
										 TRUE);
	}
	else if (dbus_message_has_member(message,
									 BERYL_DBUS_DEACTIVATE_MEMBER_NAME) )
	{
		status = dbusHandleActionMessage(connection, message, d, &path[3],
										 FALSE);
	}
	else if (dbus_message_has_member(message, BERYL_DBUS_SET_MEMBER_NAME) )
	{
		status = dbusHandleSetOptionMessage(connection, message, d, &path[3]);
	}
	else if (dbus_message_has_member(message,
									 BERYL_DBUS_GET_MEMBER_NAME))
	{
		status = dbusHandleGetOptionMessage(connection, message, d, &path[3]);
	}
	else if (dbus_message_has_member(message,
									 BERYL_DBUS_GET_METADATA_MEMBER_NAME))
	{
		status = dbusHandleGetMetadataMessage(connection, message, d,
											  &path[3]);
	}
	

	dbus_free_string_array(path);

	if (status)
		return DBUS_HANDLER_RESULT_HANDLED;

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static Bool dbusProcessMessages(void *data)
{
	CompDisplay *d = (CompDisplay *) data;
	DBusDispatchStatus status;

	DBUS_DISPLAY(d);

	do
	{
		dbus_connection_read_write_dispatch(dd->connection, 0);
		status = dbus_connection_get_dispatch_status(dd->connection);
	}
	while (status == DBUS_DISPATCH_DATA_REMAINS);

	return TRUE;
}

static void
dbusSendChangeSignalForOption(CompDisplay * d,
							  CompOptionType type,
							  CompOptionValue * value, char *path)
{
	DBusMessage *signal;

	DBUS_DISPLAY(d);

	signal = dbus_message_new_signal(path,
									 BERYL_DBUS_SERVICE_NAME,
									 BERYL_DBUS_CHANGED_SIGNAL_NAME);

	dbusAppendOptionValue(d, signal, type, value);

	dbus_connection_send(dd->connection, signal, NULL);
	dbus_connection_flush(dd->connection);

	dbus_message_unref(signal);
}

static void
dbusSendChangeSignalForDisplayOption(CompDisplay * d,
									 CompOption * o, char *plugin)
{
	char path[256];

	if (o)
	{
		sprintf(path, "/org/freedesktop/beryl/%s/allscreens/%s",
				plugin, o->name);
		dbusSendChangeSignalForOption(d, o->type, &o->value, path);
	}
}

static void
dbusSendChangeSignalForScreenOption(CompScreen * s,
									CompOption * o, char *plugin)
{
	char path[256];

	if (o)
	{
		sprintf(path, "/org/freedesktop/beryl/%s/screens%d/%s",
				plugin, s->screenNum, o->name);
		dbusSendChangeSignalForOption(s->display, o->type, &o->value, path);
	}
}

static Bool
dbusSetDisplayOption(CompDisplay * d, char *name, CompOptionValue * value)
{
	Bool status;

	DBUS_DISPLAY(d);

	UNWRAP(dd, d, setDisplayOption);
	status = (*d->setDisplayOption) (d, name, value);
	WRAP(dd, d, setDisplayOption, dbusSetDisplayOption);

	if (status)
	{
		CompOption *option;
		int nOption;

		option = compGetDisplayOptions(d, &nOption);
		dbusSendChangeSignalForDisplayOption(d,
											 compFindOption(option, nOption,
															name, 0), "core");
	}

	return status;
}

static Bool
dbusSetDisplayOptionForPlugin(CompDisplay * d,
							  char *plugin,
							  char *name, CompOptionValue * value)
{
	Bool status;

	DBUS_DISPLAY(d);

	UNWRAP(dd, d, setDisplayOptionForPlugin);
	status = (*d->setDisplayOptionForPlugin) (d, plugin, name, value);
	WRAP(dd, d, setDisplayOptionForPlugin, dbusSetDisplayOptionForPlugin);

	if (status)
	{
		CompPlugin *p;

		p = findActivePlugin(plugin);
		if (p && p->vTable->getDisplayOptions)
		{
			CompOption *option;
			int nOption;

			option = (*p->vTable->getDisplayOptions) (d, &nOption);
			dbusSendChangeSignalForDisplayOption(d,
												 compFindOption(option,
																nOption,
																name, 0),
												 p->vTable->name);
		}
	}

	return status;
}

static Bool
dbusSetScreenOption(CompScreen * s, char *name, CompOptionValue * value)
{
	Bool status;

	DBUS_SCREEN(s);

	UNWRAP(ds, s, setScreenOption);
	status = (*s->setScreenOption) (s, name, value);
	WRAP(ds, s, setScreenOption, dbusSetScreenOption);

	if (status)
	{
		CompOption *option;
		int nOption;

		option = compGetScreenOptions(s, &nOption);
		dbusSendChangeSignalForScreenOption(s,
											compFindOption(option,
														   nOption,
														   name, 0), "core");
	}

	return status;
}

static Bool
dbusSetScreenOptionForPlugin(CompScreen * s,
							 char *plugin,
							 char *name, CompOptionValue * value)
{
	Bool status;

	DBUS_SCREEN(s);

	UNWRAP(ds, s, setScreenOptionForPlugin);
	status = (*s->setScreenOptionForPlugin) (s, plugin, name, value);
	WRAP(ds, s, setScreenOptionForPlugin, dbusSetScreenOptionForPlugin);

	if (status)
	{
		CompPlugin *p;

		p = findActivePlugin(plugin);
		if (p && p->vTable->getScreenOptions)
		{
			CompOption *option;
			int nOption;

			option = (*p->vTable->getScreenOptions) (s, &nOption);
			dbusSendChangeSignalForScreenOption(s,
												compFindOption(option,
															   nOption,
															   name, 0),
												p->vTable->name);
		}
	}

	return status;
}

static Bool dbusInitDisplay(CompPlugin * p, CompDisplay * d)
{
	DbusDisplay *dd;
	DBusError error;
	dbus_bool_t status;
	int fd, ret;

	dd = malloc(sizeof(DbusDisplay));
	if (!dd)
		return FALSE;

	dd->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (dd->screenPrivateIndex < 0)
	{
		free(dd);
		return FALSE;
	}

	dbus_error_init(&error);

	dd->connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
	if (dbus_error_is_set(&error))
	{
		fprintf(stderr, "%s: dbus_bus_get error: %s\n",
				getProgramName(), error.message);

		dbus_error_free(&error);
		free(dd);

		return FALSE;
	}

	ret = dbus_bus_request_name(dd->connection,
								BERYL_DBUS_SERVICE_NAME,
								DBUS_NAME_FLAG_REPLACE_EXISTING |
								DBUS_NAME_FLAG_ALLOW_REPLACEMENT, &error);

	if (dbus_error_is_set(&error))
	{
		fprintf(stderr, "%s: dbus_bus_request_name error: %s\n",
				getProgramName(), error.message);

		/* dbus_connection_unref (dd->connection); */
		dbus_error_free(&error);
		free(dd);

		return FALSE;
	}

	dbus_error_free(&error);

	if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
	{
		fprintf(stderr, "%s: dbus_bus_request_name reply is not "
				"primary owner\n", getProgramName());

		/* dbus_connection_unref (dd->connection); */
		free(dd);

		return FALSE;
	}

	status = dbus_connection_add_filter(dd->connection,
										dbusHandleMessage, d, NULL);
	if (!status)
	{
		fprintf(stderr, "%s: dbus_connection_add_filter failed\n",
				getProgramName());

		/* dbus_connection_unref (dd->connection); */
		free(dd);

		return FALSE;
	}

	status = dbus_connection_get_unix_fd(dd->connection, &fd);
	if (!status)
	{
		fprintf(stderr, "%s: dbus_connection_get_unix_fd failed\n",
				getProgramName());

		/* dbus_connection_unref (dd->connection); */
		free(dd);

		return FALSE;
	}

	dd->watchFdHandle = compAddWatchFd(fd,
									   POLLIN | POLLPRI | POLLHUP | POLLERR,
									   dbusProcessMessages, d);

	WRAP(dd, d, setDisplayOption, dbusSetDisplayOption);
	WRAP(dd, d, setDisplayOptionForPlugin, dbusSetDisplayOptionForPlugin);

	d->privates[displayPrivateIndex].ptr = dd;

	return TRUE;
}

static void dbusFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	DBUS_DISPLAY(d);

	compRemoveWatchFd(dd->watchFdHandle);

	dbus_bus_release_name(dd->connection, BERYL_DBUS_SERVICE_NAME, NULL);

	/*
	   can't unref the connection returned by dbus_bus_get as it's
	   shared and we can't know if it's closed or not.

	   dbus_connection_unref (dd->connection);
	 */

	UNWRAP(dd, d, setDisplayOption);
	UNWRAP(dd, d, setDisplayOptionForPlugin);

	free(dd);
}

static Bool dbusInitScreen(CompPlugin * p, CompScreen * s)
{
	DbusScreen *ds;

	DBUS_DISPLAY(s->display);

	ds = malloc(sizeof(DbusScreen));
	if (!ds)
		return FALSE;

	WRAP(ds, s, setScreenOption, dbusSetScreenOption);
	WRAP(ds, s, setScreenOptionForPlugin, dbusSetScreenOptionForPlugin);

	s->privates[dd->screenPrivateIndex].ptr = ds;

	return TRUE;
}

static void dbusFiniScreen(CompPlugin * p, CompScreen * s)
{
	DBUS_SCREEN(s);

	UNWRAP(ds, s, setScreenOption);
	UNWRAP(ds, s, setScreenOptionForPlugin);

	free(ds);
}

static Bool dbusInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

static void dbusFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

CompPluginVTable dbusVTable = {
	"dbus",
	"Dbus",
	"Dbus Control Backend",
	dbusInit,
	dbusFini,
	dbusInitDisplay,
	dbusFiniDisplay,
	dbusInitScreen,
	dbusFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	0,							/* GetDisplayOptions */
	0,							/* SetDisplayOption */
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	0,							/* Deps */
	0,							/* nDeps */
	0,							/* Features */
	0,							/* nFeatures */
	BERYL_ABI_INFO,
	"beryl-dbus",
	"misc",
	0,
	0,
	True,
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &dbusVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1