]> git.sesse.net Git - vlc/blob - modules/services_discovery/xcb_apps.c
XCB screen capture discovery plugin
[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_plugin.h>
34 #include <poll.h>
35 #include <search.h>
36
37 static int  Open (vlc_object_t *);
38 static void Close (vlc_object_t *);
39
40 /*
41  * Module descriptor
42  */
43 vlc_module_begin ()
44     set_shortname (N_("Screen capture"))
45     set_description (N_("Screen capture"))
46     set_category (CAT_PLAYLIST)
47     set_subcategory (SUBCAT_PLAYLIST_SD)
48     set_capability ("services_discovery", 0)
49     set_callbacks (Open, Close)
50
51     add_shortcut ("apps")
52 vlc_module_end ()
53
54 struct services_discovery_sys_t
55 {
56     xcb_connection_t *conn;
57     vlc_thread_t      thread;
58     xcb_atom_t        net_client_list;
59     xcb_window_t      root_window;
60     void             *nodes;
61 };
62
63 static void *Run (void *);
64 static void Update (services_discovery_t *);
65 static void DelItem (void *);
66
67 /**
68  * Probes and initializes.
69  */
70 static int Open (vlc_object_t *obj)
71 {
72     services_discovery_t *sd = (services_discovery_t *)obj;
73     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
74
75     if (p_sys == NULL)
76         return VLC_ENOMEM;
77     sd->p_sys = p_sys;
78
79     /* Connect to X server */
80     char *display = var_CreateGetNonEmptyString (obj, "x11-display");
81     int snum;
82     xcb_connection_t *conn = xcb_connect (display, &snum);
83     free (display);
84     if (xcb_connection_has_error (conn))
85     {
86         free (p_sys);
87         return VLC_EGENERIC;
88     }
89     p_sys->conn = conn;
90
91     /* Find configured screen */
92     const xcb_setup_t *setup = xcb_get_setup (conn);
93     const xcb_screen_t *scr = NULL;
94     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
95          i.rem > 0; xcb_screen_next (&i))
96     {
97         if (snum == 0)
98         {
99             scr = i.data;
100             break;
101         }
102         snum--;
103     }
104     if (scr == NULL)
105     {
106         msg_Err (obj, "bad X11 screen number");
107         goto error;
108     }
109
110     p_sys->root_window = scr->root;
111     xcb_change_window_attributes (conn, scr->root, XCB_CW_EVENT_MASK,
112                                &(uint32_t) { XCB_EVENT_MASK_PROPERTY_CHANGE });
113
114     /* TODO: check that _NET_CLIENT_LIST is in _NET_SUPPORTED
115      * (and _NET_SUPPORTING_WM_CHECK) */
116     xcb_intern_atom_cookie_t ck;
117     ck = xcb_intern_atom (conn, 1, strlen ("_NET_CLIENT_LIST"),
118                           "_NET_CLIENT_LIST");
119     xcb_intern_atom_reply_t *r = xcb_intern_atom_reply (conn, ck, NULL);
120     if (r == NULL || r->atom == 0)
121     {
122         dialog_Fatal (sd, _("Application list failure"),
123                   _("Your window manager does not support application list."));
124         msg_Err (sd, "application list not support (_NET_CLIENT_LIST absent)");
125         free (r);
126         goto error;
127     }
128     p_sys->net_client_list = r->atom;
129     free (r);
130
131     p_sys->nodes = NULL;
132     Update (sd);
133
134     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
135         goto error;
136     return VLC_SUCCESS;
137
138 error:
139     xcb_disconnect (p_sys->conn);
140     free (p_sys);
141     return VLC_EGENERIC;
142 }
143
144
145 /**
146  * Releases resources
147  */
148 static void Close (vlc_object_t *obj)
149 {
150     services_discovery_t *sd = (services_discovery_t *)obj;
151     services_discovery_sys_t *p_sys = sd->p_sys;
152
153     vlc_cancel (p_sys->thread);
154     vlc_join (p_sys->thread, NULL);
155     xcb_disconnect (p_sys->conn);
156     tdestroy (p_sys->nodes, DelItem);
157     free (p_sys);
158 }
159
160 static void *Run (void *data)
161 {
162     services_discovery_t *sd = data;
163     services_discovery_sys_t *p_sys = sd->p_sys;
164     xcb_connection_t *conn = p_sys->conn;
165     int fd = xcb_get_file_descriptor (conn);
166     if (fd == -1)
167         return NULL;
168
169     while (!xcb_connection_has_error (conn))
170     {
171         xcb_generic_event_t *ev;
172         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
173
174         poll (&ufd, 1, -1);
175
176         int canc = vlc_savecancel ();
177         while ((ev = xcb_poll_for_event (conn)) != NULL)
178         {
179             if ((ev->response_type & 0x7F) == XCB_PROPERTY_NOTIFY)
180             {
181                  const xcb_property_notify_event_t *pn =
182                      (xcb_property_notify_event_t *)ev;
183                  if (pn->atom == p_sys->net_client_list)
184                      Update (sd);
185             }
186             free (ev);
187         }
188         vlc_restorecancel (canc);
189     }
190     return NULL;
191 }
192
193 struct app
194 {
195     xcb_window_t          xid; /* must be first for cmpapp */
196     input_item_t         *item;
197     services_discovery_t *owner;
198 };
199
200 static struct app *AddItem (services_discovery_t *sd, xcb_window_t xid)
201 {
202     char *mrl;
203     if (asprintf (&mrl, "window://0x%"PRIx8, xid) == -1)
204         return NULL;
205
206     input_item_t *item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
207                                                  mrl,
208                                                  0, NULL, 0, -1,
209                                                  ITEM_TYPE_CARD /* FIXME */);
210     free (mrl);
211     if (item == NULL)
212         return NULL;
213
214     struct app *app = malloc (sizeof (*app));
215     if (app == NULL)
216     {
217         vlc_gc_decref (item);
218         return NULL;
219     }
220     app->xid = xid;
221     app->item = item;
222     app->owner = sd;
223     services_discovery_AddItem (sd, item, _("Applications"));
224     return app;
225 }
226
227 static void DelItem (void *data)
228 {
229     struct app *app = data;
230
231     services_discovery_RemoveItem (app->owner, app->item);
232     vlc_gc_decref (app->item);
233     free (app);
234 }
235
236 static int cmpapp (const void *a, const void *b)
237 {
238     xcb_window_t wa = *(xcb_window_t *)a;
239     xcb_window_t wb = *(xcb_window_t *)b;
240
241     if (wa > wb)
242         return 1;
243     if (wa < wb)
244         return -1;
245     return 0;
246
247
248 static void Update (services_discovery_t *sd)
249 {
250     services_discovery_sys_t *p_sys = sd->p_sys;
251     xcb_connection_t *conn = p_sys->conn;
252
253     xcb_get_property_reply_t *r =
254         xcb_get_property_reply (conn,
255             xcb_get_property (conn, false, p_sys->root_window,
256                               p_sys->net_client_list, XA_WINDOW, 0, 1024),
257             NULL);
258     if (r == NULL)
259         return; /* FIXME: remove all entries */
260
261     xcb_window_t *ent = xcb_get_property_value (r);
262     int n = xcb_get_property_value_length (r) / 4;
263     void *newnodes = NULL, *oldnodes = p_sys->nodes;
264
265     for (int i = 0; i < n; i++)
266     {
267         xcb_window_t id = *(ent++);
268         struct app *app;
269
270         struct app **pa = tfind (&id, &oldnodes, cmpapp);
271         if (pa != NULL) /* existing entry */
272         {
273             app = *pa;
274             tdelete (app, &oldnodes, cmpapp);
275         }
276         else /* new entry */
277         {
278             app = AddItem (sd, id);
279             if (app == NULL)
280                 continue;
281         }
282
283         pa = tsearch (app, &newnodes, cmpapp);
284         if (pa == NULL /* OOM */ || *pa != app /* buggy window manager */)
285             DelItem (app);
286     }
287     free (r);
288
289     /* Remove old nodes */
290     tdestroy (oldnodes, DelItem);
291     p_sys->nodes = newnodes;
292 }