]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/window.c
48f2f2a25dfb49cffd2f081ce235caec94bc4683
[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     add_shortcut ("embed-xid")
78
79     add_integer ("drawable-xid", 0, NULL, XID_TEXT, XID_LONGTEXT, true)
80         change_volatile ()
81
82 vlc_module_end ()
83
84 static int Control (vout_window_t *, int, va_list ap);
85 static void *Thread (void *);
86
87 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
88
89 struct vout_window_sys_t
90 {
91     xcb_connection_t *conn;
92     key_handler_t *keys;
93     vlc_thread_t thread;
94
95     xcb_window_t root;
96     xcb_atom_t wm_state;
97     xcb_atom_t wm_state_above;
98     xcb_atom_t wm_state_below;
99     xcb_atom_t wm_state_fullscreen;
100 #ifdef MATCHBOX_HACK
101     xcb_atom_t mb_current_app_window;
102 #endif
103 };
104
105 /** Set an X window property from a nul-terminated string */
106 static inline
107 void set_string (xcb_connection_t *conn, xcb_window_t window,
108                  xcb_atom_t type, xcb_atom_t atom, const char *str)
109 {
110     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
111                          /* format */ 8, strlen (str), str);
112 }
113
114 /** Set an X window string property */
115 static inline
116 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
117                      xcb_atom_t atom, const char *value)
118 {
119     set_string (conn, window, XA_STRING, atom, value);
120 }
121
122 static inline
123 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
124 {
125     static const uint32_t wm_hints[8] = {
126         3, /* flags: Input, Initial state */
127         1, /* input: True */
128         1, /* initial state: Normal */
129         0, 0, 0, 0, 0, /* Icon */
130     };
131     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
132                          XA_WM_HINTS, 32, 8, wm_hints);
133 }
134
135 /** Set the Window ICCCM client machine property */
136 static inline
137 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
138 {
139     char* hostname;
140     long host_name_max = sysconf (_SC_HOST_NAME_MAX);
141     if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
142     hostname = malloc (host_name_max);
143     if(!hostname) return;
144
145     if (gethostname (hostname, host_name_max) == 0)
146     {
147         hostname[host_name_max - 1] = '\0';
148         set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
149     }
150     free(hostname);
151 }
152
153 /** Request the X11 server to internalize a string into an atom */
154 static inline
155 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
156 {
157     return xcb_intern_atom (c, 0, strlen (s), s);
158 }
159
160 /** Extract the X11 atom from an intern request cookie */
161 static
162 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
163 {
164     xcb_intern_atom_reply_t *reply;
165     xcb_atom_t atom;
166
167     reply = xcb_intern_atom_reply (conn, ck, NULL);
168     if (reply == NULL)
169         return 0;
170
171     atom = reply->atom;
172     free (reply);
173     return atom;
174 }
175
176 #define NET_WM_STATE_REMOVE 0
177 #define NET_WM_STATE_ADD    1
178 #define NET_WM_STATE_TOGGLE 2
179
180 static void CacheAtoms (vout_window_sys_t *p_sys)
181 {
182     xcb_connection_t *conn = p_sys->conn;
183     xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
184                              wm_state_below_ck, wm_state_fs_ck;
185
186     wm_state_ck = intern_string (conn, "_NET_WM_STATE");
187     wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
188     wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
189     wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
190 #ifdef MATCHBOX_HACK
191     xcb_intern_atom_cookie_t mb_current_app_window;
192     mb_current_app_window = xcb_intern_atom (conn, true,
193                                              strlen ("_MB_CURRENT_APP_WINDOW"),
194                                              "_MB_CURRENT_APP_WINDOW");
195 #endif
196
197     p_sys->wm_state = get_atom (conn, wm_state_ck);
198     p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
199     p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
200     p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
201 #ifdef MATCHBOX_HACK
202     p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
203 #endif
204 }
205
206 /**
207  * Create an X11 window.
208  */
209 static int Open (vlc_object_t *obj)
210 {
211     vout_window_t *wnd = (vout_window_t *)obj;
212     xcb_generic_error_t *err;
213     xcb_void_cookie_t ck;
214
215     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
216     if (p_sys == NULL)
217         return VLC_ENOMEM;
218
219     /* Connect to X */
220     char *display = var_InheritString (wnd, "x11-display");
221     int snum;
222
223     xcb_connection_t *conn = xcb_connect (display, &snum);
224     if (xcb_connection_has_error (conn) /*== NULL*/)
225         goto error;
226
227     /* Find configured screen */
228     const xcb_setup_t *setup = xcb_get_setup (conn);
229     const xcb_screen_t *scr = NULL;
230     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
231          i.rem > 0; xcb_screen_next (&i))
232     {
233         if (snum == 0)
234         {
235             scr = i.data;
236             break;
237         }
238         snum--;
239     }
240     if (scr == NULL)
241     {
242         msg_Err (wnd, "bad X11 screen number");
243         goto error;
244     }
245
246     /* Create window */
247     const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
248     uint32_t values[2] = {
249         /* XCB_CW_BACK_PIXEL */
250         scr->black_pixel,
251         /* XCB_CW_EVENT_MASK */
252         XCB_EVENT_MASK_KEY_PRESS,
253     };
254
255     xcb_window_t window = xcb_generate_id (conn);
256     ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
257                                     wnd->cfg->x, wnd->cfg->y,
258                                     wnd->cfg->width, wnd->cfg->height, 0,
259                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,
260                                     scr->root_visual, mask, values);
261     err = xcb_request_check (conn, ck);
262     if (err)
263     {
264         msg_Err (wnd, "creating window: X11 error %d", err->error_code);
265         free (err);
266         goto error;
267     }
268
269     wnd->handle.xid = window;
270     wnd->display.x11 = display;
271     wnd->control = Control;
272     wnd->sys = p_sys;
273
274     p_sys->conn = conn;
275     if (var_InheritBool (obj, "keyboard-events"))
276         p_sys->keys = CreateKeyHandler (obj, conn);
277     else
278         p_sys->keys = NULL;
279     p_sys->root = scr->root;
280
281     /* ICCCM
282      * No cut&paste nor drag&drop, only Window Manager communication. */
283     set_ascii_prop (conn, window, XA_WM_NAME,
284     /* xgettext: This is a plain ASCII spelling of "VLC media player"
285        for the ICCCM window name. This must be pure ASCII.
286        The limitation is partially with ICCCM and partially with VLC.
287        For Latin script languages, you may need to strip accents.
288        For other scripts, you will need to transliterate into Latin. */
289                     vlc_pgettext ("ASCII", "VLC media player"));
290
291     set_ascii_prop (conn, window, XA_WM_ICON_NAME,
292     /* xgettext: This is a plain ASCII spelling of "VLC"
293        for the ICCCM window name. This must be pure ASCII. */
294                     vlc_pgettext ("ASCII", "VLC"));
295     set_wm_hints (conn, window);
296     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
297                          XA_STRING, 8, 8, "vlc\0Vlc");
298     set_hostname_prop (conn, window);
299
300     /* EWMH */
301     xcb_intern_atom_cookie_t utf8_string_ck
302         = intern_string (conn, "UTF8_STRING");;
303     xcb_intern_atom_cookie_t net_wm_name_ck
304         = intern_string (conn, "_NET_WM_NAME");
305     xcb_intern_atom_cookie_t net_wm_icon_name_ck
306         = intern_string (conn, "_NET_WM_ICON_NAME");
307     xcb_intern_atom_cookie_t wm_window_role_ck
308         = intern_string (conn, "WM_WINDOW_ROLE");
309
310     xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
311
312     xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
313     char *title = var_InheritString (wnd, "video-title");
314     if (title)
315     {
316         set_string (conn, window, utf8, net_wm_name, title);
317         free (title);
318     }
319     else
320         set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
321
322     xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
323     set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
324
325     xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
326     set_ascii_prop (conn, window, wm_window_role, "vlc-video");
327
328     /* Cache any EWMH atom we may need later */
329     CacheAtoms (p_sys);
330 #ifdef MATCHBOX_HACK
331     if (p_sys->mb_current_app_window)
332     {
333         uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
334         xcb_change_window_attributes (conn, scr->root,
335                                       XCB_CW_EVENT_MASK, &value);
336     }
337 #endif
338
339     /* Make the window visible */
340     xcb_map_window (conn, window);
341
342     if (var_InheritBool (obj, "video-wallpaper"))
343     {
344         vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
345         vout_window_SetFullScreen (wnd, true);
346     }
347
348     /* Create the event thread. It will dequeue all events, so any checked
349      * request from this thread must be completed at this point. */
350     if ((p_sys->keys != NULL)
351      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
352         DestroyKeyHandler (p_sys->keys);
353
354 #ifdef MATCHBOX_HACK
355     if (p_sys->mb_current_app_window)
356         xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
357                              wnd->handle.xid, XCB_CURRENT_TIME);
358 #endif
359     xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
360     return VLC_SUCCESS;
361
362 error:
363     xcb_disconnect (conn);
364     free (display);
365     free (p_sys);
366     return VLC_EGENERIC;
367 }
368
369
370 /**
371  * Destroys the X11 window.
372  */
373 static void Close (vlc_object_t *obj)
374 {
375     vout_window_t *wnd = (vout_window_t *)obj;
376     vout_window_sys_t *p_sys = wnd->sys;
377     xcb_connection_t *conn = p_sys->conn;
378
379     if (p_sys->keys)
380     {
381         vlc_cancel (p_sys->thread);
382         vlc_join (p_sys->thread, NULL);
383         DestroyKeyHandler (p_sys->keys);
384     }
385     xcb_disconnect (conn);
386     free (wnd->display.x11);
387     free (p_sys);
388 }
389
390
391 /** Background thread for X11 events handling */
392 static void *Thread (void *data)
393 {
394     vout_window_t *wnd = data;
395     vout_window_sys_t *p_sys = wnd->sys;
396     xcb_connection_t *conn = p_sys->conn;
397
398     int fd = xcb_get_file_descriptor (conn);
399     if (fd == -1)
400         return NULL;
401
402     for (;;)
403     {
404         xcb_generic_event_t *ev;
405         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
406
407         poll (&ufd, 1, -1);
408
409         int canc = vlc_savecancel ();
410         while ((ev = xcb_poll_for_event (conn)) != NULL)
411         {
412             if (ProcessKeyEvent (p_sys->keys, ev) == 0)
413                 continue;
414 #ifdef MATCHBOX_HACK
415             if (p_sys->mb_current_app_window
416              && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
417             {
418                 const xcb_property_notify_event_t *pne =
419                     (xcb_property_notify_event_t *)ev;
420                 if (pne->atom == p_sys->mb_current_app_window
421                  && pne->state == XCB_PROPERTY_NEW_VALUE)
422                 {
423                     xcb_get_property_reply_t *r =
424                         xcb_get_property_reply (conn,
425                             xcb_get_property (conn, 0, pne->window, pne->atom,
426                                               XA_WINDOW, 0, 4), NULL);
427                     if (r != NULL
428                      && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
429                                  4))
430                     {
431                         msg_Dbg (wnd, "asking Matchbox for input focus");
432                         xcb_set_input_focus (conn,
433                                              XCB_INPUT_FOCUS_POINTER_ROOT,
434                                              wnd->handle.xid, pne->time);
435                         xcb_flush (conn);
436                     }
437                     free (r);
438                 }
439             }
440             else
441 #endif
442                 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
443             free (ev);
444         }
445         vlc_restorecancel (canc);
446
447         if (xcb_connection_has_error (conn))
448         {
449             msg_Err (wnd, "X server failure");
450             break;
451         }
452     }
453     return NULL;
454 }
455
456 /** Changes the EWMH state of the window */
457 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
458 {
459     vout_window_sys_t *sys = wnd->sys;
460     /* From EWMH "_WM_STATE" */
461     xcb_client_message_event_t ev = {
462          .response_type = XCB_CLIENT_MESSAGE,
463          .format = 32,
464          .window = wnd->handle.xid,
465          .type = sys->wm_state,
466     };
467
468     ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
469     ev.data.data32[1] = state;
470     ev.data.data32[2] = 0;
471     ev.data.data32[3] = 1;
472
473     /* From ICCCM "Changing Window State" */
474     xcb_send_event (sys->conn, 0, sys->root,
475                     XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
476                     XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
477                     (const char *)&ev);
478 }
479
480
481 static int Control (vout_window_t *wnd, int cmd, va_list ap)
482 {
483     vout_window_sys_t *p_sys = wnd->sys;
484     xcb_connection_t *conn = p_sys->conn;
485
486     switch (cmd)
487     {
488         case VOUT_WINDOW_SET_SIZE:
489         {
490             unsigned width = va_arg (ap, unsigned);
491             unsigned height = va_arg (ap, unsigned);
492             const uint32_t values[] = { width, height, };
493
494             xcb_configure_window (conn, wnd->handle.xid,
495                                   XCB_CONFIG_WINDOW_WIDTH |
496                                   XCB_CONFIG_WINDOW_HEIGHT, values);
497             break;
498         }
499
500         case VOUT_WINDOW_SET_STATE:
501         {
502             unsigned state = va_arg (ap, unsigned);
503             bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
504             bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
505
506             set_wm_state (wnd, above, p_sys->wm_state_above);
507             set_wm_state (wnd, below, p_sys->wm_state_below);
508             break;
509         }
510
511         case VOUT_WINDOW_SET_FULLSCREEN:
512         {
513             bool fs = va_arg (ap, int);
514             if (!fs && var_GetBool (wnd, "video-wallpaper"))
515                 return VLC_EGENERIC;
516             set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
517             break;
518         }
519
520         default:
521             msg_Err (wnd, "request %d not implemented", cmd);
522             return VLC_EGENERIC;
523     }
524     xcb_flush (p_sys->conn);
525     return VLC_SUCCESS;
526 }
527
528 /*** Embedded drawable support ***/
529
530 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
531
532 /** Acquire a drawable */
533 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
534 {
535     xcb_window_t *used;
536     size_t n = 0;
537
538     if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
539         return VLC_ENOMEM;
540
541     /* Keep a list of busy drawables, so we don't overlap videos if there are
542      * more than one video track in the stream. */
543     vlc_mutex_lock (&serializer);
544     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
545     if (used != NULL)
546     {
547         while (used[n])
548         {
549             if (used[n] == window)
550                 goto skip;
551             n++;
552         }
553     }
554
555     used = realloc (used, sizeof (*used) * (n + 2));
556     if (used != NULL)
557     {
558         used[n] = window;
559         used[n + 1] = 0;
560         var_SetAddress (obj->p_libvlc, "xid-in-use", used);
561     }
562     else
563     {
564 skip:
565         msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
566         window = 0;
567     }
568     vlc_mutex_unlock (&serializer);
569
570     return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
571 }
572
573 /** Remove this drawable from the list of busy ones */
574 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
575 {
576     xcb_window_t *used;
577     size_t n = 0;
578
579     vlc_mutex_lock (&serializer);
580     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
581     assert (used);
582     while (used[n] != window)
583     {
584         assert (used[n]);
585         n++;
586     }
587     do
588         used[n] = used[n + 1];
589     while (used[++n]);
590
591     if (n == 0)
592          var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
593     vlc_mutex_unlock (&serializer);
594
595     if (n == 0)
596         free (used);
597     /* Variables are reference-counted... */
598     var_Destroy (obj->p_libvlc, "xid-in-use");
599 }
600
601 /**
602  * Wrap an existing X11 window to embed the video.
603  */
604 static int EmOpen (vlc_object_t *obj)
605 {
606     vout_window_t *wnd = (vout_window_t *)obj;
607
608     xcb_window_t window = var_InheritInteger (obj, "drawable-xid");
609     if (window == 0)
610         return VLC_EGENERIC;
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_InheritBool (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 }