1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25 #include "config.h"
26 
27 #include "gdkselection.h"
28 #include "gdkproperty.h"
29 #include "gdkprivate.h"
30 #include "gdkprivate-x11.h"
31 #include "gdkdisplay-x11.h"
32 
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 #include <string.h>
36 
37 
38 typedef struct _OwnerInfo OwnerInfo;
39 
40 struct _OwnerInfo
41 {
42   GdkAtom    selection;
43   GdkWindow *owner;
44   gulong     serial;
45 };
46 
47 static GSList *owner_list;
48 
49 /* When a window is destroyed we check if it is the owner
50  * of any selections. This is somewhat inefficient, but
51  * owner_list is typically short, and it is a low memory,
52  * low code solution
53  */
54 void
_gdk_x11_selection_window_destroyed(GdkWindow * window)55 _gdk_x11_selection_window_destroyed (GdkWindow *window)
56 {
57   GSList *tmp_list = owner_list;
58   while (tmp_list)
59     {
60       OwnerInfo *info = tmp_list->data;
61       tmp_list = tmp_list->next;
62 
63       if (info->owner == window)
64         {
65           owner_list = g_slist_remove (owner_list, info);
66           g_free (info);
67         }
68     }
69 }
70 
71 /* We only pass through those SelectionClear events that actually
72  * reflect changes to the selection owner that we didn’t make ourself.
73  */
74 gboolean
_gdk_x11_selection_filter_clear_event(XSelectionClearEvent * event)75 _gdk_x11_selection_filter_clear_event (XSelectionClearEvent *event)
76 {
77   GSList *tmp_list = owner_list;
78   GdkDisplay *display = gdk_x11_lookup_xdisplay (event->display);
79 
80   while (tmp_list)
81     {
82       OwnerInfo *info = tmp_list->data;
83 
84       if (gdk_window_get_display (info->owner) == display &&
85           info->selection == gdk_x11_xatom_to_atom_for_display (display, event->selection))
86         {
87           if ((GDK_WINDOW_XID (info->owner) == event->window &&
88                event->serial >= info->serial))
89             {
90               owner_list = g_slist_remove (owner_list, info);
91               g_free (info);
92               return TRUE;
93             }
94           else
95             return FALSE;
96         }
97       tmp_list = tmp_list->next;
98     }
99 
100   return FALSE;
101 }
102 
103 gboolean
_gdk_x11_display_set_selection_owner(GdkDisplay * display,GdkWindow * owner,GdkAtom selection,guint32 time,gboolean send_event)104 _gdk_x11_display_set_selection_owner (GdkDisplay *display,
105                                       GdkWindow  *owner,
106                                       GdkAtom     selection,
107                                       guint32     time,
108                                       gboolean    send_event)
109 {
110   Display *xdisplay;
111   Window xwindow;
112   Atom xselection;
113   GSList *tmp_list;
114   OwnerInfo *info;
115 
116   if (gdk_display_is_closed (display))
117     return FALSE;
118 
119   if (owner)
120     {
121       if (GDK_WINDOW_DESTROYED (owner) || !GDK_WINDOW_IS_X11 (owner))
122         return FALSE;
123 
124       gdk_window_ensure_native (owner);
125       xdisplay = GDK_WINDOW_XDISPLAY (owner);
126       xwindow = GDK_WINDOW_XID (owner);
127     }
128   else
129     {
130       xdisplay = GDK_DISPLAY_XDISPLAY (display);
131       xwindow = None;
132     }
133 
134   xselection = gdk_x11_atom_to_xatom_for_display (display, selection);
135 
136   tmp_list = owner_list;
137   while (tmp_list)
138     {
139       info = tmp_list->data;
140       if (info->selection == selection)
141         {
142           owner_list = g_slist_remove (owner_list, info);
143           g_free (info);
144           break;
145         }
146       tmp_list = tmp_list->next;
147     }
148 
149   if (owner)
150     {
151       info = g_new (OwnerInfo, 1);
152       info->owner = owner;
153       info->serial = NextRequest (GDK_WINDOW_XDISPLAY (owner));
154       info->selection = selection;
155 
156       owner_list = g_slist_prepend (owner_list, info);
157     }
158 
159   XSetSelectionOwner (xdisplay, xselection, xwindow, time);
160 
161   return (XGetSelectionOwner (xdisplay, xselection) == xwindow);
162 }
163 
164 GdkWindow *
_gdk_x11_display_get_selection_owner(GdkDisplay * display,GdkAtom selection)165 _gdk_x11_display_get_selection_owner (GdkDisplay *display,
166                                       GdkAtom     selection)
167 {
168   Window xwindow;
169 
170   if (gdk_display_is_closed (display))
171     return NULL;
172 
173   xwindow = XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
174                                 gdk_x11_atom_to_xatom_for_display (display,
175                                                                    selection));
176   if (xwindow == None)
177     return NULL;
178 
179   return gdk_x11_window_lookup_for_display (display, xwindow);
180 }
181 
182 void
_gdk_x11_display_convert_selection(GdkDisplay * display,GdkWindow * requestor,GdkAtom selection,GdkAtom target,guint32 time)183 _gdk_x11_display_convert_selection (GdkDisplay *display,
184                                     GdkWindow  *requestor,
185                                     GdkAtom     selection,
186                                     GdkAtom     target,
187                                     guint32     time)
188 {
189   g_return_if_fail (selection != GDK_NONE);
190 
191   if (GDK_WINDOW_DESTROYED (requestor) || !GDK_WINDOW_IS_X11 (requestor))
192     return;
193 
194   gdk_window_ensure_native (requestor);
195 
196   XConvertSelection (GDK_WINDOW_XDISPLAY (requestor),
197                      gdk_x11_atom_to_xatom_for_display (display, selection),
198                      gdk_x11_atom_to_xatom_for_display (display, target),
199                      gdk_x11_get_xatom_by_name_for_display (display, "GDK_SELECTION"),
200                      GDK_WINDOW_XID (requestor), time);
201 }
202 
203 gint
_gdk_x11_display_get_selection_property(GdkDisplay * display,GdkWindow * requestor,guchar ** data,GdkAtom * ret_type,gint * ret_format)204 _gdk_x11_display_get_selection_property (GdkDisplay  *display,
205                                          GdkWindow   *requestor,
206                                          guchar     **data,
207                                          GdkAtom     *ret_type,
208                                          gint        *ret_format)
209 {
210   gulong nitems;
211   gulong nbytes;
212   gulong length = 0;
213   Atom prop_type;
214   gint prop_format;
215   guchar *t = NULL;
216 
217   if (GDK_WINDOW_DESTROYED (requestor) || !GDK_WINDOW_IS_X11 (requestor))
218     goto err;
219 
220   t = NULL;
221 
222   /* We can't delete the selection here, because it might be the INCR
223      protocol, in which case the client has to make sure they'll be
224      notified of PropertyChange events _before_ the property is deleted.
225      Otherwise there's no guarantee we'll win the race ... */
226   if (XGetWindowProperty (GDK_WINDOW_XDISPLAY (requestor),
227                           GDK_WINDOW_XID (requestor),
228                           gdk_x11_get_xatom_by_name_for_display (display, "GDK_SELECTION"),
229                           0, 0x1FFFFFFF /* MAXINT32 / 4 */, False,
230                           AnyPropertyType, &prop_type, &prop_format,
231                           &nitems, &nbytes, &t) != Success)
232     goto err;
233 
234   if (prop_type != None)
235     {
236       if (ret_type)
237         *ret_type = gdk_x11_xatom_to_atom_for_display (display, prop_type);
238       if (ret_format)
239         *ret_format = prop_format;
240 
241       if (prop_type == XA_ATOM ||
242           prop_type == gdk_x11_get_xatom_by_name_for_display (display, "ATOM_PAIR"))
243         {
244           Atom* atoms = (Atom*) t;
245           GdkAtom* atoms_dest;
246           gint num_atom, i;
247 
248           if (prop_format != 32)
249             goto err;
250 
251           num_atom = nitems;
252           length = sizeof (GdkAtom) * num_atom + 1;
253 
254           if (data)
255             {
256               *data = g_malloc (length);
257               (*data)[length - 1] = '\0';
258               atoms_dest = (GdkAtom *)(*data);
259 
260               for (i=0; i < num_atom; i++)
261                 atoms_dest[i] = gdk_x11_xatom_to_atom_for_display (display, atoms[i]);
262             }
263         }
264       else
265         {
266           switch (prop_format)
267             {
268             case 8:
269               length = nitems;
270               break;
271             case 16:
272               length = sizeof(short) * nitems;
273               break;
274             case 32:
275               length = sizeof(long) * nitems;
276               break;
277             default:
278               g_assert_not_reached ();
279               break;
280             }
281 
282           /* Add on an extra byte to handle null termination.  X guarantees
283              that t will be 1 longer than nitems and null terminated */
284           length += 1;
285 
286           if (data)
287             *data = g_memdup (t, length);
288         }
289 
290       if (t)
291         XFree (t);
292 
293       return length - 1;
294     }
295 
296  err:
297   if (ret_type)
298     *ret_type = GDK_NONE;
299   if (ret_format)
300     *ret_format = 0;
301   if (data)
302     *data = NULL;
303 
304   return 0;
305 }
306 
307 void
_gdk_x11_display_send_selection_notify(GdkDisplay * display,GdkWindow * requestor,GdkAtom selection,GdkAtom target,GdkAtom property,guint32 time)308 _gdk_x11_display_send_selection_notify (GdkDisplay       *display,
309                                         GdkWindow        *requestor,
310                                         GdkAtom          selection,
311                                         GdkAtom          target,
312                                         GdkAtom          property,
313                                         guint32          time)
314 {
315   XSelectionEvent xevent;
316 
317   xevent.type = SelectionNotify;
318   xevent.serial = 0;
319   xevent.send_event = True;
320   xevent.requestor = GDK_WINDOW_XID (requestor);
321   xevent.selection = gdk_x11_atom_to_xatom_for_display (display, selection);
322   xevent.target = gdk_x11_atom_to_xatom_for_display (display, target);
323   if (property == GDK_NONE)
324     xevent.property = None;
325   else
326     xevent.property = gdk_x11_atom_to_xatom_for_display (display, property);
327   xevent.time = time;
328 
329   _gdk_x11_display_send_xevent (display, xevent.requestor, False, NoEventMask, (XEvent*) & xevent);
330 }
331 
332 /**
333  * gdk_x11_display_text_property_to_text_list:
334  * @display: (type GdkX11Display): The #GdkDisplay where the encoding is defined
335  * @encoding: an atom representing the encoding. The most
336  *    common values for this are STRING, or COMPOUND_TEXT.
337  *    This is value used as the type for the property
338  * @format: the format of the property
339  * @text: The text data
340  * @length: The number of items to transform
341  * @list: location to store an  array of strings in
342  *    the encoding of the current locale. This array should be
343  *    freed using gdk_free_text_list().
344  *
345  * Convert a text string from the encoding as it is stored
346  * in a property into an array of strings in the encoding of
347  * the current locale. (The elements of the array represent the
348  * nul-separated elements of the original text string.)
349  *
350  * Returns: the number of strings stored in list, or 0,
351  *     if the conversion failed
352  *
353  * Since: 2.24
354  */
355 gint
gdk_x11_display_text_property_to_text_list(GdkDisplay * display,GdkAtom encoding,gint format,const guchar * text,gint length,gchar *** list)356 gdk_x11_display_text_property_to_text_list (GdkDisplay   *display,
357                                             GdkAtom       encoding,
358                                             gint          format,
359                                             const guchar *text,
360                                             gint          length,
361                                             gchar      ***list)
362 {
363   XTextProperty property;
364   gint count = 0;
365   gint res;
366   gchar **local_list;
367   g_return_val_if_fail (GDK_IS_DISPLAY (display), 0);
368 
369   if (list)
370     *list = NULL;
371 
372   if (gdk_display_is_closed (display))
373     return 0;
374 
375   property.value = (guchar *)text;
376   property.encoding = gdk_x11_atom_to_xatom_for_display (display, encoding);
377   property.format = format;
378   property.nitems = length;
379   res = XmbTextPropertyToTextList (GDK_DISPLAY_XDISPLAY (display), &property,
380                                    &local_list, &count);
381   if (res == XNoMemory || res == XLocaleNotSupported || res == XConverterNotFound)
382     return 0;
383   else
384     {
385       if (list)
386         *list = local_list;
387       else
388         XFreeStringList (local_list);
389 
390       return count;
391     }
392 }
393 
394 /**
395  * gdk_x11_free_text_list:
396  * @list: the value stored in the @list parameter by
397  *   a call to gdk_x11_display_text_property_to_text_list().
398  *
399  * Frees the array of strings created by
400  * gdk_x11_display_text_property_to_text_list().
401  *
402  * Since: 2.24
403  */
404 void
gdk_x11_free_text_list(gchar ** list)405 gdk_x11_free_text_list (gchar **list)
406 {
407   g_return_if_fail (list != NULL);
408 
409   XFreeStringList (list);
410 }
411 
412 static gint
make_list(const gchar * text,gint length,gboolean latin1,gchar *** list)413 make_list (const gchar  *text,
414            gint          length,
415            gboolean      latin1,
416            gchar      ***list)
417 {
418   GSList *strings = NULL;
419   gint n_strings = 0;
420   gint i;
421   const gchar *p = text;
422   const gchar *q;
423   GSList *tmp_list;
424   GError *error = NULL;
425 
426   while (p < text + length)
427     {
428       gchar *str;
429 
430       q = p;
431       while (*q && q < text + length)
432         q++;
433 
434       if (latin1)
435         {
436           str = g_convert (p, q - p,
437                            "UTF-8", "ISO-8859-1",
438                            NULL, NULL, &error);
439 
440           if (!str)
441             {
442               g_warning ("Error converting selection from STRING: %s",
443                          error->message);
444               g_error_free (error);
445             }
446         }
447       else
448         {
449           str = g_strndup (p, q - p);
450           if (!g_utf8_validate (str, -1, NULL))
451             {
452               g_warning ("Error converting selection from UTF8_STRING");
453               g_free (str);
454               str = NULL;
455             }
456         }
457 
458       if (str)
459         {
460           strings = g_slist_prepend (strings, str);
461           n_strings++;
462         }
463 
464       p = q + 1;
465     }
466 
467   if (list)
468     {
469       *list = g_new (gchar *, n_strings + 1);
470       (*list)[n_strings] = NULL;
471     }
472 
473   i = n_strings;
474   tmp_list = strings;
475   while (tmp_list)
476     {
477       if (list)
478         (*list)[--i] = tmp_list->data;
479       else
480         g_free (tmp_list->data);
481 
482       tmp_list = tmp_list->next;
483     }
484 
485   g_slist_free (strings);
486 
487   return n_strings;
488 }
489 
490 gint
_gdk_x11_display_text_property_to_utf8_list(GdkDisplay * display,GdkAtom encoding,gint format,const guchar * text,gint length,gchar *** list)491 _gdk_x11_display_text_property_to_utf8_list (GdkDisplay    *display,
492                                              GdkAtom        encoding,
493                                              gint           format,
494                                              const guchar  *text,
495                                              gint           length,
496                                              gchar       ***list)
497 {
498   if (encoding == GDK_TARGET_STRING)
499     {
500       return make_list ((gchar *)text, length, TRUE, list);
501     }
502   else if (encoding == gdk_atom_intern_static_string ("UTF8_STRING"))
503     {
504       return make_list ((gchar *)text, length, FALSE, list);
505     }
506   else
507     {
508       gchar **local_list;
509       gint local_count;
510       gint i;
511       const gchar *charset = NULL;
512       gboolean need_conversion = !g_get_charset (&charset);
513       gint count = 0;
514       GError *error = NULL;
515 
516       /* Probably COMPOUND text, we fall back to Xlib routines
517        */
518       local_count = gdk_x11_display_text_property_to_text_list (display,
519                                                                 encoding,
520                                                                 format,
521                                                                 text,
522                                                                 length,
523                                                                 &local_list);
524       if (list)
525         *list = g_new (gchar *, local_count + 1);
526 
527       for (i=0; i<local_count; i++)
528         {
529           /* list contains stuff in our default encoding
530            */
531           if (need_conversion)
532             {
533               gchar *utf = g_convert (local_list[i], -1,
534                                       "UTF-8", charset,
535                                       NULL, NULL, &error);
536               if (utf)
537                 {
538                   if (list)
539                     (*list)[count++] = utf;
540                   else
541                     g_free (utf);
542                 }
543               else
544                 {
545                   g_warning ("Error converting to UTF-8 from '%s': %s",
546                              charset, error->message);
547                   g_error_free (error);
548                   error = NULL;
549                 }
550             }
551           else
552             {
553               if (list)
554                 {
555                   if (g_utf8_validate (local_list[i], -1, NULL))
556                     (*list)[count++] = g_strdup (local_list[i]);
557                   else
558                     g_warning ("Error converting selection");
559                 }
560             }
561         }
562 
563       if (local_count)
564         gdk_x11_free_text_list (local_list);
565 
566       if (list)
567         (*list)[count] = NULL;
568 
569       return count;
570     }
571 }
572 
573 /**
574  * gdk_x11_display_string_to_compound_text:
575  * @display: (type GdkX11Display): the #GdkDisplay where the encoding is defined
576  * @str: a nul-terminated string
577  * @encoding: (out) (transfer none): location to store the encoding atom
578  *     (to be used as the type for the property)
579  * @format: (out): location to store the format of the property
580  * @ctext: (out) (array length=length): location to store newly
581  *     allocated data for the property
582  * @length: the length of @ctext, in bytes
583  *
584  * Convert a string from the encoding of the current
585  * locale into a form suitable for storing in a window property.
586  *
587  * Returns: 0 upon success, non-zero upon failure
588  *
589  * Since: 2.24
590  */
591 gint
gdk_x11_display_string_to_compound_text(GdkDisplay * display,const gchar * str,GdkAtom * encoding,gint * format,guchar ** ctext,gint * length)592 gdk_x11_display_string_to_compound_text (GdkDisplay  *display,
593                                          const gchar *str,
594                                          GdkAtom     *encoding,
595                                          gint        *format,
596                                          guchar     **ctext,
597                                          gint        *length)
598 {
599   gint res;
600   XTextProperty property;
601 
602   g_return_val_if_fail (GDK_IS_DISPLAY (display), 0);
603 
604   if (gdk_display_is_closed (display))
605     res = XLocaleNotSupported;
606   else
607     res = XmbTextListToTextProperty (GDK_DISPLAY_XDISPLAY (display),
608                                      (char **)&str, 1, XCompoundTextStyle,
609                                      &property);
610   if (res != Success)
611     {
612       property.encoding = None;
613       property.format = None;
614       property.value = NULL;
615       property.nitems = 0;
616     }
617 
618   if (encoding)
619     *encoding = gdk_x11_xatom_to_atom_for_display (display, property.encoding);
620   if (format)
621     *format = property.format;
622   if (ctext)
623     *ctext = property.value;
624   if (length)
625     *length = property.nitems;
626 
627   return res;
628 }
629 
630 /* The specifications for COMPOUND_TEXT and STRING specify that C0 and
631  * C1 are not allowed except for \n and \t, however the X conversions
632  * routines for COMPOUND_TEXT only enforce this in one direction,
633  * causing cut-and-paste of \r and \r\n separated text to fail.
634  * This routine strips out all non-allowed C0 and C1 characters
635  * from the input string and also canonicalizes \r, and \r\n to \n
636  */
637 static gchar *
sanitize_utf8(const gchar * src,gboolean return_latin1)638 sanitize_utf8 (const gchar *src,
639                gboolean return_latin1)
640 {
641   gint len = strlen (src);
642   GString *result = g_string_sized_new (len);
643   const gchar *p = src;
644 
645   while (*p)
646     {
647       if (*p == '\r')
648         {
649           p++;
650           if (*p == '\n')
651             p++;
652 
653           g_string_append_c (result, '\n');
654         }
655       else
656         {
657           gunichar ch = g_utf8_get_char (p);
658 
659           if (!((ch < 0x20 && ch != '\t' && ch != '\n') || (ch >= 0x7f && ch < 0xa0)))
660             {
661               if (return_latin1)
662                 {
663                   if (ch <= 0xff)
664                     g_string_append_c (result, ch);
665                   else
666                     g_string_append_printf (result,
667                                             ch < 0x10000 ? "\\u%04x" : "\\U%08x",
668                                             ch);
669                 }
670               else
671                 {
672                   char buf[7];
673                   gint buflen;
674 
675                   buflen = g_unichar_to_utf8 (ch, buf);
676                   g_string_append_len (result, buf, buflen);
677                 }
678             }
679 
680           p = g_utf8_next_char (p);
681         }
682     }
683 
684   return g_string_free (result, FALSE);
685 }
686 
687 gchar *
_gdk_x11_display_utf8_to_string_target(GdkDisplay * display,const gchar * str)688 _gdk_x11_display_utf8_to_string_target (GdkDisplay  *display,
689                                         const gchar *str)
690 {
691   return sanitize_utf8 (str, TRUE);
692 }
693 
694 /**
695  * gdk_x11_display_utf8_to_compound_text:
696  * @display: (type GdkX11Display): a #GdkDisplay
697  * @str: a UTF-8 string
698  * @encoding: (out): location to store resulting encoding
699  * @format: (out): location to store format of the result
700  * @ctext: (out) (array length=length): location to store the data of the result
701  * @length: location to store the length of the data
702  *     stored in @ctext
703  *
704  * Converts from UTF-8 to compound text.
705  *
706  * Returns: %TRUE if the conversion succeeded,
707  *     otherwise %FALSE
708  *
709  * Since: 2.24
710  */
711 gboolean
gdk_x11_display_utf8_to_compound_text(GdkDisplay * display,const gchar * str,GdkAtom * encoding,gint * format,guchar ** ctext,gint * length)712 gdk_x11_display_utf8_to_compound_text (GdkDisplay  *display,
713                                        const gchar *str,
714                                        GdkAtom     *encoding,
715                                        gint        *format,
716                                        guchar     **ctext,
717                                        gint        *length)
718 {
719   gboolean need_conversion;
720   const gchar *charset;
721   gchar *locale_str, *tmp_str;
722   GError *error = NULL;
723   gboolean result;
724 
725   g_return_val_if_fail (str != NULL, FALSE);
726   g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
727 
728   need_conversion = !g_get_charset (&charset);
729 
730   tmp_str = sanitize_utf8 (str, FALSE);
731 
732   if (need_conversion)
733     {
734       locale_str = g_convert (tmp_str, -1,
735                               charset, "UTF-8",
736                               NULL, NULL, &error);
737       g_free (tmp_str);
738 
739       if (!locale_str)
740         {
741           if (!g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
742             {
743               g_warning ("Error converting from UTF-8 to '%s': %s",
744                          charset, error->message);
745             }
746           g_error_free (error);
747 
748           if (encoding)
749             *encoding = None;
750           if (format)
751             *format = None;
752           if (ctext)
753             *ctext = NULL;
754           if (length)
755             *length = 0;
756 
757           return FALSE;
758         }
759     }
760   else
761     locale_str = tmp_str;
762 
763   result = gdk_x11_display_string_to_compound_text (display, locale_str,
764                                                     encoding, format,
765                                                     ctext, length);
766   result = (result == Success? TRUE : FALSE);
767 
768   g_free (locale_str);
769 
770   return result;
771 }
772 
773 /**
774  * gdk_x11_free_compound_text:
775  * @ctext: The pointer stored in @ctext from a call to
776  *   gdk_x11_display_string_to_compound_text().
777  *
778  * Frees the data returned from gdk_x11_display_string_to_compound_text().
779  *
780  * Since: 2.24
781  */
782 void
gdk_x11_free_compound_text(guchar * ctext)783 gdk_x11_free_compound_text (guchar *ctext)
784 {
785   if (ctext)
786     XFree (ctext);
787 }
788