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     input_item_t     *apps_root;
69 };
70 
71 static void *Run (void *);
72 static void UpdateApps (services_discovery_t *);
73 static void DelApp (void *);
74 static void AddDesktop(services_discovery_t *);
75 
vlc_sd_probe_Open(vlc_object_t * obj)76 static int vlc_sd_probe_Open (vlc_object_t *obj)
77 {
78     vlc_probe_t *probe = (vlc_probe_t *)obj;
79 
80     char *display = var_InheritString (obj, "x11-display");
81     xcb_connection_t *conn = xcb_connect (display, NULL);
82     free (display);
83     if (xcb_connection_has_error (conn))
84         return VLC_PROBE_CONTINUE;
85     xcb_disconnect (conn);
86     return vlc_sd_probe_Add (probe, "xcb_apps",
87                              N_("Screen capture"), SD_CAT_DEVICES);
88 }
89 
90 /**
91  * Probes and initializes.
92  */
Open(vlc_object_t * obj)93 static int Open (vlc_object_t *obj)
94 {
95     services_discovery_t *sd = (services_discovery_t *)obj;
96     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
97 
98     if (p_sys == NULL)
99         return VLC_ENOMEM;
100     sd->p_sys = p_sys;
101     sd->description = _("Screen capture");
102 
103     /* Connect to X server */
104     char *display = var_InheritString (obj, "x11-display");
105     int snum;
106     xcb_connection_t *conn = xcb_connect (display, &snum);
107     free (display);
108     if (xcb_connection_has_error (conn))
109     {
110         free (p_sys);
111         return VLC_EGENERIC;
112     }
113     p_sys->conn = conn;
114 
115     /* Find configured screen */
116     const xcb_setup_t *setup = xcb_get_setup (conn);
117     const xcb_screen_t *scr = NULL;
118     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
119          i.rem > 0; xcb_screen_next (&i))
120     {
121         if (snum == 0)
122         {
123             scr = i.data;
124             break;
125         }
126         snum--;
127     }
128     if (scr == NULL)
129     {
130         msg_Err (obj, "bad X11 screen number");
131         goto error;
132     }
133 
134     /* Add a permanent item for the entire desktop */
135     AddDesktop (sd);
136 
137     p_sys->root_window = scr->root;
138     xcb_change_window_attributes (conn, scr->root, XCB_CW_EVENT_MASK,
139                                &(uint32_t) { XCB_EVENT_MASK_PROPERTY_CHANGE });
140 
141     /* TODO: check that _NET_CLIENT_LIST is in _NET_SUPPORTED
142      * (and _NET_SUPPORTING_WM_CHECK) */
143     xcb_intern_atom_reply_t *r;
144     xcb_intern_atom_cookie_t ncl, nwn;
145 
146     ncl = xcb_intern_atom (conn, 1, strlen ("_NET_CLIENT_LIST"),
147                           "_NET_CLIENT_LIST");
148     nwn = xcb_intern_atom (conn, 0, strlen ("_NET_WM_NAME"), "_NET_WM_NAME");
149 
150     r = xcb_intern_atom_reply (conn, ncl, NULL);
151     if (r == NULL || r->atom == 0)
152     {
153         vlc_dialog_display_error (sd, _("Screen capture"),
154             _("Your window manager does not provide a list of applications."));
155         msg_Err (sd, "client list not supported (_NET_CLIENT_LIST absent)");
156     }
157     p_sys->net_client_list = r ? r->atom : 0;
158     free (r);
159     r = xcb_intern_atom_reply (conn, nwn, NULL);
160     if (r != NULL)
161     {
162         p_sys->net_wm_name = r->atom;
163         free (r);
164     }
165 
166     p_sys->apps = NULL;
167     p_sys->apps_root = input_item_NewExt("vlc://nop", _("Applications"), -1,
168                                          ITEM_TYPE_NODE, ITEM_LOCAL);
169     if (likely(p_sys->apps_root != NULL))
170         services_discovery_AddItem(sd, p_sys->apps_root);
171 
172     UpdateApps (sd);
173 
174     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
175         goto error;
176     return VLC_SUCCESS;
177 
178 error:
179     xcb_disconnect (p_sys->conn);
180     tdestroy (p_sys->apps, DelApp);
181     if (p_sys->apps_root != NULL)
182         input_item_Release(p_sys->apps_root);
183     free (p_sys);
184     return VLC_EGENERIC;
185 }
186 
187 
188 /**
189  * Releases resources
190  */
Close(vlc_object_t * obj)191 static void Close (vlc_object_t *obj)
192 {
193     services_discovery_t *sd = (services_discovery_t *)obj;
194     services_discovery_sys_t *p_sys = sd->p_sys;
195 
196     vlc_cancel (p_sys->thread);
197     vlc_join (p_sys->thread, NULL);
198     xcb_disconnect (p_sys->conn);
199     tdestroy (p_sys->apps, DelApp);
200     if (p_sys->apps_root != NULL)
201         input_item_Release(p_sys->apps_root);
202     free (p_sys);
203 }
204 
Run(void * data)205 static void *Run (void *data)
206 {
207     services_discovery_t *sd = data;
208     services_discovery_sys_t *p_sys = sd->p_sys;
209     xcb_connection_t *conn = p_sys->conn;
210     int fd = xcb_get_file_descriptor (conn);
211     if (fd == -1)
212         return NULL;
213 
214     while (!xcb_connection_has_error (conn))
215     {
216         xcb_generic_event_t *ev;
217         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
218 
219         poll (&ufd, 1, -1);
220 
221         int canc = vlc_savecancel ();
222         while ((ev = xcb_poll_for_event (conn)) != NULL)
223         {
224             if ((ev->response_type & 0x7F) == XCB_PROPERTY_NOTIFY)
225             {
226                  const xcb_property_notify_event_t *pn =
227                      (xcb_property_notify_event_t *)ev;
228                  if (pn->atom == p_sys->net_client_list)
229                      UpdateApps (sd);
230             }
231             free (ev);
232         }
233         vlc_restorecancel (canc);
234     }
235     return NULL;
236 }
237 
238 /*** Application windows ***/
239 struct app
240 {
241     xcb_window_t          xid; /* must be first for cmpapp */
242     input_item_t         *item;
243     services_discovery_t *owner;
244 };
245 
AddApp(services_discovery_t * sd,xcb_window_t xid)246 static struct app *AddApp (services_discovery_t *sd, xcb_window_t xid)
247 {
248     services_discovery_sys_t *p_sys = sd->p_sys;
249     char *mrl, *name;
250 
251     if (asprintf (&mrl, "window://0x%"PRIx8, xid) == -1)
252         return NULL;
253 
254     xcb_get_property_reply_t *r =
255         xcb_get_property_reply (p_sys->conn,
256             xcb_get_property (p_sys->conn, 0, xid, p_sys->net_wm_name, 0,
257                               0, 1023 /* max size */), NULL);
258     if (r != NULL)
259     {
260         name = strndup (xcb_get_property_value (r),
261                         xcb_get_property_value_length (r));
262         if (name != NULL)
263             EnsureUTF8 (name); /* don't trust third party apps too much ;-) */
264         free (r);
265     }
266     /* TODO: use WM_NAME (Latin-1) for very old apps */
267     else
268         name = NULL;
269 
270     input_item_t *item = input_item_NewCard (mrl, name ? name : mrl); /* FIXME */
271     free (mrl);
272     free (name);
273     if (item == NULL)
274         return NULL;
275 
276     struct app *app = malloc (sizeof (*app));
277     if (app == NULL)
278     {
279         input_item_Release (item);
280         return NULL;
281     }
282     app->xid = xid;
283     app->item = item;
284     app->owner = sd;
285     services_discovery_AddSubItem(sd, p_sys->apps_root, item);
286     return app;
287 }
288 
DelApp(void * data)289 static void DelApp (void *data)
290 {
291     struct app *app = data;
292 
293     services_discovery_RemoveItem (app->owner, app->item);
294     input_item_Release (app->item);
295     free (app);
296 }
297 
cmpapp(const void * a,const void * b)298 static int cmpapp (const void *a, const void *b)
299 {
300     xcb_window_t wa = *(xcb_window_t *)a;
301     xcb_window_t wb = *(xcb_window_t *)b;
302 
303     if (wa > wb)
304         return 1;
305     if (wa < wb)
306         return -1;
307     return 0;
308 }
309 
UpdateApps(services_discovery_t * sd)310 static void UpdateApps (services_discovery_t *sd)
311 {
312     services_discovery_sys_t *p_sys = sd->p_sys;
313     xcb_connection_t *conn = p_sys->conn;
314 
315     xcb_get_property_reply_t *r =
316         xcb_get_property_reply (conn,
317             xcb_get_property (conn, false, p_sys->root_window,
318                               p_sys->net_client_list, XA_WINDOW, 0, 1024),
319             NULL);
320     if (r == NULL)
321         return; /* FIXME: remove all entries */
322 
323     xcb_window_t *ent = xcb_get_property_value (r);
324     int n = xcb_get_property_value_length (r) / 4;
325     void *newnodes = NULL, *oldnodes = p_sys->apps;
326 
327     for (int i = 0; i < n; i++)
328     {
329         xcb_window_t id = *(ent++);
330         struct app *app;
331 
332         struct app **pa = tfind (&id, &oldnodes, cmpapp);
333         if (pa != NULL) /* existing entry */
334         {
335             app = *pa;
336             tdelete (app, &oldnodes, cmpapp);
337         }
338         else /* new entry */
339         {
340             app = AddApp (sd, id);
341             if (app == NULL)
342                 continue;
343         }
344 
345         pa = tsearch (app, &newnodes, cmpapp);
346         if (pa == NULL /* OOM */ || *pa != app /* buggy window manager */)
347             DelApp (app);
348     }
349     free (r);
350 
351     /* Remove old nodes */
352     tdestroy (oldnodes, DelApp);
353     p_sys->apps = newnodes;
354 }
355 
356 /*** Whole desktop ***/
AddDesktop(services_discovery_t * sd)357 static void AddDesktop(services_discovery_t *sd)
358 {
359     input_item_t *item;
360 
361     item = input_item_NewCard ("screen://", _("Desktop"));
362     if (item == NULL)
363         return;
364 
365     services_discovery_AddItem(sd, item);
366     input_item_Release (item);
367 }
368