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