]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/window.c
Fixed uninitialized value.
[vlc] / modules / video_output / xcb / window.c
1 /**
2  * @file window.c
3  * @brief X C Bindings window provider module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <stdarg.h>
28 #include <assert.h>
29 #include <poll.h>
30 #include <unistd.h> /* gethostname() and sysconf() */
31 #include <limits.h> /* _POSIX_HOST_NAME_MAX */
32
33 #include <xcb/xcb.h>
34 typedef xcb_atom_t Atom;
35 #include <X11/Xatom.h> /* XA_WM_NAME */
36
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_window.h>
40
41 #include "xcb_vlc.h"
42
43 #define XID_TEXT N_("ID of the video output X window")
44 #define XID_LONGTEXT N_( \
45     "VLC can embed its video output in an existing X11 window. " \
46     "This is the X identifier of that window (0 means none).")
47
48 static int  Open (vlc_object_t *);
49 static void Close (vlc_object_t *);
50 static int  EmOpen (vlc_object_t *);
51 static void EmClose (vlc_object_t *);
52
53 /*
54  * Module descriptor
55  */
56 vlc_module_begin ()
57     set_shortname (N_("X window"))
58     set_description (N_("X11 video window (XCB)"))
59     set_category (CAT_VIDEO)
60     set_subcategory (SUBCAT_VIDEO_VOUT)
61     set_capability ("vout window xid", 10)
62     set_callbacks (Open, Close)
63
64     /* Obsolete since 1.1.0: */
65     add_obsolete_bool ("x11-altfullscreen")
66     add_obsolete_bool ("xvideo-altfullscreen")
67     add_obsolete_bool ("xvmc-altfullscreen")
68     add_obsolete_bool ("glx-altfullscreen")
69
70     add_submodule ()
71     set_shortname (N_("Drawable"))
72     set_description (N_("Embedded window video"))
73     set_category (CAT_VIDEO)
74     set_subcategory (SUBCAT_VIDEO_VOUT)
75     set_capability ("vout window xid", 70)
76     set_callbacks (EmOpen, EmClose)
77
78     add_integer ("drawable-xid", 0, NULL, XID_TEXT, XID_LONGTEXT, true)
79         change_unsaveable ()
80
81 vlc_module_end ()
82
83 static int Control (vout_window_t *, int, va_list ap);
84 static void *Thread (void *);
85
86 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
87
88 struct vout_window_sys_t
89 {
90     xcb_connection_t *conn;
91     key_handler_t *keys;
92     vlc_thread_t thread;
93
94     xcb_window_t root;
95     xcb_atom_t wm_state;
96     xcb_atom_t wm_state_above;
97     xcb_atom_t wm_state_below;
98     xcb_atom_t wm_state_fullscreen;
99 #ifdef MATCHBOX_HACK
100     xcb_atom_t mb_current_app_window;
101 #endif
102 };
103
104 /** Set an X window property from a nul-terminated string */
105 static inline
106 void set_string (xcb_connection_t *conn, xcb_window_t window,
107                  xcb_atom_t type, xcb_atom_t atom, const char *str)
108 {
109     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
110                          /* format */ 8, strlen (str), str);
111 }
112
113 /** Set an X window string property */
114 static inline
115 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
116                      xcb_atom_t atom, const char *value)
117 {
118     set_string (conn, window, XA_STRING, atom, value);
119 }
120
121 static inline
122 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
123 {
124     static const uint32_t wm_hints[8] = {
125         3, /* flags: Input, Initial state */
126         1, /* input: True */
127         1, /* initial state: Normal */
128         0, 0, 0, 0, 0, /* Icon */
129     };
130     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
131                          XA_WM_HINTS, 32, 8, wm_hints);
132 }
133
134 /** Set the Window ICCCM client machine property */
135 static inline
136 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
137 {
138     char* hostname;
139     long host_name_max = sysconf (_SC_HOST_NAME_MAX);
140     if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
141     hostname = malloc (host_name_max);
142     if(!hostname) return;
143
144     if (gethostname (hostname, host_name_max) == 0)
145     {
146         hostname[host_name_max - 1] = '\0';
147         set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
148     }
149     free(hostname);
150 }
151
152 /** Request the X11 server to internalize a string into an atom */
153 static inline
154 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
155 {
156     return xcb_intern_atom (c, 0, strlen (s), s);
157 }
158
159 /** Extract the X11 atom from an intern request cookie */
160 static
161 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
162 {
163     xcb_intern_atom_reply_t *reply;
164     xcb_atom_t atom;
165
166     reply = xcb_intern_atom_reply (conn, ck, NULL);
167     if (reply == NULL)
168         return 0;
169
170     atom = reply->atom;
171     free (reply);
172     return atom;
173 }
174
175 #define NET_WM_STATE_REMOVE 0
176 #define NET_WM_STATE_ADD    1
177 #define NET_WM_STATE_TOGGLE 2
178
179 static void CacheAtoms (vout_window_sys_t *p_sys)
180 {
181     xcb_connection_t *conn = p_sys->conn;
182     xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
183                              wm_state_below_ck, wm_state_fs_ck;
184
185     wm_state_ck = intern_string (conn, "_NET_WM_STATE");
186     wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
187     wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
188     wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
189 #ifdef MATCHBOX_HACK
190     xcb_intern_atom_cookie_t mb_current_app_window;
191     mb_current_app_window = xcb_intern_atom (conn, true,
192                                              strlen ("_MB_CURRENT_APP_WINDOW"),
193                                              "_MB_CURRENT_APP_WINDOW");
194 #endif
195
196     p_sys->wm_state = get_atom (conn, wm_state_ck);
197     p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
198     p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
199     p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
200 #ifdef MATCHBOX_HACK
201     p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
202 #endif
203 }
204
205 /**
206  * Create an X11 window.
207  */
208 static int Open (vlc_object_t *obj)
209 {
210     vout_window_t *wnd = (vout_window_t *)obj;
211     xcb_generic_error_t *err;
212     xcb_void_cookie_t ck;
213
214     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
215     if (p_sys == NULL)
216         return VLC_ENOMEM;
217
218     /* Connect to X */
219     char *display = var_CreateGetNonEmptyString (wnd, "x11-display");
220     int snum;
221
222     xcb_connection_t *conn = xcb_connect (display, &snum);
223     if (xcb_connection_has_error (conn) /*== NULL*/)
224         goto error;
225
226     /* Find configured screen */
227     const xcb_setup_t *setup = xcb_get_setup (conn);
228     const xcb_screen_t *scr = NULL;
229     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
230          i.rem > 0; xcb_screen_next (&i))
231     {
232         if (snum == 0)
233         {
234             scr = i.data;
235             break;
236         }
237         snum--;
238     }
239     if (scr == NULL)
240     {
241         msg_Err (wnd, "bad X11 screen number");
242         goto error;
243     }
244
245     /* Create window */
246     const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
247     uint32_t values[2] = {
248         /* XCB_CW_BACK_PIXEL */
249         scr->black_pixel,
250         /* XCB_CW_EVENT_MASK */
251         XCB_EVENT_MASK_KEY_PRESS,
252     };
253
254     xcb_window_t window = xcb_generate_id (conn);
255     ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
256                                     wnd->cfg->x, wnd->cfg->y,
257                                     wnd->cfg->width, wnd->cfg->height, 0,
258                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,
259                                     scr->root_visual, mask, values);
260     err = xcb_request_check (conn, ck);
261     if (err)
262     {
263         msg_Err (wnd, "creating window: X11 error %d", err->error_code);
264         free (err);
265         goto error;
266     }
267
268     wnd->handle.xid = window;
269     wnd->display.x11 = display;
270     wnd->control = Control;
271     wnd->sys = p_sys;
272
273     p_sys->conn = conn;
274     if (var_CreateGetBool (obj, "keyboard-events"))
275         p_sys->keys = CreateKeyHandler (obj, conn);
276     else
277         p_sys->keys = NULL;
278     p_sys->root = scr->root;
279
280     /* ICCCM
281      * No cut&paste nor drag&drop, only Window Manager communication. */
282     /* xgettext:
283        Plain ASCII of "VLC media player" for the ICCCM window name.
284        This must be ASCII. The limitation is partially with ICCCM
285        and partially with VLC.
286        For Latin script languages, you may need to strip accents.
287        For other scripts, you will need to transliterate into Latin. */
288     set_ascii_prop (conn, window, XA_WM_NAME,
289                   vlc_pgettext ("ASCII", "VLC media player"));
290     /* xgettext:
291        Plain ASCII of "VLC" for the ICCCM window name. */
292     set_ascii_prop (conn, window, XA_WM_ICON_NAME,
293                     vlc_pgettext ("ASCII", "VLC"));
294     set_wm_hints (conn, window);
295     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
296                          XA_STRING, 8, 8, "vlc\0Vlc");
297     set_hostname_prop (conn, window);
298
299     /* EWMH */
300     xcb_intern_atom_cookie_t utf8_string_ck
301         = intern_string (conn, "UTF8_STRING");;
302     xcb_intern_atom_cookie_t net_wm_name_ck
303         = intern_string (conn, "_NET_WM_NAME");
304     xcb_intern_atom_cookie_t net_wm_icon_name_ck
305         = intern_string (conn, "_NET_WM_ICON_NAME");
306     xcb_intern_atom_cookie_t wm_window_role_ck
307         = intern_string (conn, "WM_WINDOW_ROLE");
308
309     xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
310
311     xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
312     char *title = var_CreateGetNonEmptyString (wnd, "video-title");
313     if (title)
314     {
315         set_string (conn, window, utf8, net_wm_name, title);
316         free (title);
317     }
318     else
319         set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
320
321     xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
322     set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
323
324     xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
325     set_ascii_prop (conn, window, wm_window_role, "vlc-video");
326
327     /* Cache any EWMH atom we may need later */
328     CacheAtoms (p_sys);
329 #ifdef MATCHBOX_HACK
330     if (p_sys->mb_current_app_window)
331     {
332         uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
333         xcb_change_window_attributes (conn, scr->root,
334                                       XCB_CW_EVENT_MASK, &value);
335     }
336 #endif
337
338     /* Make the window visible */
339     xcb_map_window (conn, window);
340
341     if (var_CreateGetBool (obj, "video-wallpaper"))
342     {
343         vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
344         vout_window_SetFullScreen (wnd, true);
345     }
346
347     /* Create the event thread. It will dequeue all events, so any checked
348      * request from this thread must be completed at this point. */
349     if ((p_sys->keys != NULL)
350      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
351         DestroyKeyHandler (p_sys->keys);
352
353 #ifdef MATCHBOX_HACK
354     if (p_sys->mb_current_app_window)
355         xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
356                              wnd->handle.xid, XCB_CURRENT_TIME);
357 #endif
358     xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
359     return VLC_SUCCESS;
360
361 error:
362     xcb_disconnect (conn);
363     free (display);
364     free (p_sys);
365     return VLC_EGENERIC;
366 }
367
368
369 /**
370  * Destroys the X11 window.
371  */
372 static void Close (vlc_object_t *obj)
373 {
374     vout_window_t *wnd = (vout_window_t *)obj;
375     vout_window_sys_t *p_sys = wnd->sys;
376     xcb_connection_t *conn = p_sys->conn;
377
378     if (p_sys->keys)
379     {
380         vlc_cancel (p_sys->thread);
381         vlc_join (p_sys->thread, NULL);
382         DestroyKeyHandler (p_sys->keys);
383     }
384     xcb_disconnect (conn);
385     free (wnd->display.x11);
386     free (p_sys);
387 }
388
389
390 /** Background thread for X11 events handling */
391 static void *Thread (void *data)
392 {
393     vout_window_t *wnd = data;
394     vout_window_sys_t *p_sys = wnd->sys;
395     xcb_connection_t *conn = p_sys->conn;
396
397     int fd = xcb_get_file_descriptor (conn);
398     if (fd == -1)
399         return NULL;
400
401     for (;;)
402     {
403         xcb_generic_event_t *ev;
404         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
405
406         poll (&ufd, 1, -1);
407
408         int canc = vlc_savecancel ();
409         while ((ev = xcb_poll_for_event (conn)) != NULL)
410         {
411             if (ProcessKeyEvent (p_sys->keys, ev) == 0)
412                 continue;
413 #ifdef MATCHBOX_HACK
414             if (p_sys->mb_current_app_window
415              && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
416             {
417                 const xcb_property_notify_event_t *pne =
418                     (xcb_property_notify_event_t *)ev;
419                 if (pne->atom == p_sys->mb_current_app_window
420                  && pne->state == XCB_PROPERTY_NEW_VALUE)
421                 {
422                     xcb_get_property_reply_t *r =
423                         xcb_get_property_reply (conn,
424                             xcb_get_property (conn, 0, pne->window, pne->atom,
425                                               XA_WINDOW, 0, 4), NULL);
426                     if (r != NULL
427                      && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
428                                  4))
429                     {
430                         msg_Dbg (wnd, "asking Matchbox for input focus");
431                         xcb_set_input_focus (conn,
432                                              XCB_INPUT_FOCUS_POINTER_ROOT,
433                                              wnd->handle.xid, pne->time);
434                         xcb_flush (conn);
435                     }
436                     free (r);
437                 }
438             }
439             else
440 #endif
441                 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
442             free (ev);
443         }
444         vlc_restorecancel (canc);
445
446         if (xcb_connection_has_error (conn))
447         {
448             msg_Err (wnd, "X server failure");
449             break;
450         }
451     }
452     return NULL;
453 }
454
455 /** Changes the EWMH state of the window */
456 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
457 {
458     vout_window_sys_t *sys = wnd->sys;
459     /* From EWMH "_WM_STATE" */
460     xcb_client_message_event_t ev = {
461          .response_type = XCB_CLIENT_MESSAGE,
462          .format = 32,
463          .window = wnd->handle.xid,
464          .type = sys->wm_state,
465     };
466
467     ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
468     ev.data.data32[1] = state;
469     ev.data.data32[2] = 0;
470     ev.data.data32[3] = 1;
471
472     /* From ICCCM "Changing Window State" */
473     xcb_send_event (sys->conn, 0, sys->root,
474                     XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
475                     XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
476                     (const char *)&ev);
477 }
478
479
480 static int Control (vout_window_t *wnd, int cmd, va_list ap)
481 {
482     vout_window_sys_t *p_sys = wnd->sys;
483     xcb_connection_t *conn = p_sys->conn;
484
485     switch (cmd)
486     {
487         case VOUT_WINDOW_SET_SIZE:
488         {
489             unsigned width = va_arg (ap, unsigned);
490             unsigned height = va_arg (ap, unsigned);
491             const uint32_t values[] = { width, height, };
492
493             xcb_configure_window (conn, wnd->handle.xid,
494                                   XCB_CONFIG_WINDOW_WIDTH |
495                                   XCB_CONFIG_WINDOW_HEIGHT, values);
496             break;
497         }
498
499         case VOUT_WINDOW_SET_STATE:
500         {
501             unsigned state = va_arg (ap, unsigned);
502             bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
503             bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
504
505             set_wm_state (wnd, above, p_sys->wm_state_above);
506             set_wm_state (wnd, below, p_sys->wm_state_below);
507             break;
508         }
509
510         case VOUT_WINDOW_SET_FULLSCREEN:
511         {
512             bool fs = va_arg (ap, int);
513             if (!fs && var_GetBool (wnd, "video-wallpaper"))
514                 return VLC_EGENERIC;
515             set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
516             break;
517         }
518
519         default:
520             msg_Err (wnd, "request %d not implemented", cmd);
521             return VLC_EGENERIC;
522     }
523     xcb_flush (p_sys->conn);
524     return VLC_SUCCESS;
525 }
526
527 /*** Embedded drawable support ***/
528
529 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
530
531 /** Acquire a drawable */
532 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
533 {
534     xcb_window_t *used;
535     size_t n = 0;
536
537     if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
538         return VLC_ENOMEM;
539
540     /* Keep a list of busy drawables, so we don't overlap videos if there are
541      * more than one video track in the stream. */
542     vlc_mutex_lock (&serializer);
543     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
544     if (used != NULL)
545     {
546         while (used[n])
547         {
548             if (used[n] == window)
549                 goto skip;
550             n++;
551         }
552     }
553
554     used = realloc (used, sizeof (*used) * (n + 2));
555     if (used != NULL)
556     {
557         used[n] = window;
558         used[n + 1] = 0;
559         var_SetAddress (obj->p_libvlc, "xid-in-use", used);
560     }
561     else
562     {
563 skip:
564         msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
565         window = 0;
566     }
567     vlc_mutex_unlock (&serializer);
568
569     return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
570 }
571
572 /** Remove this drawable from the list of busy ones */
573 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
574 {
575     xcb_window_t *used;
576     size_t n = 0;
577
578     vlc_mutex_lock (&serializer);
579     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
580     assert (used);
581     while (used[n] != window)
582     {
583         assert (used[n]);
584         n++;
585     }
586     do
587         used[n] = used[n + 1];
588     while (used[++n]);
589
590     if (n == 0)
591          var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
592     vlc_mutex_unlock (&serializer);
593
594     if (n == 0)
595         free (used);
596     /* Variables are reference-counted... */
597     var_Destroy (obj->p_libvlc, "xid-in-use");
598 }
599
600 /**
601  * Wrap an existing X11 window to embed the video.
602  */
603 static int EmOpen (vlc_object_t *obj)
604 {
605     vout_window_t *wnd = (vout_window_t *)obj;
606
607     xcb_window_t window = var_CreateGetInteger (obj, "drawable-xid");
608     if (window == 0)
609         return VLC_EGENERIC;
610     var_Destroy (obj, "drawable-xid");
611
612     if (AcquireDrawable (obj, window))
613         return VLC_EGENERIC;
614
615     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
616     xcb_connection_t *conn = xcb_connect (NULL, NULL);
617     if (p_sys == NULL || xcb_connection_has_error (conn))
618         goto error;
619
620     p_sys->keys = NULL;
621     wnd->handle.xid = window;
622     wnd->control = Control;
623     wnd->sys = p_sys;
624
625     p_sys->conn = conn;
626
627     xcb_get_geometry_reply_t *geo =
628         xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
629     if (geo == NULL)
630     {
631         msg_Err (obj, "bad X11 window 0x%08"PRIx8, window);
632         goto error;
633     }
634     p_sys->root = geo->root;
635     free (geo);
636
637     if (var_CreateGetBool (obj, "keyboard-events"))
638     {
639         p_sys->keys = CreateKeyHandler (obj, conn);
640         if (p_sys->keys != NULL)
641         {
642             const uint32_t mask = XCB_CW_EVENT_MASK;
643             const uint32_t values[1] = {
644                 XCB_EVENT_MASK_KEY_PRESS,
645             };
646             xcb_change_window_attributes (conn, window, mask, values);
647         }
648     }
649
650     CacheAtoms (p_sys);
651     if ((p_sys->keys != NULL)
652      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
653         DestroyKeyHandler (p_sys->keys);
654
655     xcb_flush (conn);
656
657     return VLC_SUCCESS;
658
659 error:
660     xcb_disconnect (conn);
661     free (p_sys);
662     ReleaseDrawable (obj, window);
663     return VLC_EGENERIC;
664 }
665
666 static void EmClose (vlc_object_t *obj)
667 {
668     vout_window_t *wnd = (vout_window_t *)obj;
669     xcb_window_t window = wnd->handle.xid;
670
671     Close (obj);
672     ReleaseDrawable (obj, window);
673 }