#include "gaction.h"
#include "glibintl.h"
+#include <string.h>
+
G_DEFINE_INTERFACE (GAction, g_action, G_TYPE_OBJECT)
/**
if (parameter != NULL)
g_variant_unref (parameter);
}
+
+/**
+ * g_action_parse_detailed_name:
+ * @detailed_name: a detailed action name
+ * @action_name: (out): the action name
+ * @target_value: (out): the target value, or %NULL for no target
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Parses a detailed action name into its separate name and target
+ * components.
+ *
+ * Detailed action names can have three formats.
+ *
+ * The first format is used to represent an action name with no target
+ * value and consists of just an action name containing no whitespace
+ * nor the characters ':', '(' or ')'. For example: "app.action".
+ *
+ * The second format is used to represent an action with a string-typed
+ * target value. The action name and target value are separated by a
+ * double colon ("::"). For example: "app.action::target".
+ *
+ * The third format is used to represent an action with an
+ * arbitrarily-typed target value. The target value follows the action
+ * name, surrounded in parens. For example: "app.action(42)". The
+ * target value is parsed using g_variant_parse(). If a tuple-typed
+ * value is desired, it must be specified in the same way, resulting in
+ * two sets of parens, for example: "app.action((1,2,3))".
+ *
+ * Returns: %TRUE if successful, else %FALSE with @error set
+ *
+ * Since: 2.38
+ **/
+gboolean
+g_action_parse_detailed_name (const gchar *detailed_name,
+ gchar **action_name,
+ GVariant **target_value,
+ GError **error)
+{
+ const gchar *target;
+ gsize target_len;
+ gsize base_len;
+
+ /* We decide which format we have based on which we see first between
+ * '::' '(' and '\0'.
+ */
+
+ if (*detailed_name == '\0' || *detailed_name == ' ')
+ goto bad_fmt;
+
+ base_len = strcspn (detailed_name, ": ()");
+ target = detailed_name + base_len;
+ target_len = strlen (target);
+
+ switch (target[0])
+ {
+ case ' ':
+ case ')':
+ goto bad_fmt;
+
+ case ':':
+ if (target[1] != ':')
+ goto bad_fmt;
+
+ *target_value = g_variant_ref_sink (g_variant_new_string (target + 2));
+ break;
+
+ case '(':
+ {
+ if (target[target_len - 1] != ')')
+ goto bad_fmt;
+
+ *target_value = g_variant_parse (NULL, target + 1, target + target_len - 1, NULL, error);
+ if (*target_value == NULL)
+ goto bad_fmt;
+ }
+ break;
+
+ case '\0':
+ *target_value = NULL;
+ break;
+ }
+
+ *action_name = g_strndup (detailed_name, base_len);
+
+ return TRUE;
+
+bad_fmt:
+ if (error)
+ {
+ if (*error == NULL)
+ g_set_error (error, G_VARIANT_PARSE_ERROR, G_VARIANT_PARSE_ERROR_FAILED,
+ "Detailed action name '%s' has invalid format", detailed_name);
+ else
+ g_prefix_error (error, "Detailed action name '%s' has invalid format: ", detailed_name);
+ }
+
+ return FALSE;
+}
#include "gmenu.h"
+#include "gaction.h"
#include <string.h>
/**
*
* Sets the "action" and possibly the "target" attribute of @menu_item.
*
- * If @detailed_action contains a double colon ("::") then it is used as
- * a separator between an action name and a target string. In this
- * case, this call is equivalent to calling
- * g_menu_item_set_action_and_target() with the part before the "::" and
- * with a string-type #GVariant containing the part following the "::".
- *
- * If @detailed_action doesn't contain "::" then the action is set to
- * the given string (verbatim) and the target value is unset.
+ * The format of @detailed_action is the same format parsed by
+ * g_action_parse_detailed_name().
*
* See g_menu_item_set_action_and_target() or
* g_menu_item_set_action_and_target_value() for more flexible (but
g_menu_item_set_detailed_action (GMenuItem *menu_item,
const gchar *detailed_action)
{
- const gchar *sep;
-
- sep = strstr (detailed_action, "::");
-
- if (sep != NULL)
- {
- gchar *action;
+ GError *error = NULL;
+ GVariant *target;
+ gchar *name;
- action = g_strndup (detailed_action, sep - detailed_action);
- g_menu_item_set_action_and_target (menu_item, action, "s", sep + 2);
- g_free (action);
- }
+ if (!g_action_parse_detailed_name (detailed_action, &name, &target, &error))
+ g_error ("g_menu_item_set_detailed_action: %s", error->message);
- else
- g_menu_item_set_action_and_target_value (menu_item, detailed_action, NULL);
+ g_menu_item_set_action_and_target_value (menu_item, name, target);
+ if (target)
+ g_variant_unref (target);
+ g_free (name);
}
/**
#include <gio/gio.h>
#include <stdlib.h>
+#include <string.h>
#include "gdbus-sessionbus.h"
g_object_unref (actions);
}
+static void
+test_parse_detailed (void)
+{
+ struct {
+ const gchar *detailed;
+ const gchar *expected_name;
+ const gchar *expected_target;
+ const gchar *expected_error;
+ } testcases[] = {
+ { "abc", "abc", NULL, NULL },
+ { " abc", NULL, NULL, "invalid format" },
+ { " abc", NULL, NULL, "invalid format" },
+ { "abc:", NULL, NULL, "invalid format" },
+ { ":abc", NULL, NULL, "invalid format" },
+ { "abc(", NULL, NULL, "invalid format" },
+ { "abc)", NULL, NULL, "invalid format" },
+ { "(abc", NULL, NULL, "invalid format" },
+ { ")abc", NULL, NULL, "invalid format" },
+ { "abc::xyz", "abc", "'xyz'", NULL },
+ { "abc('xyz')", "abc", "'xyz'", NULL },
+ { "abc(42)", "abc", "42", NULL },
+ { "abc(int32 42)", "abc", "42", NULL },
+ { "abc(@i 42)", "abc", "42", NULL },
+ { "abc (42)", NULL, NULL, "invalid format" },
+ { "abc(42abc)", NULL, NULL, "invalid character in number" },
+ { "abc(42, 4)", "abc", "(42, 4)", "expected end of input" },
+ { "abc(42,)", "abc", "(42,)", "expected end of input" }
+ };
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (testcases); i++)
+ {
+ GError *error = NULL;
+ GVariant *target;
+ gboolean success;
+ gchar *name;
+
+ success = g_action_parse_detailed_name (testcases[i].detailed, &name, &target, &error);
+ g_assert (success == (error == NULL));
+ if (success && testcases[i].expected_error)
+ g_error ("Unexpected success on '%s'. Expected error containing '%s'",
+ testcases[i].detailed, testcases[i].expected_error);
+
+ if (!success && !testcases[i].expected_error)
+ g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
+
+ if (!success)
+ {
+ if (!strstr (error->message, testcases[i].expected_error))
+ g_error ("Failure message '%s' for string '%s' did not contained expected substring '%s'",
+ error->message, testcases[i].detailed, testcases[i].expected_error);
+
+ g_error_free (error);
+ continue;
+ }
+
+ g_assert_cmpstr (name, ==, testcases[i].expected_name);
+ g_assert ((target == NULL) == (testcases[i].expected_target == NULL));
+ if (target)
+ {
+ GVariant *expected;
+
+ expected = g_variant_parse (NULL, testcases[i].expected_target, NULL, NULL, NULL);
+ g_assert (expected);
+
+ g_assert (g_variant_equal (expected, target));
+ g_variant_unref (expected);
+ g_variant_unref (target);
+ }
+
+ g_free (name);
+ }
+}
GHashTable *activation_counts;
g_test_add_func ("/actions/simplegroup", test_simple_group);
g_test_add_func ("/actions/stateful", test_stateful);
g_test_add_func ("/actions/entries", test_entries);
+ g_test_add_func ("/actions/parse-detailed", test_parse_detailed);
g_test_add_func ("/actions/dbus/export", test_dbus_export);
g_test_add_func ("/actions/dbus/threaded", test_dbus_threaded);
g_test_add_func ("/actions/dbus/bug679509", test_bug679509);