]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-ui-utils.c
272c425130559ac60bc66ae84f88620ec9665eaf
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
1 /*
2  * Copyright (C) 2002-2007 Imendio AB
3  * Copyright (C) 2007-2010 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
25  *          Travis Reitter <travis.reitter@collabora.co.uk>
26  *
27  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
28  *          Paolo Maggi
29  *          Jeroen Zwartepoorte
30  */
31
32 #include "config.h"
33 #include "empathy-ui-utils.h"
34
35 #include <X11/Xatom.h>
36 #include <gdk/gdkx.h>
37 #include <glib/gi18n-lib.h>
38 #include <gio/gdesktopappinfo.h>
39 #include <tp-account-widgets/tpaw-live-search.h>
40
41 #include "empathy-ft-factory.h"
42 #include "empathy-images.h"
43 #include "empathy-utils.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
46 #include "empathy-debug.h"
47
48 void
49 empathy_gtk_init (void)
50 {
51   static gboolean initialized = FALSE;
52
53   if (initialized)
54     return;
55
56   empathy_init ();
57
58   gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
59       PKGDATADIR G_DIR_SEPARATOR_S "icons");
60
61   /* Add icons from source dir if available */
62   if (g_getenv ("EMPATHY_SRCDIR") != NULL)
63     {
64       gchar *path;
65
66       path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "data",
67           "icons", "local-copy", NULL);
68
69       if (g_file_test (path, G_FILE_TEST_EXISTS))
70         gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), path);
71
72       g_free (path);
73     }
74
75   initialized = TRUE;
76 }
77
78 const gchar *
79 empathy_icon_name_for_presence (TpConnectionPresenceType presence)
80 {
81   switch (presence)
82     {
83       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
84         return EMPATHY_IMAGE_AVAILABLE;
85       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
86         return EMPATHY_IMAGE_BUSY;
87       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
88         return EMPATHY_IMAGE_AWAY;
89       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
90         if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
91                    EMPATHY_IMAGE_EXT_AWAY))
92           return EMPATHY_IMAGE_EXT_AWAY;
93
94         /* The 'extended-away' icon is not an official one so we fallback to
95          * idle if it's not implemented */
96         return EMPATHY_IMAGE_IDLE;
97       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
98         if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
99                    EMPATHY_IMAGE_HIDDEN))
100           return EMPATHY_IMAGE_HIDDEN;
101
102         /* The 'hidden' icon is not an official one so we fallback to offline if
103          * it's not implemented */
104         return EMPATHY_IMAGE_OFFLINE;
105       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
106       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
107         return EMPATHY_IMAGE_OFFLINE;
108       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
109         return EMPATHY_IMAGE_PENDING;
110       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
111       default:
112         return NULL;
113     }
114
115   return NULL;
116 }
117
118 const gchar *
119 empathy_icon_name_for_contact (EmpathyContact *contact)
120 {
121   TpConnectionPresenceType presence;
122
123   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
124       EMPATHY_IMAGE_OFFLINE);
125
126   presence = empathy_contact_get_presence (contact);
127   return empathy_icon_name_for_presence (presence);
128 }
129
130 const gchar *
131 empathy_icon_name_for_individual (FolksIndividual *individual)
132 {
133   FolksPresenceType folks_presence;
134   TpConnectionPresenceType presence;
135
136   folks_presence = folks_presence_details_get_presence_type (
137       FOLKS_PRESENCE_DETAILS (individual));
138   presence = empathy_folks_presence_type_to_tp (folks_presence);
139
140   return empathy_icon_name_for_presence (presence);
141 }
142
143 const gchar *
144 empathy_protocol_name_for_contact (EmpathyContact *contact)
145 {
146   TpAccount *account;
147
148   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
149
150   account = empathy_contact_get_account (contact);
151   if (account == NULL)
152     return NULL;
153
154   return tp_account_get_icon_name (account);
155 }
156
157 GdkPixbuf *
158 empathy_pixbuf_from_data (gchar *data,
159     gsize data_size)
160 {
161   return empathy_pixbuf_from_data_and_mime (data, data_size, NULL);
162 }
163
164 GdkPixbuf *
165 empathy_pixbuf_from_data_and_mime (gchar *data,
166            gsize data_size,
167            gchar **mime_type)
168 {
169   GdkPixbufLoader *loader;
170   GdkPixbufFormat *format;
171   GdkPixbuf *pixbuf = NULL;
172   gchar **mime_types;
173   GError *error = NULL;
174
175   if (!data)
176     return NULL;
177
178   loader = gdk_pixbuf_loader_new ();
179   if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error))
180     {
181       DEBUG ("Failed to write to pixbuf loader: %s",
182         error ? error->message : "No error given");
183       goto out;
184     }
185
186   if (!gdk_pixbuf_loader_close (loader, &error))
187     {
188       DEBUG ("Failed to close pixbuf loader: %s",
189         error ? error->message : "No error given");
190       goto out;
191     }
192
193   pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
194   if (pixbuf)
195     {
196       g_object_ref (pixbuf);
197
198       if (mime_type != NULL)
199         {
200           format = gdk_pixbuf_loader_get_format (loader);
201           mime_types = gdk_pixbuf_format_get_mime_types (format);
202
203           *mime_type = g_strdup (*mime_types);
204           if (mime_types[1] != NULL)
205             DEBUG ("Loader supports more than one mime "
206               "type! Picking the first one, %s",
207               *mime_type);
208
209           g_strfreev (mime_types);
210         }
211     }
212
213 out:
214   g_clear_error (&error);
215   g_object_unref (loader);
216
217   return pixbuf;
218 }
219
220 struct SizeData
221 {
222   gint width;
223   gint height;
224   gboolean preserve_aspect_ratio;
225 };
226
227 static void
228 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
229     int width,
230     int height,
231     struct SizeData *data)
232 {
233   g_return_if_fail (width > 0 && height > 0);
234
235   if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0))
236     {
237       if (width < data->width && height < data->height)
238         {
239           width = width;
240           height = height;
241         }
242
243       if (data->width < 0)
244         {
245           width = width * (double) data->height / (gdouble) height;
246           height = data->height;
247         }
248       else if (data->height < 0)
249         {
250           height = height * (double) data->width / (double) width;
251           width = data->width;
252         }
253       else if ((double) height * (double) data->width >
254            (double) width * (double) data->height)
255         {
256           width = 0.5 + (double) width * (double) data->height / (double) height;
257           height = data->height;
258         }
259       else
260         {
261           height = 0.5 + (double) height * (double) data->width / (double) width;
262           width = data->width;
263         }
264     }
265   else
266     {
267       if (data->width > 0)
268         width = data->width;
269
270       if (data->height > 0)
271         height = data->height;
272     }
273
274   gdk_pixbuf_loader_set_size (loader, width, height);
275 }
276
277 static void
278 empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
279 {
280   gint width, height, rowstride;
281   guchar *pixels;
282
283   width = gdk_pixbuf_get_width (pixbuf);
284   height = gdk_pixbuf_get_height (pixbuf);
285   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
286   pixels = gdk_pixbuf_get_pixels (pixbuf);
287
288   if (width < 6 || height < 6)
289     return;
290
291   /* Top left */
292   pixels[3] = 0;
293   pixels[7] = 0x80;
294   pixels[11] = 0xC0;
295   pixels[rowstride + 3] = 0x80;
296   pixels[rowstride * 2 + 3] = 0xC0;
297
298   /* Top right */
299   pixels[width * 4 - 1] = 0;
300   pixels[width * 4 - 5] = 0x80;
301   pixels[width * 4 - 9] = 0xC0;
302   pixels[rowstride + (width * 4) - 1] = 0x80;
303   pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
304
305   /* Bottom left */
306   pixels[(height - 1) * rowstride + 3] = 0;
307   pixels[(height - 1) * rowstride + 7] = 0x80;
308   pixels[(height - 1) * rowstride + 11] = 0xC0;
309   pixels[(height - 2) * rowstride + 3] = 0x80;
310   pixels[(height - 3) * rowstride + 3] = 0xC0;
311
312   /* Bottom right */
313   pixels[height * rowstride - 1] = 0;
314   pixels[(height - 1) * rowstride - 1] = 0x80;
315   pixels[(height - 2) * rowstride - 1] = 0xC0;
316   pixels[height * rowstride - 5] = 0x80;
317   pixels[height * rowstride - 9] = 0xC0;
318 }
319
320 static gboolean
321 empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
322 {
323   gint height, rowstride, i;
324   guchar *pixels;
325   guchar *row;
326
327   height = gdk_pixbuf_get_height (pixbuf);
328   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
329   pixels = gdk_pixbuf_get_pixels (pixbuf);
330
331   row = pixels;
332   for (i = 3; i < rowstride; i+=4)
333     if (row[i] < 0xfe)
334       return FALSE;
335
336   for (i = 1; i < height - 1; i++)
337     {
338       row = pixels + (i*rowstride);
339       if (row[3] < 0xfe || row[rowstride-1] < 0xfe)
340         return FALSE;
341     }
342
343   row = pixels + ((height-1) * rowstride);
344   for (i = 3; i < rowstride; i+=4)
345     if (row[i] < 0xfe)
346       return FALSE;
347
348   return TRUE;
349 }
350
351 static GdkPixbuf *
352 pixbuf_round_corners (GdkPixbuf *pixbuf)
353 {
354   GdkPixbuf *result;
355
356   if (!gdk_pixbuf_get_has_alpha (pixbuf))
357     {
358       result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
359           gdk_pixbuf_get_width (pixbuf),
360           gdk_pixbuf_get_height (pixbuf));
361
362       gdk_pixbuf_copy_area (pixbuf, 0, 0,
363           gdk_pixbuf_get_width (pixbuf),
364           gdk_pixbuf_get_height (pixbuf),
365           result,
366           0, 0);
367     }
368   else
369     {
370       result = g_object_ref (pixbuf);
371     }
372
373   if (empathy_gdk_pixbuf_is_opaque (result))
374     empathy_avatar_pixbuf_roundify (result);
375
376   return result;
377 }
378
379 static GdkPixbuf *
380 avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
381 {
382   GdkPixbuf *pixbuf;
383
384   pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
385
386   return pixbuf_round_corners (pixbuf);
387 }
388
389 static GdkPixbuf *
390 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
391     gint width,
392     gint height)
393 {
394   GdkPixbuf *pixbuf;
395   GdkPixbufLoader *loader;
396   struct SizeData data;
397   GError *error = NULL;
398
399   if (!avatar)
400     return NULL;
401
402   data.width = width;
403   data.height = height;
404   data.preserve_aspect_ratio = TRUE;
405
406   loader = gdk_pixbuf_loader_new ();
407
408   g_signal_connect (loader, "size-prepared",
409       G_CALLBACK (pixbuf_from_avatar_size_prepared_cb), &data);
410
411   if (avatar->len == 0)
412     {
413       g_warning ("Avatar has 0 length");
414       return NULL;
415     }
416   else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error))
417     {
418       g_warning ("Couldn't write avatar image:%p with "
419           "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
420           avatar->data, avatar->len, error->message);
421
422       g_error_free (error);
423       return NULL;
424     }
425
426   gdk_pixbuf_loader_close (loader, NULL);
427   pixbuf = avatar_pixbuf_from_loader (loader);
428
429   g_object_unref (loader);
430
431   return pixbuf;
432 }
433
434 GdkPixbuf *
435 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
436     gint width,
437     gint height)
438 {
439   EmpathyAvatar *avatar;
440
441   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
442
443   avatar = empathy_contact_get_avatar (contact);
444
445   return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
446 }
447
448 typedef struct
449 {
450   GSimpleAsyncResult *result;
451   guint width;
452   guint height;
453   GCancellable *cancellable;
454 } PixbufAvatarFromIndividualClosure;
455
456 static PixbufAvatarFromIndividualClosure *
457 pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual,
458     GSimpleAsyncResult *result,
459     gint width,
460     gint height,
461     GCancellable *cancellable)
462 {
463   PixbufAvatarFromIndividualClosure *closure;
464
465   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
466   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
467
468   closure = g_slice_new0 (PixbufAvatarFromIndividualClosure);
469   closure->result = g_object_ref (result);
470   closure->width = width;
471   closure->height = height;
472
473   if (cancellable != NULL)
474     closure->cancellable = g_object_ref (cancellable);
475
476   return closure;
477 }
478
479 static void
480 pixbuf_avatar_from_individual_closure_free (
481     PixbufAvatarFromIndividualClosure *closure)
482 {
483   g_clear_object (&closure->cancellable);
484   g_object_unref (closure->result);
485   g_slice_free (PixbufAvatarFromIndividualClosure, closure);
486 }
487
488 /**
489  * @pixbuf: (transfer all)
490  *
491  * Return: (transfer all)
492  */
493 static GdkPixbuf *
494 transform_pixbuf (GdkPixbuf *pixbuf)
495 {
496   GdkPixbuf *result;
497
498   result = pixbuf_round_corners (pixbuf);
499   g_object_unref (pixbuf);
500
501   return result;
502 }
503
504 static void
505 avatar_icon_load_cb (GObject *object,
506     GAsyncResult *result,
507     gpointer user_data)
508 {
509   GLoadableIcon *icon = G_LOADABLE_ICON (object);
510   PixbufAvatarFromIndividualClosure *closure = user_data;
511   GInputStream *stream;
512   GError *error = NULL;
513   GdkPixbuf *pixbuf;
514   GdkPixbuf *final_pixbuf;
515
516   stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
517   if (error != NULL)
518     {
519       DEBUG ("Failed to open avatar stream: %s", error->message);
520       g_simple_async_result_set_from_error (closure->result, error);
521       goto out;
522     }
523
524   pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream,
525       closure->width, closure->height, TRUE, closure->cancellable, &error);
526
527   g_object_unref (stream);
528
529   if (pixbuf == NULL)
530     {
531       DEBUG ("Failed to read avatar: %s", error->message);
532       g_simple_async_result_set_from_error (closure->result, error);
533       goto out;
534     }
535
536   final_pixbuf = transform_pixbuf (pixbuf);
537
538   /* Pass ownership of final_pixbuf to the result */
539   g_simple_async_result_set_op_res_gpointer (closure->result,
540       final_pixbuf, g_object_unref);
541
542 out:
543   g_simple_async_result_complete (closure->result);
544
545   g_clear_error (&error);
546   pixbuf_avatar_from_individual_closure_free (closure);
547 }
548
549 void
550 empathy_pixbuf_avatar_from_individual_scaled_async (
551     FolksIndividual *individual,
552     gint width,
553     gint height,
554     GCancellable *cancellable,
555     GAsyncReadyCallback callback,
556     gpointer user_data)
557 {
558   GLoadableIcon *avatar_icon;
559   GSimpleAsyncResult *result;
560   PixbufAvatarFromIndividualClosure *closure;
561
562   result = g_simple_async_result_new (G_OBJECT (individual),
563       callback, user_data, empathy_pixbuf_avatar_from_individual_scaled_async);
564
565   avatar_icon = folks_avatar_details_get_avatar (
566       FOLKS_AVATAR_DETAILS (individual));
567
568   if (avatar_icon == NULL)
569     {
570       g_simple_async_result_set_error (result, G_IO_ERROR,
571         G_IO_ERROR_NOT_FOUND, "no avatar found");
572
573       g_simple_async_result_complete (result);
574       g_object_unref (result);
575       return;
576     }
577
578   closure = pixbuf_avatar_from_individual_closure_new (individual, result,
579       width, height, cancellable);
580
581   g_return_if_fail (closure != NULL);
582
583   g_loadable_icon_load_async (avatar_icon, width, cancellable,
584       avatar_icon_load_cb, closure);
585
586   g_object_unref (result);
587 }
588
589 /* Return a ref on the GdkPixbuf */
590 GdkPixbuf *
591 empathy_pixbuf_avatar_from_individual_scaled_finish (
592     FolksIndividual *individual,
593     GAsyncResult *result,
594     GError **error)
595 {
596   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
597   gboolean result_valid;
598   GdkPixbuf *pixbuf;
599
600   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
601   g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
602
603   if (g_simple_async_result_propagate_error (simple, error))
604     return NULL;
605
606   result_valid = g_simple_async_result_is_valid (result,
607       G_OBJECT (individual),
608       empathy_pixbuf_avatar_from_individual_scaled_async);
609
610   g_return_val_if_fail (result_valid, NULL);
611
612   pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
613   return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
614 }
615
616 GdkPixbuf *
617 empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
618     gboolean show_protocol)
619 {
620   const gchar *icon_name;
621
622   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
623
624   icon_name = empathy_icon_name_for_contact (contact);
625
626   if (icon_name == NULL)
627     return NULL;
628
629   return empathy_pixbuf_contact_status_icon_with_icon_name (contact,
630       icon_name, show_protocol);
631 }
632
633 static GdkPixbuf * empathy_pixbuf_protocol_from_contact_scaled (
634     EmpathyContact *contact,
635     gint width,
636     gint height);
637
638 GdkPixbuf *
639 empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
640     const gchar *icon_name,
641     gboolean show_protocol)
642 {
643   GdkPixbuf *pix_status;
644   GdkPixbuf *pix_protocol;
645   gchar *icon_filename;
646   gint height, width;
647   gint numerator, denominator;
648
649   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
650       (show_protocol == FALSE), NULL);
651   g_return_val_if_fail (icon_name != NULL, NULL);
652
653   numerator = 3;
654   denominator = 4;
655
656   icon_filename = empathy_filename_from_icon_name (icon_name,
657       GTK_ICON_SIZE_MENU);
658
659   if (icon_filename == NULL)
660     {
661       DEBUG ("icon name: %s could not be found\n", icon_name);
662       return NULL;
663     }
664
665   pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
666
667   if (pix_status == NULL)
668     {
669       DEBUG ("Could not open icon %s\n", icon_filename);
670       g_free (icon_filename);
671       return NULL;
672     }
673
674   g_free (icon_filename);
675
676   if (!show_protocol)
677     return pix_status;
678
679   height = gdk_pixbuf_get_height (pix_status);
680   width = gdk_pixbuf_get_width (pix_status);
681
682   pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
683       width * numerator / denominator,
684       height * numerator / denominator);
685
686   if (pix_protocol == NULL)
687     return pix_status;
688
689   gdk_pixbuf_composite (pix_protocol, pix_status,
690       0, height - height * numerator / denominator,
691       width * numerator / denominator, height * numerator / denominator,
692       0, height - height * numerator / denominator,
693       1, 1,
694       GDK_INTERP_BILINEAR, 255);
695
696   g_object_unref (pix_protocol);
697
698   return pix_status;
699 }
700
701 static GdkPixbuf *
702 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
703     gint width,
704     gint height)
705 {
706   TpAccount *account;
707   gchar *filename;
708   GdkPixbuf *pixbuf = NULL;
709
710   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
711
712   account = empathy_contact_get_account (contact);
713   filename = empathy_filename_from_icon_name (
714       tp_account_get_icon_name (account), GTK_ICON_SIZE_MENU);
715
716   if (filename != NULL)
717     {
718       pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
719       g_free (filename);
720     }
721
722   return pixbuf;
723 }
724
725 GdkPixbuf *
726 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf,
727     gint max_size)
728 {
729   gint width, height;
730   gdouble factor;
731
732   width = gdk_pixbuf_get_width (pixbuf);
733   height = gdk_pixbuf_get_height (pixbuf);
734
735   if (width > 0 && (width > max_size || height > max_size))
736     {
737       factor = (gdouble) max_size / MAX (width, height);
738
739       width = width * factor;
740       height = height * factor;
741
742       return gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_HYPER);
743     }
744
745   return g_object_ref (pixbuf);
746 }
747
748 GdkPixbuf *
749 empathy_pixbuf_from_icon_name_sized (const gchar *icon_name,
750     gint size)
751 {
752   GtkIconTheme *theme;
753   GdkPixbuf *pixbuf;
754   GError *error = NULL;
755
756   if (!icon_name)
757     return NULL;
758
759   theme = gtk_icon_theme_get_default ();
760
761   pixbuf = gtk_icon_theme_load_icon (theme, icon_name, size, 0, &error);
762
763   if (error)
764     {
765       DEBUG ("Error loading icon: %s", error->message);
766       g_clear_error (&error);
767     }
768
769   return pixbuf;
770 }
771
772 GdkPixbuf *
773 empathy_pixbuf_from_icon_name (const gchar *icon_name,
774     GtkIconSize  icon_size)
775 {
776   gint w, h;
777   gint size = 48;
778
779   if (!icon_name)
780     return NULL;
781
782   if (gtk_icon_size_lookup (icon_size, &w, &h))
783     size = (w + h) / 2;
784
785   return empathy_pixbuf_from_icon_name_sized (icon_name, size);
786 }
787
788 gchar *
789 empathy_filename_from_icon_name (const gchar *icon_name,
790     GtkIconSize icon_size)
791 {
792   GtkIconTheme *icon_theme;
793   GtkIconInfo *icon_info;
794   gint w, h;
795   gint size = 48;
796   gchar *ret;
797
798   icon_theme = gtk_icon_theme_get_default ();
799
800   if (gtk_icon_size_lookup (icon_size, &w, &h))
801     size = (w + h) / 2;
802
803   icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
804   if (icon_info == NULL)
805     return NULL;
806
807   ret = g_strdup (gtk_icon_info_get_filename (icon_info));
808   gtk_icon_info_free (icon_info);
809
810   return ret;
811 }
812
813 /** empathy_make_absolute_url_len:
814  * @url: an url
815  * @len: a length
816  *
817  * Same as #empathy_make_absolute_url but for a limited string length
818  */
819 gchar *
820 empathy_make_absolute_url_len (const gchar *url,
821     guint len)
822 {
823   g_return_val_if_fail (url != NULL, NULL);
824
825   if (g_str_has_prefix (url, "help:") ||
826       g_str_has_prefix (url, "mailto:") ||
827       strstr (url, ":/"))
828     return g_strndup (url, len);
829
830   if (strstr (url, "@"))
831     return g_strdup_printf ("mailto:%.*s", len, url);
832
833   return g_strdup_printf ("http://%.*s", len, url);
834 }
835
836 /** empathy_make_absolute_url:
837  * @url: an url
838  *
839  * The URL opening code can't handle schemeless strings, so we try to be
840  * smart and add http if there is no scheme or doesn't look like a mail
841  * address. This should work in most cases, and let us click on strings
842  * like "www.gnome.org".
843  *
844  * Returns: a newly allocated url with proper mailto: or http:// prefix, use
845  * g_free when your are done with it
846  */
847 gchar *
848 empathy_make_absolute_url (const gchar *url)
849 {
850   return empathy_make_absolute_url_len (url, strlen (url));
851 }
852
853 void
854 empathy_url_show (GtkWidget *parent,
855       const char *url)
856 {
857   gchar  *real_url;
858   GError *error = NULL;
859
860   g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
861   g_return_if_fail (url != NULL);
862
863   real_url = empathy_make_absolute_url (url);
864
865   gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
866       gtk_get_current_event_time (), &error);
867
868   if (error)
869     {
870       GtkWidget *dialog;
871
872       dialog = gtk_message_dialog_new (NULL, 0,
873           GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
874           _("Unable to open URI"));
875
876       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
877           "%s", error->message);
878
879       g_signal_connect (dialog, "response",
880             G_CALLBACK (gtk_widget_destroy), NULL);
881
882       gtk_window_present (GTK_WINDOW (dialog));
883
884       g_clear_error (&error);
885     }
886
887   g_free (real_url);
888 }
889
890 void
891 empathy_send_file (EmpathyContact *contact,
892     GFile *file)
893 {
894   EmpathyFTFactory *factory;
895   GtkRecentManager *manager;
896   gchar *uri;
897
898   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
899   g_return_if_fail (G_IS_FILE (file));
900
901   factory = empathy_ft_factory_dup_singleton ();
902
903   empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
904       empathy_get_current_action_time ());
905
906   uri = g_file_get_uri (file);
907   manager = gtk_recent_manager_get_default ();
908   gtk_recent_manager_add_item (manager, uri);
909   g_free (uri);
910
911   g_object_unref (factory);
912 }
913
914 void
915 empathy_send_file_from_uri_list (EmpathyContact *contact,
916     const gchar *uri_list)
917 {
918   const gchar *nl;
919   GFile *file;
920
921   /* Only handle a single file for now. It would be wicked cool to be
922      able to do multiple files, offering to zip them or whatever like
923      nautilus-sendto does. Note that text/uri-list is defined to have
924      each line terminated by \r\n, but we can be tolerant of applications
925      that only use \n or don't terminate single-line entries.
926   */
927   nl = strstr (uri_list, "\r\n");
928   if (!nl)
929     nl = strchr (uri_list, '\n');
930
931   if (nl)
932     {
933       gchar *uri = g_strndup (uri_list, nl - uri_list);
934       file = g_file_new_for_uri (uri);
935       g_free (uri);
936     }
937   else
938     {
939       file = g_file_new_for_uri (uri_list);
940     }
941
942   empathy_send_file (contact, file);
943
944   g_object_unref (file);
945 }
946
947 static void
948 file_manager_send_file_response_cb (GtkDialog *widget,
949     gint response_id,
950     EmpathyContact *contact)
951 {
952   GFile *file;
953
954   if (response_id == GTK_RESPONSE_OK)
955     {
956       file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
957
958       empathy_send_file (contact, file);
959
960       g_object_unref (file);
961     }
962
963   g_object_unref (contact);
964   gtk_widget_destroy (GTK_WIDGET (widget));
965 }
966
967 static gboolean
968 filter_cb (const GtkFileFilterInfo *filter_info,
969     gpointer data)
970 {
971   /* filter out socket files */
972   return tp_strdiff (filter_info->mime_type, "inode/socket");
973 }
974
975 static GtkFileFilter *
976 create_file_filter (void)
977 {
978   GtkFileFilter *filter;
979
980   filter = gtk_file_filter_new ();
981
982   gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_MIME_TYPE, filter_cb,
983     NULL, NULL);
984
985   return filter;
986 }
987
988 void
989 empathy_send_file_with_file_chooser (EmpathyContact *contact)
990 {
991   GtkWidget *widget;
992   GtkWidget *button;
993
994   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
995
996   DEBUG ("Creating selection file chooser");
997
998   widget = gtk_file_chooser_dialog_new (_("Select a file"), NULL,
999       GTK_FILE_CHOOSER_ACTION_OPEN,
1000       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1001       NULL);
1002
1003   /* send button */
1004   button = gtk_button_new_with_mnemonic (_("_Send"));
1005   gtk_button_set_image (GTK_BUTTON (button),
1006       gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1007         GTK_ICON_SIZE_BUTTON));
1008   gtk_widget_show (button);
1009
1010   gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
1011       GTK_RESPONSE_OK);
1012
1013   gtk_widget_set_can_default (button, TRUE);
1014   gtk_dialog_set_default_response (GTK_DIALOG (widget),
1015       GTK_RESPONSE_OK);
1016
1017   gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
1018
1019   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
1020       g_get_home_dir ());
1021
1022   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget),
1023       create_file_filter ());
1024
1025   g_signal_connect (widget, "response",
1026       G_CALLBACK (file_manager_send_file_response_cb), g_object_ref (contact));
1027
1028   gtk_widget_show (widget);
1029 }
1030
1031 static void
1032 file_manager_receive_file_response_cb (GtkDialog *dialog,
1033     GtkResponseType response,
1034     EmpathyFTHandler *handler)
1035 {
1036   EmpathyFTFactory *factory;
1037   GFile *file;
1038
1039   if (response == GTK_RESPONSE_OK)
1040     {
1041       GFile *parent;
1042       GFileInfo *info;
1043       guint64 free_space, file_size;
1044       GError *error = NULL;
1045
1046       file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
1047       parent = g_file_get_parent (file);
1048       info = g_file_query_filesystem_info (parent,
1049           G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &error);
1050
1051       g_object_unref (parent);
1052
1053       if (error != NULL)
1054         {
1055           g_warning ("Error: %s", error->message);
1056
1057           g_object_unref (file);
1058           return;
1059         }
1060
1061       free_space = g_file_info_get_attribute_uint64 (info,
1062           G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
1063       file_size = empathy_ft_handler_get_total_bytes (handler);
1064
1065       g_object_unref (info);
1066
1067       if (file_size > free_space)
1068         {
1069           GtkWidget *message = gtk_message_dialog_new (GTK_WINDOW (dialog),
1070             GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
1071             GTK_BUTTONS_CLOSE,
1072             _("Insufficient free space to save file"));
1073           char *file_size_str, *free_space_str;
1074
1075           file_size_str = g_format_size (file_size);
1076           free_space_str = g_format_size (free_space);
1077
1078           gtk_message_dialog_format_secondary_text (
1079               GTK_MESSAGE_DIALOG (message),
1080               _("%s of free space are required to save this "
1081                 "file, but only %s is available. Please "
1082                 "choose another location."),
1083             file_size_str, free_space_str);
1084
1085           gtk_dialog_run (GTK_DIALOG (message));
1086
1087           g_free (file_size_str);
1088           g_free (free_space_str);
1089           gtk_widget_destroy (message);
1090
1091           g_object_unref (file);
1092
1093           return;
1094         }
1095
1096       factory = empathy_ft_factory_dup_singleton ();
1097
1098       empathy_ft_factory_set_destination_for_incoming_handler (
1099           factory, handler, file);
1100
1101       g_object_unref (factory);
1102       g_object_unref (file);
1103     }
1104   else
1105     {
1106       /* unref the handler, as we dismissed the file chooser,
1107        * and refused the transfer.
1108        */
1109       g_object_unref (handler);
1110     }
1111
1112   gtk_widget_destroy (GTK_WIDGET (dialog));
1113 }
1114
1115 void
1116 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
1117 {
1118   GtkWidget *widget;
1119   const gchar *dir;
1120   EmpathyContact *contact;
1121   gchar *title;
1122
1123   contact = empathy_ft_handler_get_contact (handler);
1124   g_assert (contact != NULL);
1125
1126   title = g_strdup_printf (_("Incoming file from %s"),
1127     empathy_contact_get_alias (contact));
1128
1129   widget = gtk_file_chooser_dialog_new (title,
1130       NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
1131       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1132       GTK_STOCK_SAVE, GTK_RESPONSE_OK,
1133       NULL);
1134
1135   gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
1136     empathy_ft_handler_get_filename (handler));
1137
1138   gtk_file_chooser_set_do_overwrite_confirmation
1139     (GTK_FILE_CHOOSER (widget), TRUE);
1140
1141   dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
1142   if (dir == NULL)
1143     /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
1144     dir = g_get_home_dir ();
1145
1146   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), dir);
1147
1148   g_signal_connect (widget, "response",
1149       G_CALLBACK (file_manager_receive_file_response_cb), handler);
1150
1151   gtk_widget_show (widget);
1152   g_free (title);
1153 }
1154
1155 void
1156 empathy_make_color_whiter (GdkRGBA *color)
1157 {
1158   const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
1159
1160   color->red = (color->red + white.red) / 2;
1161   color->green = (color->green + white.green) / 2;
1162   color->blue = (color->blue + white.blue) / 2;
1163 }
1164
1165 static void
1166 menu_deactivate_cb (GtkMenu *menu,
1167   gpointer user_data)
1168 {
1169   /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
1170   g_signal_handlers_disconnect_by_func (menu,
1171          menu_deactivate_cb, user_data);
1172
1173   gtk_menu_detach (menu);
1174 }
1175
1176 /* Convenient function to create a GtkMenu attached to @attach_to and detach
1177  * it when the menu is not displayed any more. This is useful when creating a
1178  * context menu that we want to get rid as soon as it as been displayed. */
1179 GtkWidget *
1180 empathy_context_menu_new (GtkWidget *attach_to)
1181 {
1182   GtkWidget *menu;
1183
1184   menu = gtk_menu_new ();
1185
1186   gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
1187
1188   /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
1189    * floating ref. We can either wait that @attach_to releases its ref when
1190    * it will be destroyed (when leaving Empathy most of the time) or explicitely
1191    * detach the menu when it's not displayed any more.
1192    * We go for the latter as we don't want to keep useless menus in memory
1193    * during the whole lifetime of Empathy. */
1194   g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
1195
1196   return menu;
1197 }
1198
1199 gint64
1200 empathy_get_current_action_time (void)
1201 {
1202   return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
1203 }
1204
1205 /* @words = tpaw_live_search_strip_utf8_string (@text);
1206  *
1207  * User has to pass both so we don't have to compute @words ourself each time
1208  * this function is called. */
1209 gboolean
1210 empathy_individual_match_string (FolksIndividual *individual,
1211     const char *text,
1212     GPtrArray *words)
1213 {
1214   const gchar *str;
1215   GeeSet *personas;
1216   GeeIterator *iter;
1217   gboolean retval = FALSE;
1218
1219   /* check alias name */
1220   str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1221
1222   if (tpaw_live_search_match_words (str, words))
1223     return TRUE;
1224
1225   personas = folks_individual_get_personas (individual);
1226
1227   /* check contact id, remove the @server.com part */
1228   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1229   while (retval == FALSE && gee_iterator_next (iter))
1230     {
1231       FolksPersona *persona = gee_iterator_get (iter);
1232       const gchar *p;
1233
1234       if (empathy_folks_persona_is_interesting (persona))
1235         {
1236           str = folks_persona_get_display_id (persona);
1237
1238           /* Accept the persona if @text is a full prefix of his ID; that allows
1239            * user to find, say, a jabber contact by typing his JID. */
1240           if (g_str_has_prefix (str, text))
1241             {
1242               retval = TRUE;
1243             }
1244           else
1245             {
1246               gchar *dup_str = NULL;
1247               gboolean visible;
1248
1249               p = strstr (str, "@");
1250               if (p != NULL)
1251                 str = dup_str = g_strndup (str, p - str);
1252
1253               visible = tpaw_live_search_match_words (str, words);
1254               g_free (dup_str);
1255               if (visible)
1256                 retval = TRUE;
1257             }
1258         }
1259       g_clear_object (&persona);
1260     }
1261   g_clear_object (&iter);
1262
1263   /* FIXME: Add more rules here, we could check phone numbers in
1264    * contact's vCard for example. */
1265   return retval;
1266 }
1267
1268 void
1269 empathy_launch_program (const gchar *dir,
1270     const gchar *name,
1271     const gchar *args)
1272 {
1273   GdkDisplay *display;
1274   GError *error = NULL;
1275   gchar *path, *cmd;
1276   GAppInfo *app_info;
1277   GdkAppLaunchContext *context = NULL;
1278
1279   /* Try to run from source directory if possible */
1280   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
1281       name, NULL);
1282
1283   if (!g_file_test (path, G_FILE_TEST_EXISTS))
1284     {
1285       g_free (path);
1286       path = g_build_filename (dir, name, NULL);
1287     }
1288
1289   if (args != NULL)
1290     cmd = g_strconcat (path, " ", args, NULL);
1291   else
1292     cmd = g_strdup (path);
1293
1294   app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
1295   if (app_info == NULL)
1296     {
1297       DEBUG ("Failed to create app info: %s", error->message);
1298       g_error_free (error);
1299       goto out;
1300     }
1301
1302   display = gdk_display_get_default ();
1303   context = gdk_display_get_app_launch_context (display);
1304
1305   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1306       &error))
1307     {
1308       g_warning ("Failed to launch %s: %s", name, error->message);
1309       g_error_free (error);
1310       goto out;
1311     }
1312
1313 out:
1314   tp_clear_object (&app_info);
1315   tp_clear_object (&context);
1316   g_free (path);
1317   g_free (cmd);
1318 }
1319
1320 /* Most of the workspace manipulation code has been copied from libwnck
1321  * Copyright (C) 2001 Havoc Pennington
1322  * Copyright (C) 2005-2007 Vincent Untz
1323  */
1324 static void
1325 _wnck_activate_workspace (Screen *screen,
1326     int new_active_space,
1327     Time timestamp)
1328 {
1329   Display *display;
1330   Window root;
1331   XEvent xev;
1332
1333   display = DisplayOfScreen (screen);
1334   root = RootWindowOfScreen (screen);
1335
1336   xev.xclient.type = ClientMessage;
1337   xev.xclient.serial = 0;
1338   xev.xclient.send_event = True;
1339   xev.xclient.display = display;
1340   xev.xclient.window = root;
1341   xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
1342   xev.xclient.format = 32;
1343   xev.xclient.data.l[0] = new_active_space;
1344   xev.xclient.data.l[1] = timestamp;
1345   xev.xclient.data.l[2] = 0;
1346   xev.xclient.data.l[3] = 0;
1347   xev.xclient.data.l[4] = 0;
1348
1349   gdk_error_trap_push ();
1350   XSendEvent (display, root, False,
1351       SubstructureRedirectMask | SubstructureNotifyMask,
1352       &xev);
1353   XSync (display, False);
1354   gdk_error_trap_pop_ignored ();
1355 }
1356
1357 static gboolean
1358 _wnck_get_cardinal (Screen *screen,
1359     Window xwindow,
1360     Atom atom,
1361     int *val)
1362 {
1363   Display *display;
1364   Atom type;
1365   int format;
1366   gulong nitems;
1367   gulong bytes_after;
1368   gulong *num;
1369   int err, result;
1370
1371   display = DisplayOfScreen (screen);
1372
1373   *val = 0;
1374
1375   gdk_error_trap_push ();
1376   type = None;
1377   result = XGetWindowProperty (display, xwindow, atom,
1378       0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
1379       &bytes_after, (void *) &num);
1380   err = gdk_error_trap_pop ();
1381   if (err != Success ||
1382       result != Success)
1383     return FALSE;
1384
1385   if (type != XA_CARDINAL)
1386     {
1387       XFree (num);
1388       return FALSE;
1389     }
1390
1391   *val = *num;
1392
1393   XFree (num);
1394
1395   return TRUE;
1396 }
1397
1398 static int
1399 window_get_workspace (Screen *xscreen,
1400     Window win)
1401 {
1402   int number;
1403
1404   if (!_wnck_get_cardinal (xscreen, win,
1405         gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
1406     return -1;
1407
1408   return number;
1409 }
1410
1411 /* Ask X to move to the desktop on which @window currently is
1412  * and the present @window. */
1413 void
1414 empathy_move_to_window_desktop (GtkWindow *window,
1415     guint32 timestamp)
1416 {
1417   GdkScreen *screen;
1418   Screen *xscreen;
1419   GdkWindow *gdk_window;
1420   int workspace;
1421
1422   screen = gtk_window_get_screen (window);
1423   xscreen = gdk_x11_screen_get_xscreen (screen);
1424   gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1425
1426   workspace = window_get_workspace (xscreen,
1427       gdk_x11_window_get_xid (gdk_window));
1428   if (workspace == -1)
1429     goto out;
1430
1431   _wnck_activate_workspace (xscreen, workspace, timestamp);
1432
1433 out:
1434   gtk_window_present_with_time (window, timestamp);
1435 }
1436
1437 void
1438 empathy_set_css_provider (GtkWidget *widget)
1439 {
1440   GtkCssProvider *provider;
1441   gchar *filename;
1442   GError *error = NULL;
1443   GdkScreen *screen;
1444
1445   filename = empathy_file_lookup ("empathy.css", "data");
1446
1447   provider = gtk_css_provider_new ();
1448
1449   if (!gtk_css_provider_load_from_path (provider, filename, &error))
1450     {
1451       g_warning ("Failed to load css file '%s': %s", filename, error->message);
1452       g_error_free (error);
1453       goto out;
1454     }
1455
1456   if (widget != NULL)
1457     screen = gtk_widget_get_screen (widget);
1458   else
1459     screen = gdk_screen_get_default ();
1460
1461   gtk_style_context_add_provider_for_screen (screen,
1462       GTK_STYLE_PROVIDER (provider),
1463       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1464
1465 out:
1466   g_free (filename);
1467   g_object_unref (provider);
1468 }
1469
1470 static gboolean
1471 launch_app_info (GAppInfo *app_info,
1472     GError **error)
1473 {
1474   GdkAppLaunchContext *context = NULL;
1475   GdkDisplay *display;
1476   GError *err = NULL;
1477
1478   display = gdk_display_get_default ();
1479   context = gdk_display_get_app_launch_context (display);
1480
1481   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1482         &err))
1483     {
1484       DEBUG ("Failed to launch %s: %s",
1485           g_app_info_get_display_name (app_info), err->message);
1486       g_propagate_error (error, err);
1487       return FALSE;
1488     }
1489
1490   tp_clear_object (&context);
1491   return TRUE;
1492 }
1493
1494 gboolean
1495 empathy_launch_external_app (const gchar *desktop_file,
1496     const gchar *args,
1497     GError **error)
1498 {
1499   GDesktopAppInfo *desktop_info;
1500   gboolean result;
1501   GError *err = NULL;
1502
1503   desktop_info = g_desktop_app_info_new (desktop_file);
1504   if (desktop_info == NULL)
1505     {
1506       DEBUG ("%s not found", desktop_file);
1507
1508       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1509           "%s not found", desktop_file);
1510       return FALSE;
1511     }
1512
1513   if (args == NULL)
1514     {
1515       result = launch_app_info (G_APP_INFO (desktop_info), error);
1516     }
1517   else
1518     {
1519       gchar *cmd;
1520       GAppInfo *app_info;
1521
1522       /* glib doesn't have API to start a desktop file with args... (#637875) */
1523       cmd = g_strdup_printf ("%s %s", g_app_info_get_commandline (
1524             (GAppInfo *) desktop_info), args);
1525
1526       app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &err);
1527       if (app_info == NULL)
1528         {
1529           DEBUG ("Failed to launch '%s': %s", cmd, err->message);
1530           g_free (cmd);
1531           g_object_unref (desktop_info);
1532           g_propagate_error (error, err);
1533           return FALSE;
1534         }
1535
1536       result = launch_app_info (app_info, error);
1537
1538       g_object_unref (app_info);
1539       g_free (cmd);
1540     }
1541
1542   g_object_unref (desktop_info);
1543   return result;
1544 }