XCB: try to catch mouse clicks from the owner window
[vlc.git] / modules / video_output / xcb / common.c
1 /**
2  * @file common.c
3  * @brief Common code for XCB video output plugins
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.0
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 Lesser 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 <stdlib.h>
28 #include <assert.h>
29
30 #include <sys/types.h>
31 #include <sys/shm.h>
32
33 #include <xcb/xcb.h>
34 #include <xcb/shm.h>
35
36 #include <vlc_common.h>
37 #include <vlc_vout_display.h>
38
39 #include "xcb_vlc.h"
40
41 /**
42  * Check for an error
43  */
44 int CheckError (vout_display_t *vd, xcb_connection_t *conn,
45                 const char *str, xcb_void_cookie_t ck)
46 {
47     xcb_generic_error_t *err;
48
49     err = xcb_request_check (conn, ck);
50     if (err)
51     {
52         msg_Err (vd, "%s: X11 error %d", str, err->error_code);
53         return VLC_EGENERIC;
54     }
55     return VLC_SUCCESS;
56 }
57
58 /**
59  * Connect to the X server.
60  */
61 xcb_connection_t *Connect (vlc_object_t *obj)
62 {
63     char *display = var_CreateGetNonEmptyString (obj, "x11-display");
64     xcb_connection_t *conn = xcb_connect (display, NULL);
65
66     free (display);
67     if (xcb_connection_has_error (conn) /*== NULL*/)
68     {
69         msg_Err (obj, "cannot connect to X server");
70         xcb_disconnect (conn);
71         return NULL;
72     }
73     return conn;
74 }
75
76
77 /**
78  * Create a VLC video X window object, find the corresponding X server screen,
79  * and probe the MIT-SHM extension.
80  */
81 vout_window_t *GetWindow (vout_display_t *vd,
82                           xcb_connection_t *conn,
83                           const xcb_screen_t **restrict pscreen,
84                           bool *restrict pshm)
85 {
86     /* Get window */
87     xcb_window_t root;
88     vout_window_cfg_t wnd_cfg;
89
90     memset( &wnd_cfg, 0, sizeof(wnd_cfg) );
91     wnd_cfg.type = VOUT_WINDOW_TYPE_XID;
92     wnd_cfg.width  = vd->cfg->display.width;
93     wnd_cfg.height = vd->cfg->display.height;
94
95     vout_window_t *wnd = vout_display_NewWindow (vd, &wnd_cfg);
96     if (wnd == NULL)
97     {
98         msg_Err (vd, "parent window not available");
99         return NULL;
100     }
101     else
102     {
103         xcb_get_geometry_reply_t *geo;
104         xcb_get_geometry_cookie_t ck;
105
106         ck = xcb_get_geometry (conn, wnd->handle.xid);
107         geo = xcb_get_geometry_reply (conn, ck, NULL);
108         if (geo == NULL)
109         {
110             msg_Err (vd, "parent window not valid");
111             goto error;
112         }
113         root = geo->root;
114         free (geo);
115
116         /* Subscribe to parent window resize events */
117         uint32_t value = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
118         xcb_change_window_attributes (conn, wnd->handle.xid,
119                                       XCB_CW_EVENT_MASK, &value);
120         /* Try to subscribe to click events */
121         /* (only one X11 client can get them, so might not work) */
122         value |= XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE;
123         xcb_change_window_attributes (conn, wnd->handle.xid,
124                                       XCB_CW_EVENT_MASK, &value);
125     }
126
127     /* Find the selected screen */
128     const xcb_setup_t *setup = xcb_get_setup (conn);
129     const xcb_screen_t *screen = NULL;
130     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
131          i.rem > 0 && screen == NULL; xcb_screen_next (&i))
132     {
133         if (i.data->root == root)
134             screen = i.data;
135     }
136
137     if (screen == NULL)
138     {
139         msg_Err (vd, "parent window screen not found");
140         goto error;
141     }
142     msg_Dbg (vd, "using screen 0x%"PRIx32, root);
143
144     /* Check MIT-SHM shared memory support */
145     bool shm = var_CreateGetBool (vd, "x11-shm") > 0;
146     if (shm)
147     {
148         xcb_shm_query_version_cookie_t ck;
149         xcb_shm_query_version_reply_t *r;
150
151         ck = xcb_shm_query_version (conn);
152         r = xcb_shm_query_version_reply (conn, ck, NULL);
153         if (!r)
154         {
155             msg_Err (vd, "shared memory (MIT-SHM) not available");
156             msg_Warn (vd, "display will be slow");
157             shm = false;
158         }
159         free (r);
160     }
161
162     *pscreen = screen;
163     *pshm = shm;
164     return wnd;
165
166 error:
167     vout_display_DeleteWindow (vd, wnd);
168     return NULL;
169 }
170
171 /**
172  * Gets the size of an X window.
173  */
174 int GetWindowSize (struct vout_window_t *wnd, xcb_connection_t *conn,
175                    unsigned *restrict width, unsigned *restrict height)
176 {
177     xcb_get_geometry_cookie_t ck = xcb_get_geometry (conn, wnd->handle.xid);
178     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply (conn, ck, NULL);
179
180     if (!geo)
181         return -1;
182
183     *width = geo->width;
184     *height = geo->height;
185     free (geo);
186     return 0;
187 }
188
189 /**
190  * Initialize a picture buffer as shared memory, according to the video output
191  * format. If a attach is true, the segment is attached to
192  * the X server (MIT-SHM extension).
193  */
194 int PictureResourceAlloc (vout_display_t *vd, picture_resource_t *res, size_t size,
195                           xcb_connection_t *conn, bool attach)
196 {
197     res->p_sys = malloc (sizeof(*res->p_sys));
198     if (!res->p_sys)
199         return VLC_EGENERIC;
200
201     /* Allocate shared memory segment */
202     int id = shmget (IPC_PRIVATE, size, IPC_CREAT | 0700);
203     if (id == -1)
204     {
205         msg_Err (vd, "shared memory allocation error: %m");
206         free (res->p_sys);
207         return VLC_EGENERIC;
208     }
209
210     /* Attach the segment to VLC */
211     void *shm = shmat (id, NULL, 0 /* read/write */);
212     if (-1 == (intptr_t)shm)
213     {
214         msg_Err (vd, "shared memory attachment error: %m");
215         shmctl (id, IPC_RMID, 0);
216         free (res->p_sys);
217         return VLC_EGENERIC;
218     }
219
220     xcb_shm_seg_t segment;
221     if (attach)
222     {
223         /* Attach the segment to X */
224         xcb_void_cookie_t ck;
225
226         segment = xcb_generate_id (conn);
227         ck = xcb_shm_attach_checked (conn, segment, id, 1);
228
229         if (CheckError (vd, conn, "shared memory server-side error", ck))
230         {
231             msg_Info (vd, "using buggy X11 server - SSH proxying?");
232             segment = 0;
233         }
234     }
235     else
236         segment = 0;
237
238     shmctl (id, IPC_RMID, 0);
239     res->p_sys->segment = segment;
240     res->p->p_pixels = shm;
241     return VLC_SUCCESS;
242 }
243
244 /**
245  * Release picture private data: detach the shared memory segment.
246  */
247 void PictureResourceFree (picture_resource_t *res, xcb_connection_t *conn)
248 {
249     xcb_shm_seg_t segment = res->p_sys->segment;
250
251     if (segment != 0)
252     {
253         assert (conn != NULL);
254         xcb_shm_detach (conn, segment);
255     }
256     shmdt (res->p->p_pixels);
257 }
258