Re: [RFC] Screen share window/monitor selection
David Woodhouse <[email protected]> Mon, 23 Apr 2018 16:20:46 +0100
| Newsgroups | gmane.comp.gnome.gaim.devel |
|---|---|
| Message-ID | <[email protected]> |
On Fri, 2018-04-20 at 16:34 +0100, David Woodhouse wrote: > This adds a method to PurpleRequestUiOps to request a > PurpleMediaElementInfo as a source for screen sharing. > > The UI interacts with the user to select the window/monitor to be > shared, and the UI is responsible for providing the appropriate > GStreamer element. > > The Pidgin side needs finishing ā currently I'm using videotestsrc and > a nasty hack to test with ximagesrc, but actually I think I'm going to > end up with an appsrc which is fed with frames by > gdk_pixbuf_get_from_window() or something like that. Done properly, > that will work on Windows too. > > For now I'm soliciting feedback on the basic approach and the libpurple > side of things. > > This works on top of my patch in PR#322 which allows the PRPL to > specify a PurpleMediaElementInfo to use instead of the default for the > media type, by using g_object_set_data(media, "src-element", info) > before adding streams. Now it actually works on the UI side too. I can select a monitor or a specific window to be shared. There is some X-specific magic here to identify a window by its xid; Windows support is blocked on the fact that there isn't a GStreamer source element equivalent to ximagesrc. You *can* share a videotestsrc from Windows though ;) Once PR#322 is merged, I'll make a separate PR for this as it wants its own review. _______________________________________________ Devel mailing list [email protected] https://pidgin.im/cgi-bin/mailman/listinfo/devel
pidgin-screenshare.patch
(text/x-patch, 14.5 KB)
diff --git a/libpurple/request.c b/libpurple/request.c
index d61d241..da2b5ca 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 8035ef8..282c909 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 <em>extremely</em> 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/gtkrequest.c b/pidgin/gtkrequest.c
index 397e09a..840a295 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 <gdk/gdkkeysyms.h>
@@ -77,6 +80,11 @@ typedef struct
} file;
+ struct
+ {
+ gint x, y;
+ } screen;
+
} u;
} PidginRequestData;
@@ -1703,6 +1711,332 @@ pidgin_request_folder(const char *title, const char *dirname,
return (void *)data;
}
+#ifdef USE_VV
+#ifdef HAVE_X11
+static GstElement *create_ximagesrc_cb(PurpleMedia *media, const gchar *session_id,
+ const gchar *participant)
+{
+ GObject *info;
+ GstElement *ret;
+
+ XInitThreads(); /* Don't even ask */
+ ret = gst_element_factory_make("ximagesrc", 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;
+}
+
+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_ximagesrc_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 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_ximagesrc_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);
+ gdk_window_get_origin(gdkwin, &data->u.screen.x, &data->u.screen.y);
+}
+#endif /* HAVE_X11 */
+
+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)
+{
+ 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;
+
+ /* 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);
+
+ 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);
+
+ 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);
+#endif
+
+ 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 ? "<span weight=\"bold\" size=\"larger\">"
+ "%s</span>%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)
{
@@ -1730,7 +2064,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
};
smime.p7s
(application/x-pkcs7-signature, 5.1 KB) - not displayed