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