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