mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-14 13:26:16 +01:00
3a07b2abd4
GCancellable is meant to be used in multi-thread operations but all the cancellable instances were sharing a single mutex to synchronize them which can be less optimal when many instances are in place. Especially when we're doing a lock/unlock dances that may leave another thread to take the control of a critical section in an unexpected way. This in fact was leading to some races in GCancellableSources causing leaks because we were assuming that the "cancelled" callback was always called before our dispose implementation. As per this, use per-instance mutexes. The lock is also now used only to protect the calls that may interact with cancelled state or that depends on that, as per this we can just reduce it to the cancel and reset case, other than to the connect one to prevent the race that we could have when connecting to a cancellable that is reset from another thread. We don't really need to release the locks during callbacks now as they are per instance, and there's really no function that we allowed to call during a ::cancelled signal callback that may require an unlocked state. This could been done in case with a recursive lock, that is easy enough to implement but not really needed for this case. Fixes: #2309, #2313
1711 lines
46 KiB
C
1711 lines
46 KiB
C
#include <gio/gio.h>
|
|
#include <gio/gunixsocketaddress.h>
|
|
#include <glib/gstdio.h>
|
|
#include <string.h>
|
|
|
|
#include "gdbus-sessionbus.h"
|
|
|
|
#include "glib/glib-private.h"
|
|
|
|
static void
|
|
time_out (gpointer unused G_GNUC_UNUSED)
|
|
{
|
|
g_error ("Timed out");
|
|
}
|
|
|
|
static guint
|
|
add_timeout (guint seconds)
|
|
{
|
|
#ifdef G_OS_UNIX
|
|
/* Safety-catch against the main loop having blocked */
|
|
alarm (seconds + 5);
|
|
#endif
|
|
return g_timeout_add_seconds_once (seconds, time_out, NULL);
|
|
}
|
|
|
|
static void
|
|
cancel_timeout (guint timeout_id)
|
|
{
|
|
#ifdef G_OS_UNIX
|
|
alarm (0);
|
|
#endif
|
|
g_source_remove (timeout_id);
|
|
}
|
|
|
|
/* Markup printing {{{1 */
|
|
|
|
/* This used to be part of GLib, but it was removed before the stable
|
|
* release because it wasn't generally useful. We want it here, though.
|
|
*/
|
|
static void
|
|
indent_string (GString *string,
|
|
gint indent)
|
|
{
|
|
while (indent--)
|
|
g_string_append_c (string, ' ');
|
|
}
|
|
|
|
static GString *
|
|
g_menu_markup_print_string (GString *string,
|
|
GMenuModel *model,
|
|
gint indent,
|
|
gint tabstop)
|
|
{
|
|
gboolean need_nl = FALSE;
|
|
gint i, n;
|
|
|
|
if G_UNLIKELY (string == NULL)
|
|
string = g_string_new (NULL);
|
|
|
|
n = g_menu_model_get_n_items (model);
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GMenuAttributeIter *attr_iter;
|
|
GMenuLinkIter *link_iter;
|
|
GString *contents;
|
|
GString *attrs;
|
|
|
|
attr_iter = g_menu_model_iterate_item_attributes (model, i);
|
|
link_iter = g_menu_model_iterate_item_links (model, i);
|
|
contents = g_string_new (NULL);
|
|
attrs = g_string_new (NULL);
|
|
|
|
while (g_menu_attribute_iter_next (attr_iter))
|
|
{
|
|
const char *name = g_menu_attribute_iter_get_name (attr_iter);
|
|
GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
|
|
|
|
if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
|
|
{
|
|
gchar *str;
|
|
str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
|
|
g_string_append (attrs, str);
|
|
g_free (str);
|
|
}
|
|
|
|
else
|
|
{
|
|
gchar *printed;
|
|
gchar *str;
|
|
const gchar *type;
|
|
|
|
printed = g_variant_print (value, TRUE);
|
|
type = g_variant_type_peek_string (g_variant_get_type (value));
|
|
str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
|
|
indent_string (contents, indent + tabstop);
|
|
g_string_append (contents, str);
|
|
g_free (printed);
|
|
g_free (str);
|
|
}
|
|
|
|
g_variant_unref (value);
|
|
}
|
|
g_object_unref (attr_iter);
|
|
|
|
while (g_menu_link_iter_next (link_iter))
|
|
{
|
|
const gchar *name = g_menu_link_iter_get_name (link_iter);
|
|
GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
|
|
gchar *str;
|
|
|
|
if (contents->str[0])
|
|
g_string_append_c (contents, '\n');
|
|
|
|
str = g_markup_printf_escaped ("<link name='%s'>\n", name);
|
|
indent_string (contents, indent + tabstop);
|
|
g_string_append (contents, str);
|
|
g_free (str);
|
|
|
|
g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
|
|
|
|
indent_string (contents, indent + tabstop);
|
|
g_string_append (contents, "</link>\n");
|
|
g_object_unref (menu);
|
|
}
|
|
g_object_unref (link_iter);
|
|
|
|
if (contents->str[0])
|
|
{
|
|
indent_string (string, indent);
|
|
g_string_append_printf (string, "<item%s>\n", attrs->str);
|
|
g_string_append (string, contents->str);
|
|
indent_string (string, indent);
|
|
g_string_append (string, "</item>\n");
|
|
need_nl = TRUE;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (need_nl)
|
|
g_string_append_c (string, '\n');
|
|
|
|
indent_string (string, indent);
|
|
g_string_append_printf (string, "<item%s/>\n", attrs->str);
|
|
need_nl = FALSE;
|
|
}
|
|
|
|
g_string_free (contents, TRUE);
|
|
g_string_free (attrs, TRUE);
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
/* TestItem {{{1 */
|
|
|
|
/* This utility struct is used by both the RandomMenu and MirrorMenu
|
|
* class implementations below.
|
|
*/
|
|
typedef struct {
|
|
GHashTable *attributes;
|
|
GHashTable *links;
|
|
} TestItem;
|
|
|
|
static TestItem *
|
|
test_item_new (GHashTable *attributes,
|
|
GHashTable *links)
|
|
{
|
|
TestItem *item;
|
|
|
|
item = g_slice_new (TestItem);
|
|
item->attributes = g_hash_table_ref (attributes);
|
|
item->links = g_hash_table_ref (links);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void
|
|
test_item_free (gpointer data)
|
|
{
|
|
TestItem *item = data;
|
|
|
|
g_hash_table_unref (item->attributes);
|
|
g_hash_table_unref (item->links);
|
|
|
|
g_slice_free (TestItem, item);
|
|
}
|
|
|
|
/* RandomMenu {{{1 */
|
|
#define MAX_ITEMS 5
|
|
#define TOP_ORDER 4
|
|
|
|
typedef struct {
|
|
GMenuModel parent_instance;
|
|
|
|
GSequence *items;
|
|
gint order;
|
|
} RandomMenu;
|
|
|
|
typedef GMenuModelClass RandomMenuClass;
|
|
|
|
static GType random_menu_get_type (void);
|
|
G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL)
|
|
|
|
static gboolean
|
|
random_menu_is_mutable (GMenuModel *model)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
random_menu_get_n_items (GMenuModel *model)
|
|
{
|
|
RandomMenu *menu = (RandomMenu *) model;
|
|
|
|
return g_sequence_get_length (menu->items);
|
|
}
|
|
|
|
static void
|
|
random_menu_get_item_attributes (GMenuModel *model,
|
|
gint position,
|
|
GHashTable **table)
|
|
{
|
|
RandomMenu *menu = (RandomMenu *) model;
|
|
TestItem *item;
|
|
|
|
item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
|
|
*table = g_hash_table_ref (item->attributes);
|
|
}
|
|
|
|
static void
|
|
random_menu_get_item_links (GMenuModel *model,
|
|
gint position,
|
|
GHashTable **table)
|
|
{
|
|
RandomMenu *menu = (RandomMenu *) model;
|
|
TestItem *item;
|
|
|
|
item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
|
|
*table = g_hash_table_ref (item->links);
|
|
}
|
|
|
|
static void
|
|
random_menu_finalize (GObject *object)
|
|
{
|
|
RandomMenu *menu = (RandomMenu *) object;
|
|
|
|
g_sequence_free (menu->items);
|
|
|
|
G_OBJECT_CLASS (random_menu_parent_class)
|
|
->finalize (object);
|
|
}
|
|
|
|
static void
|
|
random_menu_init (RandomMenu *menu)
|
|
{
|
|
}
|
|
|
|
static void
|
|
random_menu_class_init (GMenuModelClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
class->is_mutable = random_menu_is_mutable;
|
|
class->get_n_items = random_menu_get_n_items;
|
|
class->get_item_attributes = random_menu_get_item_attributes;
|
|
class->get_item_links = random_menu_get_item_links;
|
|
|
|
object_class->finalize = random_menu_finalize;
|
|
}
|
|
|
|
static RandomMenu * random_menu_new (GRand *rand, gint order);
|
|
|
|
static void
|
|
random_menu_change (RandomMenu *menu,
|
|
GRand *rand)
|
|
{
|
|
gint position, removes, adds;
|
|
GSequenceIter *point;
|
|
gint n_items;
|
|
gint i;
|
|
|
|
n_items = g_sequence_get_length (menu->items);
|
|
|
|
do
|
|
{
|
|
position = g_rand_int_range (rand, 0, n_items + 1);
|
|
removes = g_rand_int_range (rand, 0, n_items - position + 1);
|
|
adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
|
|
}
|
|
while (removes == 0 && adds == 0);
|
|
|
|
point = g_sequence_get_iter_at_pos (menu->items, position + removes);
|
|
|
|
if (removes)
|
|
{
|
|
GSequenceIter *start;
|
|
|
|
start = g_sequence_get_iter_at_pos (menu->items, position);
|
|
g_sequence_remove_range (start, point);
|
|
}
|
|
|
|
for (i = 0; i < adds; i++)
|
|
{
|
|
const gchar *label;
|
|
GHashTable *links;
|
|
GHashTable *attributes;
|
|
|
|
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
|
|
links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
|
|
|
|
if (menu->order > 0 && g_rand_boolean (rand))
|
|
{
|
|
RandomMenu *child;
|
|
const gchar *subtype;
|
|
|
|
child = random_menu_new (rand, menu->order - 1);
|
|
|
|
if (g_rand_boolean (rand))
|
|
{
|
|
subtype = G_MENU_LINK_SECTION;
|
|
/* label some section headers */
|
|
if (g_rand_boolean (rand))
|
|
label = "Section";
|
|
else
|
|
label = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* label all submenus */
|
|
subtype = G_MENU_LINK_SUBMENU;
|
|
label = "Submenu";
|
|
}
|
|
|
|
g_hash_table_insert (links, g_strdup (subtype), child);
|
|
}
|
|
else
|
|
/* label all terminals */
|
|
label = "Menu Item";
|
|
|
|
if (label)
|
|
g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
|
|
|
|
g_sequence_insert_before (point, test_item_new (attributes, links));
|
|
g_hash_table_unref (links);
|
|
g_hash_table_unref (attributes);
|
|
}
|
|
|
|
g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
|
|
}
|
|
|
|
static RandomMenu *
|
|
random_menu_new (GRand *rand,
|
|
gint order)
|
|
{
|
|
RandomMenu *menu;
|
|
|
|
menu = g_object_new (random_menu_get_type (), NULL);
|
|
menu->items = g_sequence_new (test_item_free);
|
|
menu->order = order;
|
|
|
|
random_menu_change (menu, rand);
|
|
|
|
return menu;
|
|
}
|
|
|
|
/* MirrorMenu {{{1 */
|
|
typedef struct {
|
|
GMenuModel parent_instance;
|
|
|
|
GMenuModel *clone_of;
|
|
GSequence *items;
|
|
gulong handler_id;
|
|
} MirrorMenu;
|
|
|
|
typedef GMenuModelClass MirrorMenuClass;
|
|
|
|
static GType mirror_menu_get_type (void);
|
|
G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL)
|
|
|
|
static gboolean
|
|
mirror_menu_is_mutable (GMenuModel *model)
|
|
{
|
|
MirrorMenu *menu = (MirrorMenu *) model;
|
|
|
|
return menu->handler_id != 0;
|
|
}
|
|
|
|
static gint
|
|
mirror_menu_get_n_items (GMenuModel *model)
|
|
{
|
|
MirrorMenu *menu = (MirrorMenu *) model;
|
|
|
|
return g_sequence_get_length (menu->items);
|
|
}
|
|
|
|
static void
|
|
mirror_menu_get_item_attributes (GMenuModel *model,
|
|
gint position,
|
|
GHashTable **table)
|
|
{
|
|
MirrorMenu *menu = (MirrorMenu *) model;
|
|
TestItem *item;
|
|
|
|
item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
|
|
*table = g_hash_table_ref (item->attributes);
|
|
}
|
|
|
|
static void
|
|
mirror_menu_get_item_links (GMenuModel *model,
|
|
gint position,
|
|
GHashTable **table)
|
|
{
|
|
MirrorMenu *menu = (MirrorMenu *) model;
|
|
TestItem *item;
|
|
|
|
item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
|
|
*table = g_hash_table_ref (item->links);
|
|
}
|
|
|
|
static void
|
|
mirror_menu_finalize (GObject *object)
|
|
{
|
|
MirrorMenu *menu = (MirrorMenu *) object;
|
|
|
|
if (menu->handler_id)
|
|
g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
|
|
|
|
g_sequence_free (menu->items);
|
|
g_object_unref (menu->clone_of);
|
|
|
|
G_OBJECT_CLASS (mirror_menu_parent_class)
|
|
->finalize (object);
|
|
}
|
|
|
|
static void
|
|
mirror_menu_init (MirrorMenu *menu)
|
|
{
|
|
}
|
|
|
|
static void
|
|
mirror_menu_class_init (GMenuModelClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
class->is_mutable = mirror_menu_is_mutable;
|
|
class->get_n_items = mirror_menu_get_n_items;
|
|
class->get_item_attributes = mirror_menu_get_item_attributes;
|
|
class->get_item_links = mirror_menu_get_item_links;
|
|
|
|
object_class->finalize = mirror_menu_finalize;
|
|
}
|
|
|
|
static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
|
|
|
|
static void
|
|
mirror_menu_changed (GMenuModel *model,
|
|
gint position,
|
|
gint removed,
|
|
gint added,
|
|
gpointer user_data)
|
|
{
|
|
MirrorMenu *menu = user_data;
|
|
GSequenceIter *point;
|
|
gint i;
|
|
|
|
g_assert (model == menu->clone_of);
|
|
|
|
point = g_sequence_get_iter_at_pos (menu->items, position + removed);
|
|
|
|
if (removed)
|
|
{
|
|
GSequenceIter *start;
|
|
|
|
start = g_sequence_get_iter_at_pos (menu->items, position);
|
|
g_sequence_remove_range (start, point);
|
|
}
|
|
|
|
for (i = position; i < position + added; i++)
|
|
{
|
|
GMenuAttributeIter *attr_iter;
|
|
GMenuLinkIter *link_iter;
|
|
GHashTable *links;
|
|
GHashTable *attributes;
|
|
const gchar *name;
|
|
GMenuModel *child;
|
|
GVariant *value;
|
|
|
|
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
|
|
links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
|
|
|
|
attr_iter = g_menu_model_iterate_item_attributes (model, i);
|
|
while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
|
|
{
|
|
g_hash_table_insert (attributes, g_strdup (name), value);
|
|
}
|
|
g_object_unref (attr_iter);
|
|
|
|
link_iter = g_menu_model_iterate_item_links (model, i);
|
|
while (g_menu_link_iter_get_next (link_iter, &name, &child))
|
|
{
|
|
g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
|
|
g_object_unref (child);
|
|
}
|
|
g_object_unref (link_iter);
|
|
|
|
g_sequence_insert_before (point, test_item_new (attributes, links));
|
|
g_hash_table_unref (attributes);
|
|
g_hash_table_unref (links);
|
|
}
|
|
|
|
g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
|
|
}
|
|
|
|
static MirrorMenu *
|
|
mirror_menu_new (GMenuModel *clone_of)
|
|
{
|
|
MirrorMenu *menu;
|
|
|
|
menu = g_object_new (mirror_menu_get_type (), NULL);
|
|
menu->items = g_sequence_new (test_item_free);
|
|
menu->clone_of = g_object_ref (clone_of);
|
|
|
|
if (g_menu_model_is_mutable (clone_of))
|
|
menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
|
|
mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
|
|
|
|
return menu;
|
|
}
|
|
|
|
/* check_menus_equal(), assert_menus_equal() {{{1 */
|
|
static gboolean
|
|
check_menus_equal (GMenuModel *a,
|
|
GMenuModel *b)
|
|
{
|
|
gboolean equal = TRUE;
|
|
gint a_n, b_n;
|
|
gint i;
|
|
|
|
a_n = g_menu_model_get_n_items (a);
|
|
b_n = g_menu_model_get_n_items (b);
|
|
|
|
if (a_n != b_n)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < a_n; i++)
|
|
{
|
|
GMenuAttributeIter *attr_iter;
|
|
GVariant *a_value, *b_value;
|
|
GMenuLinkIter *link_iter;
|
|
GMenuModel *a_menu, *b_menu;
|
|
const gchar *name;
|
|
|
|
attr_iter = g_menu_model_iterate_item_attributes (a, i);
|
|
while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
|
|
{
|
|
b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
|
|
equal &= b_value && g_variant_equal (a_value, b_value);
|
|
if (b_value)
|
|
g_variant_unref (b_value);
|
|
g_variant_unref (a_value);
|
|
}
|
|
g_object_unref (attr_iter);
|
|
|
|
attr_iter = g_menu_model_iterate_item_attributes (b, i);
|
|
while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
|
|
{
|
|
a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
|
|
equal &= a_value && g_variant_equal (a_value, b_value);
|
|
if (a_value)
|
|
g_variant_unref (a_value);
|
|
g_variant_unref (b_value);
|
|
}
|
|
g_object_unref (attr_iter);
|
|
|
|
link_iter = g_menu_model_iterate_item_links (a, i);
|
|
while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
|
|
{
|
|
b_menu = g_menu_model_get_item_link (b, i, name);
|
|
equal &= b_menu && check_menus_equal (a_menu, b_menu);
|
|
if (b_menu)
|
|
g_object_unref (b_menu);
|
|
g_object_unref (a_menu);
|
|
}
|
|
g_object_unref (link_iter);
|
|
|
|
link_iter = g_menu_model_iterate_item_links (b, i);
|
|
while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
|
|
{
|
|
a_menu = g_menu_model_get_item_link (a, i, name);
|
|
equal &= a_menu && check_menus_equal (a_menu, b_menu);
|
|
if (a_menu)
|
|
g_object_unref (a_menu);
|
|
g_object_unref (b_menu);
|
|
}
|
|
g_object_unref (link_iter);
|
|
}
|
|
|
|
return equal;
|
|
}
|
|
|
|
static void
|
|
assert_menus_equal (GMenuModel *a,
|
|
GMenuModel *b)
|
|
{
|
|
if (!check_menus_equal (a, b))
|
|
{
|
|
GString *string;
|
|
|
|
string = g_string_new ("\n <a>\n");
|
|
g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
|
|
g_string_append (string, " </a>\n\n-------------\n <b>\n");
|
|
g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
|
|
g_string_append (string, " </b>\n");
|
|
g_error ("%s", string->str);
|
|
}
|
|
}
|
|
|
|
static void
|
|
assert_menuitem_equal (GMenuItem *item,
|
|
GMenuModel *model,
|
|
gint index)
|
|
{
|
|
GMenuAttributeIter *attr_iter;
|
|
GMenuLinkIter *link_iter;
|
|
const gchar *name;
|
|
GVariant *value;
|
|
GMenuModel *linked_model;
|
|
|
|
/* NOTE we can't yet test whether item has attributes or links that
|
|
* are not in the model, because there's no iterator API for menu
|
|
* items */
|
|
|
|
attr_iter = g_menu_model_iterate_item_attributes (model, index);
|
|
while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
|
|
{
|
|
GVariant *item_value;
|
|
|
|
item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
|
|
g_assert (item_value && g_variant_equal (item_value, value));
|
|
|
|
g_variant_unref (item_value);
|
|
g_variant_unref (value);
|
|
}
|
|
|
|
link_iter = g_menu_model_iterate_item_links (model, index);
|
|
while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
|
|
{
|
|
GMenuModel *item_linked_model;
|
|
|
|
item_linked_model = g_menu_item_get_link (item, name);
|
|
g_assert (linked_model == item_linked_model);
|
|
|
|
g_object_unref (item_linked_model);
|
|
g_object_unref (linked_model);
|
|
}
|
|
|
|
g_object_unref (attr_iter);
|
|
g_object_unref (link_iter);
|
|
}
|
|
|
|
/* Test cases {{{1 */
|
|
static void
|
|
test_equality (void)
|
|
{
|
|
GRand *randa, *randb;
|
|
guint32 seed;
|
|
gint i;
|
|
|
|
seed = g_test_rand_int ();
|
|
|
|
randa = g_rand_new_with_seed (seed);
|
|
randb = g_rand_new_with_seed (seed);
|
|
|
|
for (i = 0; i < 500; i++)
|
|
{
|
|
RandomMenu *a, *b;
|
|
|
|
a = random_menu_new (randa, TOP_ORDER);
|
|
b = random_menu_new (randb, TOP_ORDER);
|
|
assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
|
|
g_object_unref (b);
|
|
g_object_unref (a);
|
|
}
|
|
|
|
g_rand_int (randa);
|
|
|
|
for (i = 0; i < 500;)
|
|
{
|
|
RandomMenu *a, *b;
|
|
|
|
a = random_menu_new (randa, TOP_ORDER);
|
|
b = random_menu_new (randb, TOP_ORDER);
|
|
if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
|
|
{
|
|
/* by chance, they may really be equal. double check. */
|
|
GString *as, *bs;
|
|
|
|
as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
|
|
bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
|
|
g_assert_cmpstr (as->str, ==, bs->str);
|
|
g_string_free (bs, TRUE);
|
|
g_string_free (as, TRUE);
|
|
|
|
/* we're here because randa and randb just generated equal
|
|
* menus. they may do it again, so throw away randb and make
|
|
* a fresh one.
|
|
*/
|
|
g_rand_free (randb);
|
|
randb = g_rand_new_with_seed (g_rand_int (randa));
|
|
}
|
|
else
|
|
/* make sure we get enough unequals (ie: no GRand failure) */
|
|
i++;
|
|
|
|
g_object_unref (b);
|
|
g_object_unref (a);
|
|
}
|
|
|
|
g_rand_free (randb);
|
|
g_rand_free (randa);
|
|
}
|
|
|
|
static void
|
|
test_random (void)
|
|
{
|
|
RandomMenu *random;
|
|
MirrorMenu *mirror;
|
|
GRand *rand;
|
|
gint i;
|
|
|
|
rand = g_rand_new_with_seed (g_test_rand_int ());
|
|
random = random_menu_new (rand, TOP_ORDER);
|
|
mirror = mirror_menu_new (G_MENU_MODEL (random));
|
|
|
|
for (i = 0; i < 500; i++)
|
|
{
|
|
assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
|
|
random_menu_change (random, rand);
|
|
}
|
|
|
|
g_object_unref (mirror);
|
|
g_object_unref (random);
|
|
|
|
g_rand_free (rand);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GDBusConnection *client_connection;
|
|
GDBusConnection *server_connection;
|
|
GDBusServer *server;
|
|
|
|
GThread *service_thread;
|
|
/* Protects server_connection and service_loop. */
|
|
GMutex service_loop_lock;
|
|
GCond service_loop_cond;
|
|
|
|
GMainLoop *service_loop;
|
|
} PeerConnection;
|
|
|
|
static gboolean
|
|
on_new_connection (GDBusServer *server,
|
|
GDBusConnection *connection,
|
|
gpointer user_data)
|
|
{
|
|
PeerConnection *data = user_data;
|
|
|
|
g_mutex_lock (&data->service_loop_lock);
|
|
data->server_connection = g_object_ref (connection);
|
|
g_cond_broadcast (&data->service_loop_cond);
|
|
g_mutex_unlock (&data->service_loop_lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
create_service_loop (GMainContext *service_context,
|
|
PeerConnection *data)
|
|
{
|
|
g_assert (data->service_loop == NULL);
|
|
g_mutex_lock (&data->service_loop_lock);
|
|
data->service_loop = g_main_loop_new (service_context, FALSE);
|
|
g_cond_broadcast (&data->service_loop_cond);
|
|
g_mutex_unlock (&data->service_loop_lock);
|
|
}
|
|
|
|
static void
|
|
teardown_service_loop (PeerConnection *data)
|
|
{
|
|
g_mutex_lock (&data->service_loop_lock);
|
|
g_clear_pointer (&data->service_loop, g_main_loop_unref);
|
|
g_mutex_unlock (&data->service_loop_lock);
|
|
}
|
|
|
|
static void
|
|
await_service_loop (PeerConnection *data)
|
|
{
|
|
g_mutex_lock (&data->service_loop_lock);
|
|
while (data->service_loop == NULL)
|
|
g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
|
|
g_mutex_unlock (&data->service_loop_lock);
|
|
}
|
|
|
|
static void
|
|
await_server_connection (PeerConnection *data)
|
|
{
|
|
g_mutex_lock (&data->service_loop_lock);
|
|
while (data->server_connection == NULL)
|
|
g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
|
|
g_mutex_unlock (&data->service_loop_lock);
|
|
}
|
|
|
|
static gpointer
|
|
service_thread_func (gpointer user_data)
|
|
{
|
|
PeerConnection *data = user_data;
|
|
GMainContext *service_context;
|
|
GError *error;
|
|
gchar *address;
|
|
gchar *tmpdir;
|
|
GDBusServerFlags flags;
|
|
gchar *guid;
|
|
|
|
service_context = g_main_context_new ();
|
|
g_main_context_push_thread_default (service_context);
|
|
|
|
tmpdir = NULL;
|
|
flags = G_DBUS_SERVER_FLAGS_NONE;
|
|
|
|
#ifdef G_OS_UNIX
|
|
tmpdir = g_dir_make_tmp ("test-dbus-peer-XXXXXX", NULL);
|
|
address = g_strdup_printf ("unix:tmpdir=%s", tmpdir);
|
|
#else
|
|
address = g_strdup ("nonce-tcp:");
|
|
flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
|
|
#endif
|
|
|
|
guid = g_dbus_generate_guid ();
|
|
|
|
error = NULL;
|
|
data->server = g_dbus_server_new_sync (address,
|
|
flags,
|
|
guid,
|
|
NULL,
|
|
NULL,
|
|
&error);
|
|
g_assert_no_error (error);
|
|
g_free (address);
|
|
g_free (guid);
|
|
|
|
g_signal_connect (data->server,
|
|
"new-connection",
|
|
G_CALLBACK (on_new_connection),
|
|
data);
|
|
|
|
g_dbus_server_start (data->server);
|
|
|
|
create_service_loop (service_context, data);
|
|
g_main_loop_run (data->service_loop);
|
|
|
|
g_main_context_pop_thread_default (service_context);
|
|
|
|
teardown_service_loop (data);
|
|
g_main_context_unref (service_context);
|
|
|
|
if (tmpdir)
|
|
{
|
|
g_rmdir (tmpdir);
|
|
g_free (tmpdir);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
peer_connection_up (PeerConnection *data)
|
|
{
|
|
GError *error;
|
|
|
|
memset (data, '\0', sizeof (PeerConnection));
|
|
|
|
g_mutex_init (&data->service_loop_lock);
|
|
g_cond_init (&data->service_loop_cond);
|
|
|
|
/* bring up a server - we run the server in a different thread to
|
|
avoid deadlocks */
|
|
data->service_thread = g_thread_new ("test_dbus_peer",
|
|
service_thread_func,
|
|
data);
|
|
await_service_loop (data);
|
|
g_assert (data->server != NULL);
|
|
|
|
/* bring up a connection and accept it */
|
|
error = NULL;
|
|
data->client_connection =
|
|
g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (data->server),
|
|
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
|
|
NULL, /* GDBusAuthObserver */
|
|
NULL, /* cancellable */
|
|
&error);
|
|
g_assert_no_error (error);
|
|
g_assert (data->client_connection != NULL);
|
|
await_server_connection (data);
|
|
}
|
|
|
|
static void
|
|
peer_connection_down (PeerConnection *data)
|
|
{
|
|
g_object_unref (data->client_connection);
|
|
g_object_unref (data->server_connection);
|
|
|
|
g_dbus_server_stop (data->server);
|
|
g_object_unref (data->server);
|
|
|
|
g_main_loop_quit (data->service_loop);
|
|
g_thread_join (data->service_thread);
|
|
|
|
g_mutex_clear (&data->service_loop_lock);
|
|
g_cond_clear (&data->service_loop_cond);
|
|
}
|
|
|
|
struct roundtrip_state
|
|
{
|
|
RandomMenu *random;
|
|
MirrorMenu *proxy_mirror;
|
|
GDBusMenuModel *proxy;
|
|
GMainLoop *loop;
|
|
GRand *rand;
|
|
gint success;
|
|
gint count;
|
|
};
|
|
|
|
static gboolean
|
|
roundtrip_step (gpointer data)
|
|
{
|
|
struct roundtrip_state *state = data;
|
|
|
|
if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
|
|
check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
|
|
{
|
|
state->success++;
|
|
state->count = 0;
|
|
|
|
if (state->success < 100)
|
|
random_menu_change (state->random, state->rand);
|
|
else
|
|
g_main_loop_quit (state->loop);
|
|
}
|
|
else if (state->count == 100)
|
|
{
|
|
assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
|
|
g_assert_not_reached ();
|
|
}
|
|
else
|
|
state->count++;
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
do_roundtrip (GDBusConnection *exporter_connection,
|
|
GDBusConnection *proxy_connection)
|
|
{
|
|
struct roundtrip_state state;
|
|
guint export_id;
|
|
guint id;
|
|
|
|
state.rand = g_rand_new_with_seed (g_test_rand_int ());
|
|
|
|
state.random = random_menu_new (state.rand, 2);
|
|
export_id = g_dbus_connection_export_menu_model (exporter_connection,
|
|
"/",
|
|
G_MENU_MODEL (state.random),
|
|
NULL);
|
|
state.proxy = g_dbus_menu_model_get (proxy_connection,
|
|
g_dbus_connection_get_unique_name (proxy_connection),
|
|
"/");
|
|
state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
|
|
state.count = 0;
|
|
state.success = 0;
|
|
|
|
id = g_timeout_add (10, roundtrip_step, &state);
|
|
|
|
state.loop = g_main_loop_new (NULL, FALSE);
|
|
g_main_loop_run (state.loop);
|
|
|
|
g_main_loop_unref (state.loop);
|
|
g_source_remove (id);
|
|
g_object_unref (state.proxy);
|
|
g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
|
|
g_object_unref (state.random);
|
|
g_object_unref (state.proxy_mirror);
|
|
g_rand_free (state.rand);
|
|
}
|
|
|
|
static void
|
|
test_dbus_roundtrip (void)
|
|
{
|
|
GDBusConnection *bus;
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
do_roundtrip (bus, bus);
|
|
g_object_unref (bus);
|
|
}
|
|
|
|
static void
|
|
test_dbus_peer_roundtrip (void)
|
|
{
|
|
PeerConnection peer;
|
|
|
|
#ifdef _GLIB_ADDRESS_SANITIZER
|
|
g_test_message ("Ensure that no GCancellableSource are leaked");
|
|
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
|
|
#endif
|
|
|
|
peer_connection_up (&peer);
|
|
do_roundtrip (peer.server_connection, peer.client_connection);
|
|
peer_connection_down (&peer);
|
|
}
|
|
|
|
static gint items_changed_count;
|
|
|
|
static void
|
|
items_changed (GMenuModel *model,
|
|
gint position,
|
|
gint removed,
|
|
gint added,
|
|
gpointer data)
|
|
{
|
|
items_changed_count++;
|
|
}
|
|
|
|
static gboolean
|
|
stop_loop (gpointer data)
|
|
{
|
|
GMainLoop *loop = data;
|
|
|
|
g_main_loop_quit (loop);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
do_subscriptions (GDBusConnection *exporter_connection,
|
|
GDBusConnection *proxy_connection)
|
|
{
|
|
GMenu *menu;
|
|
GDBusMenuModel *proxy;
|
|
GMainLoop *loop;
|
|
GError *error = NULL;
|
|
guint export_id;
|
|
guint timeout_id;
|
|
|
|
timeout_id = add_timeout (60);
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
menu = g_menu_new ();
|
|
|
|
export_id = g_dbus_connection_export_menu_model (exporter_connection,
|
|
"/",
|
|
G_MENU_MODEL (menu),
|
|
&error);
|
|
g_assert_no_error (error);
|
|
|
|
proxy = g_dbus_menu_model_get (proxy_connection,
|
|
g_dbus_connection_get_unique_name (proxy_connection),
|
|
"/");
|
|
items_changed_count = 0;
|
|
g_signal_connect (proxy, "items-changed",
|
|
G_CALLBACK (items_changed), NULL);
|
|
|
|
g_menu_append (menu, "item1", NULL);
|
|
g_menu_append (menu, "item2", NULL);
|
|
g_menu_append (menu, "item3", NULL);
|
|
|
|
g_assert_cmpint (items_changed_count, ==, 0);
|
|
|
|
/* We don't subscribe to change-notification until we look at the items */
|
|
g_timeout_add (100, stop_loop, loop);
|
|
g_main_loop_run (loop);
|
|
|
|
/* Looking at the items triggers subscription */
|
|
g_menu_model_get_n_items (G_MENU_MODEL (proxy));
|
|
|
|
while (items_changed_count < 1)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
/* We get all three items in one batch */
|
|
g_assert_cmpint (items_changed_count, ==, 1);
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
|
|
|
|
/* If we wait, we don't get any more */
|
|
g_timeout_add (100, stop_loop, loop);
|
|
g_main_loop_run (loop);
|
|
g_assert_cmpint (items_changed_count, ==, 1);
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
|
|
|
|
/* Now we're subscribed, we get changes individually */
|
|
g_menu_append (menu, "item4", NULL);
|
|
g_menu_append (menu, "item5", NULL);
|
|
g_menu_append (menu, "item6", NULL);
|
|
g_menu_remove (menu, 0);
|
|
g_menu_remove (menu, 0);
|
|
|
|
while (items_changed_count < 6)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
g_assert_cmpint (items_changed_count, ==, 6);
|
|
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
|
|
|
|
/* After destroying the proxy and waiting a bit, we don't get any more
|
|
* items-changed signals */
|
|
g_object_unref (proxy);
|
|
|
|
g_timeout_add (100, stop_loop, loop);
|
|
g_main_loop_run (loop);
|
|
|
|
g_menu_remove (menu, 0);
|
|
g_menu_remove (menu, 0);
|
|
|
|
g_timeout_add (100, stop_loop, loop);
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (items_changed_count, ==, 6);
|
|
|
|
g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
|
|
g_object_unref (menu);
|
|
|
|
g_main_loop_unref (loop);
|
|
cancel_timeout (timeout_id);
|
|
}
|
|
|
|
static void
|
|
test_dbus_subscriptions (void)
|
|
{
|
|
GDBusConnection *bus;
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
do_subscriptions (bus, bus);
|
|
g_object_unref (bus);
|
|
}
|
|
|
|
static void
|
|
test_dbus_peer_subscriptions (void)
|
|
{
|
|
PeerConnection peer;
|
|
|
|
#ifdef _GLIB_ADDRESS_SANITIZER
|
|
g_test_message ("Ensure that no GCancellableSource are leaked");
|
|
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
|
|
#endif
|
|
|
|
peer_connection_up (&peer);
|
|
do_subscriptions (peer.server_connection, peer.client_connection);
|
|
peer_connection_down (&peer);
|
|
}
|
|
|
|
static void
|
|
test_dbus_export_error_handling (void)
|
|
{
|
|
GRand *rand = NULL;
|
|
RandomMenu *menu = NULL;
|
|
GDBusConnection *bus;
|
|
GError *local_error = NULL;
|
|
guint id1, id2;
|
|
|
|
g_test_summary ("Test that error handling of menu model export failure works");
|
|
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3366");
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
|
|
rand = g_rand_new_with_seed (g_test_rand_int ());
|
|
menu = random_menu_new (rand, 2);
|
|
|
|
id1 = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &local_error);
|
|
g_assert_no_error (local_error);
|
|
g_assert_cmpuint (id1, !=, 0);
|
|
|
|
/* Trigger a failure by trying to export on a path which is already in use */
|
|
id2 = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &local_error);
|
|
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS);
|
|
g_assert_cmpuint (id2, ==, 0);
|
|
g_clear_error (&local_error);
|
|
|
|
g_dbus_connection_unexport_menu_model (bus, id1);
|
|
|
|
while (g_main_context_iteration (NULL, FALSE));
|
|
|
|
g_clear_object (&menu);
|
|
g_rand_free (rand);
|
|
g_clear_object (&bus);
|
|
}
|
|
|
|
static gpointer
|
|
do_modify (gpointer data)
|
|
{
|
|
RandomMenu *menu = data;
|
|
GRand *rand;
|
|
gint i;
|
|
|
|
rand = g_rand_new_with_seed (g_test_rand_int ());
|
|
|
|
for (i = 0; i < 10000; i++)
|
|
{
|
|
random_menu_change (menu, rand);
|
|
}
|
|
|
|
g_rand_free (rand);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gpointer
|
|
do_export (gpointer data)
|
|
{
|
|
GMenuModel *menu = data;
|
|
gint i;
|
|
GDBusConnection *bus;
|
|
gchar *path;
|
|
GError *error = NULL;
|
|
guint id;
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
path = g_strdup_printf ("/%p", data);
|
|
|
|
for (i = 0; i < 10000; i++)
|
|
{
|
|
id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
|
|
g_assert_no_error (error);
|
|
g_dbus_connection_unexport_menu_model (bus, id);
|
|
while (g_main_context_iteration (NULL, FALSE));
|
|
}
|
|
|
|
g_free (path);
|
|
|
|
g_object_unref (bus);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
test_dbus_threaded (void)
|
|
{
|
|
RandomMenu *menu[10];
|
|
GThread *call[10];
|
|
GThread *export[10];
|
|
gint i;
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
GRand *rand = g_rand_new_with_seed (g_test_rand_int ());
|
|
menu[i] = random_menu_new (rand, 2);
|
|
call[i] = g_thread_new ("call", do_modify, menu[i]);
|
|
export[i] = g_thread_new ("export", do_export, menu[i]);
|
|
g_rand_free (rand);
|
|
}
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
g_thread_join (call[i]);
|
|
g_thread_join (export[i]);
|
|
}
|
|
|
|
for (i = 0; i < 10; i++)
|
|
g_object_unref (menu[i]);
|
|
}
|
|
|
|
static void
|
|
test_attributes (void)
|
|
{
|
|
GMenu *menu;
|
|
GMenuItem *item;
|
|
GVariant *v;
|
|
|
|
menu = g_menu_new ();
|
|
|
|
item = g_menu_item_new ("test", NULL);
|
|
g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
|
|
g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
|
|
|
|
g_menu_item_set_attribute (item, "double", "d", 1.5);
|
|
v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
|
|
g_menu_item_set_attribute_value (item, "complex", v);
|
|
g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
|
|
|
|
g_menu_append_item (menu, item);
|
|
|
|
g_menu_item_set_attribute (item, "double", "d", G_PI);
|
|
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
|
|
|
|
v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
|
|
g_variant_unref (v);
|
|
|
|
v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
|
|
g_variant_unref (v);
|
|
|
|
v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
|
|
g_variant_unref (v);
|
|
|
|
v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
|
|
g_variant_unref (v);
|
|
|
|
g_menu_remove_all (menu);
|
|
|
|
g_object_unref (menu);
|
|
g_object_unref (item);
|
|
}
|
|
|
|
static void
|
|
test_attribute_iter (void)
|
|
{
|
|
GMenu *menu;
|
|
GMenuItem *item;
|
|
const gchar *name;
|
|
GVariant *v;
|
|
GMenuAttributeIter *iter;
|
|
GHashTable *found;
|
|
|
|
menu = g_menu_new ();
|
|
|
|
item = g_menu_item_new ("test", NULL);
|
|
g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
|
|
g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
|
|
|
|
g_menu_item_set_attribute (item, "double", "d", 1.5);
|
|
v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
|
|
g_menu_item_set_attribute_value (item, "complex", v);
|
|
g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
|
|
|
|
g_menu_append_item (menu, item);
|
|
|
|
g_menu_item_set_attribute (item, "double", "d", G_PI);
|
|
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
|
|
|
|
found = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
|
|
|
|
iter = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), 0);
|
|
while (g_menu_attribute_iter_get_next (iter, &name, &v))
|
|
g_hash_table_insert (found, g_strdup (name), v);
|
|
g_object_unref (iter);
|
|
|
|
g_assert_cmpint (g_hash_table_size (found), ==, 6);
|
|
|
|
v = g_hash_table_lookup (found, "label");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
|
|
|
|
v = g_hash_table_lookup (found, "boolean");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
|
|
|
|
v = g_hash_table_lookup (found, "string");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
|
|
|
|
v = g_hash_table_lookup (found, "double");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
|
|
|
|
v = g_hash_table_lookup (found, "complex");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
|
|
|
|
v = g_hash_table_lookup (found, "test-123");
|
|
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
|
|
|
|
g_hash_table_unref (found);
|
|
|
|
g_menu_remove_all (menu);
|
|
|
|
g_object_unref (menu);
|
|
g_object_unref (item);
|
|
}
|
|
|
|
static void
|
|
test_links (void)
|
|
{
|
|
GMenu *menu;
|
|
GMenuModel *m;
|
|
GMenuModel *x;
|
|
GMenuItem *item;
|
|
|
|
m = G_MENU_MODEL (g_menu_new ());
|
|
g_menu_append (G_MENU (m), "test", NULL);
|
|
|
|
menu = g_menu_new ();
|
|
|
|
item = g_menu_item_new ("test2", NULL);
|
|
g_menu_item_set_link (item, "submenu", m);
|
|
g_menu_prepend_item (menu, item);
|
|
g_object_unref (item);
|
|
|
|
item = g_menu_item_new ("test1", NULL);
|
|
g_menu_item_set_link (item, "section", m);
|
|
g_menu_insert_item (menu, 0, item);
|
|
g_object_unref (item);
|
|
|
|
item = g_menu_item_new ("test3", NULL);
|
|
g_menu_item_set_link (item, "wallet", m);
|
|
g_menu_insert_item (menu, 1000, item);
|
|
g_object_unref (item);
|
|
|
|
item = g_menu_item_new ("test4", NULL);
|
|
g_menu_item_set_link (item, "purse", m);
|
|
g_menu_item_set_link (item, "purse", NULL);
|
|
g_menu_append_item (menu, item);
|
|
g_object_unref (item);
|
|
|
|
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
|
|
|
|
x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
|
|
g_assert (x == m);
|
|
g_object_unref (x);
|
|
|
|
x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
|
|
g_assert (x == m);
|
|
g_object_unref (x);
|
|
|
|
x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
|
|
g_assert (x == m);
|
|
g_object_unref (x);
|
|
|
|
x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
|
|
g_assert (x == NULL);
|
|
|
|
g_object_unref (m);
|
|
g_object_unref (menu);
|
|
}
|
|
|
|
static void
|
|
test_mutable (void)
|
|
{
|
|
GMenu *menu;
|
|
|
|
menu = g_menu_new ();
|
|
g_menu_append (menu, "test", "test");
|
|
|
|
g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
|
|
g_menu_freeze (menu);
|
|
g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
|
|
|
|
g_object_unref (menu);
|
|
}
|
|
|
|
static void
|
|
test_convenience (void)
|
|
{
|
|
GMenu *m1, *m2;
|
|
GMenu *sub;
|
|
GMenuItem *item;
|
|
|
|
m1 = g_menu_new ();
|
|
m2 = g_menu_new ();
|
|
sub = g_menu_new ();
|
|
|
|
g_menu_prepend (m1, "label1", "do::something");
|
|
g_menu_insert (m2, 0, "label1", "do::something");
|
|
|
|
g_menu_append (m1, "label2", "do::somethingelse");
|
|
g_menu_insert (m2, -1, "label2", "do::somethingelse");
|
|
|
|
g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
|
|
item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
|
|
g_menu_insert_item (m2, 10, item);
|
|
g_object_unref (item);
|
|
|
|
g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
|
|
g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
|
|
|
|
g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
|
|
g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
|
|
|
|
g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
|
|
item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
|
|
g_menu_insert_item (m2, 5, item);
|
|
g_object_unref (item);
|
|
|
|
g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
|
|
g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
|
|
|
|
g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
|
|
g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
|
|
|
|
assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
|
|
|
|
g_object_unref (m1);
|
|
g_object_unref (m2);
|
|
g_object_unref (sub);
|
|
}
|
|
|
|
static void
|
|
test_menuitem (void)
|
|
{
|
|
GMenu *menu;
|
|
GMenu *submenu;
|
|
GMenuItem *item;
|
|
GIcon *icon;
|
|
gboolean b;
|
|
gchar *s;
|
|
|
|
menu = g_menu_new ();
|
|
submenu = g_menu_new ();
|
|
|
|
item = g_menu_item_new ("label", "action");
|
|
g_menu_item_set_attribute (item, "attribute", "b", TRUE);
|
|
g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
|
|
g_menu_append_item (menu, item);
|
|
|
|
icon = g_themed_icon_new ("bla");
|
|
g_menu_item_set_icon (item, icon);
|
|
g_object_unref (icon);
|
|
|
|
g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
|
|
g_assert (b);
|
|
|
|
g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
|
|
g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
|
|
g_assert (b);
|
|
g_assert_cmpstr (s, ==, "string");
|
|
g_free (s);
|
|
|
|
g_object_unref (item);
|
|
|
|
item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
|
|
assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
|
|
g_object_unref (item);
|
|
|
|
g_object_unref (menu);
|
|
g_object_unref (submenu);
|
|
}
|
|
|
|
static GDBusInterfaceInfo *
|
|
org_gtk_Menus_get_interface (void)
|
|
{
|
|
static GDBusInterfaceInfo *interface_info;
|
|
|
|
if (interface_info == NULL)
|
|
{
|
|
GError *error = NULL;
|
|
GDBusNodeInfo *info;
|
|
|
|
info = g_dbus_node_info_new_for_xml ("<node>"
|
|
" <interface name='org.gtk.Menus'>"
|
|
" <method name='Start'>"
|
|
" <arg type='au' name='groups' direction='in'/>"
|
|
" <arg type='a(uuaa{sv})' name='content' direction='out'/>"
|
|
" </method>"
|
|
" <method name='End'>"
|
|
" <arg type='au' name='groups' direction='in'/>"
|
|
" </method>"
|
|
" <signal name='Changed'>"
|
|
" arg type='a(uuuuaa{sv})' name='changes'/>"
|
|
" </signal>"
|
|
" </interface>"
|
|
"</node>", &error);
|
|
if (info == NULL)
|
|
g_error ("%s\n", error->message);
|
|
interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
|
|
g_assert (interface_info != NULL);
|
|
g_dbus_interface_info_ref (interface_info);
|
|
g_dbus_node_info_unref (info);
|
|
}
|
|
|
|
return interface_info;
|
|
}
|
|
|
|
static void
|
|
g_menu_exporter_method_call (GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *method_name,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation,
|
|
gpointer user_data)
|
|
{
|
|
const struct {
|
|
guint position;
|
|
guint removed;
|
|
} data[] = {
|
|
{ -2, 4 },
|
|
{ 0, 3 },
|
|
{ 4, 1 }
|
|
};
|
|
gsize i;
|
|
GError *error = NULL;
|
|
|
|
g_dbus_method_invocation_return_value (invocation, g_variant_new_parsed ("@(a(uuaa{sv})) ([(0, 0, [{ 'label': <'test'> }])],)"));
|
|
|
|
/* invalid signatures */
|
|
g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
|
|
g_variant_new_parsed ("([(1, 2, 3)],)"), &error);
|
|
g_assert_no_error (error);
|
|
|
|
/* add an item at an invalid position */
|
|
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
|
|
g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
|
|
g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [{ 'label': <'test'> }])],)", 0, 0, 2, 0),
|
|
&error);
|
|
g_assert_no_error (error);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (data); i++)
|
|
{
|
|
GVariant *params;
|
|
|
|
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
|
|
params = g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [])],)", 0, 0, data[i].position, data[i].removed);
|
|
g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", params, &error);
|
|
g_assert_no_error (error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
menu_changed (GMenuModel *menu,
|
|
gint position,
|
|
gint removed,
|
|
gint added,
|
|
gpointer user_data)
|
|
{
|
|
unsigned int *counter = user_data;
|
|
|
|
*counter += 1;
|
|
}
|
|
|
|
static void
|
|
test_input_validation (void)
|
|
{
|
|
const GDBusInterfaceVTable vtable = {
|
|
g_menu_exporter_method_call, NULL, NULL, { NULL, }
|
|
};
|
|
GError *error = NULL;
|
|
GDBusConnection *bus;
|
|
GDBusMenuModel *proxy;
|
|
guint id;
|
|
const gchar *bus_name;
|
|
GMainLoop *loop;
|
|
unsigned int n_signal_emissions = 0;
|
|
gulong signal_id;
|
|
|
|
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/861");
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
|
g_assert_no_error (error);
|
|
|
|
id = g_dbus_connection_register_object (bus, "/", org_gtk_Menus_get_interface (),
|
|
&vtable, NULL, NULL, &error);
|
|
g_assert_no_error (error);
|
|
|
|
bus_name = g_dbus_connection_get_unique_name (bus);
|
|
proxy = g_dbus_menu_model_get (bus, bus_name, "/");
|
|
|
|
signal_id = g_signal_connect (proxy, "items-changed", G_CALLBACK (menu_changed), &n_signal_emissions);
|
|
|
|
/* get over laziness */
|
|
g_menu_model_get_n_items (G_MENU_MODEL (proxy));
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
g_timeout_add (100, stop_loop, loop);
|
|
g_main_loop_run (loop);
|
|
|
|
/* "items-changed" should only be emitted for the initial contents of
|
|
* the menu. Subsequent calls are all invalid.
|
|
*/
|
|
g_assert_cmpuint (n_signal_emissions, ==, 1);
|
|
|
|
g_test_assert_expected_messages ();
|
|
|
|
g_main_loop_unref (loop);
|
|
g_dbus_connection_unregister_object (bus, id);
|
|
g_signal_handler_disconnect (proxy, signal_id);
|
|
g_object_unref (proxy);
|
|
g_object_unref (bus);
|
|
}
|
|
|
|
/* Epilogue {{{1 */
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
gboolean ret;
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
session_bus_up ();
|
|
|
|
g_test_add_func ("/gmenu/equality", test_equality);
|
|
g_test_add_func ("/gmenu/random", test_random);
|
|
g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
|
|
g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
|
|
g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
|
|
g_test_add_func ("/gmenu/dbus/peer/roundtrip", test_dbus_peer_roundtrip);
|
|
g_test_add_func ("/gmenu/dbus/peer/subscriptions", test_dbus_peer_subscriptions);
|
|
g_test_add_func ("/gmenu/dbus/export/error-handling", test_dbus_export_error_handling);
|
|
g_test_add_func ("/gmenu/attributes", test_attributes);
|
|
g_test_add_func ("/gmenu/attributes/iterate", test_attribute_iter);
|
|
g_test_add_func ("/gmenu/links", test_links);
|
|
g_test_add_func ("/gmenu/mutable", test_mutable);
|
|
g_test_add_func ("/gmenu/convenience", test_convenience);
|
|
g_test_add_func ("/gmenu/menuitem", test_menuitem);
|
|
g_test_add_func ("/gmenu/input-validation", test_input_validation);
|
|
|
|
ret = g_test_run ();
|
|
|
|
session_bus_down ();
|
|
|
|
return ret;
|
|
}
|
|
/* vim:set foldmethod=marker: */
|