/** * @file xcb_apps.c * @brief List of application windows XCB module for VLC media player */ /***************************************************************************** * Copyright © 2009 Rémi Denis-Courmont * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #include typedef xcb_atom_t Atom; #include /* XA_WINDOW */ #include #include #include #include #include #ifdef HAVE_SEARCH_H # include #endif #include static int Open (vlc_object_t *); static void Close (vlc_object_t *); static int vlc_sd_probe_Open (vlc_object_t *); /* * Module descriptor */ vlc_module_begin () set_shortname (N_("Screen capture")) set_description (N_("Screen capture")) set_category (CAT_PLAYLIST) set_subcategory (SUBCAT_PLAYLIST_SD) set_capability ("services_discovery", 0) set_callbacks (Open, Close) add_shortcut ("apps", "screen") VLC_SD_PROBE_SUBMODULE vlc_module_end () struct services_discovery_sys_t { xcb_connection_t *conn; vlc_thread_t thread; xcb_atom_t net_client_list; xcb_atom_t net_wm_name; xcb_window_t root_window; void *apps; input_item_t *apps_root; }; static void *Run (void *); static void UpdateApps (services_discovery_t *); static void DelApp (void *); static void AddDesktop(services_discovery_t *); static int vlc_sd_probe_Open (vlc_object_t *obj) { vlc_probe_t *probe = (vlc_probe_t *)obj; char *display = var_InheritString (obj, "x11-display"); xcb_connection_t *conn = xcb_connect (display, NULL); free (display); if (xcb_connection_has_error (conn)) return VLC_PROBE_CONTINUE; xcb_disconnect (conn); return vlc_sd_probe_Add (probe, "xcb_apps", N_("Screen capture"), SD_CAT_DEVICES); } /** * Probes and initializes. */ static int Open (vlc_object_t *obj) { services_discovery_t *sd = (services_discovery_t *)obj; services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys)); if (p_sys == NULL) return VLC_ENOMEM; sd->p_sys = p_sys; sd->description = _("Screen capture"); /* Connect to X server */ char *display = var_InheritString (obj, "x11-display"); int snum; xcb_connection_t *conn = xcb_connect (display, &snum); free (display); if (xcb_connection_has_error (conn)) { free (p_sys); return VLC_EGENERIC; } p_sys->conn = conn; /* Find configured screen */ const xcb_setup_t *setup = xcb_get_setup (conn); const xcb_screen_t *scr = NULL; for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup); i.rem > 0; xcb_screen_next (&i)) { if (snum == 0) { scr = i.data; break; } snum--; } if (scr == NULL) { msg_Err (obj, "bad X11 screen number"); goto error; } /* Add a permanent item for the entire desktop */ AddDesktop (sd); p_sys->root_window = scr->root; xcb_change_window_attributes (conn, scr->root, XCB_CW_EVENT_MASK, &(uint32_t) { XCB_EVENT_MASK_PROPERTY_CHANGE }); /* TODO: check that _NET_CLIENT_LIST is in _NET_SUPPORTED * (and _NET_SUPPORTING_WM_CHECK) */ xcb_intern_atom_reply_t *r; xcb_intern_atom_cookie_t ncl, nwn; ncl = xcb_intern_atom (conn, 1, strlen ("_NET_CLIENT_LIST"), "_NET_CLIENT_LIST"); nwn = xcb_intern_atom (conn, 0, strlen ("_NET_WM_NAME"), "_NET_WM_NAME"); r = xcb_intern_atom_reply (conn, ncl, NULL); if (r == NULL || r->atom == 0) { vlc_dialog_display_error (sd, _("Screen capture"), _("Your window manager does not provide a list of applications.")); msg_Err (sd, "client list not supported (_NET_CLIENT_LIST absent)"); } p_sys->net_client_list = r ? r->atom : 0; free (r); r = xcb_intern_atom_reply (conn, nwn, NULL); if (r != NULL) { p_sys->net_wm_name = r->atom; free (r); } p_sys->apps = NULL; p_sys->apps_root = input_item_NewExt("vlc://nop", _("Applications"), -1, ITEM_TYPE_NODE, ITEM_LOCAL); if (likely(p_sys->apps_root != NULL)) services_discovery_AddItem(sd, p_sys->apps_root); UpdateApps (sd); if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW)) goto error; return VLC_SUCCESS; error: xcb_disconnect (p_sys->conn); tdestroy (p_sys->apps, DelApp); if (p_sys->apps_root != NULL) input_item_Release(p_sys->apps_root); free (p_sys); return VLC_EGENERIC; } /** * Releases resources */ static void Close (vlc_object_t *obj) { services_discovery_t *sd = (services_discovery_t *)obj; services_discovery_sys_t *p_sys = sd->p_sys; vlc_cancel (p_sys->thread); vlc_join (p_sys->thread, NULL); xcb_disconnect (p_sys->conn); tdestroy (p_sys->apps, DelApp); if (p_sys->apps_root != NULL) input_item_Release(p_sys->apps_root); free (p_sys); } static void *Run (void *data) { services_discovery_t *sd = data; services_discovery_sys_t *p_sys = sd->p_sys; xcb_connection_t *conn = p_sys->conn; int fd = xcb_get_file_descriptor (conn); if (fd == -1) return NULL; while (!xcb_connection_has_error (conn)) { xcb_generic_event_t *ev; struct pollfd ufd = { .fd = fd, .events = POLLIN, }; poll (&ufd, 1, -1); int canc = vlc_savecancel (); while ((ev = xcb_poll_for_event (conn)) != NULL) { if ((ev->response_type & 0x7F) == XCB_PROPERTY_NOTIFY) { const xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *)ev; if (pn->atom == p_sys->net_client_list) UpdateApps (sd); } free (ev); } vlc_restorecancel (canc); } return NULL; } /*** Application windows ***/ struct app { xcb_window_t xid; /* must be first for cmpapp */ input_item_t *item; services_discovery_t *owner; }; static struct app *AddApp (services_discovery_t *sd, xcb_window_t xid) { services_discovery_sys_t *p_sys = sd->p_sys; char *mrl, *name; if (asprintf (&mrl, "window://0x%"PRIx8, xid) == -1) return NULL; xcb_get_property_reply_t *r = xcb_get_property_reply (p_sys->conn, xcb_get_property (p_sys->conn, 0, xid, p_sys->net_wm_name, 0, 0, 1023 /* max size */), NULL); if (r != NULL) { name = strndup (xcb_get_property_value (r), xcb_get_property_value_length (r)); if (name != NULL) EnsureUTF8 (name); /* don't trust third party apps too much ;-) */ free (r); } /* TODO: use WM_NAME (Latin-1) for very old apps */ else name = NULL; input_item_t *item = input_item_NewCard (mrl, name ? name : mrl); /* FIXME */ free (mrl); free (name); if (item == NULL) return NULL; struct app *app = malloc (sizeof (*app)); if (app == NULL) { input_item_Release (item); return NULL; } app->xid = xid; app->item = item; app->owner = sd; services_discovery_AddSubItem(sd, p_sys->apps_root, item); return app; } static void DelApp (void *data) { struct app *app = data; services_discovery_RemoveItem (app->owner, app->item); input_item_Release (app->item); free (app); } static int cmpapp (const void *a, const void *b) { xcb_window_t wa = *(xcb_window_t *)a; xcb_window_t wb = *(xcb_window_t *)b; if (wa > wb) return 1; if (wa < wb) return -1; return 0; } static void UpdateApps (services_discovery_t *sd) { services_discovery_sys_t *p_sys = sd->p_sys; xcb_connection_t *conn = p_sys->conn; xcb_get_property_reply_t *r = xcb_get_property_reply (conn, xcb_get_property (conn, false, p_sys->root_window, p_sys->net_client_list, XA_WINDOW, 0, 1024), NULL); if (r == NULL) return; /* FIXME: remove all entries */ xcb_window_t *ent = xcb_get_property_value (r); int n = xcb_get_property_value_length (r) / 4; void *newnodes = NULL, *oldnodes = p_sys->apps; for (int i = 0; i < n; i++) { xcb_window_t id = *(ent++); struct app *app; struct app **pa = tfind (&id, &oldnodes, cmpapp); if (pa != NULL) /* existing entry */ { app = *pa; tdelete (app, &oldnodes, cmpapp); } else /* new entry */ { app = AddApp (sd, id); if (app == NULL) continue; } pa = tsearch (app, &newnodes, cmpapp); if (pa == NULL /* OOM */ || *pa != app /* buggy window manager */) DelApp (app); } free (r); /* Remove old nodes */ tdestroy (oldnodes, DelApp); p_sys->apps = newnodes; } /*** Whole desktop ***/ static void AddDesktop(services_discovery_t *sd) { input_item_t *item; item = input_item_NewCard ("screen://", _("Desktop")); if (item == NULL) return; services_discovery_AddItem(sd, item); input_item_Release (item); }