diff options
author | Ted Gould <ted@gould.cx> | 2012-04-09 09:15:17 -0500 |
---|---|---|
committer | Ted Gould <ted@gould.cx> | 2012-04-09 09:15:17 -0500 |
commit | 475110aa2fe3a47cf6331fbd5941f34a9461fd81 (patch) | |
tree | bf2318e8b94cf0b74ea17b496e9d5331a3c065ec /libdbusmenu-glib | |
parent | e0eb750e8c36a4116247ea80b4d055a9f821459a (diff) | |
parent | 2d60549a2394ce7d73907abcaca22b48553d6c5b (diff) | |
download | libdbusmenu-475110aa2fe3a47cf6331fbd5941f34a9461fd81.tar.gz libdbusmenu-475110aa2fe3a47cf6331fbd5941f34a9461fd81.tar.bz2 libdbusmenu-475110aa2fe3a47cf6331fbd5941f34a9461fd81.zip |
Catching up to trunk
Diffstat (limited to 'libdbusmenu-glib')
-rw-r--r-- | libdbusmenu-glib/Makefile.am | 10 | ||||
-rw-r--r-- | libdbusmenu-glib/client.c | 573 | ||||
-rw-r--r-- | libdbusmenu-glib/client.h | 6 | ||||
-rw-r--r-- | libdbusmenu-glib/dbus-menu.xml | 46 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.c | 6 | ||||
-rw-r--r-- | libdbusmenu-glib/server.c | 251 |
6 files changed, 788 insertions, 104 deletions
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am index 6fc3fb8..b2e796c 100644 --- a/libdbusmenu-glib/Makefile.am +++ b/libdbusmenu-glib/Makefile.am @@ -6,6 +6,7 @@ EXTRA_DIST = \ clean-namespaces.xslt \ dbusmenu-glib-0.4.pc.in \ dbus-menu.xml \ + dbus-menu-clean.xml \ client-marshal.list \ menuitem-marshal.list \ server-marshal.list @@ -57,12 +58,14 @@ libdbusmenu_glib_la_SOURCES = \ client.c libdbusmenu_glib_la_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ -version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \ -no-undefined \ -export-symbols-regex "^[^_].*" libdbusmenu_glib_la_CFLAGS = \ $(DBUSMENUGLIB_CFLAGS) \ + $(COVERAGE_CFLAGS) \ -Wall -Werror -Wno-error=deprecated-declarations \ -DG_LOG_DOMAIN="\"LIBDBUSMENU-GLIB\"" @@ -180,7 +183,12 @@ introspection_sources = \ Dbusmenu-0.4.gir: libdbusmenu-glib.la Dbusmenu_0_4_gir_INCLUDES = \ GObject-2.0 -Dbusmenu_0_4_gir_CFLAGS = $(DBUSMENUGLIB_CFLAGS) -I$(top_srcdir) +Dbusmenu_0_4_gir_CFLAGS = \ + $(DBUSMENUGLIB_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + -I$(top_srcdir) +Dbusmenu_0_4_gir_LDFLAGS = \ + $(COVERAGE_LDFLAGS) Dbusmenu_0_4_gir_LIBS = libdbusmenu-glib.la Dbusmenu_0_4_gir_FILES = $(introspection_sources) Dbusmenu_0_4_gir_NAMESPACE = Dbusmenu diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c index e64d923..34f8b8d 100644 --- a/libdbusmenu-glib/client.c +++ b/libdbusmenu-glib/client.c @@ -52,7 +52,8 @@ enum { PROP_DBUSOBJECT, PROP_DBUSNAME, PROP_STATUS, - PROP_TEXT_DIRECTION + PROP_TEXT_DIRECTION, + PROP_GROUP_EVENTS }; /* Signals */ @@ -66,6 +67,12 @@ enum { LAST_SIGNAL }; +/* Errors */ +enum { + ERROR_DISPOSAL, + ERROR_ID_NOT_FOUND +}; + typedef void (*properties_func) (GVariant * properties, GError * error, gpointer user_data); static guint signals[LAST_SIGNAL] = { 0 }; @@ -100,6 +107,13 @@ struct _DbusmenuClientPrivate DbusmenuTextDirection text_direction; DbusmenuStatus status; GStrv icon_dirs; + + gboolean group_events; + guint event_idle; + GQueue * events_to_go; /* type: event_data_t * */ + + guint about_to_show_idle; + GQueue * about_to_show_to_go; /* type: about_to_show_t * */ }; typedef struct _newItemPropData newItemPropData; @@ -120,6 +134,7 @@ struct _properties_listener_t { typedef struct _event_data_t event_data_t; struct _event_data_t { + gint id; DbusmenuClient * client; DbusmenuMenuitem * menuitem; gchar * event; @@ -171,6 +186,8 @@ static void menuproxy_prop_changed_cb (GDBusProxy * proxy, GVariant * properties static void menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data); static void menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data); static void type_handler_destroy (gpointer user_data); +static void event_data_end (event_data_t * eventd, GError * error); +static void about_to_show_finish_pntr (gpointer data, gpointer user_data); /* Globals */ static GDBusNodeInfo * dbusmenu_node_info = NULL; @@ -309,6 +326,10 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass) "Signals which direction the default text direction is for the menus", DBUSMENU_TYPE_TEXT_DIRECTION, DBUSMENU_TEXT_DIRECTION_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_GROUP_EVENTS, + g_param_spec_boolean(DBUSMENU_CLIENT_PROP_GROUP_EVENTS, "Whether or not multiple events should be grouped", + "Event grouping lowers the number of messages on DBus and will be set automatically based on the version to optimize traffic. It can be disabled for testing or other purposes.", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); if (dbusmenu_node_info == NULL) { GError * error = NULL; @@ -380,6 +401,13 @@ dbusmenu_client_init (DbusmenuClient *self) priv->status = DBUSMENU_STATUS_NORMAL; priv->icon_dirs = NULL; + priv->group_events = FALSE; + priv->event_idle = 0; + priv->events_to_go = NULL; + + priv->about_to_show_idle = 0; + priv->about_to_show_to_go = NULL; + return; } @@ -393,6 +421,32 @@ dbusmenu_client_dispose (GObject *object) priv->delayed_idle = 0; } + if (priv->event_idle != 0) { + g_source_remove(priv->event_idle); + priv->event_idle = 0; + } + + if (priv->about_to_show_idle != 0) { + g_source_remove(priv->about_to_show_idle); + priv->about_to_show_idle = 0; + } + + if (priv->events_to_go != NULL) { + g_warning("Getting to client dispose with events pending. This is odd. Probably there's a ref count problem somewhere, but we're going to be cool about it now and clean up. But there's probably a bug."); + GError * error = g_error_new_literal(error_domain(), ERROR_DISPOSAL, "Client disposed before event signal returned"); + g_queue_foreach(priv->events_to_go, (GFunc)event_data_end, error); + g_queue_free(priv->events_to_go); + priv->events_to_go = NULL; + g_error_free(error); + } + + if (priv->about_to_show_to_go != NULL) { + g_warning("Getting to client dispose with about_to_show's pending. This is odd. Probably there's a ref count problem somewhere, but we're going to be cool about it now and clean up. But there's probably a bug."); + g_queue_foreach(priv->about_to_show_to_go, about_to_show_finish_pntr, GINT_TO_POINTER(FALSE)); + g_queue_free(priv->about_to_show_to_go); + priv->about_to_show_to_go = NULL; + } + /* Only used for queueing up a new command, so we can just drop this array. */ if (priv->delayed_property_list != NULL) { @@ -517,6 +571,9 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) build_proxies(DBUSMENU_CLIENT(obj)); } break; + case PROP_GROUP_EVENTS: + priv->group_events = g_value_get_boolean(value); + break; default: g_warning("Unknown property %d.", id); return; @@ -543,6 +600,9 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) case PROP_TEXT_DIRECTION: g_value_set_enum(value, priv->text_direction); break; + case PROP_GROUP_EVENTS: + g_value_set_boolean(value, priv->group_events); + break; default: g_warning("Unknown property %d.", id); return; @@ -601,64 +661,66 @@ get_properties_callback (GObject *obj, GAsyncResult * res, gpointer user_data) listener->callback(NULL, error, listener->user_data); } g_error_free(error); - goto out; } /* Callback all the folks we can find */ - GVariant * parent = g_variant_get_child_value(params, 0); - GVariantIter iter; - g_variant_iter_init(&iter, parent); - GVariant * child; - while ((child = g_variant_iter_next_value(&iter)) != NULL) { - if (g_strcmp0(g_variant_get_type_string(child), "(ia{sv})") != 0) { - g_warning("Properties return signature is not '(ia{sv})' it is '%s'", g_variant_get_type_string(child)); - g_variant_unref(child); - continue; - } + if (error == NULL) { + GVariant * parent = g_variant_get_child_value(params, 0); + GVariantIter iter; + g_variant_iter_init(&iter, parent); + GVariant * child; + while ((child = g_variant_iter_next_value(&iter)) != NULL) { + if (g_strcmp0(g_variant_get_type_string(child), "(ia{sv})") != 0) { + g_warning("Properties return signature is not '(ia{sv})' it is '%s'", g_variant_get_type_string(child)); + g_variant_unref(child); + continue; + } - GVariant * idv = g_variant_get_child_value(child, 0); - gint id = g_variant_get_int32(idv); - g_variant_unref(idv); + GVariant * idv = g_variant_get_child_value(child, 0); + gint id = g_variant_get_int32(idv); + g_variant_unref(idv); - GVariant * properties = g_variant_get_child_value(child, 1); + GVariant * properties = g_variant_get_child_value(child, 1); - properties_listener_t * listener = find_listener(listeners, 0, id); - if (listener == NULL) { - g_warning("Unable to find listener for ID %d", id); + properties_listener_t * listener = find_listener(listeners, 0, id); + if (listener == NULL) { + g_warning("Unable to find listener for ID %d", id); + g_variant_unref(properties); + g_variant_unref(child); + continue; + } + + if (!listener->replied) { + listener->callback(properties, NULL, listener->user_data); + listener->replied = TRUE; + } else { + g_warning("Odd, we've already replied to the listener on ID %d", id); + } g_variant_unref(properties); g_variant_unref(child); - continue; } - - if (!listener->replied) { - listener->callback(properties, NULL, listener->user_data); - listener->replied = TRUE; - } else { - g_warning("Odd, we've already replied to the listener on ID %d", id); - } - g_variant_unref(properties); - g_variant_unref(child); + g_variant_unref(parent); + g_variant_unref(params); } - g_variant_unref(parent); - g_variant_unref(params); /* Provide errors for those who we can't */ - GError * localerror = NULL; - for (i = 0; i < listeners->len; i++) { - properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i); - if (!listener->replied) { - g_warning("Generating properties error for: %d", listener->id); - if (localerror == NULL) { - g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID"); + if (error == NULL && listeners->len > 0) { + GError * localerror = NULL; + for (i = 0; i < listeners->len; i++) { + properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i); + if (!listener->replied) { + g_debug("Generating properties error for: %d", listener->id); + if (localerror == NULL) { + g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID"); + } + listener->callback(NULL, localerror, listener->user_data); } - listener->callback(NULL, localerror, listener->user_data); } - } - if (localerror != NULL) { - g_error_free(localerror); + if (localerror != NULL) { + g_error_free(localerror); + } } -out: /* Clean up */ g_array_free(listeners, TRUE); g_object_unref(cbdata->client); @@ -1084,6 +1146,7 @@ menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data) g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_TEXT_DIRECTION); g_variant_unref(textdir); + textdir = NULL; } /* Check the status if available */ @@ -1099,6 +1162,7 @@ menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data) g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_STATUS); g_variant_unref(status); + status = NULL; } /* Get the icon theme directories if available */ @@ -1113,6 +1177,33 @@ menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data) g_signal_emit(G_OBJECT(client), signals[ICON_THEME_DIRS], 0, priv->icon_dirs, TRUE); g_variant_unref(icon_dirs); + icon_dirs = NULL; + } + + /* Get the dbusmenu protocol version if available */ + GVariant * version = g_dbus_proxy_get_cached_property(priv->menuproxy, "Version"); + if (version != NULL) { + guint32 remote_version = 0; + + if (g_variant_is_of_type(version, G_VARIANT_TYPE_UINT32)) { + remote_version = g_variant_get_uint32(version); + } + + gboolean old_group = priv->group_events; + /* Figure out if we can group the events or not */ + if (remote_version >= 3) { + priv->group_events = TRUE; + } else { + priv->group_events = FALSE; + } + + /* Notify listeners if we changed the value */ + if (old_group != priv->group_events) { + g_object_notify(G_OBJECT(client), DBUSMENU_CLIENT_PROP_GROUP_EVENTS); + } + + g_variant_unref(version); + version = NULL; } /* If we get here, we don't need the DBus proxy */ @@ -1194,6 +1285,19 @@ menuproxy_prop_changed_cb (GDBusProxy * proxy, GVariant * properties, GStrv inva priv->icon_dirs = g_variant_dup_strv(value, NULL); dirs_changed = TRUE; } + if (g_strcmp0(key, "Version") == 0) { + guint32 remote_version = 0; + + if (g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) { + remote_version = g_variant_get_uint32(value); + } + + if (remote_version >= 3) { + priv->group_events = TRUE; + } else { + priv->group_events = FALSE; + } + } } if (olddir != priv->text_direction) { @@ -1439,7 +1543,7 @@ menuitem_get_properties_new_cb (GVariant * properties, GError * error, gpointer newItemPropData * propdata = (newItemPropData *)data; if (error != NULL) { - g_warning("Error getting properties on a new menuitem: %s", error->message); + g_debug("Error getting properties on a new menuitem: %s", error->message); goto out; } @@ -1486,6 +1590,22 @@ out: return; } +/* A function to work with an event_data_t and make sure it gets + free'd and in a terminal state. */ +static void +event_data_end (event_data_t * edata, GError * error) +{ + g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, edata->variant, edata->timestamp, error, TRUE); + + g_variant_unref(edata->variant); + g_free(edata->event); + g_object_unref(edata->menuitem); + g_object_unref(edata->client); + g_free(edata); + + return; +} + /* Respond to the call function to make sure that the other side got it, or print a warning. */ static void @@ -1501,13 +1621,7 @@ menuitem_call_cb (GObject * proxy, GAsyncResult * res, gpointer userdata) g_warning("Unable to call event '%s' on menu item %d: %s", edata->event, dbusmenu_menuitem_get_id(edata->menuitem), error->message); } - g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, edata->variant, edata->timestamp, error, TRUE); - - g_variant_unref(edata->variant); - g_free(edata->event); - g_object_unref(edata->menuitem); - g_object_unref(edata->client); - g_free(edata); + event_data_end(edata, error); if (G_UNLIKELY(error != NULL)) { g_error_free(error); @@ -1519,6 +1633,128 @@ menuitem_call_cb (GObject * proxy, GAsyncResult * res, gpointer userdata) return; } +/* Looks at event_data_t structs to match an ID */ +gint +event_data_find (gconstpointer data, gconstpointer user_data) +{ + event_data_t * edata = (event_data_t *)data; + gint id = GPOINTER_TO_INT(user_data); + + if (edata->id == id) { + return 0; + } else { + return -1; + } +} + +/* The callback from the dbus message to pass events to the + to the server en masse */ +static void +event_group_cb (GObject * proxy, GAsyncResult * res, gpointer user_data) +{ + GQueue * events = (GQueue *)user_data; + + GError * error = NULL; + GVariant * params; + params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error); + + if (error != NULL) { + /* If we got an actual DBus error, we should just pass that + along and finish up */ + g_queue_foreach(events, (GFunc)event_data_end, error); + g_queue_free(events); + events = NULL; + return; + } + + gint id = 0; + GVariant * array = g_variant_get_child_value(params, 0); + GVariantIter iter; + g_variant_iter_init(&iter, array); + + while (g_variant_iter_loop(&iter, "i", &id)) { + GList * item = g_queue_find_custom(events, GINT_TO_POINTER(id), event_data_find); + + if (item != NULL) { + GError * iderror = g_error_new(error_domain(), ERROR_ID_NOT_FOUND, "Unable to find ID: %d", id); + event_data_end((event_data_t *)item->data, iderror); + g_queue_delete_link(events, item); + g_error_free(iderror); + } + } + + g_variant_unref(array); + g_variant_unref(params); + + /* If we have any left send non-error responses */ + g_queue_foreach(events, (GFunc)event_data_end, NULL); + g_queue_free(events); + return; +} + +/* Turn an event structure into the variant builder form */ +static void +events_to_builder (gpointer data, gpointer user_data) +{ + event_data_t * edata = (event_data_t *)data; + GVariantBuilder * builder = (GVariantBuilder *)user_data; + + GVariantBuilder tuple; + g_variant_builder_init(&tuple, G_VARIANT_TYPE_TUPLE); + + g_variant_builder_add_value(&tuple, g_variant_new_int32(edata->id)); + g_variant_builder_add_value(&tuple, g_variant_new_string(edata->event)); + g_variant_builder_add_value(&tuple, g_variant_new_variant(edata->variant)); + g_variant_builder_add_value(&tuple, g_variant_new_uint32(edata->timestamp)); + + GVariant * vtuple = g_variant_builder_end(&tuple); + g_variant_builder_add_value(builder, vtuple); + return; +} + +/* Group all the events into a single Dbus message and send + that out */ +static gboolean +event_idle_cb (gpointer user_data) +{ + g_return_val_if_fail(DBUSMENU_IS_CLIENT(user_data), FALSE); + DbusmenuClient * client = DBUSMENU_CLIENT(user_data); + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data); + + /* We use prepend for speed, but now we want to have them + in the order they were called incase that matters. */ + GQueue * levents = priv->events_to_go; + priv->events_to_go = NULL; + priv->event_idle = 0; + + GVariantBuilder array; + g_variant_builder_init(&array, G_VARIANT_TYPE("a(isvu)")); + g_queue_foreach(levents, events_to_builder, &array); + GVariant * vevents = g_variant_builder_end(&array); + + if (g_signal_has_handler_pending (client, signals[EVENT_RESULT], 0, TRUE)) { + g_dbus_proxy_call(priv->menuproxy, + "EventGroup", + g_variant_new_tuple(&vevents, 1), + G_DBUS_CALL_FLAGS_NONE, + 1000, /* timeout */ + NULL, /* cancellable */ + event_group_cb, levents); + } else { + g_dbus_proxy_call(priv->menuproxy, + "EventGroup", + g_variant_new_tuple(&vevents, 1), + G_DBUS_CALL_FLAGS_NONE, + 1000, /* timeout */ + NULL, /* cancellable */ + NULL, NULL); + g_queue_foreach(levents, (GFunc)event_data_end, NULL); + g_queue_free(levents); + } + + return FALSE; +} + /* Sends the event over DBus to the server on the other side of the bus. */ void @@ -1538,7 +1774,20 @@ dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name variant = g_variant_new_int32(0); } + /* Don't bother with the reply handling if nobody is watching... */ + if (!priv->group_events && !g_signal_has_handler_pending (client, signals[EVENT_RESULT], 0, TRUE)) { + g_dbus_proxy_call(priv->menuproxy, + "Event", + g_variant_new("(isvu)", id, name, variant, timestamp), + G_DBUS_CALL_FLAGS_NONE, + 1000, /* timeout */ + NULL, /* cancellable */ + NULL, NULL); + return; + } + event_data_t * edata = g_new0(event_data_t, 1); + edata->id = id; edata->client = client; g_object_ref(client); edata->menuitem = mi; @@ -1548,25 +1797,181 @@ dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name edata->variant = variant; g_variant_ref_sink(variant); - g_dbus_proxy_call(priv->menuproxy, - "Event", - g_variant_new("(isvu)", id, name, variant, timestamp), - G_DBUS_CALL_FLAGS_NONE, - 1000, /* timeout */ - NULL, /* cancellable */ - menuitem_call_cb, - edata); + if (!priv->group_events) { + g_dbus_proxy_call(priv->menuproxy, + "Event", + g_variant_new("(isvu)", id, name, variant, timestamp), + G_DBUS_CALL_FLAGS_NONE, + 1000, /* timeout */ + NULL, /* cancellable */ + menuitem_call_cb, + edata); + } else { + if (priv->events_to_go == NULL) { + priv->events_to_go = g_queue_new(); + } + + g_queue_push_tail(priv->events_to_go, edata); + + if (priv->event_idle == 0) { + priv->event_idle = g_idle_add(event_idle_cb, client); + } + } return; } typedef struct _about_to_show_t about_to_show_t; struct _about_to_show_t { + gint id; DbusmenuClient * client; void (*cb) (gpointer data); gpointer cb_data; }; +/* Takes an about_to_show_t structure and calls the callback correctly + and updates the layout if needed. */ +static void +about_to_show_finish (about_to_show_t * data, gboolean need_update) +{ + /* If we need to update, do that first. */ + if (need_update) { + update_layout(data->client); + } + + if (data->cb != NULL) { + data->cb(data->cb_data); + } + + g_object_unref(data->client); + g_free(data); + + return; +} + +/* A little function to match prototypes and make sure to convert from + a pointer to an int correctly */ +static void +about_to_show_finish_pntr (gpointer data, gpointer user_data) +{ + return about_to_show_finish((about_to_show_t *)data, GPOINTER_TO_INT(user_data)); +} + +/* Respond to the DBus message from sending a bunch of about-to-show events + to the server */ +static void +about_to_show_group_cb (GObject * proxy, GAsyncResult * res, gpointer userdata) +{ + GError * error = NULL; + GQueue * showers = (GQueue *)userdata; + GVariant * params = NULL; + + params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error); + + if (error != NULL) { + g_warning("Unable to send about_to_show_group: %s", error->message); + /* Note: we're just ensuring only the callback gets called */ + g_error_free(error); + error = NULL; + } else { + GVariant * updates = g_variant_get_child_value(params, 0); + GVariantIter iter; + + /* Okay, so this is kinda interesting. We actually don't care which + entries asked us to update the structure, as it's quite simply a + single structure. So if we have any ask, we get the update once to + avoid itterating through all the structures. */ + if (g_variant_iter_init(&iter, updates) > 0) { + about_to_show_t * first = (about_to_show_t *)g_queue_peek_head(showers); + update_layout(first->client); + } + + g_variant_unref(updates); + g_variant_unref(params); + params = NULL; + } + + g_queue_foreach(showers, about_to_show_finish_pntr, GINT_TO_POINTER(FALSE)); + g_queue_free(showers); + + return; +} + +/* Check to see if this about to show entry has a callback associated + with it */ +static void +about_to_show_idle_callbacks (gpointer data, gpointer user_data) +{ + about_to_show_t * abts = (about_to_show_t *)data; + gboolean * got_callbacks = (gboolean *)user_data; + + if (abts->cb != NULL) { + *got_callbacks = TRUE; + } + + return; +} + +/* Take the ID out of the about to show structure and put it into the + variant builder */ +static void +about_to_show_idle_ids (gpointer data, gpointer user_data) +{ + about_to_show_t * abts = (about_to_show_t *)data; + GVariantBuilder * builder = (GVariantBuilder *)user_data; + + g_variant_builder_add_value(builder, g_variant_new_int32(abts->id)); + + return; +} + +/* Function that gets called with all the queued about_to_show messages, let's + get these guys on the bus! */ +static gboolean +about_to_show_idle (gpointer user_data) +{ + DbusmenuClient * client = DBUSMENU_CLIENT(user_data); + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + + /* Reset our object global props and take ownership of these entries */ + priv->about_to_show_idle = 0; + GQueue * showers = priv->about_to_show_to_go; + priv->about_to_show_to_go = NULL; + + /* Figure out if we've got any callbacks */ + gboolean got_callbacks = FALSE; + g_queue_foreach(showers, about_to_show_idle_callbacks, &got_callbacks); + + /* Build a list of the IDs */ + GVariantBuilder idarray; + g_variant_builder_init(&idarray, G_VARIANT_TYPE("ai")); + g_queue_foreach(showers, about_to_show_idle_ids, &idarray); + GVariant * ids = g_variant_builder_end(&idarray); + + /* Setup our callbacks */ + GAsyncReadyCallback cb = NULL; + gpointer cb_data = NULL; + if (got_callbacks) { + cb = about_to_show_group_cb; + cb_data = showers; + } else { + g_queue_foreach(showers, about_to_show_finish_pntr, GINT_TO_POINTER(FALSE)); + g_queue_free(showers); + } + + /* Let's call it! */ + g_dbus_proxy_call(priv->menuproxy, + "AboutToShowGroup", + g_variant_new_tuple(&ids, 1), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + NULL, /* cancellable */ + cb, + cb_data); + + return FALSE; +} + /* Reports errors and responds to update request that were a result of sending the about to show signal. */ static void @@ -1590,18 +1995,7 @@ about_to_show_cb (GObject * proxy, GAsyncResult * res, gpointer userdata) g_variant_unref(params); } - /* If we need to update, do that first. */ - if (need_update) { - update_layout(data->client); - } - - if (data->cb != NULL) { - data->cb(data->cb_data); - } - - g_object_unref(data->client); - g_free(data); - + about_to_show_finish(data, need_update); return; } @@ -1617,19 +2011,40 @@ dbusmenu_client_send_about_to_show(DbusmenuClient * client, gint id, void (*cb)( g_return_if_fail(priv != NULL); about_to_show_t * data = g_new0(about_to_show_t, 1); + data->id = id; data->client = client; data->cb = cb; data->cb_data = cb_data; g_object_ref(client); - g_dbus_proxy_call(priv->menuproxy, - "AboutToShow", - g_variant_new("(i)", id), - G_DBUS_CALL_FLAGS_NONE, - -1, /* timeout */ - NULL, /* cancellable */ - about_to_show_cb, - data); + if (priv->group_events) { + if (priv->about_to_show_to_go == NULL) { + priv->about_to_show_to_go = g_queue_new(); + } + + g_queue_push_tail(priv->about_to_show_to_go, data); + + if (priv->about_to_show_idle == 0) { + priv->about_to_show_idle = g_idle_add(about_to_show_idle, client); + } + } else { + /* If there's no callback we don't need this data, let's + clean it up in a consistent way */ + if (cb == NULL) { + about_to_show_finish(data, FALSE); + data = NULL; + } + + g_dbus_proxy_call(priv->menuproxy, + "AboutToShow", + g_variant_new("(i)", id), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + NULL, /* cancellable */ + about_to_show_cb, + data); + } + return; } diff --git a/libdbusmenu-glib/client.h b/libdbusmenu-glib/client.h index d0fca32..ae22863 100644 --- a/libdbusmenu-glib/client.h +++ b/libdbusmenu-glib/client.h @@ -105,6 +105,12 @@ G_BEGIN_DECLS * String to access property #DbusmenuClient:text-direction */ #define DBUSMENU_CLIENT_PROP_TEXT_DIRECTION "text-direction" +/** + * DBUSMENU_CLIENT_PROP_GROUP_EVENTS: + * + * String to access property #DbusmenuClient:group-events + */ +#define DBUSMENU_CLIENT_PROP_GROUP_EVENTS "group-events" /** * DBUSMENU_CLIENT_TYPES_DEFAULT: diff --git a/libdbusmenu-glib/dbus-menu.xml b/libdbusmenu-glib/dbus-menu.xml index 4b5a5d8..de6868c 100644 --- a/libdbusmenu-glib/dbus-menu.xml +++ b/libdbusmenu-glib/dbus-menu.xml @@ -326,6 +326,26 @@ License version 3 and version 2.1 along with this program. If not, see </arg> </method> + <method name="EventGroup"> + <dox:d> + Used to pass a set of events as a single message for possibily several + different menuitems. This is done to optimize DBus traffic. + </dox:d> + <arg type="a(isvu)" name="events" direction="in"> + <dox:d> + An array of all the events that should be passed. This tuple should + match the parameters of the 'Event' signal. Which is roughly: + id, eventID, data and timestamp. + </dox:d> + </arg> + <arg type="ai" name="idErrors" direction="out"> + <dox:d> + I list of menuitem IDs that couldn't be found. If none of the ones + in the list can be found, a DBus error is returned. + </dox:d> + </arg> + </method> + <method name="AboutToShow"> <dox:d> This is called by the applet to notify the application that it is about @@ -343,6 +363,32 @@ License version 3 and version 2.1 along with this program. If not, see </arg> </method> + <method name="AboutToShowGroup"> + <dox:d> + A function to tell several menus being shown that they are about to + be shown to the user. This is likely only useful for programitc purposes + so while the return values are returned, in general, the singular function + should be used in most user interacation scenarios. + </dox:d> + <arg type="ai" name="ids" direction="in"> + <dox:d> + The IDs of the menu items who's submenus are being shown. + </dox:d> + </arg> + <arg type="ai" name="updatesNeeded" direction="out"> + <dox:d> + The IDs of the menus that need updates. Note: if no update information + is needed the DBus message should set the no reply flag. + </dox:d> + </arg> + <arg type="ai" name="idErrors" direction="out"> + <dox:d> + I list of menuitem IDs that couldn't be found. If none of the ones + in the list can be found, a DBus error is returned. + </dox:d> + </arg> + </method> + <!-- Signals --> <signal name="ItemsPropertiesUpdated"> <dox:d> diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c index 18db4ef..c81c36e 100644 --- a/libdbusmenu-glib/menuitem.c +++ b/libdbusmenu-glib/menuitem.c @@ -1253,7 +1253,7 @@ dbusmenu_menuitem_property_set_variant (DbusmenuMenuitem * mi, const gchar * pro becuse it has been unref'd when replaced in the hash table. But the fact that there was a value is the imporant part. */ - if (!inhash || replaced) { + if (replaced) { GVariant * signalval = value; if (signalval == NULL) { @@ -1462,8 +1462,8 @@ dbusmenu_menuitem_property_remove (DbusmenuMenuitem * mi, const gchar * property * by the menuitem but the list is not and should be freed using * g_list_free() when the calling function is done with it. * - * Return value: (transfer container): A list of strings or NULL if there are - * none. + * Return value: (transfer container) (element-type utf8): A list of + * strings or NULL if there are none. */ GList * dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi) diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c index 524e777..091b243 100644 --- a/libdbusmenu-glib/server.c +++ b/libdbusmenu-glib/server.c @@ -42,7 +42,7 @@ License version 3 and version 2.1 along with this program. If not, see static void layout_update_signal (DbusmenuServer * server); -#define DBUSMENU_VERSION_NUMBER 2 +#define DBUSMENU_VERSION_NUMBER 3 #define DBUSMENU_INTERFACE "com.canonical.dbusmenu" /* Privates, I'll show you mine... */ @@ -64,6 +64,8 @@ struct _DbusmenuServerPrivate GArray * prop_array; guint property_idle; + + GHashTable * lookup_cache; }; #define DBUSMENU_SERVER_GET_PRIVATE(o) (DBUSMENU_SERVER(o)->priv) @@ -116,7 +118,9 @@ enum { METHOD_GET_PROPERTY, METHOD_GET_PROPERTIES, METHOD_EVENT, + METHOD_EVENT_GROUP, METHOD_ABOUT_TO_SHOW, + METHOD_ABOUT_TO_SHOW_GROUP, /* Counter, do not remove! */ METHOD_COUNT }; @@ -189,9 +193,15 @@ static void bus_get_properties (DbusmenuServer * server, static void bus_event (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation); +static void bus_event_group (DbusmenuServer * server, + GVariant * params, + GDBusMethodInvocation * invocation); static void bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation); +static void bus_about_to_show_group (DbusmenuServer * server, + GVariant * params, + GDBusMethodInvocation * invocation); static void find_servers_cb (GDBusConnection * connection, const gchar * sender, const gchar * path, @@ -356,9 +366,15 @@ dbusmenu_server_class_init (DbusmenuServerClass *class) dbusmenu_method_table[METHOD_EVENT].interned_name = g_intern_static_string("Event"); dbusmenu_method_table[METHOD_EVENT].func = bus_event; + dbusmenu_method_table[METHOD_EVENT_GROUP].interned_name = g_intern_static_string("EventGroup"); + dbusmenu_method_table[METHOD_EVENT_GROUP].func = bus_event_group; + dbusmenu_method_table[METHOD_ABOUT_TO_SHOW].interned_name = g_intern_static_string("AboutToShow"); dbusmenu_method_table[METHOD_ABOUT_TO_SHOW].func = bus_about_to_show; + dbusmenu_method_table[METHOD_ABOUT_TO_SHOW_GROUP].interned_name = g_intern_static_string("AboutToShowGroup"); + dbusmenu_method_table[METHOD_ABOUT_TO_SHOW_GROUP].func = bus_about_to_show_group; + return; } @@ -378,6 +394,8 @@ dbusmenu_server_init (DbusmenuServer *self) priv->find_server_signal = 0; priv->dbus_registration = 0; + priv->lookup_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + default_text_direction(self); priv->status = DBUSMENU_STATUS_NORMAL; priv->icon_dirs = NULL; @@ -450,10 +468,50 @@ dbusmenu_server_finalize (GObject *object) priv->icon_dirs = NULL; } + if (priv->lookup_cache) { + g_hash_table_destroy(priv->lookup_cache); + priv->lookup_cache = NULL; + } + G_OBJECT_CLASS (dbusmenu_server_parent_class)->finalize (object); return; } +static DbusmenuMenuitem * +lookup_menuitem_by_id (DbusmenuServer * server, gint id) +{ + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + DbusmenuMenuitem *res = (DbusmenuMenuitem *) g_hash_table_lookup(priv->lookup_cache, GINT_TO_POINTER(id)); + if (!res && id == 0) { + return priv->root; + } + + return res; +} + +static void +cache_remove_entries_for_menuitem (GHashTable * cache, DbusmenuMenuitem * item) +{ + g_hash_table_remove(cache, GINT_TO_POINTER(dbusmenu_menuitem_get_id(item))); + + GList *child, *children = dbusmenu_menuitem_get_children(item); + for (child = children; child != NULL; child = child->next) { + cache_remove_entries_for_menuitem(cache, child->data); + } +} + +static void +cache_add_entries_for_menuitem (GHashTable * cache, DbusmenuMenuitem * item) +{ + g_hash_table_insert(cache, GINT_TO_POINTER(dbusmenu_menuitem_get_id(item)), g_object_ref(item)); + + GList *child, *children = dbusmenu_menuitem_get_children(item); + for (child = children; child != NULL; child = child->next) { + cache_add_entries_for_menuitem(cache, child->data); + } +} + static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) { @@ -480,6 +538,7 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) if (priv->root != NULL) { dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, obj); dbusmenu_menuitem_set_root(priv->root, FALSE); + cache_remove_entries_for_menuitem(priv->lookup_cache, priv->root); GList * properties = dbusmenu_menuitem_properties_list(priv->root); GList * iter; @@ -495,6 +554,7 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) priv->root = DBUSMENU_MENUITEM(g_value_get_object(value)); if (priv->root != NULL) { g_object_ref(G_OBJECT(priv->root)); + cache_add_entries_for_menuitem(priv->lookup_cache, priv->root); dbusmenu_menuitem_set_root(priv->root, TRUE); dbusmenu_menuitem_foreach(priv->root, menuitem_signals_create, obj); @@ -1161,6 +1221,7 @@ static void menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint pos, DbusmenuServer * server) { menuitem_signals_create(child, server); + cache_add_entries_for_menuitem(server->priv->lookup_cache, child); g_list_foreach(dbusmenu_menuitem_get_children(child), added_check_children, server); layout_update_signal(server); @@ -1171,6 +1232,7 @@ static void menuitem_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server) { menuitem_signals_remove(child, server); + cache_remove_entries_for_menuitem(server->priv->lookup_cache, child); layout_update_signal(server); return; } @@ -1259,7 +1321,7 @@ bus_get_layout (DbusmenuServer * server, GVariant * params, GDBusMethodInvocatio GVariant * items = NULL; if (priv->root != NULL) { - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, parent); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, parent); if (mi != NULL) { items = dbusmenu_menuitem_build_variant(mi, props, recurse); @@ -1318,7 +1380,7 @@ bus_get_property (DbusmenuServer * server, GVariant * params, GDBusMethodInvocat g_variant_get(params, "(i&s)", &id, &property); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); if (mi == NULL) { g_dbus_method_invocation_return_error(invocation, @@ -1361,7 +1423,7 @@ bus_get_properties (DbusmenuServer * server, GVariant * params, GDBusMethodInvoc gint32 id; g_variant_get(params, "(i)", &id); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); if (mi == NULL) { g_dbus_method_invocation_return_error(invocation, @@ -1424,7 +1486,7 @@ bus_get_group_properties (DbusmenuServer * server, GVariant * params, GDBusMetho gint32 id; while (g_variant_iter_loop(ids, "i", &id)) { - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); if (mi == NULL) continue; if (!builder_init) { @@ -1523,7 +1585,7 @@ bus_get_children (DbusmenuServer * server, GVariant * params, GDBusMethodInvocat return; } - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); if (mi == NULL) { g_dbus_method_invocation_return_error(invocation, @@ -1585,6 +1647,28 @@ event_local_handler (gpointer user_data) return FALSE; } +/* The core menu finding and doing the work part of the two + event functions */ +static gboolean +bus_event_core (DbusmenuServer * server, gint32 id, gchar * event_type, GVariant * data, guint32 timestamp) +{ + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); + + if (mi == NULL) { + return FALSE; + } + + idle_event_t * event_data = g_new0(idle_event_t, 1); + event_data->mi = g_object_ref(mi); + event_data->eventid = g_strdup(event_type); + event_data->timestamp = timestamp; + event_data->variant = g_variant_ref(data); + + g_timeout_add(0, event_local_handler, event_data); + + return TRUE; +} + /* Handles the events coming off of DBus */ static void bus_event (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) @@ -1606,32 +1690,94 @@ bus_event (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * i g_variant_get(params, "(isvu)", &id, &etype, &data, &ts); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); - - if (mi == NULL) { + if (!bus_event_core(server, id, etype, data, ts)) { g_dbus_method_invocation_return_error(invocation, error_quark(), INVALID_MENUITEM_ID, "The ID supplied %d does not refer to a menu item we have", id); - g_free(etype); - g_variant_unref(data); - } else { - idle_event_t * event_data = g_new0(idle_event_t, 1); - event_data->mi = g_object_ref(mi); - event_data->eventid = etype; /* give away our allocation */ - event_data->timestamp = ts; - event_data->variant = data; /* give away our reference */ + if (~g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED) { + g_dbus_method_invocation_return_value(invocation, NULL); + } + } + + g_free(etype); + g_variant_unref(data); + + return; +} + +/* Respond to the event group method that will send events to a + variety of menuitems */ +static void +bus_event_group (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) +{ + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + if (priv->root == NULL) { + g_dbus_method_invocation_return_error(invocation, + error_quark(), + NO_VALID_LAYOUT, + "There currently isn't a layout in this server"); + return; + } + + GVariant * events = g_variant_get_child_value(params, 0); + gint32 id; + gchar *etype; + GVariant *data; + guint32 ts; + GVariantIter iter; + GVariantBuilder builder; - g_timeout_add(0, event_local_handler, event_data); + g_variant_iter_init(&iter, events); + g_variant_builder_init(&builder, G_VARIANT_TYPE("ai")); + gboolean gotone = FALSE; - g_dbus_method_invocation_return_value(invocation, NULL); + while (g_variant_iter_loop(&iter, "(isvu)", &id, &etype, &data, &ts)) { + if (bus_event_core(server, id, etype, data, ts)) { + gotone = TRUE; + } else { + g_variant_builder_add_value(&builder, g_variant_new_int32(id)); + } + } + + GVariant * errors = g_variant_builder_end(&builder); + g_variant_ref_sink(errors); + + if (gotone) { + if (~g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED) { + g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&errors, 1)); + } + } else { + gchar * ids = g_variant_print(errors, FALSE); + g_dbus_method_invocation_return_error(invocation, + error_quark(), + INVALID_MENUITEM_ID, + "The IDs supplied '%s' do not refer to any menu items we have", + ids); + g_free(ids); } + g_variant_unref(errors); + g_variant_unref(events); + return; } +/* Does the about-to-show in an idle loop so we don't block things */ +/* NOTE: this only works so easily as we don't return the value, if we + were to do that it would get more complex. */ +static gboolean +bus_about_to_show_idle (gpointer user_data) +{ + DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(user_data); + dbusmenu_menuitem_send_about_to_show(mi, NULL, NULL); + g_object_unref(mi); + return FALSE; +} + /* Recieve the About To Show function. Pass it to our menu item. */ static void bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) @@ -1648,7 +1794,7 @@ bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvoca gint32 id; g_variant_get(params, "(i)", &id); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); if (mi == NULL) { g_dbus_method_invocation_return_error(invocation, @@ -1659,7 +1805,7 @@ bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvoca return; } - dbusmenu_menuitem_send_about_to_show(mi, NULL, NULL); + g_timeout_add(0, bus_about_to_show_idle, g_object_ref(mi)); /* GTK+ does not support about-to-show concept for now */ g_dbus_method_invocation_return_value(invocation, @@ -1667,6 +1813,69 @@ bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvoca return; } +/* Handle the about to show on a set of menus and tell all of them that + we love them */ +static void +bus_about_to_show_group (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) +{ + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + if (priv->root == NULL) { + g_dbus_method_invocation_return_error(invocation, + error_quark(), + NO_VALID_LAYOUT, + "There currently isn't a layout in this server"); + return; + } + + gint32 id; + GVariantIter iter; + GVariantBuilder builder; + + g_variant_iter_init(&iter, params); + g_variant_builder_init(&builder, G_VARIANT_TYPE("ai")); + gboolean gotone = FALSE; + + while (g_variant_iter_loop(&iter, "(i)", &id)) { + DbusmenuMenuitem * mi = lookup_menuitem_by_id(server, id); + if (mi != NULL) { + g_timeout_add(0, bus_about_to_show_idle, g_object_ref(mi)); + gotone = TRUE; + } else { + g_variant_builder_add_value(&builder, g_variant_new_int32(id)); + } + } + + GVariant * errors = g_variant_builder_end(&builder); + g_variant_ref_sink(errors); + + if (gotone) { + if (~g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED) { + GVariantBuilder tuple; + g_variant_builder_init(&tuple, G_VARIANT_TYPE_TUPLE); + + /* Updates needed */ + g_variant_builder_add_value(&tuple, g_variant_new_array(G_VARIANT_TYPE_INT32, NULL, 0)); + /* Errors */ + g_variant_builder_add_value(&tuple, errors); + + g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&tuple)); + } + } else { + gchar * ids = g_variant_print(errors, FALSE); + g_dbus_method_invocation_return_error(invocation, + error_quark(), + INVALID_MENUITEM_ID, + "The IDs supplied '%s' do not refer to any menu items we have", + ids); + g_free(ids); + } + + g_variant_unref(errors); + + return; +} + /* Public Interface */ /** dbusmenu_server_new: |