Add initial implementation of g_dbus_add_signal_watch

With g_dbus_add_signal_watch there is no need to register multiple filters
for dbus nor add matching rules manually.
diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
index 244f797..170b669 100644
--- a/gdbus/gdbus.h
+++ b/gdbus/gdbus.h
@@ -125,8 +125,10 @@
 				GDBusWatchFunction function,
 				void *user_data, GDBusDestroyFunction destroy);
 guint g_dbus_add_signal_watch(DBusConnection *connection,
-				const char *rule, GDBusSignalFunction function,
-				void *user_data, GDBusDestroyFunction destroy);
+				const char *sender, const char *path,
+				const char *interface, const char *member,
+				GDBusSignalFunction function, void *user_data,
+				GDBusDestroyFunction destroy);
 gboolean g_dbus_remove_watch(DBusConnection *connection, guint tag);
 void g_dbus_remove_all_watches(DBusConnection *connection);
 
diff --git a/gdbus/watch.c b/gdbus/watch.c
index 45dc367..3883f4d 100644
--- a/gdbus/watch.c
+++ b/gdbus/watch.c
@@ -37,168 +37,110 @@
 #define error(fmt...)
 #define debug(fmt...)
 
-static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+static DBusHandlerResult message_filter(DBusConnection *connection,
 					DBusMessage *message, void *user_data);
 
 static guint listener_id = 0;
-static GSList *name_listeners = NULL;
+static GSList *listeners = NULL;
 
-struct name_callback {
+struct filter_callback {
 	GDBusWatchFunction conn_func;
 	GDBusWatchFunction disc_func;
+	GDBusSignalFunction signal_func;
+	GDBusDestroyFunction destroy_func;
 	void *user_data;
 	guint id;
 };
 
-struct name_data {
+struct filter_data {
 	DBusConnection *connection;
-	char *name;
+	DBusHandleMessageFunction handle_func;
+	char *sender;
+	char *path;
+	char *interface;
+	char *member;
+	char *argument;
 	GSList *callbacks;
 	GSList *processed;
 	gboolean lock;
+	gboolean registered;
 };
 
-static struct name_data *name_data_find(DBusConnection *connection,
-							const char *name)
+static struct filter_data *filter_data_find(DBusConnection *connection,
+							const char *sender,
+							const char *path,
+							const char *interface,
+							const char *member,
+							const char *argument)
 {
 	GSList *current;
 
-	for (current = name_listeners;
+	for (current = listeners;
 			current != NULL; current = current->next) {
-		struct name_data *data = current->data;
+		struct filter_data *data = current->data;
 
 		if (connection != data->connection)
 			continue;
 
-		if (name == NULL || g_str_equal(name, data->name))
-			return data;
+		if (sender && data->sender &&
+				g_str_equal(sender, data->sender) == FALSE)
+			continue;
+
+		if (path && data->path &&
+				g_str_equal(path, data->path) == FALSE)
+			continue;
+
+		if (interface && data->interface &&
+				g_str_equal(interface, data->interface) == FALSE)
+			continue;
+
+		if (member && data->member &&
+				g_str_equal(member, data->member) == FALSE)
+			continue;
+
+		if (argument && data->argument &&
+				g_str_equal(argument, data->argument) == FALSE)
+			continue;
+
+		return data;
 	}
 
 	return NULL;
 }
 
-static struct name_callback *name_callback_find(GSList *callbacks, guint id)
+static void format_rule(struct filter_data *data, char *rule, size_t size)
 {
-	GSList *current;
+	int offset;
 
-	for (current = callbacks; current != NULL; current = current->next) {
-		struct name_callback *cb = current->data;
-		if (cb->id == id)
-			return cb;
-	}
+	offset = snprintf(rule, size, "type='signal'");
 
-	return NULL;
+	if (data->sender)
+		offset += snprintf(rule + offset, size - offset,
+				",sender='%s'", data->sender);
+	if (data->path)
+		offset += snprintf(rule + offset, size - offset,
+				",path='%s'", data->path);
+	if (data->interface)
+		offset += snprintf(rule + offset, size - offset,
+				",interface='%s'", data->interface);
+	if (data->member)
+		offset += snprintf(rule + offset, size - offset,
+				",member='%s'", data->member);
+	if (data->argument)
+		snprintf(rule + offset, size - offset,
+				",arg0='%s'", data->argument);
 }
 
-static void name_data_call_and_free(struct name_data *data)
-{
-	GSList *l;
-
-	for (l = data->callbacks; l != NULL; l = l->next) {
-		struct name_callback *cb = l->data;
-		if (cb->disc_func)
-			cb->disc_func(data->connection, cb->user_data);
-		g_free(cb);
-	}
-
-	g_slist_free(data->callbacks);
-	g_free(data->name);
-	g_free(data);
-}
-
-static void name_data_free(struct name_data *data)
-{
-	GSList *l;
-
-	for (l = data->callbacks; l != NULL; l = l->next)
-		g_free(l->data);
-
-	g_slist_free(data->callbacks);
-	g_free(data->name);
-	g_free(data);
-}
-
-static int name_data_add(DBusConnection *connection, const char *name,
-						GDBusWatchFunction connect,
-						GDBusWatchFunction disconnect,
-						void *user_data, guint id)
-{
-	int first = 1;
-	struct name_data *data = NULL;
-	struct name_callback *cb = NULL;
-
-	cb = g_new(struct name_callback, 1);
-
-	cb->conn_func = connect;
-	cb->disc_func = disconnect;
-	cb->user_data = user_data;
-	cb->id = id;
-
-	data = name_data_find(connection, name);
-	if (data) {
-		first = 0;
-		goto done;
-	}
-
-	data = g_new0(struct name_data, 1);
-
-	data->connection = connection;
-	data->name = g_strdup(name);
-
-	name_listeners = g_slist_append(name_listeners, data);
-
-done:
-	if (data->lock)
-		data->processed = g_slist_append(data->processed, cb);
-	else
-		data->callbacks = g_slist_append(data->callbacks, cb);
-
-	return first;
-}
-
-static void name_data_remove(DBusConnection *connection,
-					const char *name, guint id)
-{
-	struct name_data *data;
-	struct name_callback *cb = NULL;
-
-	data = name_data_find(connection, name);
-	if (!data)
-		return;
-
-	cb = name_callback_find(data->callbacks, id);
-	if (cb) {
-		data->callbacks = g_slist_remove(data->callbacks, cb);
-		g_free(cb);
-	}
-
-	if (data->callbacks)
-		return;
-
-	name_listeners = g_slist_remove(name_listeners, data);
-	name_data_free(data);
-
-	/* Remove filter if there are no listeners left for the connection */
-	data = name_data_find(connection, NULL);
-	if (!data)
-		dbus_connection_remove_filter(connection,
-						name_exit_filter,
-						NULL);
-}
-
-static gboolean add_match(DBusConnection *connection, const char *name)
+static gboolean add_match(struct filter_data *data,
+				DBusHandleMessageFunction filter)
 {
 	DBusError err;
-	char match_string[128];
+	char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
 
-	snprintf(match_string, sizeof(match_string),
-			"interface=%s,member=NameOwnerChanged,arg0=%s",
-			DBUS_INTERFACE_DBUS, name);
-
+	format_rule(data, rule, sizeof(rule));
 	dbus_error_init(&err);
 
-	dbus_bus_add_match(connection, match_string, &err);
-
+	dbus_bus_add_match(data->connection, rule, &err);
 	if (dbus_error_is_set(&err)) {
 		error("Adding match rule \"%s\" failed: %s", match_string,
 				err.message);
@@ -206,22 +148,22 @@
 		return FALSE;
 	}
 
+	data->handle_func = filter;
+	data->registered = TRUE;
+
 	return TRUE;
 }
 
-static gboolean remove_match(DBusConnection *connection, const char *name)
+static gboolean remove_match(struct filter_data *data)
 {
 	DBusError err;
-	char match_string[128];
+	char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
 
-	snprintf(match_string, sizeof(match_string),
-			"interface=%s,member=NameOwnerChanged,arg0=%s",
-			DBUS_INTERFACE_DBUS, name);
+	format_rule(data, rule, sizeof(rule));
 
 	dbus_error_init(&err);
 
-	dbus_bus_remove_match(connection, match_string, &err);
-
+	dbus_bus_remove_match(data->connection, rule, &err);
 	if (dbus_error_is_set(&err)) {
 		error("Removing owner match rule for %s failed: %s",
 				name, err.message);
@@ -232,16 +174,193 @@
 	return TRUE;
 }
 
-static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+static struct filter_data *filter_data_get(DBusConnection *connection,
+					DBusHandleMessageFunction filter,
+					const char *sender,
+					const char *path,
+					const char *interface,
+					const char *member,
+					const char *argument)
+{
+	struct filter_data *data;
+
+	if (!filter_data_find(connection, NULL, NULL, NULL, NULL, NULL)) {
+		if (!dbus_connection_add_filter(connection,
+					message_filter, NULL, NULL)) {
+			error("dbus_connection_add_filter() failed");
+			return NULL;
+		}
+	}
+
+	data = filter_data_find(connection, sender, path, interface, member,
+					argument);
+	if (data)
+		return data;
+
+	data = g_new0(struct filter_data, 1);
+
+	data->connection = dbus_connection_ref(connection);
+	data->sender = g_strdup(sender);
+	data->path = g_strdup(path);
+	data->interface = g_strdup(interface);
+	data->member = g_strdup(member);
+	data->argument = g_strdup(argument);
+
+	if (!add_match(data, filter)) {
+		g_free(data);
+		return NULL;
+	}
+
+	listeners = g_slist_append(listeners, data);
+
+	return data;
+}
+
+static struct filter_callback *filter_data_find_callback(
+						struct filter_data *data,
+						guint id)
+{
+	GSList *l;
+
+	for (l = data->callbacks; l; l = l->next) {
+		struct filter_callback *cb = l->data;
+		if (cb->id == id)
+			return cb;
+	}
+	for (l = data->processed; l; l = l->next) {
+		struct filter_callback *cb = l->data;
+		if (cb->id == id)
+			return cb;
+	}
+
+	return NULL;
+}
+
+static void filter_data_free(struct filter_data *data)
+{
+	GSList *l;
+
+	for (l = data->callbacks; l != NULL; l = l->next)
+		g_free(l->data);
+
+	g_slist_free(data->callbacks);
+	g_free(data->sender);
+	g_free(data->path);
+	g_free(data->interface);
+	g_free(data->member);
+	g_free(data->argument);
+	dbus_connection_unref(data->connection);
+	g_free(data);
+}
+
+static void filter_data_call_and_free(struct filter_data *data)
+{
+	GSList *l;
+
+	for (l = data->callbacks; l != NULL; l = l->next) {
+		struct filter_callback *cb = l->data;
+		if (cb->disc_func)
+			cb->disc_func(data->connection, cb->user_data);
+		if (cb->destroy_func)
+			cb->destroy_func(cb->user_data);
+		g_free(cb);
+	}
+
+	filter_data_free(data);
+}
+
+static struct filter_callback *filter_data_add_callback(
+						struct filter_data *data,
+						GDBusWatchFunction connect,
+						GDBusWatchFunction disconnect,
+						GDBusSignalFunction signal,
+						GDBusDestroyFunction destroy,
+						void *user_data)
+{
+	struct filter_callback *cb = NULL;
+
+	cb = g_new(struct filter_callback, 1);
+
+	cb->conn_func = connect;
+	cb->disc_func = disconnect;
+	cb->signal_func = signal;
+	cb->destroy_func = destroy;
+	cb->user_data = user_data;
+	cb->id = ++listener_id;
+
+	if (data->lock)
+		data->processed = g_slist_append(data->processed, cb);
+	else
+		data->callbacks = g_slist_append(data->callbacks, cb);
+
+	return cb;
+}
+
+static gboolean filter_data_remove_callback(struct filter_data *data,
+						struct filter_callback *cb)
+{
+	data->callbacks = g_slist_remove(data->callbacks, cb);
+	data->processed = g_slist_remove(data->processed, cb);
+
+	if (cb->destroy_func)
+		cb->destroy_func(cb->user_data);
+
+	g_free(cb);
+
+	/* Don't remove the filter if other callbacks exist or data is lock
+	 * processing callbacks */
+	if (data->callbacks || data->lock)
+		return TRUE;
+
+	if (data->registered && !remove_match(data))
+		return FALSE;
+
+	/* Remove filter if there are no listeners left for the connection */
+	data = filter_data_find(data->connection, NULL, NULL, NULL, NULL,
+					NULL);
+	if (!data)
+		dbus_connection_remove_filter(data->connection, message_filter,
+						NULL);
+
+	listeners = g_slist_remove(listeners, data);
+	filter_data_free(data);
+
+	return TRUE;
+}
+
+static DBusHandlerResult signal_filter(DBusConnection *connection,
 					DBusMessage *message, void *user_data)
 {
-	struct name_data *data;
-	struct name_callback *cb;
-	char *name, *old, *new;
+	struct filter_data *data = user_data;
+	struct filter_callback *cb;
 
-	if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
-							"NameOwnerChanged"))
-		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	while (data->callbacks) {
+		cb = data->callbacks->data;
+
+		if (cb->signal_func && !cb->signal_func(connection, message,
+							cb->user_data)) {
+			filter_data_remove_callback(data, cb);
+			continue;
+		}
+
+		/* Check if the watch was removed/freed by the callback
+		 * function */
+		if (!g_slist_find(data->callbacks, cb))
+			continue;
+
+		data->callbacks = g_slist_remove(data->callbacks, cb);
+		data->processed = g_slist_append(data->processed, cb);
+	}
+
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult service_filter(DBusConnection *connection,
+					DBusMessage *message, void *user_data)
+{
+	struct filter_data *data = user_data;
+	struct filter_callback *cb;
+	char *name, *old, *new;
 
 	if (!dbus_message_get_args(message, NULL,
 				DBUS_TYPE_STRING, &name,
@@ -252,14 +371,6 @@
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
 
-	data = name_data_find(connection, name);
-	if (!data) {
-		error("Got NameOwnerChanged signal for %s which has no listeners", name);
-		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-	}
-
-	data->lock = TRUE;
-
 	while (data->callbacks) {
 		cb = data->callbacks->data;
 
@@ -286,24 +397,56 @@
 		data->processed = g_slist_append(data->processed, cb);
 	}
 
-	data->callbacks = data->processed;
-	data->processed = NULL;
-	data->lock = FALSE;
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult message_filter(DBusConnection *connection,
+					DBusMessage *message, void *user_data)
+{
+	struct filter_data *data;
+	const char *sender, *path, *iface, *member, *arg = NULL;
+
+	/* Only filter signals */
+	if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	sender = dbus_message_get_sender(message);
+	path = dbus_message_get_path(message);
+	iface = dbus_message_get_interface(message);
+	member = dbus_message_get_member(message);
+	dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID);
+
+	data = filter_data_find(connection, sender, path, iface, member, arg);
+	if (!data) {
+		error("Got %s.%s signal which has no listeners", iface, member);
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	if (data->handle_func) {
+		data->lock = TRUE;
+
+		data->handle_func(connection, message, data);
+
+		data->callbacks = data->processed;
+		data->processed = NULL;
+		data->lock = FALSE;
+	}
 
 	if (data->callbacks)
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
-	name_listeners = g_slist_remove(name_listeners, data);
-	name_data_free(data);
+	remove_match(data);
+
+	listeners = g_slist_remove(listeners, data);
+	filter_data_free(data);
 
 	/* Remove filter if there no listener left for the connection */
-	data = name_data_find(connection, NULL);
+	data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL);
 	if (!data)
-		dbus_connection_remove_filter(connection, name_exit_filter,
+		dbus_connection_remove_filter(connection, message_filter,
 						NULL);
 
-	remove_match(connection, name);
-
 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
@@ -397,38 +540,27 @@
 				GDBusWatchFunction disconnect,
 				void *user_data, GDBusDestroyFunction destroy)
 {
-	int first;
+	struct filter_data *data;
+	struct filter_callback *cb;
 
-	if (!name_data_find(connection, NULL)) {
-		if (!dbus_connection_add_filter(connection,
-					name_exit_filter, NULL, NULL)) {
-			error("dbus_connection_add_filter() failed");
-			return 0;
-		}
-	}
+	if (!name)
+		return 0;
 
-	listener_id++;
-	first = name_data_add(connection, name, connect, disconnect,
-						user_data, listener_id);
-	/* The filter is already added if this is not the first callback
-	 * registration for the name */
-	if (!first)
-		goto done;
+	data = filter_data_get(connection, service_filter, NULL, NULL,
+				DBUS_INTERFACE_DBUS, "NameOwnerChanged",
+				name);
+	if (!data)
+		return 0;
 
-	if (name) {
-		debug("name_listener_add(%s)", name);
+	cb = filter_data_add_callback(data, connect, disconnect, NULL, NULL,
+					user_data);
+	if (!cb)
+		return 0;
 
-		if (!add_match(connection, name)) {
-			name_data_remove(connection, name, listener_id);
-			return 0;
-		}
-	}
-
-done:
 	if (connect)
 		check_service(connection, name, connect, user_data);
 
-	return listener_id;
+	return cb->id;
 }
 
 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
@@ -440,72 +572,57 @@
 }
 
 guint g_dbus_add_signal_watch(DBusConnection *connection,
-				const char *rule, GDBusSignalFunction function,
-				void *user_data, GDBusDestroyFunction destroy)
+				const char *sender, const char *path,
+				const char *interface, const char *member,
+				GDBusSignalFunction function, void *user_data,
+				GDBusDestroyFunction destroy)
 {
-	return 0;
+	struct filter_data *data;
+	struct filter_callback *cb;
+
+	data = filter_data_get(connection, signal_filter, sender, path,
+				interface, member, NULL);
+	if (!data)
+		return 0;
+
+	cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
+					user_data);
+	if (!cb)
+		return 0;
+
+	return cb->id;
 }
 
 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
 {
-	struct name_data *data;
-	struct name_callback *cb;
-	GSList *ldata, *lcb;
+	struct filter_data *data;
+	struct filter_callback *cb;
+	GSList *ldata;
 
 	if (id == 0)
 		return FALSE;
 
-	for (ldata = name_listeners; ldata; ldata = ldata->next) {
+	for (ldata = listeners; ldata; ldata = ldata->next) {
 		data = ldata->data;
-		for (lcb = data->callbacks; lcb; lcb = lcb->next) {
-			cb = lcb->data;
-			if (cb->id == id)
-				goto remove;
-		}
-		for (lcb = data->processed; lcb; lcb = lcb->next) {
-			cb = lcb->data;
-			if (cb->id == id)
-				goto remove;
+
+		cb = filter_data_find_callback(data, id);
+		if (cb) {
+			filter_data_remove_callback(data, cb);
+			return TRUE;
 		}
 	}
 
 	return FALSE;
-
-remove:
-	data->callbacks = g_slist_remove(data->callbacks, cb);
-	data->processed = g_slist_remove(data->processed, cb);
-	g_free(cb);
-
-	/* Don't remove the filter if other callbacks exist or data is lock
-	 * processing callbacks */
-	if (data->callbacks || data->lock)
-		return TRUE;
-
-	if (data->name) {
-		if (!remove_match(data->connection, data->name))
-			return FALSE;
-	}
-
-	name_listeners = g_slist_remove(name_listeners, data);
-	name_data_free(data);
-
-	/* Remove filter if there are no listeners left for the connection */
-	data = name_data_find(connection, NULL);
-	if (!data)
-		dbus_connection_remove_filter(connection, name_exit_filter,
-						NULL);
-
-	return TRUE;
 }
 
 void g_dbus_remove_all_watches(DBusConnection *connection)
 {
-	struct name_data *data;
+	struct filter_data *data;
 
-	while ((data = name_data_find(connection, NULL))) {
-		name_listeners = g_slist_remove(name_listeners, data);
-		name_data_call_and_free(data);
+	while ((data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL))) {
+		listeners = g_slist_remove(listeners, data);
+		filter_data_call_and_free(data);
 	}
 
-	dbus_connection_remove_filter(connection, name_exit_filter, NULL);
+	dbus_connection_remove_filter(connection, message_filter, NULL);
 }