pidgin/gtkrequest.c

branch
release-2.x.y
changeset 40394
3c8f61de927c
parent 39011
a2b3cc337c5e
child 40395
87d96850a2d7
equal deleted inserted replaced
40393:bac3b9138b65 40394:3c8f61de927c
77 { 77 {
78 gboolean savedialog; 78 gboolean savedialog;
79 gchar *name; 79 gchar *name;
80 80
81 } file; 81 } file;
82
83 struct
84 {
85 #ifdef HAVE_GIOUNIX
86 GCancellable *cancellable;
87 GDBusConnection *dbus_connection;
88 gchar *session_path;
89 guint signal_id;
90 guint32 node_id;
91 #endif
92
93 } screenshare;
82 94
83 } u; 95 } u;
84 96
85 } PidginRequestData; 97 } PidginRequestData;
86 98
1705 1717
1706 return (void *)data; 1718 return (void *)data;
1707 } 1719 }
1708 1720
1709 #ifdef USE_VV 1721 #ifdef USE_VV
1722
1723 #ifdef HAVE_GIOUNIX
1724 #include <gio/gunixfdlist.h>
1725
1726 static gboolean portal_failed;
1727
1728 static void screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data);
1729
1730 static void portal_cancel_cb(PidginRequestData *data)
1731 {
1732 screenshare_cancel_cb(NULL, data);
1733 }
1734
1735 static void portal_fallback(PidginRequestData *data)
1736 {
1737 purple_debug_info("pidgin", "Fallback from XDP portal screenshare\n");
1738 portal_failed = TRUE;
1739
1740 if (data->dialog) {
1741 pidgin_auto_parent_window(data->dialog);
1742 gtk_widget_show_all(data->dialog);
1743 } else {
1744 portal_cancel_cb(data);
1745 }
1746 }
1747
1748 static void request_completed_cb(GObject *object, GAsyncResult *res, gpointer _data)
1749 {
1750 PidginRequestData *data = _data;
1751 GError *error = NULL;
1752 GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
1753 res,
1754 &error);
1755 if (!msg || g_dbus_message_to_gerror(msg, &error)) {
1756 /* This is the expected failure mode when XDP screencast isn't available.
1757 * Don't be too noisy about it; just fall back to direct mode. */
1758 purple_debug_info("pidgin",
1759 "ScreenCast call failed: %s\n", error->message);
1760 portal_fallback(data);
1761 }
1762 }
1763
1764
1765 static guint portal_session_nr, portal_request_nr;
1766
1767 static gchar *portal_request_path(GDBusConnection *dc, GVariantBuilder *b)
1768 {
1769 const gchar *bus_name = g_dbus_connection_get_unique_name(dc);
1770 gchar *dot, *request_str = g_strdup_printf("u%u", portal_request_nr++);
1771 gchar *request_path = g_strdup_printf("/org/freedesktop/portal/desktop/request/%s/%s",
1772 bus_name + 1, request_str);
1773
1774 g_variant_builder_add(b, "{sv}", "handle_token", g_variant_new_take_string(request_str));
1775
1776 dot = request_path;
1777 while ((dot = strchr(dot, '.')))
1778 *dot = '_';
1779
1780 return request_path;
1781 }
1782
1783 static void screen_cast_call(PidginRequestData *data, const gchar *method, const gchar *str_arg,
1784 GVariantBuilder *opts, GDBusSignalCallback cb)
1785 {
1786 GDBusMessage *msg;
1787 GVariantBuilder b;
1788 gchar *request_path;
1789
1790 if (data->u.screenshare.signal_id)
1791 g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
1792 data->u.screenshare.signal_id);
1793
1794 if (!opts)
1795 opts = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
1796 request_path = portal_request_path(data->u.screenshare.dbus_connection, opts);
1797
1798 data->u.screenshare.signal_id = g_dbus_connection_signal_subscribe(data->u.screenshare.dbus_connection,
1799 "org.freedesktop.portal.Desktop",
1800 "org.freedesktop.portal.Request",
1801 "Response", request_path, NULL, 0,
1802 cb, data, NULL);
1803 g_free(request_path);
1804
1805 msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
1806 "/org/freedesktop/portal/desktop",
1807 "org.freedesktop.portal.ScreenCast",
1808 method);
1809
1810 g_variant_builder_init(&b, G_VARIANT_TYPE_TUPLE);
1811 if (data->u.screenshare.session_path)
1812 g_variant_builder_add(&b, "o", data->u.screenshare.session_path);
1813 if (str_arg)
1814 g_variant_builder_add(&b, "s", str_arg);
1815 g_variant_builder_add(&b, "a{sv}", opts);
1816
1817 g_dbus_message_set_body(msg, g_variant_builder_end(&b));
1818
1819 g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
1820 0, 200, NULL, NULL, request_completed_cb, data);
1821 }
1822
1823 static GstElement *create_pipewiresrc_cb(PurpleMedia *media, const gchar *session_id,
1824 const gchar *participant)
1825 {
1826 GstElement *ret;
1827 GObject *info;
1828 gchar *node_id;
1829
1830 info = g_object_get_data(G_OBJECT(media), "src-element");
1831 if (!info)
1832 return NULL;
1833
1834 ret = gst_element_factory_make("pipewiresrc", NULL);
1835 if (ret == NULL)
1836 return NULL;
1837
1838 /* Take the node-id and fd from the PurpleMediaElementInfo
1839 * and apply them to the pipewiresrc */
1840 node_id = g_strdup_printf("%u",
1841 GPOINTER_TO_UINT(g_object_get_data(info, "node-id")));
1842 g_object_set(ret,"path", node_id, "do-timestamp", TRUE,
1843 "fd", GPOINTER_TO_UINT(g_object_get_data(info, "fd")),
1844 NULL);
1845 g_free(node_id);
1846
1847 return ret;
1848 }
1849
1850 static void close_pipewire_fd(gpointer _fd)
1851 {
1852 int fd = GPOINTER_TO_INT(_fd);
1853
1854 close(fd);
1855 }
1856
1857 static void pipewire_fd_cb(GObject *object, GAsyncResult *res, gpointer _data)
1858 {
1859 PidginRequestData *data = _data;
1860 GError *error = NULL;
1861 GUnixFDList *l;
1862 int pipewire_fd;
1863 GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
1864 res,
1865 &error);
1866 if (!msg || g_dbus_message_to_gerror(msg, &error)) {
1867 purple_debug_info("pidgin", "OpenPipeWireRemote request failed: %s\n", error->message);
1868 purple_notify_error(NULL, _("Screen share error"),
1869 _("OpenPipeWireRemote request failed"), error->message);
1870 g_clear_error(&error);
1871 portal_cancel_cb(data);
1872 return;
1873 }
1874 l = g_dbus_message_get_unix_fd_list(msg);
1875 if (!l) {
1876 purple_debug_info("pidgin", "OpenPipeWireRemote request failed to yield a file descriptor\n");
1877 purple_notify_error(NULL, _("Screen share error"), _("OpenPipeWireRemote request failed"),
1878 _("No file descriptor found"));
1879 portal_cancel_cb(data);
1880 return;
1881 }
1882 pipewire_fd = g_unix_fd_list_get(l, 0, NULL);
1883
1884 if (data->cbs[0] != NULL) {
1885 GObject *info;
1886 info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1887 "id", "screenshare-window",
1888 "name", "Screen share single window",
1889 "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
1890 PURPLE_MEDIA_ELEMENT_ONE_SRC,
1891 "create-cb", create_pipewiresrc_cb, NULL);
1892 g_object_set_data_full(info, "fd", GUINT_TO_POINTER(pipewire_fd), close_pipewire_fd);
1893 g_object_set_data(info, "node-id", GUINT_TO_POINTER(data->u.screenshare.node_id));
1894 /* When the DBus connection closes, the session ends. So keep it attached
1895 to the PurpleMediaElementInfo, which in turn should be attached to
1896 the owning PurpleMedia for the lifetime of the session. */
1897 g_object_set_data_full(info, "dbus-connection",
1898 g_object_ref(data->u.screenshare.dbus_connection),
1899 g_object_unref);
1900 ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
1901 }
1902
1903 purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
1904 }
1905
1906
1907 static void get_pipewire_fd(PidginRequestData *data)
1908 {
1909 GDBusMessage *msg;
1910 GVariant *args;
1911
1912 if (data->u.screenshare.signal_id)
1913 g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
1914 data->u.screenshare.signal_id);
1915 data->u.screenshare.signal_id = 0;
1916
1917 msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
1918 "/org/freedesktop/portal/desktop",
1919 "org.freedesktop.portal.ScreenCast",
1920 "OpenPipeWireRemote");
1921
1922 args = g_variant_new("(oa{sv})", data->u.screenshare.session_path, NULL);
1923 g_dbus_message_set_body(msg, args);
1924
1925 g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
1926 0, 200, NULL, NULL, pipewire_fd_cb, data);
1927 }
1928
1929 static void started_cb(GDBusConnection *dc, const gchar *sender_name,
1930 const gchar *object_path, const gchar *interface_name,
1931 const gchar *signal_name, GVariant *params, gpointer _data)
1932 {
1933 PidginRequestData *data = _data;
1934 GVariant *args, *streams;
1935 guint code;
1936
1937 g_variant_get(params, "(u@a{sv})", &code, &args);
1938 if (code || !g_variant_lookup(args, "streams", "@a(ua{sv})", &streams) ||
1939 g_variant_n_children(streams) != 1) {
1940 purple_debug_info("pidgin", "Screencast Start call returned %d\n", code);
1941 purple_notify_error(NULL, _("Screen share error"),
1942 _("Screencast \"Start\" failed"), NULL);
1943 portal_cancel_cb(data);
1944 return;
1945 }
1946
1947 g_variant_get_child(streams, 0, "(u@a{sv})", &data->u.screenshare.node_id, NULL);
1948
1949 get_pipewire_fd(data);
1950 }
1951
1952 static void source_selected_cb(GDBusConnection *dc, const gchar *sender_name,
1953 const gchar *object_path, const gchar *interface_name,
1954 const gchar *signal_name, GVariant *params, gpointer _data)
1955 {
1956 PidginRequestData *data = _data;
1957 guint code;
1958
1959 g_variant_get(params, "(u@a{sv})", &code, NULL);
1960 if (code) {
1961 purple_debug_info("pidgin", "Screencast SelectSources call returned %d\n", code);
1962 purple_notify_error(NULL, _("Screen share error"),
1963 _("Screencast \"SelectSources\" failed"), NULL);
1964 portal_cancel_cb(data);
1965 return;
1966 }
1967
1968 screen_cast_call(data, "Start", "", NULL, started_cb);
1969 }
1970
1971 static void sess_created_cb(GDBusConnection *dc, const gchar *sender_name,
1972 const gchar *object_path, const gchar *interface_name,
1973 const gchar *signal_name, GVariant *params, gpointer _data)
1974 {
1975 PidginRequestData *data = _data;
1976 GVariantBuilder opts;
1977 GVariant *args;
1978 guint code;
1979
1980 g_variant_get(params, "(u@a{sv})", &code, &args);
1981 if (code || !g_variant_lookup(args, "session_handle", "s",
1982 &data->u.screenshare.session_path)) {
1983 purple_debug_info("pidgin", "Screencast CreateSession call returned %d\n", code);
1984 purple_notify_error(NULL, _("Screen share error"),
1985 _("Screencast \"CreateSession\" failed."), NULL);
1986 portal_cancel_cb(data);
1987 return;
1988 }
1989
1990 g_variant_builder_init(&opts, G_VARIANT_TYPE("a{sv}"));
1991 g_variant_builder_add(&opts, "{sv}", "multiple", g_variant_new_boolean(FALSE));
1992 g_variant_builder_add(&opts, "{sv}", "types", g_variant_new_uint32(3));
1993
1994 screen_cast_call(data, "SelectSources", NULL, &opts, source_selected_cb);
1995 }
1996
1997 static void portal_conn_cb(GObject *object, GAsyncResult *res, gpointer _data)
1998 {
1999 PidginRequestData *data = _data;
2000 GVariantBuilder opts;
2001 GError *error = NULL;
2002 gchar *session_token;
2003
2004 data->u.screenshare.dbus_connection = g_dbus_connection_new_for_address_finish(res, &error);
2005 if (!data->u.screenshare.dbus_connection) {
2006 purple_debug_info("pidgin", "Connection to XDP portal failed: %s\n", error->message);
2007 portal_fallback(data);
2008 return;
2009 }
2010
2011 session_token = g_strdup_printf("u%u", portal_session_nr++);
2012
2013 g_variant_builder_init(&opts, G_VARIANT_TYPE("a{sv}"));
2014 g_variant_builder_add(&opts, "{sv}", "session_handle_token",
2015 g_variant_new_take_string(session_token));
2016
2017 screen_cast_call(data, "CreateSession", NULL, &opts, sess_created_cb);
2018 }
2019
2020 static gboolean request_xdp_portal_screenshare(PidginRequestData *data)
2021 {
2022 gchar *addr;
2023
2024 if (portal_failed)
2025 return FALSE;
2026
2027 data->u.screenshare.cancellable = g_cancellable_new();
2028
2029 /* We create a new connection instead of using g_bus_get() because it
2030 * makes cleanup a *lot* easier. Just kill the connection. */
2031 addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2032 if (!addr) {
2033 portal_failed = TRUE;
2034 return FALSE;
2035 }
2036
2037 g_dbus_connection_new_for_address(addr,
2038 G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
2039 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL,
2040 data->u.screenshare.cancellable, portal_conn_cb, data);
2041 g_free(addr);
2042 return TRUE;
2043 }
2044
2045 #endif
2046
1710 static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id, 2047 static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id,
1711 const gchar *participant); 2048 const gchar *participant);
1712 2049
1713 #ifdef HAVE_X11 2050 #ifdef HAVE_X11
1714 static gboolean 2051 static gboolean
1909 } 2246 }
1910 2247
1911 static void 2248 static void
1912 screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data) 2249 screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data)
1913 { 2250 {
1914 generic_response_start(data); 2251 if (data->dialog)
2252 generic_response_start(data);
1915 2253
1916 if (data->cbs[0] != NULL) 2254 if (data->cbs[0] != NULL)
1917 ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, NULL); 2255 ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, NULL);
1918 2256
1919 purple_request_close(PURPLE_REQUEST_SCREENSHARE, data); 2257 purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
2053 g_free(name); 2391 g_free(name);
2054 } 2392 }
2055 g_object_set_data(G_OBJECT(dialog), "radio", radio); 2393 g_object_set_data(G_OBJECT(dialog), "radio", radio);
2056 } 2394 }
2057 2395
2396 #ifdef HAVE_GIOUNIX
2397 /*
2398 * We create the dialog for direct x11/win share here anyway, because
2399 * it's simpler than storing everything we need to create it, including
2400 * the PurpleAccount which we can't just take a ref on because it isn't
2401 * just a GObject yet. On fallback, the dialog can be used immediately.
2402 */
2403 if (request_xdp_portal_screenshare(data)) {
2404 purple_debug_info("pidgin", "Attempt XDP portal screenshare\n");
2405 return data;
2406 }
2407 #endif
2408
2409 purple_debug_info("pidgin", "Using direct screenshare\n");
2410
2058 /* Show everything. */ 2411 /* Show everything. */
2059 pidgin_auto_parent_window(dialog); 2412 pidgin_auto_parent_window(dialog);
2060 2413
2061 gtk_widget_show_all(dialog); 2414 gtk_widget_show_all(dialog);
2062 2415
2070 { 2423 {
2071 PidginRequestData *data = (PidginRequestData *)ui_handle; 2424 PidginRequestData *data = (PidginRequestData *)ui_handle;
2072 2425
2073 g_free(data->cbs); 2426 g_free(data->cbs);
2074 2427
2075 gtk_widget_destroy(data->dialog); 2428 if (data->dialog)
2429 gtk_widget_destroy(data->dialog);
2076 2430
2077 if (type == PURPLE_REQUEST_FIELDS) 2431 if (type == PURPLE_REQUEST_FIELDS)
2078 purple_request_fields_destroy(data->u.multifield.fields); 2432 purple_request_fields_destroy(data->u.multifield.fields);
2079 else if (type == PURPLE_REQUEST_FILE) 2433 else if (type == PURPLE_REQUEST_FILE)
2080 g_free(data->u.file.name); 2434 g_free(data->u.file.name);
2435 else if (type == PURPLE_REQUEST_SCREENSHARE) {
2436 #ifdef HAVE_GIOUNIX
2437 g_cancellable_cancel(data->u.screenshare.cancellable);
2438 if (data->u.screenshare.signal_id)
2439 g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
2440 data->u.screenshare.signal_id);
2441 g_clear_object(&data->u.screenshare.dbus_connection);
2442 g_free(data->u.screenshare.session_path);
2443 g_clear_object(&data->u.screenshare.cancellable);
2444 #endif
2445 }
2081 2446
2082 g_free(data); 2447 g_free(data);
2083 } 2448 }
2084 2449
2085 static PurpleRequestUiOps ops = 2450 static PurpleRequestUiOps ops =

mercurial