diff --git a/configure.ac b/configure.ac
index 2ac444d76c..f5f091b5e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -346,6 +346,9 @@ I can find them.
AC_SUBST(GLIB_CFLAGS)
AC_SUBST(GLIB_LIBS)
+PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0 >= 2.26],
+ AC_DEFINE(HAVE_GIOUNIX, 1, [Have gio-unix]), [])
+
GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0`
AC_SUBST(GLIB_GENMARSHAL)
diff --git a/libpurple/request.c b/libpurple/request.c
index d61d24182e..da2b5cad99 100644
--- a/libpurple/request.c
+++ b/libpurple/request.c
@@ -1501,6 +1501,31 @@ purple_request_folder(void *handle, const char *title, const char *dirname,
return NULL;
}
+void *
+purple_request_screenshare_media(void *handle, const char *title,
+ const char *primary, const char *secondary,
+ PurpleAccount *account, GCallback cb,
+ void *user_data)
+{
+ PurpleRequestUiOps *ops;
+
+ ops = purple_request_get_ui_ops();
+
+ if (ops != NULL && ops->request_screenshare_media != NULL) {
+ PurpleRequestInfo *info;
+
+ info = g_new0(PurpleRequestInfo, 1);
+ info->type = PURPLE_REQUEST_SCREENSHARE;
+ info->handle = handle;
+ info->ui_handle = ops->request_screenshare_media(title, primary, secondary,
+ account, cb, user_data);
+ handles = g_list_append(handles, info);
+ return info->ui_handle;
+ }
+
+ return NULL;
+}
+
static void
purple_request_close_info(PurpleRequestInfo *info)
{
diff --git a/libpurple/request.h b/libpurple/request.h
index 8035ef806b..282c909c78 100644
--- a/libpurple/request.h
+++ b/libpurple/request.h
@@ -47,7 +47,8 @@ typedef enum
PURPLE_REQUEST_ACTION, /**< Action request. */
PURPLE_REQUEST_FIELDS, /**< Multiple fields request. */
PURPLE_REQUEST_FILE, /**< File open or save request. */
- PURPLE_REQUEST_FOLDER /**< Folder selection request. */
+ PURPLE_REQUEST_FOLDER, /**< Folder selection request. */
+ PURPLE_REQUEST_SCREENSHARE /**< Screenshare media request. */
} PurpleRequestType;
@@ -246,9 +247,12 @@ typedef struct
void *user_data,
size_t action_count, va_list actions);
+ void *(*request_screenshare_media)(const char *title, const char *primary,
+ const char *secondary, PurpleAccount *account,
+ GCallback cb, void *user_data);
+
void (*_purple_reserved1)(void);
void (*_purple_reserved2)(void);
- void (*_purple_reserved3)(void);
} PurpleRequestUiOps;
typedef void (*PurpleRequestInputCb)(void *, const char *);
@@ -261,6 +265,7 @@ typedef void (*PurpleRequestActionCb)(void *, int);
typedef void (*PurpleRequestChoiceCb)(void *, int);
typedef void (*PurpleRequestFieldsCb)(void *, PurpleRequestFields *fields);
typedef void (*PurpleRequestFileCb)(void *, const char *filename);
+typedef void (*PurpleRequestScreenshareCb)(void *, GObject *info);
#ifdef __cplusplus
extern "C" {
@@ -1576,6 +1581,30 @@ void *purple_request_folder(void *handle, const char *title, const char *dirname
PurpleAccount *account, const char *who, PurpleConversation *conv,
void *user_data);
+
+/**
+ * Displays a dialog allowing the user to select a window/monitor etc. for
+ * screen sharing. Returns a #PurpleMediaElementInfo to the callback or @c
+ * NULL if the request is cancelled.
+ *
+ * @param handle The plugin or connection handle. For some things this
+ * is extremely important. See the comments on
+ * purple_request_input().
+ * @param title The title of the message, or @c NULL if it should have
+ * no title.
+ * @param primary The main point of the message, or @c NULL if you're
+ * feeling enigmatic.
+ * @param secondary Secondary information, or @c NULL if there is none.
+ * @param cb The callback for the @c OK button.
+ * @param user_data The data to pass to the callback.
+ *
+ * @return A UI-specific handle.
+ */
+void *purple_request_screenshare_media(void *handle, const char *title,
+ const char *primary, const char *secondary,
+ PurpleAccount *account, GCallback cb,
+ void *user_data);
+
/*@}*/
/**************************************************************************/
diff --git a/pidgin/Makefile.am b/pidgin/Makefile.am
index 2b9f23c6c7..2278b88978 100644
--- a/pidgin/Makefile.am
+++ b/pidgin/Makefile.am
@@ -161,6 +161,7 @@ pidgin_LDADD = \
$(GTKSPELL_LIBS) \
$(LIBXML_LIBS) \
$(GTK_LIBS) \
+ $(GIO_UNIX_LIBS) \
$(top_builddir)/libpurple/libpurple.la
AM_CPPFLAGS = \
@@ -176,6 +177,7 @@ AM_CPPFLAGS = \
$(GSTREAMER_CFLAGS) \
$(DEBUG_CFLAGS) \
$(GTK_CFLAGS) \
+ $(GIO_UNIX_CFLAGS) \
$(DBUS_CFLAGS) \
$(GTKSPELL_CFLAGS) \
$(LIBXML_CFLAGS) \
diff --git a/pidgin/gtkmain.c b/pidgin/gtkmain.c
index 13aa1de9c6..9418271795 100644
--- a/pidgin/gtkmain.c
+++ b/pidgin/gtkmain.c
@@ -70,13 +70,16 @@
#include "pidginstock.h"
#include "gtkwhiteboard.h"
+#ifdef HAVE_X11
+#include
+#endif
+
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
-
#ifdef HAVE_SIGNAL_H
/*
@@ -749,6 +752,11 @@ int main(int argc, char *argv[])
gtk_rc_add_default_file(search_path);
g_free(search_path);
+#if defined(HAVE_X11) && defined(USE_VV)
+ /* GStreamer elements such as ximagesrc may require this */
+ XInitThreads();
+#endif
+
gui_check = gtk_init_check(&argc, &argv);
if (!gui_check) {
char *display = gdk_get_display();
diff --git a/pidgin/gtkrequest.c b/pidgin/gtkrequest.c
index 397e09adfa..db6a444833 100644
--- a/pidgin/gtkrequest.c
+++ b/pidgin/gtkrequest.c
@@ -36,6 +36,9 @@
#include "gtkutils.h"
#include "pidginstock.h"
#include "gtkblist.h"
+#ifdef USE_VV
+#include "media-gst.h"
+#endif
#include
@@ -77,6 +80,16 @@ typedef struct
} file;
+ struct
+ {
+ GCancellable *cancellable;
+ GDBusConnection *dbus_connection;
+ gchar *session_path;
+ guint signal_id;
+ guint32 node_id;
+
+ } screenshare;
+
} u;
} PidginRequestData;
@@ -1703,6 +1716,696 @@ pidgin_request_folder(const char *title, const char *dirname,
return (void *)data;
}
+#ifdef USE_VV
+
+#ifdef HAVE_GIOUNIX
+#include
+
+static gboolean portal_failed;
+
+static void screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data);
+
+static void portal_cancel_cb(PidginRequestData *data)
+{
+ screenshare_cancel_cb(NULL, data);
+}
+
+static void portal_fallback(PidginRequestData *data)
+{
+ portal_failed = TRUE;
+
+ // XXX fall back this time, not next!
+ portal_cancel_cb(data);
+}
+
+static void request_completed_cb(GObject *object, GAsyncResult *res, gpointer _data)
+{
+ PidginRequestData *data = _data;
+ GError *error = NULL;
+ GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
+ res,
+ &error);
+ if (!msg || g_dbus_message_to_gerror(msg, &error)) {
+ printf("msg failed: %s\n", error->message);
+ g_clear_error(&error);
+ portal_fallback(data);
+ }
+}
+
+
+static guint portal_session_nr, portal_request_nr;
+
+static gchar *portal_session_path(GDBusConnection *dc, GVariantBuilder *b)
+{
+ const gchar *bus_name = g_dbus_connection_get_unique_name(dc);
+ gchar *dot, *session_str = g_strdup_printf("u%u", portal_session_nr++);
+ gchar *session_path = g_strdup_printf("/org/freedesktop/portal/desktop/session/%s/%s",
+ bus_name + 1, session_str);
+
+ g_variant_builder_add(b, "{sv}", "session_handle_token", g_variant_new_take_string(session_str));
+
+ dot = session_path;
+ while ((dot = strchr(dot, '.')))
+ *dot = '_';
+
+ return session_path;
+}
+
+static gchar *portal_request_path(GDBusConnection *dc, GVariantBuilder *b)
+{
+ const gchar *bus_name = g_dbus_connection_get_unique_name(dc);
+ gchar *dot, *request_str = g_strdup_printf("u%u", portal_request_nr++);
+ gchar *request_path = g_strdup_printf("/org/freedesktop/portal/desktop/request/%s/%s",
+ bus_name + 1, request_str);
+
+ g_variant_builder_add(b, "{sv}", "handle_token", g_variant_new_take_string(request_str));
+
+ dot = request_path;
+ while ((dot = strchr(dot, '.')))
+ *dot = '_';
+
+ return request_path;
+}
+
+static void screen_cast_call(PidginRequestData *data, const gchar *method,
+ const gchar *obj_arg1, const gchar *str_arg1,
+ GVariantBuilder *opts, GDBusSignalCallback cb)
+{
+ GDBusMessage *msg;
+ GVariant *args;
+ gchar *request_path;
+
+ if (data->u.screenshare.signal_id)
+ g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
+ data->u.screenshare.signal_id);
+
+ if (!opts)
+ opts = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ request_path = portal_request_path(data->u.screenshare.dbus_connection, opts);
+
+ data->u.screenshare.signal_id = g_dbus_connection_signal_subscribe(data->u.screenshare.dbus_connection,
+ "org.freedesktop.portal.Desktop",
+ "org.freedesktop.portal.Request",
+ "Response", request_path, NULL, 0,
+ cb, data, NULL);
+ g_free(request_path);
+
+ msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.ScreenCast",
+ method);
+
+ if (str_arg1)
+ args = g_variant_new("(osa{sv})", obj_arg1, str_arg1, opts);
+ else if (obj_arg1)
+ args = g_variant_new("(oa{sv})", obj_arg1, opts);
+ else
+ args = g_variant_new("(a{sv})", opts);
+
+ g_dbus_message_set_body(msg, args);
+
+ g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
+ 0, 200, NULL, NULL, request_completed_cb, data);
+}
+
+static guint32 portal_response(GVariant *resp, GVariant **params)
+{
+ GVariantIter iter;
+ GVariant *code;
+ guint32 ret;
+
+ if (!g_variant_is_container(resp))
+ return -1;
+
+ g_variant_iter_init(&iter, resp);
+ code = g_variant_iter_next_value(&iter);
+ if (!code || !g_variant_is_of_type(code, G_VARIANT_TYPE_UINT32))
+ return -1;
+
+ ret = g_variant_get_uint32(code);
+ if (params)
+ *params = g_variant_iter_next_value(&iter);
+
+ return ret;
+}
+static GstElement *create_pipewiresrc_cb(PurpleMedia *media, const gchar *session_id,
+ const gchar *participant)
+{
+ GObject *info;
+ GstElement *ret;
+ gchar *node_id;
+
+ info = g_object_get_data(G_OBJECT(media), "src-element");
+ if (!info)
+ return NULL;
+
+ ret = gst_element_factory_make("pipewiresrc", NULL);
+ if (ret == NULL)
+ return NULL;
+
+ node_id = g_strdup_printf("%u", GPOINTER_TO_UINT(g_object_get_data(info, "node-id")));
+ g_object_set(ret, "fd", GPOINTER_TO_UINT(g_object_get_data(info, "fd")),
+ "path", node_id, "do-timestamp", TRUE, NULL);
+ g_free(node_id);
+
+ return ret;
+}
+
+static void pipewire_fd_cb(GObject *object, GAsyncResult *res, gpointer _data)
+{
+ PidginRequestData *data = _data;
+ GError *error = NULL;
+ GUnixFDList *l;
+ int pipewire_fd;
+ GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
+ res,
+ &error);
+ if (!msg || g_dbus_message_to_gerror(msg, &error)) {
+ printf("msg failed: %s\n", error->message);
+ g_clear_error(&error);
+ portal_fallback(data);
+ }
+ l = g_dbus_message_get_unix_fd_list(msg);
+ if (!l)
+ portal_fallback(data);
+ pipewire_fd = g_unix_fd_list_get(l, 0, NULL);
+
+ if (data->cbs[0] != NULL) {
+ GObject *info;
+ info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
+ "id", "screenshare-window",
+ "name", "Screen share single window",
+ "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
+ PURPLE_MEDIA_ELEMENT_ONE_SRC,
+ "create-cb", create_pipewiresrc_cb, NULL);
+ g_object_set_data_full(info, "node-id", GUINT_TO_POINTER(data->u.screenshare.node_id), (GDestroyNotify)close);
+ g_object_set_data(info, "fd", GUINT_TO_POINTER(pipewire_fd));
+ ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
+ }
+
+ printf("got pipewire fd %d\n", pipewire_fd);
+}
+
+
+static void get_pipewire_fd(PidginRequestData *data)
+{
+ GDBusMessage *msg;
+ GVariant *args;
+
+ if (data->u.screenshare.signal_id)
+ g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
+ data->u.screenshare.signal_id);
+ data->u.screenshare.signal_id = 0;
+
+ msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.ScreenCast",
+ "OpenPipeWireRemote");
+
+ args = g_variant_new("(oa{sv})", data->u.screenshare.session_path, NULL);
+ g_dbus_message_set_body(msg, args);
+
+ g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
+ 0, 200, NULL, NULL, pipewire_fd_cb, data);
+}
+
+static void started_cb(GDBusConnection *dc, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *params, gpointer _data)
+{
+ PidginRequestData *data = _data;
+ GVariantDict dict;
+ GVariantIter iter;
+ GVariant *dict_v, *streams, *stream, *id_v;
+
+ printf("signal %s indicated %d: %s\n", signal_name,
+ portal_response(params, NULL), g_variant_print(params, TRUE));
+
+ if (portal_response(params, &dict_v)) {
+ portal_cancel_cb(data);
+ return;
+ }
+
+ // XX: Be more defensive
+ g_variant_dict_init(&dict, dict_v);
+ streams = g_variant_dict_lookup_value(&dict, "streams", G_VARIANT_TYPE("a(ua{sv})"));
+ g_variant_dict_clear(&dict);
+ if (!streams) {
+ printf("no streams\n");
+ portal_cancel_cb(data);
+ return;
+ }
+
+ g_variant_iter_init(&iter, streams);
+ stream = g_variant_iter_next_value(&iter);
+ if (!stream) {
+ printf("empty streams\n");
+ portal_cancel_cb(data);
+ return;
+ }
+ g_variant_iter_init(&iter, stream);
+ id_v = g_variant_iter_next_value(&iter);
+ if (!id_v || !g_variant_is_of_type(id_v, G_VARIANT_TYPE_UINT32)) {
+ printf("broken stream\n");
+ portal_fallback(data);
+ return;
+ }
+ data->u.screenshare.node_id = g_variant_get_uint32(id_v);
+
+ get_pipewire_fd(data);
+}
+
+static void source_selected_cb(GDBusConnection *dc, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *params, gpointer _data)
+{
+ PidginRequestData *data = _data;
+
+ if (portal_response(params, NULL)) {
+ portal_fallback(data);
+ return;
+ }
+
+ screen_cast_call(data, "Start", data->u.screenshare.session_path,
+ "", NULL, started_cb);
+}
+
+static void sess_created_cb(GDBusConnection *dc, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *params, gpointer _data)
+{
+ PidginRequestData *data = _data;
+ GVariantBuilder *opts;
+
+ if (portal_response(params, NULL)) {
+ portal_fallback(data);
+ return;
+ }
+
+ opts = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(opts, "{sv}", "multiple", g_variant_new_boolean(FALSE));
+ g_variant_builder_add(opts, "{sv}", "types", g_variant_new_uint32(3));
+
+ screen_cast_call(data, "SelectSources",
+ data->u.screenshare.session_path, NULL,
+ opts, source_selected_cb);
+}
+
+static void portal_conn_cb(GObject *object, GAsyncResult *res, gpointer _data)
+{
+ PidginRequestData *data = _data;
+ GVariantBuilder *opts;
+
+ data->u.screenshare.dbus_connection = g_bus_get_finish(res, NULL);
+ if (!data->u.screenshare.dbus_connection) {
+ portal_fallback(data);
+ return;
+ }
+
+ opts = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ data->u.screenshare.session_path = portal_session_path(data->u.screenshare.dbus_connection, opts);
+
+ screen_cast_call(data, "CreateSession", NULL, NULL,
+ opts, sess_created_cb);
+}
+
+static gboolean request_xdp_portal_screenshare(PidginRequestData *data)
+{
+ if (portal_failed)
+ return FALSE;
+
+ data->u.screenshare.cancellable = g_cancellable_new();
+
+ g_bus_get(G_BUS_TYPE_SESSION, NULL, portal_conn_cb, data);
+ return TRUE;
+}
+
+#endif
+
+static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id,
+ const gchar *participant);
+
+#ifdef HAVE_X11
+static gboolean
+grab_event (GtkWidget *child, GdkEvent *event, PidginRequestData *data)
+{
+ GdkScreen *screen = gdk_screen_get_default();
+ GObject *info;
+ GdkWindow *gdkroot = gdk_get_default_root_window();
+ Window xroot = GDK_WINDOW_XID(gdkroot), xwindow, parent, *children;
+ unsigned int nchildren, xmask;
+ Display *xdisplay = GDK_SCREEN_XDISPLAY(screen);
+ int rootx, rooty, winx, winy;
+
+ if (event->type != GDK_BUTTON_PRESS)
+ return FALSE;
+
+ XQueryPointer(xdisplay, xroot, &xroot, &xwindow, &rootx, &rooty, &winx, &winy, &xmask);
+
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+
+ /* Find WM window (direct child of root) */
+ while (1) {
+ if (!XQueryTree(xdisplay, xwindow, &xroot, &parent, &children, &nchildren))
+ break;
+
+ if (nchildren)
+ XFree(children);
+
+ if (xroot == parent)
+ break;
+
+ xwindow = parent;
+ }
+
+ generic_response_start(data);
+
+ if (data->cbs[0] != NULL) {
+ info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
+ "id", "screenshare-window",
+ "name", "Screen share single window",
+ "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
+ PURPLE_MEDIA_ELEMENT_ONE_SRC,
+ "create-cb", create_screensrc_cb, NULL);
+ g_object_set_data(info, "window-id", GUINT_TO_POINTER(xwindow));
+ ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
+ }
+
+ purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
+
+ return FALSE;
+}
+
+static void
+screenshare_window_cb(GtkWidget *button, PidginRequestData *data)
+{
+ GdkCursor *cursor;
+ GdkWindow *gdkwin = gtk_widget_get_window(GTK_WIDGET(data->dialog));
+
+ if (!GTK_WIDGET_HAS_FOCUS(button))
+ gtk_widget_grab_focus(button);
+
+ gtk_widget_add_events(GTK_WIDGET(data->dialog),
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ g_signal_connect(data->dialog, "event", G_CALLBACK(grab_event), data);
+
+ cursor = gdk_cursor_new(GDK_CROSSHAIR);
+ gdk_pointer_grab(gdkwin, FALSE,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK,
+ NULL, cursor, GDK_CURRENT_TIME);
+}
+
+static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id,
+ const gchar *participant)
+{
+ GObject *info;
+ GstElement *ret;
+
+ ret = gst_element_factory_make("ximagesrc", NULL);
+ if (ret == NULL)
+ return NULL;
+
+ g_object_set(ret, "use-damage", 0, NULL);
+
+ info = g_object_get_data(G_OBJECT(media), "src-element");
+ if (info) {
+ Window xid = GPOINTER_TO_UINT(g_object_get_data(info, "window-id"));
+ int monitor_no = GPOINTER_TO_INT(g_object_get_data(info, "monitor-no"));
+ if (xid) {
+ g_object_set(ret, "xid", xid, NULL);
+ } else if (monitor_no >= 0) {
+ GdkScreen *screen = gdk_screen_get_default();
+ GdkRectangle geom;
+
+ gdk_screen_get_monitor_geometry(screen, monitor_no, &geom);
+ g_object_set(ret, "startx", geom.x, "starty", geom.y,
+ "endx", geom.x + geom.width - 1,
+ "endy", geom.y + geom.height - 1, NULL);
+ }
+ }
+
+ return ret;
+}
+#elif defined (_WIN32)
+static GstElement *create_screensrc_cb(PPurpleMedia *media, const gchar *session_id,
+ const gchar *participant)
+{
+ GObject *info;
+ GstElement *ret;
+
+ ret = gst_element_factory_make("gdiscreencapsrc", NULL);
+ if (ret == NULL)
+ return NULL;
+
+ g_object_set(ret, "cursor", TRUE);
+
+ info = g_object_get_data(G_OBJECT(media), "src-element");
+ if (info) {
+ int monitor_no = GPOINTER_TO_INT(g_object_get_data(info, "monitor-no"));
+ if (monitor_no >= 0)
+ g_object_set(ret, "monitor", monitor_no);
+ }
+
+ return ret;
+}
+#else
+/* We don't actually need to break the build just because we can't do
+ * screencap, but gtkmedia.c is going to break the USE_VV build if it
+ * isn't WIN32 or X11 anyway, so we might as well. */
+#error "Unsupported windowing system"
+#endif
+
+static void
+screenshare_monitor_cb(GtkWidget *button, PidginRequestData *data)
+{
+ GtkWidget *radio;
+ GObject *info;
+ int monitor_no = -1;
+
+ generic_response_start(data);
+
+ if (!GTK_WIDGET_HAS_FOCUS(button))
+ gtk_widget_grab_focus(button);
+
+ radio = g_object_get_data(G_OBJECT(data->dialog), "radio");
+ if (radio) {
+ GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
+
+ while (group) {
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) {
+ monitor_no = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(group->data),
+ "monitor-no"));
+ break;
+ }
+ group = group->next;
+ }
+ }
+ if (data->cbs[0] != NULL) {
+ info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
+ "id", "screenshare-monitor",
+ "name", "Screen share monitor",
+ "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
+ PURPLE_MEDIA_ELEMENT_ONE_SRC,
+ "create-cb", create_screensrc_cb, NULL);
+ g_object_set_data(info, "monitor-no", GINT_TO_POINTER(monitor_no));
+ ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
+ }
+
+ purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
+}
+
+static GstElement *create_videotest_cb(PurpleMedia *media, const gchar *session_id,
+ const gchar *participant)
+{
+ return gst_element_factory_make("videotestsrc", NULL);
+}
+
+static void
+screenshare_videotest_cb(GtkWidget *button, PidginRequestData *data)
+{
+ GObject *info;
+
+ generic_response_start(data);
+
+ if (!GTK_WIDGET_HAS_FOCUS(button))
+ gtk_widget_grab_focus(button);
+
+ if (data->cbs[0] != NULL) {
+ info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
+ "id", "screenshare-videotestsrc",
+ "name", "Screen share test source",
+ "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
+ PURPLE_MEDIA_ELEMENT_ONE_SRC,
+ "create-cb", create_videotest_cb, NULL);
+ ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
+ }
+
+ purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
+}
+
+static void
+screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data)
+{
+ if (data->dialog)
+ generic_response_start(data);
+
+ if (data->cbs[0] != NULL)
+ ((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, NULL);
+
+ purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
+}
+
+static gboolean
+destroy_screenshare_cb(GtkWidget *dialog, GdkEvent *event,
+ PidginRequestData *data)
+{
+ screenshare_cancel_cb(NULL, data);
+ return FALSE;
+}
+
+static void *pidgin_request_screenshare_media(const char *title, const char *primary,
+ const char *secondary, PurpleAccount *account,
+ GCallback cb, void *user_data)
+{
+ PidginRequestData *data;
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *radio = NULL;
+ GdkScreen *screen;
+ char *label_text;
+ char *primary_esc, *secondary_esc;
+
+ data = g_new0(PidginRequestData, 1);
+ data->type = PURPLE_REQUEST_SCREENSHARE;
+ data->user_data = user_data;
+
+ data->cb_count = 1;
+ data->cbs = g_new0(GCallback, 1);
+ data->cbs[0] = cb;
+
+#ifdef HAVE_GIOUNIX
+ if (request_xdp_portal_screenshare(data))
+ return data;
+#endif
+
+ /* Create the dialog. */
+ data->dialog = dialog = gtk_dialog_new();
+
+ if (title != NULL)
+ gtk_window_set_title(GTK_WINDOW(dialog), title);
+#ifdef _WIN32
+ else
+ gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
+#endif
+
+ button = pidgin_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
+ G_CALLBACK(screenshare_cancel_cb), data);
+ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+
+ if (g_getenv("PIDGIN_SHARE_VIDEOTEST") != NULL) {
+ button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Test image"),
+ G_CALLBACK(screenshare_videotest_cb), data);
+ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(dialog), button);
+ }
+
+#ifdef HAVE_X11
+ button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Select window"),
+ G_CALLBACK(screenshare_window_cb), data);
+ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(dialog), button);
+#endif
+
+ button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Use monitor"),
+ G_CALLBACK(screenshare_monitor_cb), data);
+ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(dialog), button);
+
+ g_signal_connect(G_OBJECT(dialog), "delete_event",
+ G_CALLBACK(destroy_screenshare_cb), data);
+
+ /* Setup the dialog */
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
+ gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
+ gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+ gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
+ gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
+
+ /* Setup the main horizontal box */
+ hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
+
+
+ /* Vertical box */
+ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+
+ pidgin_widget_decorate_account(hbox, account);
+
+ /* Descriptive label */
+ primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
+ secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
+ label_text = g_strdup_printf((primary ? ""
+ "%s%s%s" : "%s%s%s"),
+ (primary ? primary_esc : ""),
+ ((primary && secondary) ? "\n\n" : ""),
+ (secondary ? secondary_esc : ""));
+ g_free(primary_esc);
+ g_free(secondary_esc);
+
+ label = gtk_label_new(NULL);
+
+ gtk_label_set_markup(GTK_LABEL(label), label_text);
+ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_label_set_selectable(GTK_LABEL(label), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
+
+ g_free(label_text);
+
+ screen = gdk_screen_get_default();
+ if (screen) {
+ int nr_monitors = gdk_screen_get_n_monitors(screen);
+ int primary = gdk_screen_get_primary_monitor(screen);
+ int i;
+
+ for (i = 0; i < nr_monitors; i++) {
+ GdkRectangle geom;
+ gchar *name;
+ gchar *label;
+
+ name = gdk_screen_get_monitor_plug_name(screen, i);
+ gdk_screen_get_monitor_geometry(screen, i, &geom);
+
+ label = g_strdup_printf(_("%s (%d✕%d @ %d,%d)"),
+ name ? name : _("Unknown output"),
+ geom.width, geom.height,
+ geom.x, geom.y);
+ radio = gtk_radio_button_new_with_label_from_widget((GtkRadioButton *)radio, label);
+ g_object_set_data(G_OBJECT(radio), "monitor-no", GINT_TO_POINTER(i));
+ gtk_box_pack_start(GTK_BOX(vbox), radio, FALSE, FALSE, 0);
+ if (i == primary)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
+
+ g_free(label);
+ g_free(name);
+ }
+ g_object_set_data(G_OBJECT(dialog), "radio", radio);
+ }
+
+ /* Show everything. */
+ pidgin_auto_parent_window(dialog);
+
+ gtk_widget_show_all(dialog);
+
+ return data;
+
+}
+#endif /* USE_VV */
+
static void
pidgin_close_request(PurpleRequestType type, void *ui_handle)
{
@@ -1710,12 +2413,22 @@ pidgin_close_request(PurpleRequestType type, void *ui_handle)
g_free(data->cbs);
- gtk_widget_destroy(data->dialog);
+ if (data->dialog)
+ gtk_widget_destroy(data->dialog);
if (type == PURPLE_REQUEST_FIELDS)
purple_request_fields_destroy(data->u.multifield.fields);
else if (type == PURPLE_REQUEST_FILE)
g_free(data->u.file.name);
+ else if (type == PURPLE_REQUEST_SCREENSHARE) {
+ g_cancellable_cancel(data->u.screenshare.cancellable);
+ if (data->u.screenshare.signal_id)
+ g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
+ data->u.screenshare.signal_id);
+ g_clear_object(&data->u.screenshare.dbus_connection);
+ g_free(data->u.screenshare.session_path);
+ g_clear_object(&data->u.screenshare.cancellable);
+ }
g_free(data);
}
@@ -1730,7 +2443,11 @@ static PurpleRequestUiOps ops =
pidgin_close_request,
pidgin_request_folder,
pidgin_request_action_with_icon,
+#ifdef USE_VV
+ pidgin_request_screenshare_media,
+#else
NULL,
+#endif
NULL,
NULL
};