1db34f5f96257c9328150cae93f521b33462d4bd
[vlc.git] / modules / services_discovery / xcb_apps.c
1 /**
2  * @file xcb_apps.c
3  * @brief List of application windows XCB module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program 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 Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26 #include <stdarg.h>
27 #include <xcb/xcb.h>
28 typedef xcb_atom_t Atom;
29 #include <X11/Xatom.h> /* XA_WINDOW */
30 #include <vlc_common.h>
31 #include <vlc_services_discovery.h>
32 #include <vlc_dialog.h>
33 #include <vlc_charset.h>
34 #include <vlc_plugin.h>
35 #ifdef HAVE_SEARCH_H
36 # include <search.h>
37 #endif
38 #include <poll.h>
39
40 static int  Open (vlc_object_t *);
41 static void Close (vlc_object_t *);
42 static int vlc_sd_probe_Open (vlc_object_t *);
43
44 /*
45  * Module descriptor
46  */
47 vlc_module_begin ()
48     set_shortname (N_("Screen capture"))
49     set_description (N_("Screen capture"))
50     set_category (CAT_PLAYLIST)
51     set_subcategory (SUBCAT_PLAYLIST_SD)
52     set_capability ("services_discovery", 0)
53     set_callbacks (Open, Close)
54
55     add_shortcut ("apps", "screen")
56
57     VLC_SD_PROBE_SUBMODULE
58 vlc_module_end ()
59
60 struct services_discovery_sys_t
61 {
62     xcb_connection_t *conn;
63     vlc_thread_t      thread;
64     xcb_atom_t        net_client_list;
65     xcb_atom_t        net_wm_name;
66     xcb_window_t      root_window;
67     void             *apps;
68 };
69
70 static void *Run (void *);
71 static void UpdateApps (services_discovery_t *);
72 static void DelApp (void *);
73 static void AddDesktop(services_discovery_t *);
74
75 static int vlc_sd_probe_Open (vlc_object_t *obj)
76 {
77     vlc_probe_t *probe = (vlc_probe_t *)obj;
78
79     char *display = var_InheritString (obj, "x11-display");
80     xcb_connection_t *conn = xcb_connect (display, NULL);
81     free (display);
82     if (xcb_connection_has_error (conn))
83         return VLC_PROBE_CONTINUE;
84     xcb_disconnect (conn);
85     return vlc_sd_probe_Add (probe, "xcb_apps{longname=\"Screen capture\"}",
86                              N_("Screen capture"), SD_CAT_DEVICES);
87 }
88
89 /**
90  * Probes and initializes.
91  */
92 static int Open (vlc_object_t *obj)
93 {
94     services_discovery_t *sd = (services_discovery_t *)obj;
95     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
96
97     if (p_sys == NULL)
98         return VLC_ENOMEM;
99     sd->p_sys = p_sys;
100
101     /* Connect to X server */
102     char *display = var_InheritString (obj, "x11-display");
103     int snum;
104     xcb_connection_t *conn = xcb_connect (display, &snum);
105     free (display);
106     if (xcb_connection_has_error (conn))
107     {
108         free (p_sys);
109         return VLC_EGENERIC;
110     }
111     p_sys->conn = conn;
112
113     /* Find configured screen */
114     const xcb_setup_t *setup = xcb_get_setup (conn);
115     const xcb_screen_t *scr = NULL;
116     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
117          i.rem > 0; xcb_screen_next (&i))
118     {
119         if (snum == 0)
120         {
121             scr = i.data;
122             break;
123         }
124         snum--;
125     }
126     if (scr == NULL)
127     {
128         msg_Err (obj, "bad X11 screen number");
129         goto error;
130     }
131
132     /* Add a permanent item for the entire desktop */
133     AddDesktop (sd);
134
135     p_sys->root_window = scr->root;
136     xcb_change_window_attributes (conn, scr->root, XCB_CW_EVENT_MASK,
137                                &(uint32_t) { XCB_EVENT_MASK_PROPERTY_CHANGE });
138
139     /* TODO: check that _NET_CLIENT_LIST is in _NET_SUPPORTED
140      * (and _NET_SUPPORTING_WM_CHECK) */
141     xcb_intern_atom_reply_t *r;
142     xcb_intern_atom_cookie_t ncl, nwn;
143
144     ncl = xcb_intern_atom (conn, 1, strlen ("_NET_CLIENT_LIST"),
145                           "_NET_CLIENT_LIST");
146     nwn = xcb_intern_atom (conn, 0, strlen ("_NET_WM_NAME"), "_NET_WM_NAME");
147
148     r = xcb_intern_atom_reply (conn, ncl, NULL);
149     if (r == NULL || r->atom == 0)
150     {
151         dialog_Fatal (sd, _("Screen capture"),
152             _("Your window manager does not provide a list of applications."));
153         msg_Err (sd, "client list not supported (_NET_CLIENT_LIST absent)");
154     }
155     p_sys->net_client_list = r ? r->atom : 0;
156     free (r);
157     r = xcb_intern_atom_reply (conn, nwn, NULL);
158     if (r != NULL)
159     {
160         p_sys->net_wm_name = r->atom;
161         free (r);
162     }
163
164     p_sys->apps = NULL;
165     UpdateApps (sd);
166
167     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
168         goto error;
169     return VLC_SUCCESS;
170
171 error:
172     xcb_disconnect (p_sys->conn);
173     free (p_sys);
174     return VLC_EGENERIC;
175 }
176
177
178 /**
179  * Releases resources
180  */
181 static void Close (vlc_object_t *obj)
182 {
183     services_discovery_t *sd = (services_discovery_t *)obj;
184     services_discovery_sys_t *p_sys = sd->p_sys;
185
186     vlc_cancel (p_sys->thread);
187     vlc_join (p_sys->thread, NULL);
188     xcb_disconnect (p_sys->conn);
189     tdestroy (p_sys->apps, DelApp);
190     free (p_sys);
191 }
192
193 static void *Run (void *data)
194 {
195     services_discovery_t *sd = data;
196     services_discovery_sys_t *p_sys = sd->p_sys;
197     xcb_connection_t *conn = p_sys->conn;
198     int fd = xcb_get_file_descriptor (conn);
199     if (fd == -1)
200         return NULL;
201
202     while (!xcb_connection_has_error (conn))
203     {
204         xcb_generic_event_t *ev;
205         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
206
207         poll (&ufd, 1, -1);
208
209         int canc = vlc_savecancel ();
210         while ((ev = xcb_poll_for_event (conn)) != NULL)
211         {
212             if ((ev->response_type & 0x7F) == XCB_PROPERTY_NOTIFY)
213             {
214                  const xcb_property_notify_event_t *pn =
215                      (xcb_property_notify_event_t *)ev;
216                  if (pn->atom == p_sys->net_client_list)
217                      UpdateApps (sd);
218             }
219             free (ev);
220         }
221         vlc_restorecancel (canc);
222     }
223     return NULL;
224 }
225
226 /*** Application windows ***/
227 struct app
228 {
229     xcb_window_t          xid; /* must be first for cmpapp */
230     input_item_t         *item;
231     services_discovery_t *owner;
232 };
233
234 static struct app *AddApp (services_discovery_t *sd, xcb_window_t xid)
235 {
236     services_discovery_sys_t *p_sys = sd->p_sys;
237     char *mrl, *name;
238
239     if (asprintf (&mrl, "window://0x%"PRIx8, xid) == -1)
240         return NULL;
241
242     xcb_get_property_reply_t *r =
243         xcb_get_property_reply (p_sys->conn,
244             xcb_get_property (p_sys->conn, 0, xid, p_sys->net_wm_name, 0,
245                               0, 1023 /* max size */), NULL);
246     if (r != NULL)
247     {
248         name = strndup (xcb_get_property_value (r),
249                         xcb_get_property_value_length (r));
250         if (name != NULL)
251             EnsureUTF8 (name); /* don't trust third party apps too much ;-) */
252         free (r);
253     }
254     /* TODO: use WM_NAME (Latin-1) for very old apps */
255     else
256         name = NULL;
257
258     input_item_t *item = input_item_NewWithType (mrl, name ? name : mrl,
259                                                  0, NULL, 0, -1,
260                                                  ITEM_TYPE_CARD /* FIXME */);
261     free (mrl);
262     free (name);
263     if (item == NULL)
264         return NULL;
265
266     struct app *app = malloc (sizeof (*app));
267     if (app == NULL)
268     {
269         vlc_gc_decref (item);
270         return NULL;
271     }
272     app->xid = xid;
273     app->item = item;
274     app->owner = sd;
275     services_discovery_AddItem (sd, item, _("Applications"));
276     return app;
277 }
278
279 static void DelApp (void *data)
280 {
281     struct app *app = data;
282
283     services_discovery_RemoveItem (app->owner, app->item);
284     vlc_gc_decref (app->item);
285     free (app);
286 }
287
288 static int cmpapp (const void *a, const void *b)
289 {
290     xcb_window_t wa = *(xcb_window_t *)a;
291     xcb_window_t wb = *(xcb_window_t *)b;
292
293     if (wa > wb)
294         return 1;
295     if (wa < wb)
296         return -1;
297     return 0;
298
299
300 static void UpdateApps (services_discovery_t *sd)
301 {
302     services_discovery_sys_t *p_sys = sd->p_sys;
303     xcb_connection_t *conn = p_sys->conn;
304
305     xcb_get_property_reply_t *r =
306         xcb_get_property_reply (conn,
307             xcb_get_property (conn, false, p_sys->root_window,
308                               p_sys->net_client_list, XA_WINDOW, 0, 1024),
309             NULL);
310     if (r == NULL)
311         return; /* FIXME: remove all entries */
312
313     xcb_window_t *ent = xcb_get_property_value (r);
314     int n = xcb_get_property_value_length (r) / 4;
315     void *newnodes = NULL, *oldnodes = p_sys->apps;
316
317     for (int i = 0; i < n; i++)
318     {
319         xcb_window_t id = *(ent++);
320         struct app *app;
321
322         struct app **pa = tfind (&id, &oldnodes, cmpapp);
323         if (pa != NULL) /* existing entry */
324         {
325             app = *pa;
326             tdelete (app, &oldnodes, cmpapp);
327         }
328         else /* new entry */
329         {
330             app = AddApp (sd, id);
331             if (app == NULL)
332                 continue;
333         }
334
335         pa = tsearch (app, &newnodes, cmpapp);
336         if (pa == NULL /* OOM */ || *pa != app /* buggy window manager */)
337             DelApp (app);
338     }
339     free (r);
340
341     /* Remove old nodes */
342     tdestroy (oldnodes, DelApp);
343     p_sys->apps = newnodes;
344 }
345
346 /*** Whole desktop ***/
347 static void AddDesktop(services_discovery_t *sd)
348 {
349     input_item_t *item;
350
351     item = input_item_NewWithType ("screen://", _("Desktop"),
352                                    0, NULL, 0, -1, ITEM_TYPE_CARD);
353     if (item == NULL)
354         return;
355
356     services_discovery_AddItem (sd, item, NULL);
357 }