1/* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Alexander Larsson <alexl@redhat.com>
19 */
20
21#include "config.h"
22#include <stdlib.h>
23#include <string.h>
24
25#include "gicon.h"
26#include "gthemedicon.h"
27#include "gfileicon.h"
28#include "gemblemedicon.h"
29#include "gbytesicon.h"
30#include "gfile.h"
31#include "gioerror.h"
32#include "gioenumtypes.h"
33#include "gvfs.h"
34
35#include "glibintl.h"
36
37
38/* There versioning of this is implicit, version 1 would be ".1 " */
39#define G_ICON_SERIALIZATION_MAGIC0 ". "
40
41/**
42 * SECTION:gicon
43 * @short_description: Interface for icons
44 * @include: gio/gio.h
45 *
46 * #GIcon is a very minimal interface for icons. It provides functions
47 * for checking the equality of two icons, hashing of icons and
48 * serializing an icon to and from strings.
49 *
50 * #GIcon does not provide the actual pixmap for the icon as this is out
51 * of GIO's scope, however implementations of #GIcon may contain the name
52 * of an icon (see #GThemedIcon), or the path to an icon (see #GLoadableIcon).
53 *
54 * To obtain a hash of a #GIcon, see g_icon_hash().
55 *
56 * To check if two #GIcons are equal, see g_icon_equal().
57 *
58 * For serializing a #GIcon, use g_icon_serialize() and
59 * g_icon_deserialize().
60 *
61 * If you want to consume #GIcon (for example, in a toolkit) you must
62 * be prepared to handle at least the three following cases:
63 * #GLoadableIcon, #GThemedIcon and #GEmblemedIcon. It may also make
64 * sense to have fast-paths for other cases (like handling #GdkPixbuf
65 * directly, for example) but all compliant #GIcon implementations
66 * outside of GIO must implement #GLoadableIcon.
67 *
68 * If your application or library provides one or more #GIcon
69 * implementations you need to ensure that your new implementation also
70 * implements #GLoadableIcon. Additionally, you must provide an
71 * implementation of g_icon_serialize() that gives a result that is
72 * understood by g_icon_deserialize(), yielding one of the built-in icon
73 * types.
74 **/
75
76typedef GIconIface GIconInterface;
77G_DEFINE_INTERFACE(GIcon, g_icon, G_TYPE_OBJECT)
78
79static void
80g_icon_default_init (GIconInterface *iface)
81{
82}
83
84/**
85 * g_icon_hash:
86 * @icon: (not nullable): #gconstpointer to an icon object.
87 *
88 * Gets a hash for an icon.
89 *
90 * Virtual: hash
91 * Returns: a #guint containing a hash for the @icon, suitable for
92 * use in a #GHashTable or similar data structure.
93 **/
94guint
95g_icon_hash (gconstpointer icon)
96{
97 GIconIface *iface;
98
99 g_return_val_if_fail (G_IS_ICON (icon), 0);
100
101 iface = G_ICON_GET_IFACE (icon);
102
103 return (* iface->hash) ((GIcon *)icon);
104}
105
106/**
107 * g_icon_equal:
108 * @icon1: (nullable): pointer to the first #GIcon.
109 * @icon2: (nullable): pointer to the second #GIcon.
110 *
111 * Checks if two icons are equal.
112 *
113 * Returns: %TRUE if @icon1 is equal to @icon2. %FALSE otherwise.
114 **/
115gboolean
116g_icon_equal (GIcon *icon1,
117 GIcon *icon2)
118{
119 GIconIface *iface;
120
121 if (icon1 == NULL && icon2 == NULL)
122 return TRUE;
123
124 if (icon1 == NULL || icon2 == NULL)
125 return FALSE;
126
127 if (G_TYPE_FROM_INSTANCE (icon1) != G_TYPE_FROM_INSTANCE (icon2))
128 return FALSE;
129
130 iface = G_ICON_GET_IFACE (icon1);
131
132 return (* iface->equal) (icon1, icon2);
133}
134
135static gboolean
136g_icon_to_string_tokenized (GIcon *icon, GString *s)
137{
138 GPtrArray *tokens;
139 gint version;
140 GIconIface *icon_iface;
141 guint i;
142
143 g_return_val_if_fail (icon != NULL, FALSE);
144 g_return_val_if_fail (G_IS_ICON (icon), FALSE);
145
146 icon_iface = G_ICON_GET_IFACE (icon);
147 if (icon_iface->to_tokens == NULL)
148 return FALSE;
149
150 tokens = g_ptr_array_new ();
151 if (!icon_iface->to_tokens (icon, tokens, &version))
152 {
153 g_ptr_array_free (array: tokens, TRUE);
154 return FALSE;
155 }
156
157 /* format: TypeName[.Version] <token_0> .. <token_N-1>
158 version 0 is implicit and can be omitted
159 all the tokens are url escaped to ensure they have no spaces in them */
160
161 g_string_append (string: s, val: g_type_name_from_instance (instance: (GTypeInstance *)icon));
162 if (version != 0)
163 g_string_append_printf (string: s, format: ".%d", version);
164
165 for (i = 0; i < tokens->len; i++)
166 {
167 char *token;
168
169 token = g_ptr_array_index (tokens, i);
170
171 g_string_append_c (s, ' ');
172 /* We really only need to escape spaces here, so allow lots of otherwise reserved chars */
173 g_string_append_uri_escaped (string: s, unescaped: token,
174 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
175
176 g_free (mem: token);
177 }
178
179 g_ptr_array_free (array: tokens, TRUE);
180
181 return TRUE;
182}
183
184/**
185 * g_icon_to_string:
186 * @icon: a #GIcon.
187 *
188 * Generates a textual representation of @icon that can be used for
189 * serialization such as when passing @icon to a different process or
190 * saving it to persistent storage. Use g_icon_new_for_string() to
191 * get @icon back from the returned string.
192 *
193 * The encoding of the returned string is proprietary to #GIcon except
194 * in the following two cases
195 *
196 * - If @icon is a #GFileIcon, the returned string is a native path
197 * (such as `/path/to/my icon.png`) without escaping
198 * if the #GFile for @icon is a native file. If the file is not
199 * native, the returned string is the result of g_file_get_uri()
200 * (such as `sftp://path/to/my%20icon.png`).
201 *
202 * - If @icon is a #GThemedIcon with exactly one name and no fallbacks,
203 * the encoding is simply the name (such as `network-server`).
204 *
205 * Virtual: to_tokens
206 * Returns: (nullable): An allocated NUL-terminated UTF8 string or
207 * %NULL if @icon can't be serialized. Use g_free() to free.
208 *
209 * Since: 2.20
210 */
211gchar *
212g_icon_to_string (GIcon *icon)
213{
214 gchar *ret;
215
216 g_return_val_if_fail (icon != NULL, NULL);
217 g_return_val_if_fail (G_IS_ICON (icon), NULL);
218
219 ret = NULL;
220
221 if (G_IS_FILE_ICON (icon))
222 {
223 GFile *file;
224
225 file = g_file_icon_get_file (G_FILE_ICON (icon));
226 if (g_file_is_native (file))
227 {
228 ret = g_file_get_path (file);
229 if (!g_utf8_validate (str: ret, max_len: -1, NULL))
230 {
231 g_free (mem: ret);
232 ret = NULL;
233 }
234 }
235 else
236 ret = g_file_get_uri (file);
237 }
238 else if (G_IS_THEMED_ICON (icon))
239 {
240 char **names = NULL;
241 gboolean use_default_fallbacks = FALSE;
242
243 g_object_get (G_OBJECT (icon),
244 first_property_name: "names", &names,
245 "use-default-fallbacks", &use_default_fallbacks,
246 NULL);
247 /* Themed icon initialized with a single name and no fallbacks. */
248 if (names != NULL &&
249 names[0] != NULL &&
250 names[0][0] != '.' && /* Allowing icons starting with dot would break G_ICON_SERIALIZATION_MAGIC0 */
251 g_utf8_validate (str: names[0], max_len: -1, NULL) && /* Only return utf8 strings */
252 names[1] == NULL &&
253 ! use_default_fallbacks)
254 ret = g_strdup (str: names[0]);
255
256 g_strfreev (str_array: names);
257 }
258
259 if (ret == NULL)
260 {
261 GString *s;
262
263 s = g_string_new (G_ICON_SERIALIZATION_MAGIC0);
264
265 if (g_icon_to_string_tokenized (icon, s))
266 ret = g_string_free (string: s, FALSE);
267 else
268 g_string_free (string: s, TRUE);
269 }
270
271 return ret;
272}
273
274static GIcon *
275g_icon_new_from_tokens (char **tokens,
276 GError **error)
277{
278 GIcon *icon;
279 char *typename, *version_str;
280 GType type;
281 gpointer klass;
282 GIconIface *icon_iface;
283 gint version;
284 char *endp;
285 int num_tokens;
286 int i;
287
288 icon = NULL;
289 klass = NULL;
290
291 num_tokens = g_strv_length (str_array: tokens);
292
293 if (num_tokens < 1)
294 {
295 g_set_error (err: error,
296 G_IO_ERROR,
297 code: G_IO_ERROR_INVALID_ARGUMENT,
298 _("Wrong number of tokens (%d)"),
299 num_tokens);
300 goto out;
301 }
302
303 typename = tokens[0];
304 version_str = strchr (s: typename, c: '.');
305 if (version_str)
306 {
307 *version_str = 0;
308 version_str += 1;
309 }
310
311
312 type = g_type_from_name (name: tokens[0]);
313 if (type == 0)
314 {
315 g_set_error (err: error,
316 G_IO_ERROR,
317 code: G_IO_ERROR_INVALID_ARGUMENT,
318 _("No type for class name %s"),
319 tokens[0]);
320 goto out;
321 }
322
323 if (!g_type_is_a (type, G_TYPE_ICON))
324 {
325 g_set_error (err: error,
326 G_IO_ERROR,
327 code: G_IO_ERROR_INVALID_ARGUMENT,
328 _("Type %s does not implement the GIcon interface"),
329 tokens[0]);
330 goto out;
331 }
332
333 klass = g_type_class_ref (type);
334 if (klass == NULL)
335 {
336 g_set_error (err: error,
337 G_IO_ERROR,
338 code: G_IO_ERROR_INVALID_ARGUMENT,
339 _("Type %s is not classed"),
340 tokens[0]);
341 goto out;
342 }
343
344 version = 0;
345 if (version_str)
346 {
347 version = strtol (nptr: version_str, endptr: &endp, base: 10);
348 if (endp == NULL || *endp != '\0')
349 {
350 g_set_error (err: error,
351 G_IO_ERROR,
352 code: G_IO_ERROR_INVALID_ARGUMENT,
353 _("Malformed version number: %s"),
354 version_str);
355 goto out;
356 }
357 }
358
359 icon_iface = g_type_interface_peek (instance_class: klass, G_TYPE_ICON);
360 g_assert (icon_iface != NULL);
361
362 if (icon_iface->from_tokens == NULL)
363 {
364 g_set_error (err: error,
365 G_IO_ERROR,
366 code: G_IO_ERROR_INVALID_ARGUMENT,
367 _("Type %s does not implement from_tokens() on the GIcon interface"),
368 tokens[0]);
369 goto out;
370 }
371
372 for (i = 1; i < num_tokens; i++)
373 {
374 char *escaped;
375
376 escaped = tokens[i];
377 tokens[i] = g_uri_unescape_string (escaped_string: escaped, NULL);
378 g_free (mem: escaped);
379 }
380
381 icon = icon_iface->from_tokens (tokens + 1, num_tokens - 1, version, error);
382
383 out:
384 if (klass != NULL)
385 g_type_class_unref (g_class: klass);
386 return icon;
387}
388
389static void
390ensure_builtin_icon_types (void)
391{
392 g_type_ensure (G_TYPE_THEMED_ICON);
393 g_type_ensure (G_TYPE_FILE_ICON);
394 g_type_ensure (G_TYPE_EMBLEMED_ICON);
395 g_type_ensure (G_TYPE_EMBLEM);
396}
397
398/* handles the 'simple' cases: GFileIcon and GThemedIcon */
399static GIcon *
400g_icon_new_for_string_simple (const gchar *str)
401{
402 gchar *scheme;
403 GIcon *icon;
404
405 if (str[0] == '.')
406 return NULL;
407
408 /* handle special GFileIcon and GThemedIcon cases */
409 scheme = g_uri_parse_scheme (uri: str);
410 if (scheme != NULL || str[0] == '/' || str[0] == G_DIR_SEPARATOR)
411 {
412 GFile *location;
413 location = g_file_new_for_commandline_arg (arg: str);
414 icon = g_file_icon_new (file: location);
415 g_object_unref (object: location);
416 }
417 else
418 icon = g_themed_icon_new (iconname: str);
419
420 g_free (mem: scheme);
421
422 return icon;
423}
424
425/**
426 * g_icon_new_for_string:
427 * @str: A string obtained via g_icon_to_string().
428 * @error: Return location for error.
429 *
430 * Generate a #GIcon instance from @str. This function can fail if
431 * @str is not valid - see g_icon_to_string() for discussion.
432 *
433 * If your application or library provides one or more #GIcon
434 * implementations you need to ensure that each #GType is registered
435 * with the type system prior to calling g_icon_new_for_string().
436 *
437 * Returns: (transfer full): An object implementing the #GIcon
438 * interface or %NULL if @error is set.
439 *
440 * Since: 2.20
441 **/
442GIcon *
443g_icon_new_for_string (const gchar *str,
444 GError **error)
445{
446 GIcon *icon = NULL;
447
448 g_return_val_if_fail (str != NULL, NULL);
449
450 icon = g_icon_new_for_string_simple (str);
451 if (icon)
452 return icon;
453
454 ensure_builtin_icon_types ();
455
456 if (g_str_has_prefix (str, G_ICON_SERIALIZATION_MAGIC0))
457 {
458 gchar **tokens;
459
460 /* handle tokenized encoding */
461 tokens = g_strsplit (string: str + sizeof (G_ICON_SERIALIZATION_MAGIC0) - 1, delimiter: " ", max_tokens: 0);
462 icon = g_icon_new_from_tokens (tokens, error);
463 g_strfreev (str_array: tokens);
464 }
465 else
466 g_set_error_literal (err: error,
467 G_IO_ERROR,
468 code: G_IO_ERROR_INVALID_ARGUMENT,
469 _("Can’t handle the supplied version of the icon encoding"));
470
471 return icon;
472}
473
474static GEmblem *
475g_icon_deserialize_emblem (GVariant *value)
476{
477 GVariant *emblem_metadata;
478 GVariant *emblem_data;
479 const gchar *origin_nick;
480 GIcon *emblem_icon;
481 GEmblem *emblem;
482
483 g_variant_get (value, format_string: "(v@a{sv})", &emblem_data, &emblem_metadata);
484
485 emblem = NULL;
486
487 emblem_icon = g_icon_deserialize (value: emblem_data);
488 if (emblem_icon != NULL)
489 {
490 /* Check if we should create it with an origin. */
491 if (g_variant_lookup (dictionary: emblem_metadata, key: "origin", format_string: "&s", &origin_nick))
492 {
493 GEnumClass *origin_class;
494 GEnumValue *origin_value;
495
496 origin_class = g_type_class_ref (type: G_TYPE_EMBLEM_ORIGIN);
497 origin_value = g_enum_get_value_by_nick (enum_class: origin_class, nick: origin_nick);
498 if (origin_value)
499 emblem = g_emblem_new_with_origin (icon: emblem_icon, origin: origin_value->value);
500 g_type_class_unref (g_class: origin_class);
501 }
502
503 /* We didn't create it with an origin, so do it without. */
504 if (emblem == NULL)
505 emblem = g_emblem_new (icon: emblem_icon);
506
507 g_object_unref (object: emblem_icon);
508 }
509
510 g_variant_unref (value: emblem_metadata);
511 g_variant_unref (value: emblem_data);
512
513 return emblem;
514}
515
516static GIcon *
517g_icon_deserialize_emblemed (GVariant *value)
518{
519 GVariantIter *emblems;
520 GVariant *icon_data;
521 GIcon *main_icon;
522 GIcon *icon;
523
524 g_variant_get (value, format_string: "(va(va{sv}))", &icon_data, &emblems);
525 main_icon = g_icon_deserialize (value: icon_data);
526
527 if (main_icon)
528 {
529 GVariant *emblem_data;
530
531 icon = g_emblemed_icon_new (icon: main_icon, NULL);
532
533 while ((emblem_data = g_variant_iter_next_value (iter: emblems)))
534 {
535 GEmblem *emblem;
536
537 emblem = g_icon_deserialize_emblem (value: emblem_data);
538
539 if (emblem)
540 {
541 g_emblemed_icon_add_emblem (G_EMBLEMED_ICON (icon), emblem);
542 g_object_unref (object: emblem);
543 }
544
545 g_variant_unref (value: emblem_data);
546 }
547
548 g_object_unref (object: main_icon);
549 }
550 else
551 icon = NULL;
552
553 g_variant_iter_free (iter: emblems);
554 g_variant_unref (value: icon_data);
555
556 return icon;
557}
558
559/**
560 * g_icon_deserialize:
561 * @value: (transfer none): a #GVariant created with g_icon_serialize()
562 *
563 * Deserializes a #GIcon previously serialized using g_icon_serialize().
564 *
565 * Returns: (nullable) (transfer full): a #GIcon, or %NULL when deserialization fails.
566 *
567 * Since: 2.38
568 */
569GIcon *
570g_icon_deserialize (GVariant *value)
571{
572 const gchar *tag;
573 GVariant *val;
574 GIcon *icon;
575
576 g_return_val_if_fail (value != NULL, NULL);
577 g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING) ||
578 g_variant_is_of_type (value, G_VARIANT_TYPE ("(sv)")), NULL);
579
580 /* Handle some special cases directly so that people can hard-code
581 * stuff into GMenuModel xml files without resorting to using GVariant
582 * text format to describe one of the explicitly-tagged possibilities
583 * below.
584 */
585 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
586 return g_icon_new_for_string_simple (str: g_variant_get_string (value, NULL));
587
588 /* Otherwise, use the tagged union format */
589 g_variant_get (value, format_string: "(&sv)", &tag, &val);
590
591 icon = NULL;
592
593 if (g_str_equal (v1: tag, v2: "file") && g_variant_is_of_type (value: val, G_VARIANT_TYPE_STRING))
594 {
595 GFile *file;
596
597 file = g_file_new_for_commandline_arg (arg: g_variant_get_string (value: val, NULL));
598 icon = g_file_icon_new (file);
599 g_object_unref (object: file);
600 }
601 else if (g_str_equal (v1: tag, v2: "themed") && g_variant_is_of_type (value: val, G_VARIANT_TYPE_STRING_ARRAY))
602 {
603 const gchar **names;
604 gsize size;
605
606 names = g_variant_get_strv (value: val, length: &size);
607 icon = g_themed_icon_new_from_names (iconnames: (gchar **) names, len: size);
608 g_free (mem: names);
609 }
610 else if (g_str_equal (v1: tag, v2: "bytes") && g_variant_is_of_type (value: val, G_VARIANT_TYPE_BYTESTRING))
611 {
612 GBytes *bytes;
613
614 bytes = g_variant_get_data_as_bytes (value: val);
615 icon = g_bytes_icon_new (bytes);
616 g_bytes_unref (bytes);
617 }
618 else if (g_str_equal (v1: tag, v2: "emblem") && g_variant_is_of_type (value: val, G_VARIANT_TYPE ("(va{sv})")))
619 {
620 GEmblem *emblem;
621
622 emblem = g_icon_deserialize_emblem (value: val);
623 if (emblem)
624 icon = G_ICON (emblem);
625 }
626 else if (g_str_equal (v1: tag, v2: "emblemed") && g_variant_is_of_type (value: val, G_VARIANT_TYPE ("(va(va{sv}))")))
627 {
628 icon = g_icon_deserialize_emblemed (value: val);
629 }
630 else if (g_str_equal (v1: tag, v2: "gvfs"))
631 {
632 GVfsClass *class;
633 GVfs *vfs;
634
635 vfs = g_vfs_get_default ();
636 class = G_VFS_GET_CLASS (vfs);
637 if (class->deserialize_icon)
638 icon = (* class->deserialize_icon) (vfs, val);
639 }
640
641 g_variant_unref (value: val);
642
643 return icon;
644}
645
646/**
647 * g_icon_serialize:
648 * @icon: a #GIcon
649 *
650 * Serializes a #GIcon into a #GVariant. An equivalent #GIcon can be retrieved
651 * back by calling g_icon_deserialize() on the returned value.
652 * As serialization will avoid using raw icon data when possible, it only
653 * makes sense to transfer the #GVariant between processes on the same machine,
654 * (as opposed to over the network), and within the same file system namespace.
655 *
656 * Returns: (nullable) (transfer full): a #GVariant, or %NULL when serialization fails. The #GVariant will not be floating.
657 *
658 * Since: 2.38
659 */
660GVariant *
661g_icon_serialize (GIcon *icon)
662{
663 GIconInterface *iface;
664 GVariant *result;
665
666 iface = G_ICON_GET_IFACE (icon);
667
668 if (!iface->serialize)
669 {
670 g_critical ("g_icon_serialize() on icon type '%s' is not implemented", G_OBJECT_TYPE_NAME (icon));
671 return NULL;
672 }
673
674 result = (* iface->serialize) (icon);
675
676 if (result)
677 {
678 g_variant_take_ref (value: result);
679
680 if (!g_variant_is_of_type (value: result, G_VARIANT_TYPE ("(sv)")))
681 {
682 g_critical ("g_icon_serialize() on icon type '%s' returned GVariant of type '%s' but it must return "
683 "one with type '(sv)'", G_OBJECT_TYPE_NAME (icon), g_variant_get_type_string (result));
684 g_variant_unref (value: result);
685 result = NULL;
686 }
687 }
688
689 return result;
690}
691

source code of gtk/subprojects/glib/gio/gicon.c