| 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 |