]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-ui-utils.c
Merge branch 'gnome-3-4'
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-2007 Imendio AB
4  * Copyright (C) 2007-2010 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Richard Hult <richard@imendio.com>
23  *          Martyn Russell <martyn@imendio.com>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
26  *          Travis Reitter <travis.reitter@collabora.co.uk>
27  *
28  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
29  *          Paolo Maggi
30  *          Jeroen Zwartepoorte
31  */
32
33 #include <config.h>
34
35 #include <string.h>
36 #include <X11/Xatom.h>
37 #include <gdk/gdkx.h>
38 #include <glib/gi18n-lib.h>
39 #include <gtk/gtk.h>
40 #include <gio/gio.h>
41
42 #include <telepathy-glib/util.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         gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
65                                            PKGDATADIR G_DIR_SEPARATOR_S "icons");
66
67         /* Add icons from source dir if available */
68         if (g_getenv ("EMPATHY_SRCDIR") != NULL) {
69                 gchar *path;
70
71                 path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "data",
72                                 "icons", "local-copy", NULL);
73                 if (g_file_test (path, G_FILE_TEST_EXISTS)) {
74                         gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), path);
75                 }
76
77                 g_free (path);
78         }
79
80         initialized = TRUE;
81 }
82
83 static GtkBuilder *
84 builder_get_file_valist (const gchar *filename,
85                          const gchar *first_object,
86                          va_list      args)
87 {
88         GtkBuilder  *gui;
89         const gchar *name;
90         GObject    **object_ptr;
91         GError      *error = NULL;
92
93         DEBUG ("Loading file %s", filename);
94
95         gui = gtk_builder_new ();
96         gtk_builder_set_translation_domain (gui, GETTEXT_PACKAGE);
97         if (!gtk_builder_add_from_file (gui, filename, &error)) {
98                 g_critical ("GtkBuilder Error (%s): %s",
99                                 filename, error->message);
100                 g_clear_error (&error);
101                 g_object_unref (gui);
102
103                 /* we need to iterate and set all of the pointers to NULL */
104                 for (name = first_object; name;
105                      name = va_arg (args, const gchar *)) {
106                         object_ptr = va_arg (args, GObject**);
107
108                         *object_ptr = NULL;
109                 }
110
111                 return NULL;
112         }
113
114         for (name = first_object; name; name = va_arg (args, const gchar *)) {
115                 object_ptr = va_arg (args, GObject**);
116
117                 *object_ptr = gtk_builder_get_object (gui, name);
118
119                 if (!*object_ptr) {
120                         g_warning ("File is missing object '%s'.", name);
121                         continue;
122                 }
123         }
124
125         return gui;
126 }
127
128 GtkBuilder *
129 empathy_builder_get_file (const gchar *filename,
130                           const gchar *first_object,
131                           ...)
132 {
133         GtkBuilder *gui;
134         va_list     args;
135
136         va_start (args, first_object);
137         gui = builder_get_file_valist (filename, first_object, args);
138         va_end (args);
139
140         return gui;
141 }
142
143 void
144 empathy_builder_connect (GtkBuilder  *gui,
145                          gpointer     user_data,
146                          const gchar *first_object,
147                          ...)
148 {
149         va_list      args;
150         const gchar *name;
151         const gchar *sig;
152         GObject     *object;
153         GCallback    callback;
154
155         va_start (args, first_object);
156         for (name = first_object; name; name = va_arg (args, const gchar *)) {
157                 sig = va_arg (args, const gchar *);
158                 callback = va_arg (args, GCallback);
159
160                 object = gtk_builder_get_object (gui, name);
161                 if (!object) {
162                         g_warning ("File is missing object '%s'.", name);
163                         continue;
164                 }
165
166                 g_signal_connect (object, sig, callback, user_data);
167         }
168
169         va_end (args);
170 }
171
172 GtkWidget *
173 empathy_builder_unref_and_keep_widget (GtkBuilder *gui,
174                                        GtkWidget  *widget)
175 {
176         /* On construction gui sinks the initial reference to widget. When gui
177          * is finalized it will drop its ref to widget. We take our own ref to
178          * prevent widget being finalised. The widget is forced to have a
179          * floating reference, like when it was initially unowned so that it can
180          * be used like any other GtkWidget. */
181
182         g_object_ref (widget);
183         g_object_force_floating (G_OBJECT (widget));
184         g_object_unref (gui);
185
186         return widget;
187 }
188
189 const gchar *
190 empathy_icon_name_for_presence (TpConnectionPresenceType presence)
191 {
192         switch (presence) {
193         case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
194                 return EMPATHY_IMAGE_AVAILABLE;
195         case TP_CONNECTION_PRESENCE_TYPE_BUSY:
196                 return EMPATHY_IMAGE_BUSY;
197         case TP_CONNECTION_PRESENCE_TYPE_AWAY:
198                 return EMPATHY_IMAGE_AWAY;
199         case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
200                 if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
201                                              EMPATHY_IMAGE_EXT_AWAY))
202                         return EMPATHY_IMAGE_EXT_AWAY;
203
204                 /* The 'extended-away' icon is not an official one so we fallback to idle if
205                  * it's not implemented */
206                 return EMPATHY_IMAGE_IDLE;
207         case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
208                 if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
209                                              EMPATHY_IMAGE_HIDDEN))
210                         return EMPATHY_IMAGE_HIDDEN;
211
212                 /* The 'hidden' icon is not an official one so we fallback to offline if
213                  * it's not implemented */
214                 return EMPATHY_IMAGE_OFFLINE;
215         case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
216         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
217                 return EMPATHY_IMAGE_OFFLINE;
218         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
219                 return EMPATHY_IMAGE_PENDING;
220         case TP_CONNECTION_PRESENCE_TYPE_UNSET:
221         default:
222                 return NULL;
223         }
224
225         return NULL;
226 }
227
228 const gchar *
229 empathy_icon_name_for_contact (EmpathyContact *contact)
230 {
231         TpConnectionPresenceType presence;
232
233         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact),
234                               EMPATHY_IMAGE_OFFLINE);
235
236         presence = empathy_contact_get_presence (contact);
237         return empathy_icon_name_for_presence (presence);
238 }
239
240 const gchar *
241 empathy_icon_name_for_individual (FolksIndividual *individual)
242 {
243         FolksPresenceType folks_presence;
244         TpConnectionPresenceType presence;
245
246         folks_presence =
247             folks_presence_details_get_presence_type (
248                 FOLKS_PRESENCE_DETAILS (individual));
249         presence = empathy_folks_presence_type_to_tp (folks_presence);
250
251         return empathy_icon_name_for_presence (presence);
252 }
253
254 const gchar *
255 empathy_protocol_name_for_contact (EmpathyContact   *contact)
256 {
257         TpAccount     *account;
258
259         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
260
261         account = empathy_contact_get_account (contact);
262         if (account == NULL) {
263                 return NULL;
264         }
265
266         return tp_account_get_icon_name (account);
267 }
268
269 GdkPixbuf *
270 empathy_pixbuf_from_data (gchar *data,
271                           gsize  data_size)
272 {
273         return empathy_pixbuf_from_data_and_mime (data, data_size, NULL);
274 }
275
276 GdkPixbuf *
277 empathy_pixbuf_from_data_and_mime (gchar  *data,
278                                    gsize   data_size,
279                                    gchar **mime_type)
280 {
281         GdkPixbufLoader *loader;
282         GdkPixbufFormat *format;
283         GdkPixbuf       *pixbuf = NULL;
284         gchar          **mime_types;
285         GError          *error = NULL;
286
287         if (!data) {
288                 return NULL;
289         }
290
291         loader = gdk_pixbuf_loader_new ();
292         if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error)) {
293                 DEBUG ("Failed to write to pixbuf loader: %s",
294                         error ? error->message : "No error given");
295                 goto out;
296         }
297         if (!gdk_pixbuf_loader_close (loader, &error)) {
298                 DEBUG ("Failed to close pixbuf loader: %s",
299                         error ? error->message : "No error given");
300                 goto out;
301         }
302
303         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
304         if (pixbuf) {
305                 g_object_ref (pixbuf);
306
307                 if (mime_type != NULL) {
308                         format = gdk_pixbuf_loader_get_format (loader);
309                         mime_types = gdk_pixbuf_format_get_mime_types (format);
310
311                         *mime_type = g_strdup (*mime_types);
312                         if (mime_types[1] != NULL) {
313                                 DEBUG ("Loader supports more than one mime "
314                                         "type! Picking the first one, %s",
315                                         *mime_type);
316                         }
317                         g_strfreev (mime_types);
318                 }
319         }
320
321 out:
322         g_clear_error (&error);
323         g_object_unref (loader);
324
325         return pixbuf;
326 }
327
328 struct SizeData {
329         gint     width;
330         gint     height;
331         gboolean preserve_aspect_ratio;
332 };
333
334 static void
335 pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
336                                      int              width,
337                                      int              height,
338                                      struct SizeData *data)
339 {
340         g_return_if_fail (width > 0 && height > 0);
341
342         if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
343                 if (width < data->width && height < data->height) {
344                         width = width;
345                         height = height;
346                 }
347
348                 if (data->width < 0) {
349                         width = width * (double) data->height / (gdouble) height;
350                         height = data->height;
351                 } else if (data->height < 0) {
352                         height = height * (double) data->width / (double) width;
353                         width = data->width;
354                 } else if ((double) height * (double) data->width >
355                            (double) width * (double) data->height) {
356                         width = 0.5 + (double) width * (double) data->height / (double) height;
357                         height = data->height;
358                 } else {
359                         height = 0.5 + (double) height * (double) data->width / (double) width;
360                         width = data->width;
361                 }
362         } else {
363                 if (data->width > 0) {
364                         width = data->width;
365                 }
366
367                 if (data->height > 0) {
368                         height = data->height;
369                 }
370         }
371
372         gdk_pixbuf_loader_set_size (loader, width, height);
373 }
374
375 static void
376 empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
377 {
378         gint width, height, rowstride;
379         guchar *pixels;
380
381         width = gdk_pixbuf_get_width (pixbuf);
382         height = gdk_pixbuf_get_height (pixbuf);
383         rowstride = gdk_pixbuf_get_rowstride (pixbuf);
384         pixels = gdk_pixbuf_get_pixels (pixbuf);
385
386         if (width < 6 || height < 6) {
387                 return;
388         }
389
390         /* Top left */
391         pixels[3] = 0;
392         pixels[7] = 0x80;
393         pixels[11] = 0xC0;
394         pixels[rowstride + 3] = 0x80;
395         pixels[rowstride * 2 + 3] = 0xC0;
396
397         /* Top right */
398         pixels[width * 4 - 1] = 0;
399         pixels[width * 4 - 5] = 0x80;
400         pixels[width * 4 - 9] = 0xC0;
401         pixels[rowstride + (width * 4) - 1] = 0x80;
402         pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
403
404         /* Bottom left */
405         pixels[(height - 1) * rowstride + 3] = 0;
406         pixels[(height - 1) * rowstride + 7] = 0x80;
407         pixels[(height - 1) * rowstride + 11] = 0xC0;
408         pixels[(height - 2) * rowstride + 3] = 0x80;
409         pixels[(height - 3) * rowstride + 3] = 0xC0;
410
411         /* Bottom right */
412         pixels[height * rowstride - 1] = 0;
413         pixels[(height - 1) * rowstride - 1] = 0x80;
414         pixels[(height - 2) * rowstride - 1] = 0xC0;
415         pixels[height * rowstride - 5] = 0x80;
416         pixels[height * rowstride - 9] = 0xC0;
417 }
418
419 static gboolean
420 empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
421 {
422         gint height, rowstride, i;
423         guchar *pixels;
424         guchar *row;
425
426         height = gdk_pixbuf_get_height (pixbuf);
427         rowstride = gdk_pixbuf_get_rowstride (pixbuf);
428         pixels = gdk_pixbuf_get_pixels (pixbuf);
429
430         row = pixels;
431         for (i = 3; i < rowstride; i+=4) {
432                 if (row[i] < 0xfe) {
433                         return FALSE;
434                 }
435         }
436
437         for (i = 1; i < height - 1; i++) {
438                 row = pixels + (i*rowstride);
439                 if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
440                         return FALSE;
441                 }
442         }
443
444         row = pixels + ((height-1) * rowstride);
445         for (i = 3; i < rowstride; i+=4) {
446                 if (row[i] < 0xfe) {
447                         return FALSE;
448                 }
449         }
450
451         return TRUE;
452 }
453
454 static GdkPixbuf *
455 avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
456 {
457         GdkPixbuf *pixbuf;
458
459         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
460         if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
461                 GdkPixbuf *rounded_pixbuf;
462
463                 rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
464                                                  gdk_pixbuf_get_width (pixbuf),
465                                                  gdk_pixbuf_get_height (pixbuf));
466                 gdk_pixbuf_copy_area (pixbuf, 0, 0,
467                                       gdk_pixbuf_get_width (pixbuf),
468                                       gdk_pixbuf_get_height (pixbuf),
469                                       rounded_pixbuf,
470                                       0, 0);
471                 pixbuf = rounded_pixbuf;
472         } else {
473                 g_object_ref (pixbuf);
474         }
475
476         if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
477                 empathy_avatar_pixbuf_roundify (pixbuf);
478         }
479
480         return pixbuf;
481 }
482
483 GdkPixbuf *
484 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
485                                   gint          width,
486                                   gint          height)
487 {
488         GdkPixbuf        *pixbuf;
489         GdkPixbufLoader  *loader;
490         struct SizeData   data;
491         GError           *error = NULL;
492
493         if (!avatar) {
494                 return NULL;
495         }
496
497         data.width = width;
498         data.height = height;
499         data.preserve_aspect_ratio = TRUE;
500
501         loader = gdk_pixbuf_loader_new ();
502
503         g_signal_connect (loader, "size-prepared",
504                           G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
505                           &data);
506
507         if (avatar->len == 0) {
508                 g_warning ("Avatar has 0 length");
509                 return NULL;
510         } else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
511                 g_warning ("Couldn't write avatar image:%p with "
512                            "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
513                            avatar->data, avatar->len, error->message);
514                 g_error_free (error);
515                 return NULL;
516         }
517
518         gdk_pixbuf_loader_close (loader, NULL);
519         pixbuf = avatar_pixbuf_from_loader (loader);
520
521         g_object_unref (loader);
522
523         return pixbuf;
524 }
525
526 GdkPixbuf *
527 empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
528                                           gint           width,
529                                           gint           height)
530 {
531         EmpathyAvatar *avatar;
532
533         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
534
535         avatar = empathy_contact_get_avatar (contact);
536
537         return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
538 }
539
540 typedef struct {
541         FolksIndividual *individual;
542         GSimpleAsyncResult *result;
543         guint width;
544         guint height;
545         struct SizeData size_data;
546         GdkPixbufLoader *loader;
547         GCancellable *cancellable;
548         guint8 data[512];
549 } PixbufAvatarFromIndividualClosure;
550
551 static PixbufAvatarFromIndividualClosure *
552 pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
553                                            GSimpleAsyncResult *result,
554                                            gint                width,
555                                            gint                height,
556                                            GCancellable       *cancellable)
557 {
558         PixbufAvatarFromIndividualClosure *closure;
559
560         g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
561         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
562
563         closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
564         closure->individual = g_object_ref (individual);
565         closure->result = g_object_ref (result);
566         closure->width = width;
567         closure->height = height;
568         if (cancellable != NULL)
569                 closure->cancellable = g_object_ref (cancellable);
570
571         return closure;
572 }
573
574 static void
575 pixbuf_avatar_from_individual_closure_free (
576                 PixbufAvatarFromIndividualClosure *closure)
577 {
578         g_clear_object (&closure->cancellable);
579         tp_clear_object (&closure->loader);
580         g_object_unref (closure->individual);
581         g_object_unref (closure->result);
582         g_free (closure);
583 }
584
585 static void
586 avatar_icon_load_close_cb (GObject      *object,
587                            GAsyncResult *result,
588                            gpointer      user_data)
589 {
590         GError *error = NULL;
591
592         g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
593
594         if (error != NULL) {
595                 DEBUG ("Failed to close pixbuf stream: %s", error->message);
596                 g_error_free (error);
597         }
598 }
599
600 static void
601 avatar_icon_load_read_cb (GObject      *object,
602                           GAsyncResult *result,
603                           gpointer      user_data)
604 {
605         GInputStream *stream = G_INPUT_STREAM (object);
606         PixbufAvatarFromIndividualClosure *closure = user_data;
607         gssize n_read;
608         GError *error = NULL;
609
610         /* Finish reading this chunk from the stream */
611         n_read = g_input_stream_read_finish (stream, result, &error);
612         if (error != NULL) {
613                 DEBUG ("Failed to finish read from pixbuf stream: %s",
614                         error->message);
615                 g_simple_async_result_set_from_error (closure->result, error);
616                 goto out_close;
617         }
618
619         /* Write the chunk to the pixbuf loader */
620         if (!gdk_pixbuf_loader_write (closure->loader, (guchar *) closure->data,
621                         n_read, &error)) {
622                 DEBUG ("Failed to write to pixbuf loader: %s",
623                         error ? error->message : "No error given");
624                 g_simple_async_result_set_from_error (closure->result, error);
625                 goto out_close;
626         }
627
628         if (n_read == 0) {
629                 /* EOF? */
630                 if (!gdk_pixbuf_loader_close (closure->loader, &error)) {
631                         DEBUG ("Failed to close pixbuf loader: %s",
632                                 error ? error->message : "No error given");
633                         g_simple_async_result_set_from_error (closure->result, error);
634                         goto out;
635                 }
636
637                 /* We're done. */
638                 g_simple_async_result_set_op_res_gpointer (closure->result,
639                         avatar_pixbuf_from_loader (closure->loader),
640                         g_object_unref);
641
642                 goto out;
643         } else {
644                 /* Loop round and read another chunk. */
645                 g_input_stream_read_async (stream, closure->data,
646                         G_N_ELEMENTS (closure->data),
647                         G_PRIORITY_DEFAULT, closure->cancellable,
648                         avatar_icon_load_read_cb, closure);
649
650                 return;
651         }
652
653 out_close:
654         /* We must close the pixbuf loader before unreffing it. */
655         gdk_pixbuf_loader_close (closure->loader, NULL);
656
657 out:
658         /* Close the file for safety (even though it should be
659          * automatically closed when the stream is finalised). */
660         g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, NULL,
661                 (GAsyncReadyCallback) avatar_icon_load_close_cb, NULL);
662
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 static void
670 avatar_icon_load_cb (GObject      *object,
671                      GAsyncResult *result,
672                      gpointer      user_data)
673 {
674         GLoadableIcon *icon = G_LOADABLE_ICON (object);
675         PixbufAvatarFromIndividualClosure *closure = user_data;
676         GInputStream *stream;
677         GError *error = NULL;
678
679         stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
680         if (error != NULL) {
681                 DEBUG ("Failed to open avatar stream: %s", error->message);
682                 g_simple_async_result_set_from_error (closure->result, error);
683                 goto out;
684         }
685
686         closure->size_data.width = closure->width;
687         closure->size_data.height = closure->height;
688         closure->size_data.preserve_aspect_ratio = TRUE;
689
690         /* Load the data into a pixbuf loader in chunks. */
691         closure->loader = gdk_pixbuf_loader_new ();
692
693         g_signal_connect (closure->loader, "size-prepared",
694                           G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
695                           &(closure->size_data));
696
697         /* Begin to read the first chunk. */
698         g_input_stream_read_async (stream, closure->data,
699                         G_N_ELEMENTS (closure->data),
700                         G_PRIORITY_DEFAULT, closure->cancellable,
701                         avatar_icon_load_read_cb, closure);
702
703         g_object_unref (stream);
704
705         return;
706
707 out:
708         g_simple_async_result_complete (closure->result);
709
710         g_clear_error (&error);
711         tp_clear_object (&stream);
712         pixbuf_avatar_from_individual_closure_free (closure);
713 }
714
715 void
716 empathy_pixbuf_avatar_from_individual_scaled_async (
717                 FolksIndividual     *individual,
718                 gint                 width,
719                 gint                 height,
720                 GCancellable        *cancellable,
721                 GAsyncReadyCallback  callback,
722                 gpointer             user_data)
723 {
724         GLoadableIcon *avatar_icon;
725         GSimpleAsyncResult *result;
726         PixbufAvatarFromIndividualClosure *closure;
727
728         result = g_simple_async_result_new (G_OBJECT (individual),
729                         callback, user_data,
730                         empathy_pixbuf_avatar_from_individual_scaled_async);
731
732         avatar_icon =
733                 folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
734         if (avatar_icon == NULL) {
735                 g_simple_async_result_set_error (result, G_IO_ERROR,
736                         G_IO_ERROR_NOT_FOUND, "no avatar found");
737
738                 g_simple_async_result_complete (result);
739                 g_object_unref (result);
740                 return;
741         }
742
743         closure = pixbuf_avatar_from_individual_closure_new (individual, result,
744                                                              width, height,
745                                                              cancellable);
746
747         g_return_if_fail (closure != NULL);
748
749         g_loadable_icon_load_async (avatar_icon, width, cancellable,
750                         avatar_icon_load_cb, closure);
751
752         g_object_unref (result);
753 }
754
755 /* Return a ref on the GdkPixbuf */
756 GdkPixbuf *
757 empathy_pixbuf_avatar_from_individual_scaled_finish (
758                 FolksIndividual *individual,
759                 GAsyncResult *result,
760                 GError **error)
761 {
762         GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
763         gboolean result_valid;
764         GdkPixbuf *pixbuf;
765
766         g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
767         g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
768
769         if (g_simple_async_result_propagate_error (simple, error))
770                 return NULL;
771
772         result_valid = g_simple_async_result_is_valid (result,
773                         G_OBJECT (individual),
774                         empathy_pixbuf_avatar_from_individual_scaled_async);
775         g_return_val_if_fail (result_valid, NULL);
776
777         pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
778         return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
779 }
780
781 GdkPixbuf *
782 empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
783                                    gboolean       show_protocol)
784 {
785         const gchar *icon_name;
786
787         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
788
789         icon_name = empathy_icon_name_for_contact (contact);
790
791         if (icon_name == NULL) {
792                 return NULL;
793         }
794         return empathy_pixbuf_contact_status_icon_with_icon_name (contact,
795             icon_name,
796             show_protocol);
797 }
798
799 GdkPixbuf *
800 empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
801                                           const gchar    *icon_name,
802                                           gboolean       show_protocol)
803 {
804         GdkPixbuf *pix_status;
805         GdkPixbuf *pix_protocol;
806         gchar     *icon_filename;
807         gint       height, width;
808         gint       numerator, denominator;
809
810         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
811                         (show_protocol == FALSE), NULL);
812         g_return_val_if_fail (icon_name != NULL, NULL);
813
814         numerator = 3;
815         denominator = 4;
816
817         icon_filename = empathy_filename_from_icon_name (icon_name,
818                                                          GTK_ICON_SIZE_MENU);
819         if (icon_filename == NULL) {
820                 DEBUG ("icon name: %s could not be found\n", icon_name);
821                 return NULL;
822         }
823
824         pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
825
826         if (pix_status == NULL) {
827                 DEBUG ("Could not open icon %s\n", icon_filename);
828                 g_free (icon_filename);
829                 return NULL;
830         }
831
832         g_free (icon_filename);
833
834         if (!show_protocol)
835                 return pix_status;
836
837         height = gdk_pixbuf_get_height (pix_status);
838         width = gdk_pixbuf_get_width (pix_status);
839
840         pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
841                                                                     width * numerator / denominator,
842                                                                     height * numerator / denominator);
843
844         if (pix_protocol == NULL) {
845                 return pix_status;
846         }
847         gdk_pixbuf_composite (pix_protocol, pix_status,
848             0, height - height * numerator / denominator,
849             width * numerator / denominator, height * numerator / denominator,
850             0, height - height * numerator / denominator,
851             1, 1,
852             GDK_INTERP_BILINEAR, 255);
853
854         g_object_unref (pix_protocol);
855
856         return pix_status;
857 }
858
859 GdkPixbuf *
860 empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
861                                           gint           width,
862                                           gint           height)
863 {
864         TpAccount *account;
865         gchar     *filename;
866         GdkPixbuf *pixbuf = NULL;
867
868         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
869
870         account = empathy_contact_get_account (contact);
871         filename = empathy_filename_from_icon_name (tp_account_get_icon_name (account),
872                                                     GTK_ICON_SIZE_MENU);
873         if (filename != NULL) {
874                 pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
875                 g_free (filename);
876         }
877
878         return pixbuf;
879 }
880
881 GdkPixbuf *
882 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
883 {
884         gint      width, height;
885         gdouble   factor;
886
887         width = gdk_pixbuf_get_width (pixbuf);
888         height = gdk_pixbuf_get_height (pixbuf);
889
890         if (width > 0 && (width > max_size || height > max_size)) {
891                 factor = (gdouble) max_size / MAX (width, height);
892
893                 width = width * factor;
894                 height = height * factor;
895
896                 return gdk_pixbuf_scale_simple (pixbuf,
897                                                 width, height,
898                                                 GDK_INTERP_HYPER);
899         }
900
901         return g_object_ref (pixbuf);
902 }
903
904 GdkPixbuf *
905 empathy_pixbuf_from_icon_name_sized (const gchar *icon_name,
906                                      gint size)
907 {
908         GtkIconTheme *theme;
909         GdkPixbuf *pixbuf;
910         GError *error = NULL;
911
912         if (!icon_name) {
913                 return NULL;
914         }
915
916         theme = gtk_icon_theme_get_default ();
917
918         pixbuf = gtk_icon_theme_load_icon (theme,
919                                            icon_name,
920                                            size,
921                                            0,
922                                            &error);
923         if (error) {
924                 DEBUG ("Error loading icon: %s", error->message);
925                 g_clear_error (&error);
926         }
927
928         return pixbuf;
929 }
930
931 GdkPixbuf *
932 empathy_pixbuf_from_icon_name (const gchar *icon_name,
933                                GtkIconSize  icon_size)
934 {
935         gint  w, h;
936         gint  size = 48;
937
938         if (!icon_name) {
939                 return NULL;
940         }
941
942         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
943                 size = (w + h) / 2;
944         }
945
946         return empathy_pixbuf_from_icon_name_sized (icon_name, size);
947 }
948
949 gchar *
950 empathy_filename_from_icon_name (const gchar *icon_name,
951                                  GtkIconSize  icon_size)
952 {
953         GtkIconTheme *icon_theme;
954         GtkIconInfo  *icon_info;
955         gint          w, h;
956         gint          size = 48;
957         gchar        *ret;
958
959         icon_theme = gtk_icon_theme_get_default ();
960
961         if (gtk_icon_size_lookup (icon_size, &w, &h)) {
962                 size = (w + h) / 2;
963         }
964
965         icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
966         if (icon_info == NULL)
967                 return NULL;
968
969         ret = g_strdup (gtk_icon_info_get_filename (icon_info));
970         gtk_icon_info_free (icon_info);
971
972         return ret;
973 }
974
975 /* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
976  * that to make it easier to apply changes from the original code.
977  */
978 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
979
980 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
981  * decomposable character it consumes the decomposition length from the given
982  * offset.  So it's useful when the offset was calculated for the normalized
983  * version of str, but we need a pointer to str itself. */
984 static const gchar *
985 pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
986 {
987         gchar *casefold, *normal;
988         const gchar *p, *q;
989
990         p = str;
991         while (offset > 0)
992         {
993                 q = g_utf8_next_char (p);
994                 casefold = g_utf8_casefold (p, q - p);
995                 normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
996                 offset -= g_utf8_strlen (normal, -1);
997                 g_free (casefold);
998                 g_free (normal);
999                 p = q;
1000         }
1001         return p;
1002 }
1003
1004 static const gchar *
1005 g_utf8_strcasestr (const gchar *haystack, const gchar *needle)
1006 {
1007         gsize needle_len;
1008         gsize haystack_len;
1009         const gchar *ret = NULL;
1010         gchar *p;
1011         gchar *casefold;
1012         gchar *caseless_haystack;
1013         gint i;
1014
1015         g_return_val_if_fail (haystack != NULL, NULL);
1016         g_return_val_if_fail (needle != NULL, NULL);
1017
1018         casefold = g_utf8_casefold (haystack, -1);
1019         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1020         g_free (casefold);
1021
1022         needle_len = g_utf8_strlen (needle, -1);
1023         haystack_len = g_utf8_strlen (caseless_haystack, -1);
1024
1025         if (needle_len == 0)
1026         {
1027                 ret = (gchar *) haystack;
1028                 goto finally_1;
1029         }
1030
1031         if (haystack_len < needle_len)
1032         {
1033                 ret = NULL;
1034                 goto finally_1;
1035         }
1036
1037         p = (gchar *) caseless_haystack;
1038         needle_len = strlen (needle);
1039         i = 0;
1040
1041         while (*p)
1042         {
1043                 if ((strncmp (p, needle, needle_len) == 0))
1044                 {
1045                         ret = pointer_from_offset_skipping_decomp (haystack, i);
1046                         goto finally_1;
1047                 }
1048
1049                 p = g_utf8_next_char (p);
1050                 i++;
1051         }
1052
1053 finally_1:
1054         g_free (caseless_haystack);
1055
1056         return ret;
1057 }
1058
1059 static gboolean
1060 g_utf8_caselessnmatch (const char *s1, const char *s2,
1061                        gssize n1, gssize n2)
1062 {
1063         gchar *casefold;
1064         gchar *normalized_s1;
1065         gchar *normalized_s2;
1066         gint len_s1;
1067         gint len_s2;
1068         gboolean ret = FALSE;
1069
1070         g_return_val_if_fail (s1 != NULL, FALSE);
1071         g_return_val_if_fail (s2 != NULL, FALSE);
1072         g_return_val_if_fail (n1 > 0, FALSE);
1073         g_return_val_if_fail (n2 > 0, FALSE);
1074
1075         casefold = g_utf8_casefold (s1, n1);
1076         normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1077         g_free (casefold);
1078
1079         casefold = g_utf8_casefold (s2, n2);
1080         normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1081         g_free (casefold);
1082
1083         len_s1 = strlen (normalized_s1);
1084         len_s2 = strlen (normalized_s2);
1085
1086         if (len_s1 < len_s2)
1087                 goto finally_2;
1088
1089         ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
1090
1091 finally_2:
1092         g_free (normalized_s1);
1093         g_free (normalized_s2);
1094
1095         return ret;
1096 }
1097
1098 static void
1099 forward_chars_with_skipping (GtkTextIter *iter,
1100                              gint         count,
1101                              gboolean     skip_invisible,
1102                              gboolean     skip_nontext,
1103                              gboolean     skip_decomp)
1104 {
1105         gint i;
1106
1107         g_return_if_fail (count >= 0);
1108
1109         i = count;
1110
1111         while (i > 0)
1112         {
1113                 gboolean ignored = FALSE;
1114
1115                 /* minimal workaround to avoid the infinite loop of bug #168247.
1116                  * It doesn't fix the problemjust the symptom...
1117                  */
1118                 if (gtk_text_iter_is_end (iter))
1119                         return;
1120
1121                 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
1122                         ignored = TRUE;
1123
1124                 if (!ignored && skip_invisible &&
1125                     /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE)
1126                         ignored = TRUE;
1127
1128                 if (!ignored && skip_decomp)
1129                 {
1130                         /* being UTF8 correct sucks; this accounts for extra
1131                            offsets coming from canonical decompositions of
1132                            UTF8 characters (e.g. accented characters) which
1133                            g_utf8_normalize () performs */
1134                         gchar *normal;
1135                         gchar buffer[6];
1136                         gint buffer_len;
1137
1138                         buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
1139                         normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
1140                         i -= (g_utf8_strlen (normal, -1) - 1);
1141                         g_free (normal);
1142                 }
1143
1144                 gtk_text_iter_forward_char (iter);
1145
1146                 if (!ignored)
1147                         --i;
1148         }
1149 }
1150
1151 static gboolean
1152 lines_match (const GtkTextIter *start,
1153              const gchar      **lines,
1154              gboolean           visible_only,
1155              gboolean           slice,
1156              GtkTextIter       *match_start,
1157              GtkTextIter       *match_end)
1158 {
1159         GtkTextIter next;
1160         gchar *line_text;
1161         const gchar *found;
1162         gint offset;
1163
1164         if (*lines == NULL || **lines == '\0')
1165         {
1166                 if (match_start)
1167                         *match_start = *start;
1168                 if (match_end)
1169                         *match_end = *start;
1170                 return TRUE;
1171         }
1172
1173         next = *start;
1174         gtk_text_iter_forward_line (&next);
1175
1176         /* No more text in buffer, but *lines is nonempty */
1177         if (gtk_text_iter_equal (start, &next))
1178                 return FALSE;
1179
1180         if (slice)
1181         {
1182                 if (visible_only)
1183                         line_text = gtk_text_iter_get_visible_slice (start, &next);
1184                 else
1185                         line_text = gtk_text_iter_get_slice (start, &next);
1186         }
1187         else
1188         {
1189                 if (visible_only)
1190                         line_text = gtk_text_iter_get_visible_text (start, &next);
1191                 else
1192                         line_text = gtk_text_iter_get_text (start, &next);
1193         }
1194
1195         if (match_start) /* if this is the first line we're matching */
1196         {
1197                 found = g_utf8_strcasestr (line_text, *lines);
1198         }
1199         else
1200         {
1201                 /* If it's not the first line, we have to match from the
1202                  * start of the line.
1203                  */
1204                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
1205                                            strlen (*lines)))
1206                         found = line_text;
1207                 else
1208                         found = NULL;
1209         }
1210
1211         if (found == NULL)
1212         {
1213                 g_free (line_text);
1214                 return FALSE;
1215         }
1216
1217         /* Get offset to start of search string */
1218         offset = g_utf8_strlen (line_text, found - line_text);
1219
1220         next = *start;
1221
1222         /* If match start needs to be returned, set it to the
1223          * start of the search string.
1224          */
1225         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
1226         if (match_start)
1227         {
1228                 *match_start = next;
1229         }
1230
1231         /* Go to end of search string */
1232         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
1233
1234         g_free (line_text);
1235
1236         ++lines;
1237
1238         if (match_end)
1239                 *match_end = next;
1240
1241         /* pass NULL for match_start, since we don't need to find the
1242          * start again.
1243          */
1244         return lines_match (&next, lines, visible_only, slice, NULL, match_end);
1245 }
1246
1247 /* strsplit () that retains the delimiter as part of the string. */
1248 static gchar **
1249 strbreakup (const char *string,
1250             const char *delimiter,
1251             gint        max_tokens)
1252 {
1253         GSList *string_list = NULL, *slist;
1254         gchar **str_array, *s, *casefold, *new_string;
1255         guint i, n = 1;
1256
1257         g_return_val_if_fail (string != NULL, NULL);
1258         g_return_val_if_fail (delimiter != NULL, NULL);
1259
1260         if (max_tokens < 1)
1261                 max_tokens = G_MAXINT;
1262
1263         s = strstr (string, delimiter);
1264         if (s)
1265         {
1266                 guint delimiter_len = strlen (delimiter);
1267
1268                 do
1269                 {
1270                         guint len;
1271
1272                         len = s - string + delimiter_len;
1273                         new_string = g_new (gchar, len + 1);
1274                         strncpy (new_string, string, len);
1275                         new_string[len] = 0;
1276                         casefold = g_utf8_casefold (new_string, -1);
1277                         g_free (new_string);
1278                         new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1279                         g_free (casefold);
1280                         string_list = g_slist_prepend (string_list, new_string);
1281                         n++;
1282                         string = s + delimiter_len;
1283                         s = strstr (string, delimiter);
1284                 } while (--max_tokens && s);
1285         }
1286
1287         if (*string)
1288         {
1289                 n++;
1290                 casefold = g_utf8_casefold (string, -1);
1291                 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1292                 g_free (casefold);
1293                 string_list = g_slist_prepend (string_list, new_string);
1294         }
1295
1296         str_array = g_new (gchar*, n);
1297
1298         i = n - 1;
1299
1300         str_array[i--] = NULL;
1301         for (slist = string_list; slist; slist = slist->next)
1302                 str_array[i--] = slist->data;
1303
1304         g_slist_free (string_list);
1305
1306         return str_array;
1307 }
1308
1309 gboolean
1310 empathy_text_iter_forward_search (const GtkTextIter   *iter,
1311                                  const gchar         *str,
1312                                  GtkTextIter         *match_start,
1313                                  GtkTextIter         *match_end,
1314                                  const GtkTextIter   *limit)
1315 {
1316         gchar **lines = NULL;
1317         GtkTextIter match;
1318         gboolean retval = FALSE;
1319         GtkTextIter search;
1320         gboolean visible_only;
1321         gboolean slice;
1322
1323         g_return_val_if_fail (iter != NULL, FALSE);
1324         g_return_val_if_fail (str != NULL, FALSE);
1325
1326         if (limit && gtk_text_iter_compare (iter, limit) >= 0)
1327                 return FALSE;
1328
1329         if (*str == '\0') {
1330                 /* If we can move one char, return the empty string there */
1331                 match = *iter;
1332
1333                 if (gtk_text_iter_forward_char (&match)) {
1334                         if (limit && gtk_text_iter_equal (&match, limit)) {
1335                                 return FALSE;
1336                         }
1337
1338                         if (match_start) {
1339                                 *match_start = match;
1340                         }
1341                         if (match_end) {
1342                                 *match_end = match;
1343                         }
1344                         return TRUE;
1345                 } else {
1346                         return FALSE;
1347                 }
1348         }
1349
1350         visible_only = TRUE;
1351         slice = FALSE;
1352
1353         /* locate all lines */
1354         lines = strbreakup (str, "\n", -1);
1355
1356         search = *iter;
1357
1358         do {
1359                 /* This loop has an inefficient worst-case, where
1360                  * gtk_text_iter_get_text () is called repeatedly on
1361                  * a single line.
1362                  */
1363                 GtkTextIter end;
1364
1365                 if (limit && gtk_text_iter_compare (&search, limit) >= 0) {
1366                         break;
1367                 }
1368
1369                 if (lines_match (&search, (const gchar**)lines,
1370                                  visible_only, slice, &match, &end)) {
1371                         if (limit == NULL ||
1372                             (limit && gtk_text_iter_compare (&end, limit) <= 0)) {
1373                                 retval = TRUE;
1374
1375                                 if (match_start) {
1376                                         *match_start = match;
1377                                 }
1378                                 if (match_end) {
1379                                         *match_end = end;
1380                                 }
1381                         }
1382                         break;
1383                 }
1384         } while (gtk_text_iter_forward_line (&search));
1385
1386         g_strfreev ((gchar **) lines);
1387
1388         return retval;
1389 }
1390
1391 static const gchar *
1392 g_utf8_strrcasestr (const gchar *haystack, const gchar *needle)
1393 {
1394         gsize needle_len;
1395         gsize haystack_len;
1396         const gchar *ret = NULL;
1397         gchar *p;
1398         gchar *casefold;
1399         gchar *caseless_haystack;
1400         gint i;
1401
1402         g_return_val_if_fail (haystack != NULL, NULL);
1403         g_return_val_if_fail (needle != NULL, NULL);
1404
1405         casefold = g_utf8_casefold (haystack, -1);
1406         caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
1407         g_free (casefold);
1408
1409         needle_len = g_utf8_strlen (needle, -1);
1410         haystack_len = g_utf8_strlen (caseless_haystack, -1);
1411
1412         if (needle_len == 0)
1413         {
1414                 ret = (gchar *) haystack;
1415                 goto finally_1;
1416         }
1417
1418         if (haystack_len < needle_len)
1419         {
1420                 ret = NULL;
1421                 goto finally_1;
1422         }
1423
1424         i = haystack_len - needle_len;
1425         p = g_utf8_offset_to_pointer (caseless_haystack, i);
1426         needle_len = strlen (needle);
1427
1428         while (p >= caseless_haystack)
1429         {
1430                 if (strncmp (p, needle, needle_len) == 0)
1431                 {
1432                         ret = pointer_from_offset_skipping_decomp (haystack, i);
1433                         goto finally_1;
1434                 }
1435
1436                 p = g_utf8_prev_char (p);
1437                 i--;
1438         }
1439
1440 finally_1:
1441         g_free (caseless_haystack);
1442
1443         return ret;
1444 }
1445
1446 static gboolean
1447 backward_lines_match (const GtkTextIter *start,
1448                       const gchar      **lines,
1449                       gboolean           visible_only,
1450                       gboolean           slice,
1451                       GtkTextIter       *match_start,
1452                       GtkTextIter       *match_end)
1453 {
1454         GtkTextIter line, next;
1455         gchar *line_text;
1456         const gchar *found;
1457         gint offset;
1458
1459         if (*lines == NULL || **lines == '\0')
1460         {
1461                 if (match_start)
1462                         *match_start = *start;
1463                 if (match_end)
1464                         *match_end = *start;
1465                 return TRUE;
1466         }
1467
1468         line = next = *start;
1469         if (gtk_text_iter_get_line_offset (&next) == 0)
1470         {
1471                 if (!gtk_text_iter_backward_line (&next))
1472                         return FALSE;
1473         }
1474         else
1475                 gtk_text_iter_set_line_offset (&next, 0);
1476
1477         if (slice)
1478         {
1479                 if (visible_only)
1480                         line_text = gtk_text_iter_get_visible_slice (&next, &line);
1481                 else
1482                         line_text = gtk_text_iter_get_slice (&next, &line);
1483         }
1484         else
1485         {
1486                 if (visible_only)
1487                         line_text = gtk_text_iter_get_visible_text (&next, &line);
1488                 else
1489                         line_text = gtk_text_iter_get_text (&next, &line);
1490         }
1491
1492         if (match_start) /* if this is the first line we're matching */
1493         {
1494                 found = g_utf8_strrcasestr (line_text, *lines);
1495         }
1496         else
1497         {
1498                 /* If it's not the first line, we have to match from the
1499                  * start of the line.
1500                  */
1501                 if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
1502                                            strlen (*lines)))
1503                         found = line_text;
1504                 else
1505                         found = NULL;
1506         }
1507
1508         if (found == NULL)
1509         {
1510                 g_free (line_text);
1511                 return FALSE;
1512         }
1513
1514         /* Get offset to start of search string */
1515         offset = g_utf8_strlen (line_text, found - line_text);
1516
1517         forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
1518
1519         /* If match start needs to be returned, set it to the
1520          * start of the search string.
1521          */
1522         if (match_start)
1523         {
1524                 *match_start = next;
1525         }
1526
1527         /* Go to end of search string */
1528         forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
1529
1530         g_free (line_text);
1531
1532         ++lines;
1533
1534         if (match_end)
1535                 *match_end = next;
1536
1537         /* try to match the rest of the lines forward, passing NULL
1538          * for match_start so lines_match will try to match the entire
1539          * line */
1540         return lines_match (&next, lines, visible_only,
1541                             slice, NULL, match_end);
1542 }
1543
1544 gboolean
1545 empathy_text_iter_backward_search (const GtkTextIter   *iter,
1546                                   const gchar         *str,
1547                                   GtkTextIter         *match_start,
1548                                   GtkTextIter         *match_end,
1549                                   const GtkTextIter   *limit)
1550 {
1551         gchar **lines = NULL;
1552         GtkTextIter match;
1553         gboolean retval = FALSE;
1554         GtkTextIter search;
1555         gboolean visible_only;
1556         gboolean slice;
1557
1558         g_return_val_if_fail (iter != NULL, FALSE);
1559         g_return_val_if_fail (str != NULL, FALSE);
1560
1561         if (limit && gtk_text_iter_compare (iter, limit) <= 0)
1562                 return FALSE;
1563
1564         if (*str == '\0')
1565         {
1566                 /* If we can move one char, return the empty string there */
1567                 match = *iter;
1568
1569                 if (gtk_text_iter_backward_char (&match))
1570                 {
1571                         if (limit && gtk_text_iter_equal (&match, limit))
1572                                 return FALSE;
1573
1574                         if (match_start)
1575                                 *match_start = match;
1576                         if (match_end)
1577                                 *match_end = match;
1578                         return TRUE;
1579                 }
1580                 else
1581                 {
1582                         return FALSE;
1583                 }
1584         }
1585
1586         visible_only = TRUE;
1587         slice = TRUE;
1588
1589         /* locate all lines */
1590         lines = strbreakup (str, "\n", -1);
1591
1592         search = *iter;
1593
1594         while (TRUE)
1595         {
1596                 /* This loop has an inefficient worst-case, where
1597                  * gtk_text_iter_get_text () is called repeatedly on
1598                  * a single line.
1599                  */
1600                 GtkTextIter end;
1601
1602                 if (limit && gtk_text_iter_compare (&search, limit) <= 0)
1603                         break;
1604
1605                 if (backward_lines_match (&search, (const gchar**)lines,
1606                                           visible_only, slice, &match, &end))
1607                 {
1608                         if (limit == NULL || (limit &&
1609                                               gtk_text_iter_compare (&end, limit) > 0))
1610                         {
1611                                 retval = TRUE;
1612
1613                                 if (match_start)
1614                                         *match_start = match;
1615                                 if (match_end)
1616                                         *match_end = end;
1617                         }
1618                         break;
1619                 }
1620
1621                 if (gtk_text_iter_get_line_offset (&search) == 0)
1622                 {
1623                         if (!gtk_text_iter_backward_line (&search))
1624                                 break;
1625                 }
1626                 else
1627                 {
1628                         gtk_text_iter_set_line_offset (&search, 0);
1629                 }
1630         }
1631
1632         g_strfreev ((gchar **) lines);
1633
1634         return retval;
1635 }
1636
1637 /* Takes care of moving the window to the current workspace. */
1638 void
1639 empathy_window_present_with_time (GtkWindow *window,
1640                         guint32 timestamp)
1641 {
1642         GdkWindow *gdk_window;
1643
1644         g_return_if_fail (GTK_IS_WINDOW (window));
1645
1646         /* Move the window to the current workspace before trying to show it.
1647          * This is the behaviour people expect when clicking on the statusbar icon. */
1648         gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
1649         if (gdk_window) {
1650                 gint x, y;
1651                 gint w, h;
1652
1653                 /* Has no effect if the WM has viewports, like compiz */
1654                 gdk_x11_window_move_to_current_desktop (gdk_window);
1655
1656                 /* If window is still off-screen, hide it to force it to
1657                  * reposition on the current workspace. */
1658                 gtk_window_get_position (window, &x, &y);
1659                 gtk_window_get_size (window, &w, &h);
1660                 if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
1661                         gtk_widget_hide (GTK_WIDGET (window));
1662         }
1663
1664         if (timestamp == GDK_CURRENT_TIME)
1665                 gtk_window_present (window);
1666         else
1667                 gtk_window_present_with_time (window, timestamp);
1668 }
1669
1670 void
1671 empathy_window_present (GtkWindow *window)
1672 {
1673   empathy_window_present_with_time (window, gtk_get_current_event_time ());
1674 }
1675
1676 GtkWindow *
1677 empathy_get_toplevel_window (GtkWidget *widget)
1678 {
1679         GtkWidget *toplevel;
1680
1681         g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
1682
1683         toplevel = gtk_widget_get_toplevel (widget);
1684         if (GTK_IS_WINDOW (toplevel) &&
1685             gtk_widget_is_toplevel (toplevel)) {
1686                 return GTK_WINDOW (toplevel);
1687         }
1688
1689         return NULL;
1690 }
1691
1692 /** empathy_make_absolute_url_len:
1693  * @url: an url
1694  * @len: a length
1695  *
1696  * Same as #empathy_make_absolute_url but for a limited string length
1697  */
1698 gchar *
1699 empathy_make_absolute_url_len (const gchar *url,
1700                                guint len)
1701 {
1702         g_return_val_if_fail (url != NULL, NULL);
1703
1704         if (g_str_has_prefix (url, "help:") ||
1705             g_str_has_prefix (url, "mailto:") ||
1706             strstr (url, ":/")) {
1707                 return g_strndup (url, len);
1708         }
1709
1710         if (strstr (url, "@")) {
1711                 return g_strdup_printf ("mailto:%.*s", len, url);
1712         }
1713
1714         return g_strdup_printf ("http://%.*s", len, url);
1715 }
1716
1717 /** empathy_make_absolute_url:
1718  * @url: an url
1719  *
1720  * The URL opening code can't handle schemeless strings, so we try to be
1721  * smart and add http if there is no scheme or doesn't look like a mail
1722  * address. This should work in most cases, and let us click on strings
1723  * like "www.gnome.org".
1724  *
1725  * Returns: a newly allocated url with proper mailto: or http:// prefix, use
1726  * g_free when your are done with it
1727  */
1728 gchar *
1729 empathy_make_absolute_url (const gchar *url)
1730 {
1731         return empathy_make_absolute_url_len (url, strlen (url));
1732 }
1733
1734 void
1735 empathy_url_show (GtkWidget *parent,
1736                   const char *url)
1737 {
1738         gchar  *real_url;
1739         GError *error = NULL;
1740
1741         g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent));
1742         g_return_if_fail (url != NULL);
1743
1744         real_url = empathy_make_absolute_url (url);
1745
1746         gtk_show_uri (parent ? gtk_widget_get_screen (parent) : NULL, real_url,
1747                       gtk_get_current_event_time (), &error);
1748
1749         if (error) {
1750                 GtkWidget *dialog;
1751
1752                 dialog = gtk_message_dialog_new (NULL, 0,
1753                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1754                                                  _("Unable to open URI"));
1755                 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1756                                                           "%s", error->message);
1757
1758                 g_signal_connect (dialog, "response",
1759                                   G_CALLBACK (gtk_widget_destroy),
1760                                   NULL);
1761                 gtk_window_present (GTK_WINDOW (dialog));
1762
1763                 g_clear_error (&error);
1764         }
1765
1766         g_free (real_url);
1767 }
1768
1769 void
1770 empathy_send_file (EmpathyContact *contact, GFile *file)
1771 {
1772         EmpathyFTFactory *factory;
1773         GtkRecentManager *manager;
1774         gchar *uri;
1775
1776         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1777         g_return_if_fail (G_IS_FILE (file));
1778
1779         factory = empathy_ft_factory_dup_singleton ();
1780
1781         empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
1782                 empathy_get_current_action_time ());
1783
1784         uri = g_file_get_uri (file);
1785         manager = gtk_recent_manager_get_default ();
1786         gtk_recent_manager_add_item (manager, uri);
1787         g_free (uri);
1788
1789         g_object_unref (factory);
1790 }
1791
1792 void
1793 empathy_send_file_from_uri_list (EmpathyContact *contact, const gchar *uri_list)
1794 {
1795         const gchar *nl;
1796         GFile *file;
1797
1798         /* Only handle a single file for now.  It would be wicked cool to be
1799            able to do multiple files, offering to zip them or whatever like
1800            nautilus-sendto does.  Note that text/uri-list is defined to have
1801            each line terminated by \r\n, but we can be tolerant of applications
1802            that only use \n or don't terminate single-line entries.
1803         */
1804         nl = strstr (uri_list, "\r\n");
1805         if (!nl) {
1806                 nl = strchr (uri_list, '\n');
1807         }
1808         if (nl) {
1809                 gchar *uri = g_strndup (uri_list, nl - uri_list);
1810                 file = g_file_new_for_uri (uri);
1811                 g_free (uri);
1812         }
1813         else {
1814                 file = g_file_new_for_uri (uri_list);
1815         }
1816
1817         empathy_send_file (contact, file);
1818
1819         g_object_unref (file);
1820 }
1821
1822 static void
1823 file_manager_send_file_response_cb (GtkDialog      *widget,
1824                                     gint            response_id,
1825                                     EmpathyContact *contact)
1826 {
1827         GFile *file;
1828
1829         if (response_id == GTK_RESPONSE_OK) {
1830                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
1831
1832                 empathy_send_file (contact, file);
1833
1834                 g_object_unref (file);
1835         }
1836
1837         g_object_unref (contact);
1838         gtk_widget_destroy (GTK_WIDGET (widget));
1839 }
1840
1841 static gboolean
1842 filter_cb (const GtkFileFilterInfo *filter_info,
1843                 gpointer data)
1844 {
1845         /* filter out socket files */
1846         return tp_strdiff (filter_info->mime_type, "inode/socket");
1847 }
1848
1849 static GtkFileFilter *
1850 create_file_filter (void)
1851 {
1852         GtkFileFilter *filter;
1853
1854         filter = gtk_file_filter_new ();
1855
1856         gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_MIME_TYPE, filter_cb,
1857                 NULL, NULL);
1858
1859         return filter;
1860 }
1861
1862 void
1863 empathy_send_file_with_file_chooser (EmpathyContact *contact)
1864 {
1865         GtkWidget               *widget;
1866         GtkWidget               *button;
1867
1868         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1869
1870         DEBUG ("Creating selection file chooser");
1871
1872         widget = gtk_file_chooser_dialog_new (_("Select a file"),
1873                                               NULL,
1874                                               GTK_FILE_CHOOSER_ACTION_OPEN,
1875                                               GTK_STOCK_CANCEL,
1876                                               GTK_RESPONSE_CANCEL,
1877                                               NULL);
1878
1879         /* send button */
1880         button = gtk_button_new_with_mnemonic (_("_Send"));
1881         gtk_button_set_image (GTK_BUTTON (button),
1882                 gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1883                                               GTK_ICON_SIZE_BUTTON));
1884         gtk_widget_show (button);
1885         gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
1886                                       GTK_RESPONSE_OK);
1887         gtk_widget_set_can_default (button, TRUE);
1888         gtk_dialog_set_default_response (GTK_DIALOG (widget),
1889                                          GTK_RESPONSE_OK);
1890
1891         gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
1892
1893         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
1894                 g_get_home_dir ());
1895
1896         gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget),
1897                 create_file_filter ());
1898
1899         g_signal_connect (widget, "response",
1900                           G_CALLBACK (file_manager_send_file_response_cb),
1901                           g_object_ref (contact));
1902
1903         gtk_widget_show (widget);
1904 }
1905
1906 static void
1907 file_manager_receive_file_response_cb (GtkDialog *dialog,
1908                                        GtkResponseType response,
1909                                        EmpathyFTHandler *handler)
1910 {
1911         EmpathyFTFactory *factory;
1912         GFile *file;
1913
1914         if (response == GTK_RESPONSE_OK) {
1915                 GFile *parent;
1916                 GFileInfo *info;
1917                 guint64 free_space, file_size;
1918                 GError *error = NULL;
1919
1920                 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
1921                 parent = g_file_get_parent (file);
1922                 info = g_file_query_filesystem_info (parent,
1923                                 G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
1924                                 NULL, &error);
1925
1926                 g_object_unref (parent);
1927
1928                 if (error != NULL) {
1929                         g_warning ("Error: %s", error->message);
1930
1931                         g_object_unref (file);
1932                         return;
1933                 }
1934
1935                 free_space = g_file_info_get_attribute_uint64 (info,
1936                                 G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
1937                 file_size = empathy_ft_handler_get_total_bytes (handler);
1938
1939                 g_object_unref (info);
1940
1941                 if (file_size > free_space) {
1942                         GtkWidget *message = gtk_message_dialog_new (
1943                                 GTK_WINDOW (dialog),
1944                                 GTK_DIALOG_MODAL,
1945                                 GTK_MESSAGE_ERROR,
1946                                 GTK_BUTTONS_CLOSE,
1947                                 _("Insufficient free space to save file"));
1948                         char *file_size_str, *free_space_str;
1949
1950                         file_size_str = g_format_size (file_size);
1951                         free_space_str = g_format_size (free_space);
1952
1953                         gtk_message_dialog_format_secondary_text (
1954                                 GTK_MESSAGE_DIALOG (message),
1955                                 _("%s of free space are required to save this "
1956                                   "file, but only %s is available. Please "
1957                                   "choose another location."),
1958                                 file_size_str, free_space_str);
1959
1960                         gtk_dialog_run (GTK_DIALOG (message));
1961
1962                         g_free (file_size_str);
1963                         g_free (free_space_str);
1964                         gtk_widget_destroy (message);
1965
1966                         g_object_unref (file);
1967
1968                         return;
1969                 }
1970
1971                 factory = empathy_ft_factory_dup_singleton ();
1972
1973                 empathy_ft_factory_set_destination_for_incoming_handler (
1974                                 factory, handler, file);
1975
1976                 g_object_unref (factory);
1977                 g_object_unref (file);
1978         } else {
1979                 /* unref the handler, as we dismissed the file chooser,
1980                  * and refused the transfer.
1981                  */
1982                 g_object_unref (handler);
1983         }
1984
1985         gtk_widget_destroy (GTK_WIDGET (dialog));
1986 }
1987
1988 void
1989 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
1990 {
1991         GtkWidget *widget;
1992         const gchar *dir;
1993         EmpathyContact *contact;
1994         gchar *title;
1995
1996         contact = empathy_ft_handler_get_contact (handler);
1997         g_assert (contact != NULL);
1998
1999         title = g_strdup_printf (_("Incoming file from %s"),
2000                 empathy_contact_get_alias (contact));
2001
2002         widget = gtk_file_chooser_dialog_new (title,
2003                                               NULL,
2004                                               GTK_FILE_CHOOSER_ACTION_SAVE,
2005                                               GTK_STOCK_CANCEL,
2006                                               GTK_RESPONSE_CANCEL,
2007                                               GTK_STOCK_SAVE,
2008                                               GTK_RESPONSE_OK,
2009                                               NULL);
2010         gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
2011                 empathy_ft_handler_get_filename (handler));
2012         gtk_file_chooser_set_do_overwrite_confirmation
2013                 (GTK_FILE_CHOOSER (widget), TRUE);
2014
2015         dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
2016         if (dir == NULL)
2017                 /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
2018                 dir = g_get_home_dir ();
2019
2020         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), dir);
2021
2022         g_signal_connect (widget, "response",
2023                 G_CALLBACK (file_manager_receive_file_response_cb), handler);
2024
2025         gtk_widget_show (widget);
2026         g_free (title);
2027 }
2028
2029 void
2030 empathy_make_color_whiter (GdkRGBA *color)
2031 {
2032         const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
2033
2034         color->red = (color->red + white.red) / 2;
2035         color->green = (color->green + white.green) / 2;
2036         color->blue = (color->blue + white.blue) / 2;
2037 }
2038
2039 static void
2040 menu_deactivate_cb (GtkMenu *menu,
2041         gpointer user_data)
2042 {
2043         /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
2044         g_signal_handlers_disconnect_by_func (menu,
2045                      menu_deactivate_cb, user_data);
2046
2047         gtk_menu_detach (menu);
2048 }
2049
2050 /* Convenient function to create a GtkMenu attached to @attach_to and detach
2051  * it when the menu is not displayed any more. This is useful when creating a
2052  * context menu that we want to get rid as soon as it as been displayed. */
2053 GtkWidget *
2054 empathy_context_menu_new (GtkWidget *attach_to)
2055 {
2056         GtkWidget *menu;
2057
2058         menu = gtk_menu_new ();
2059
2060         gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
2061
2062         /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
2063          * floating ref. We can either wait that @attach_to releases its ref when
2064          * it will be destroyed (when leaving Empathy most of the time) or explicitely
2065          * detach the menu when it's not displayed any more.
2066          * We go for the latter as we don't want to keep useless menus in memory
2067          * during the whole lifetime of Empathy. */
2068         g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
2069
2070         return menu;
2071 }
2072
2073 gint64
2074 empathy_get_current_action_time (void)
2075 {
2076   return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
2077 }
2078
2079 /* @words = empathy_live_search_strip_utf8_string (@text);
2080  *
2081  * User has to pass both so we don't have to compute @words ourself each time
2082  * this function is called. */
2083 gboolean
2084 empathy_individual_match_string (FolksIndividual *individual,
2085     const char *text,
2086     GPtrArray *words)
2087 {
2088   const gchar *str;
2089   GeeSet *personas;
2090   GeeIterator *iter;
2091   gboolean retval = FALSE;
2092
2093   /* check alias name */
2094   str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
2095
2096   if (empathy_live_search_match_words (str, words))
2097     return TRUE;
2098
2099   personas = folks_individual_get_personas (individual);
2100
2101   /* check contact id, remove the @server.com part */
2102   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2103   while (retval == FALSE && gee_iterator_next (iter))
2104     {
2105       FolksPersona *persona = gee_iterator_get (iter);
2106       const gchar *p;
2107
2108       if (empathy_folks_persona_is_interesting (persona))
2109         {
2110           str = folks_persona_get_display_id (persona);
2111
2112           /* Accept the persona if @text is a full prefix of his ID; that allows
2113            * user to find, say, a jabber contact by typing his JID. */
2114           if (g_str_has_prefix (str, text))
2115             {
2116               retval = TRUE;
2117             }
2118           else
2119             {
2120               gchar *dup_str = NULL;
2121               gboolean visible;
2122
2123               p = strstr (str, "@");
2124               if (p != NULL)
2125                 str = dup_str = g_strndup (str, p - str);
2126
2127               visible = empathy_live_search_match_words (str, words);
2128               g_free (dup_str);
2129               if (visible)
2130                 retval = TRUE;
2131             }
2132         }
2133       g_clear_object (&persona);
2134     }
2135   g_clear_object (&iter);
2136
2137   /* FIXME: Add more rules here, we could check phone numbers in
2138    * contact's vCard for example. */
2139   return retval;
2140 }
2141
2142 void
2143 empathy_launch_program (const gchar *dir,
2144     const gchar *name,
2145     const gchar *args)
2146 {
2147   GdkDisplay *display;
2148   GError *error = NULL;
2149   gchar *path, *cmd;
2150   GAppInfo *app_info;
2151   GdkAppLaunchContext *context = NULL;
2152
2153   /* Try to run from source directory if possible */
2154   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
2155       name, NULL);
2156
2157   if (!g_file_test (path, G_FILE_TEST_EXISTS))
2158     {
2159       g_free (path);
2160       path = g_build_filename (dir, name, NULL);
2161     }
2162
2163   if (args != NULL)
2164     cmd = g_strconcat (path, " ", args, NULL);
2165   else
2166     cmd = g_strdup (path);
2167
2168   app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
2169   if (app_info == NULL)
2170     {
2171       DEBUG ("Failed to create app info: %s", error->message);
2172       g_error_free (error);
2173       goto out;
2174     }
2175
2176   display = gdk_display_get_default ();
2177   context = gdk_display_get_app_launch_context (display);
2178
2179   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
2180       &error))
2181     {
2182       g_warning ("Failed to launch %s: %s", name, error->message);
2183       g_error_free (error);
2184       goto out;
2185     }
2186
2187 out:
2188   tp_clear_object (&app_info);
2189   tp_clear_object (&context);
2190   g_free (path);
2191   g_free (cmd);
2192 }
2193
2194 /* Most of the workspace manipulation code has been copied from libwnck
2195  * Copyright (C) 2001 Havoc Pennington
2196  * Copyright (C) 2005-2007 Vincent Untz
2197  */
2198 static void
2199 _wnck_activate_workspace (Screen *screen,
2200     int new_active_space,
2201     Time timestamp)
2202 {
2203   Display *display;
2204   Window   root;
2205   XEvent   xev;
2206
2207   display = DisplayOfScreen (screen);
2208   root = RootWindowOfScreen (screen);
2209
2210   xev.xclient.type = ClientMessage;
2211   xev.xclient.serial = 0;
2212   xev.xclient.send_event = True;
2213   xev.xclient.display = display;
2214   xev.xclient.window = root;
2215   xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
2216   xev.xclient.format = 32;
2217   xev.xclient.data.l[0] = new_active_space;
2218   xev.xclient.data.l[1] = timestamp;
2219   xev.xclient.data.l[2] = 0;
2220   xev.xclient.data.l[3] = 0;
2221   xev.xclient.data.l[4] = 0;
2222
2223   gdk_error_trap_push ();
2224   XSendEvent (display, root, False,
2225       SubstructureRedirectMask | SubstructureNotifyMask,
2226       &xev);
2227   XSync (display, False);
2228   gdk_error_trap_pop_ignored ();
2229 }
2230
2231 static gboolean
2232 _wnck_get_cardinal (Screen *screen,
2233     Window xwindow,
2234     Atom atom,
2235     int *val)
2236 {
2237   Display *display;
2238   Atom type;
2239   int format;
2240   gulong nitems;
2241   gulong bytes_after;
2242   gulong *num;
2243   int err, result;
2244
2245   display = DisplayOfScreen (screen);
2246
2247   *val = 0;
2248
2249   gdk_error_trap_push ();
2250   type = None;
2251   result = XGetWindowProperty (display, xwindow, atom,
2252       0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
2253       &bytes_after, (void *) &num);
2254   err = gdk_error_trap_pop ();
2255   if (err != Success ||
2256       result != Success)
2257     return FALSE;
2258
2259   if (type != XA_CARDINAL)
2260     {
2261       XFree (num);
2262       return FALSE;
2263     }
2264
2265   *val = *num;
2266
2267   XFree (num);
2268
2269   return TRUE;
2270 }
2271
2272 static int
2273 window_get_workspace (Screen *xscreen,
2274     Window win)
2275 {
2276   int number;
2277
2278   if (!_wnck_get_cardinal (xscreen, win,
2279         gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
2280     return -1;
2281
2282   return number;
2283 }
2284
2285 /* Ask X to move to the desktop on which @window currently is
2286  * and the present @window. */
2287 void
2288 empathy_move_to_window_desktop (GtkWindow *window,
2289     guint32 timestamp)
2290 {
2291   GdkScreen *screen;
2292   Screen *xscreen;
2293   GdkWindow *gdk_window;
2294   int workspace;
2295
2296   screen = gtk_window_get_screen (window);
2297   xscreen = gdk_x11_screen_get_xscreen (screen);
2298   gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
2299
2300   workspace = window_get_workspace (xscreen,
2301       gdk_x11_window_get_xid (gdk_window));
2302   if (workspace == -1)
2303     goto out;
2304
2305   _wnck_activate_workspace (xscreen, workspace, timestamp);
2306
2307 out:
2308   gtk_window_present_with_time (window, timestamp);
2309 }
2310
2311 void
2312 empathy_set_css_provider (GtkWidget *widget)
2313 {
2314   GtkCssProvider *provider;
2315   gchar *filename;
2316   GError *error = NULL;
2317   GdkScreen *screen;
2318
2319   filename = empathy_file_lookup ("empathy.css", "data");
2320
2321   provider = gtk_css_provider_new ();
2322
2323   if (!gtk_css_provider_load_from_path (provider, filename, &error))
2324     {
2325       g_warning ("Failed to load css file '%s': %s", filename, error->message);
2326       g_error_free (error);
2327       goto out;
2328     }
2329
2330   if (widget != NULL)
2331     screen = gtk_widget_get_screen (widget);
2332   else
2333     screen = gdk_screen_get_default ();
2334
2335   gtk_style_context_add_provider_for_screen (screen,
2336       GTK_STYLE_PROVIDER (provider),
2337       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2338
2339 out:
2340   g_free (filename);
2341   g_object_unref (provider);
2342 }