[kdbus] KDBUS_ITEM_PAYLOAD_OFF items are (once again) relative to msg header
[platform/upstream/glib.git] / gio / gmenuexporter.c
index 6d9df45..89cc309 100644 (file)
  *  Lesser General Public License for more details.
  *
  *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- *  USA.
+ *  License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: Ryan Lortie <desrt@desrt.ca>
  */
 
+#include "config.h"
+
 #include "gmenuexporter.h"
 
 #include "gdbusmethodinvocation.h"
  * SECTION:gmenuexporter
  * @title: GMenuModel exporter
  * @short_description: Export GMenuModels on D-Bus
- * @see_also: #GMenuModel, #GMenuProxy
+ * @include: gio/gio.h
+ * @see_also: #GMenuModel, #GDBusMenuModel
  *
  * These functions support exporting a #GMenuModel on D-Bus.
  * The D-Bus interface that is used is a private implementation
  * detail.
  *
  * To access an exported #GMenuModel remotely, use
- * g_menu_proxy_get() to obtain a #GMenuProxy.
+ * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
  */
 
 /* {{{1 D-Bus Interface description */
 
-/* The org.gtk.Menus interface
- * ===========================
- *
- * The interface is primarily concerned with three things:
- *
- * - communicating menus to the client
- * - establishing links between menus and other menus
- * - notifying clients of changes
- *
- * As a basic principle, it is recognised that the menu structure
- * of an application is often large. It is also recognised that some
- * menus are liable to frequently change without the user ever having
- * opened the menu. For both of these reasons, the individual menus are
- * arranged into subscription groups. Each subscription group is specified
- * by an unsigned integer. The assignment of integers need not be consecutive.
- *
- * Within a subscription group there are multiple menus. Each menu is
- * identified with an unsigned integer, unique to its subscription group.
- *
- * By convention, the primary menu is numbered 0 without subscription group 0.
- *
- * Actionable menu items (ie: those that produce some effect in the
- * application when they are activated) have a related action, specified by
- * a string. This string specifies the name of the action, according to the
- * org.gtk.Actions interface, at the same object path as the menu.
- *
- * Methods
- * -------
- *
- * Start :: (au) → (a(uuaa{sv}))
- *
- *   The Start method is used to indicate that a client is interested in
- *   tracking and displaying the content of the menus of a particular list
- *   of subscription groups.
- *
- *   Most typically, the client will request subscription group 0 to start.
- *
- *   The call has two effects. First, it replies with all menus defined
- *   within the requested subscription groups. The format of the reply is
- *   an array of tuples, where the items in each tuple are:
- *   - the subscription group of the menu
- *   - the number of the menu within that group
- *   - an array of menu items
- *
- *   Each menu item is a dictionary of attributes (a{sv}).
- *
- *   Secondly, this call has a side effect: it atomically requests that
- *   the Changed signal start to be emitted for the requested subscription
- *   group. Each group has a subscription count and only signals changes
- *   on itself when this count is greater than zero.
- *
- *   If a group is specified multiple times then the result is that the
- *   contents of that group is only returned once, but the subscription
- *   count is increased multiple times.
- *
- *   If a client disconnects from the bus while holding subscriptions then
- *   its subscriptions will be cancelled. This prevents "leaking" subscriptions
- *   in the case of crashes and is also useful for applications that want
- *   to exit without manually cleaning up.
- *
- * End :: (au)
- *
- *   The End method reverses the previous effects of a call to Start.
- *
- *   When clients are no longer interested in the contents of a subscription
- *   group, they should call the End method.
- *
- *   The parameter lists the subscription groups. A subscription group
- *   needs to be cancelled the same number of times as it was requested.
- *   For this reason, it might make sense to specify the same subscription
- *   group multiple times (if multiple Start calls were made for this group).
- *
- * Signals
- * -------
- *
- * Changed :: (a(uuuuaa{sv}))
- *
- *   The changed signal indicates changes to a particular menu.
- *
- *   The changes come as an array of tuples where the items in each tuple are:
- *   - the subscription group of the menu
- *   - the number of the menu within that group
- *   - the position in the menu at which to make the change
- *   - the number of items to delete from that position
- *   - a list of new items to insert at that position
- *
- *   Each new menu item is a dictionary of attributes (a{sv}).
- *
- * Attributes
- * ----------
- *
- * label (string): the label to display
- * action (string): the name of the action
- * target (variant): the parameter to pass when activating the action
- * :section ((uu)): the menu to use to populate that section, specified
- *     as a pair of subscription group and menu within that group
- * :submenu ((uu)): the menu to use as a submenu, specified
- *     as a pair of subscription group and menu within that group
+/* For documentation of this interface, see
+ * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
  */
 
 static GDBusInterfaceInfo *
@@ -269,7 +174,10 @@ g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
       GMenuExporterGroup *group;
       GMenuExporterLink *tmp;
 
-      if (0) /* [magic] */
+      /* keep sections in the same group, but create new groups
+       * otherwise
+       */
+      if (!g_str_equal (name, "section"))
         group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
       else
         group = menu->group;
@@ -450,7 +358,15 @@ g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
       group->prepared = TRUE;
 
       menu = g_hash_table_lookup (group->menus, 0);
-      g_menu_exporter_menu_prepare (menu);
+
+      /* If the group was created by a subscription and does not yet
+       * exist, it won't have a root menu...
+       *
+       * That menu will be prepared if it is ever added (due to
+       * group->prepared == TRUE).
+       */
+      if (menu)
+        g_menu_exporter_menu_prepare (menu);
     }
 
   group->subscribed++;
@@ -561,6 +477,7 @@ g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
   count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
   g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
 
+  /* Group will be created (as empty/unsubscribed if it does not exist) */
   group = g_menu_exporter_lookup_group (remote->exporter, group_id);
   g_menu_exporter_group_subscribe (group, builder);
 }
@@ -649,7 +566,8 @@ g_menu_exporter_name_vanished (GDBusConnection *connection,
 {
   GMenuExporter *exporter = user_data;
 
-  g_assert (exporter->connection == connection);
+  /* connection == NULL when we get called because the connection closed */
+  g_assert (exporter->connection == connection || connection == NULL);
 
   g_hash_table_remove (exporter->remotes, name);
 }
@@ -768,9 +686,10 @@ g_menu_exporter_create_group (GMenuExporter *exporter)
 }
 
 static void
-g_menu_exporter_free (GMenuExporter *exporter)
+g_menu_exporter_free (gpointer user_data)
 {
-  g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
+  GMenuExporter *exporter = user_data;
+
   g_menu_exporter_menu_free (exporter->root);
   g_hash_table_unref (exporter->remotes);
   g_hash_table_unref (exporter->groups);
@@ -810,57 +729,10 @@ g_menu_exporter_method_call (GDBusConnection       *connection,
   g_variant_unref (group_ids);
 }
 
-static GDBusConnection *
-g_menu_exporter_get_connection (GMenuExporter *exporter)
-{
-  return exporter->connection;
-}
-
-static const gchar *
-g_menu_exporter_get_object_path (GMenuExporter *exporter)
-{
-  return exporter->object_path;
-}
-
-static GMenuExporter *
-g_menu_exporter_new (GDBusConnection  *connection,
-                     const gchar      *object_path,
-                     GMenuModel       *model,
-                     GError          **error)
-{
-  const GDBusInterfaceVTable vtable = {
-    g_menu_exporter_method_call,
-  };
-  GMenuExporter *exporter;
-  guint id;
-
-  exporter = g_slice_new0 (GMenuExporter);
-
-  id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
-                                          &vtable, exporter, NULL, error);
-
-  if (id == 0)
-    {
-      g_slice_free (GMenuExporter, exporter);
-      return NULL;
-    }
-
-  exporter->connection = g_object_ref (connection);
-  exporter->object_path = g_strdup (object_path);
-  exporter->registration_id = id;
-  exporter->groups = g_hash_table_new (NULL, NULL);
-  exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
-  exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), model);
-
-  return exporter;
-}
-
 /* {{{1 Public API */
 
-static GHashTable *g_menu_exporter_exported_menus;
-
 /**
- * g_menu_model_dbus_export_start:
+ * g_dbus_connection_export_menu_model:
  * @connection: a #GDBusConnection
  * @object_path: a D-Bus object path
  * @menu: a #GMenuModel
@@ -871,114 +743,69 @@ static GHashTable *g_menu_exporter_exported_menus;
  * The implemented D-Bus API should be considered private.
  * It is subject to change in the future.
  *
- * A given menu model can only be exported on one object path
- * and an object path can only have one action group exported
- * on it. If either constraint is violated, the export will
- * fail and %FALSE will be returned (with @error set accordingly).
+ * An object path can only have one menu model exported on it. If this
+ * constraint is violated, the export will fail and 0 will be
+ * returned (with @error set accordingly).
+ *
+ * You can unexport the menu model using
+ * g_dbus_connection_unexport_menu_model() with the return value of
+ * this function.
  *
- * Use g_menu_model_dbus_export_stop() to stop exporting @menu
- * or g_menu_model_dbus_export_query() to find out if and where
- * a given menu model is exported.
+ * Returns: the ID of the export (never zero), or 0 in case of failure
  *
- * Returns: %TRUE if the export is successful, or %FALSE (with
- *     @error set) in the event of a failure.
+ * Since: 2.32
  */
-gboolean
-g_menu_model_dbus_export_start (GDBusConnection  *connection,
-                                const gchar      *object_path,
-                                GMenuModel       *menu,
-                                GError          **error)
+guint
+g_dbus_connection_export_menu_model (GDBusConnection  *connection,
+                                     const gchar      *object_path,
+                                     GMenuModel       *menu,
+                                     GError          **error)
 {
+  const GDBusInterfaceVTable vtable = {
+    g_menu_exporter_method_call,
+  };
   GMenuExporter *exporter;
+  guint id;
 
-  if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
-    g_menu_exporter_exported_menus = g_hash_table_new (NULL, NULL);
+  exporter = g_slice_new0 (GMenuExporter);
+
+  id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
+                                          &vtable, exporter, g_menu_exporter_free, error);
 
-  if G_UNLIKELY (g_hash_table_lookup (g_menu_exporter_exported_menus, menu))
+  if (id == 0)
     {
-      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS, "The given GMenuModel has already been exported");
-      return FALSE;
+      g_slice_free (GMenuExporter, exporter);
+      return 0;
     }
 
-  exporter = g_menu_exporter_new (connection, object_path, menu, error);
-
-  if (exporter == NULL)
-    return FALSE;
-
-  g_hash_table_insert (g_menu_exporter_exported_menus, menu, exporter);
-
-  return TRUE;
-}
-
-/**
- * g_menu_model_dbus_export_stop:
- * @menu: a #GMenuModel
- *
- * Stops the export of @menu.
- *
- * This reverses the effect of a previous call to
- * g_menu_model_dbus_export_start() for @menu.
- *
- * Returns: %TRUE if an export was stopped or %FALSE
- *     if @menu was not exported in the first place
- */
-gboolean
-g_menu_model_dbus_export_stop (GMenuModel *menu)
-{
-  GMenuExporter *exporter;
-
-  if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
-    return FALSE;
-
-  exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
-  if G_UNLIKELY (exporter == NULL)
-    return FALSE;
-
-  g_hash_table_remove (g_menu_exporter_exported_menus, menu);
-  g_menu_exporter_free (exporter);
+  exporter->connection = g_object_ref (connection);
+  exporter->object_path = g_strdup (object_path);
+  exporter->groups = g_hash_table_new (NULL, NULL);
+  exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
+  exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
 
-  return TRUE;
+  return id;
 }
 
 /**
- * g_menu_model_dbus_export_query:
- * @menu: a #GMenuModel
- * @connection: (out): the #GDBusConnection used for exporting
- * @object_path: (out): the object path used for exporting
- *
- * Queries if and where @menu is exported.
+ * g_dbus_connection_unexport_menu_model:
+ * @connection: a #GDBusConnection
+ * @export_id: the ID from g_dbus_connection_export_menu_model()
  *
- * If @menu is exported, %TRUE is returned. If @connection is
- * non-%NULL then it is set to the #GDBusConnection used for
- * the export. If @object_path is non-%NULL then it is set to
- * the object path.
+ * Reverses the effect of a previous call to
+ * g_dbus_connection_export_menu_model().
  *
- * If the @menu is not exported, %FALSE is returned and
- * @connection and @object_path remain unmodified.
+ * It is an error to call this function with an ID that wasn't returned
+ * from g_dbus_connection_export_menu_model() or to call it with the
+ * same ID more than once.
  *
- * Returns: %TRUE if @menu was exported, else %FALSE
+ * Since: 2.32
  */
-gboolean
-g_menu_model_dbus_export_query (GMenuModel       *menu,
-                                GDBusConnection **connection,
-                                const gchar     **object_path)
+void
+g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
+                                       guint            export_id)
 {
-  GMenuExporter *exporter;
-
-  if (g_menu_exporter_exported_menus == NULL)
-    return FALSE;
-
-  exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
-  if (exporter == NULL)
-    return FALSE;
-
-  if (connection)
-    *connection = g_menu_exporter_get_connection (exporter);
-
-  if (object_path)
-    *object_path = g_menu_exporter_get_object_path (exporter);
-
-  return TRUE;
+  g_dbus_connection_unregister_object (connection, export_id);
 }
 
 /* {{{1 Epilogue */