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