1 /**
2  * @file gtkrequest.c GTK+ Request API
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
8  * Pidgin is the legal property of its developers, whose names are too numerous
9  * to list here.  Please refer to the COPYRIGHT file distributed with this
10  * source distribution.
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
25  */
26 #include "internal.h"
27 #include "pidgin.h"
28 
29 #include "debug.h"
30 #include "prefs.h"
31 #include "util.h"
32 
33 #include "gtkimhtml.h"
34 #include "gtkimhtmltoolbar.h"
35 #include "gtkrequest.h"
36 #include "gtkutils.h"
37 #include "pidginstock.h"
38 #include "gtkblist.h"
39 #ifdef USE_VV
40 #include "media-gst.h"
41 #ifdef HAVE_GIOUNIX
42 #include <gio/gunixfdlist.h>
43 #endif
44 #endif
45 
46 #include <gdk/gdkkeysyms.h>
47 
48 static GtkWidget * create_account_field(PurpleRequestField *field);
49 
50 typedef struct
51 {
52 	PurpleRequestType type;
53 
54 	void *user_data;
55 	GtkWidget *dialog;
56 
57 	GtkWidget *ok_button;
58 
59 	size_t cb_count;
60 	GCallback *cbs;
61 
62 	union
63 	{
64 		struct
65 		{
66 			GtkWidget *entry;
67 
68 			gboolean multiline;
69 			gchar *hint;
70 
71 		} input;
72 
73 		struct
74 		{
75 			PurpleRequestFields *fields;
76 
77 		} multifield;
78 
79 		struct
80 		{
81 			gboolean savedialog;
82 			gchar *name;
83 
84 		} file;
85 
86 		struct
87 		{
88 #ifdef HAVE_GIOUNIX
89 			GDBusConnection *dbus_connection;
90 #endif
91 			GCancellable *cancellable;
92 			gchar *session_path;
93 			guint signal_id;
94 			guint32 node_id;
95 			guint portal_session_nr;
96 			guint portal_request_nr;
97 
98 		} screenshare;
99 
100 	} u;
101 
102 } PidginRequestData;
103 
104 static void
pidgin_widget_decorate_account(GtkWidget * cont,PurpleAccount * account)105 pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
106 {
107 	GtkWidget *image;
108 	GdkPixbuf *pixbuf;
109 	GtkTooltips *tips;
110 
111 	if (!account)
112 		return;
113 
114 	pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
115 	image = gtk_image_new_from_pixbuf(pixbuf);
116 	g_object_unref(G_OBJECT(pixbuf));
117 
118 	tips = gtk_tooltips_new();
119 	gtk_tooltips_set_tip(tips, image, purple_account_get_username(account), NULL);
120 
121 	if (GTK_IS_DIALOG(cont)) {
122 		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(cont)->action_area), image, FALSE, TRUE, 0);
123 		gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(cont)->action_area), image, 0);
124 	} else if (GTK_IS_HBOX(cont)) {
125 		gtk_misc_set_alignment(GTK_MISC(image), 0, 0);
126 		gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0);
127 	}
128 	gtk_widget_show(image);
129 }
130 
131 static void
generic_response_start(PidginRequestData * data)132 generic_response_start(PidginRequestData *data)
133 {
134 	g_return_if_fail(data != NULL);
135 
136 	/* Tell the user we're doing something. */
137 	pidgin_set_cursor(GTK_WIDGET(data->dialog), GDK_WATCH);
138 }
139 
140 static void
input_response_cb(GtkDialog * dialog,gint id,PidginRequestData * data)141 input_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
142 {
143 	const char *value;
144 	char *multiline_value = NULL;
145 
146 	generic_response_start(data);
147 
148 	if (data->u.input.multiline) {
149 		GtkTextIter start_iter, end_iter;
150 		GtkTextBuffer *buffer =
151 			gtk_text_view_get_buffer(GTK_TEXT_VIEW(data->u.input.entry));
152 
153 		gtk_text_buffer_get_start_iter(buffer, &start_iter);
154 		gtk_text_buffer_get_end_iter(buffer, &end_iter);
155 
156 		if (purple_strequal(data->u.input.hint, "html"))
157 			multiline_value = gtk_imhtml_get_markup(GTK_IMHTML(data->u.input.entry));
158 		else
159 			multiline_value = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
160 										 FALSE);
161 
162 		value = multiline_value;
163 	}
164 	else
165 		value = gtk_entry_get_text(GTK_ENTRY(data->u.input.entry));
166 
167 	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
168 		((PurpleRequestInputCb)data->cbs[id])(data->user_data, value);
169 	else if (data->cbs[1] != NULL)
170 		((PurpleRequestInputCb)data->cbs[1])(data->user_data, value);
171 
172 	if (data->u.input.multiline)
173 		g_free(multiline_value);
174 
175 	purple_request_close(PURPLE_REQUEST_INPUT, data);
176 }
177 
178 static void
action_response_cb(GtkDialog * dialog,gint id,PidginRequestData * data)179 action_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
180 {
181 	generic_response_start(data);
182 
183 	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
184 		((PurpleRequestActionCb)data->cbs[id])(data->user_data, id);
185 
186 	purple_request_close(PURPLE_REQUEST_INPUT, data);
187 }
188 
189 
190 static void
choice_response_cb(GtkDialog * dialog,gint id,PidginRequestData * data)191 choice_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
192 {
193 	GtkWidget *radio = g_object_get_data(G_OBJECT(dialog), "radio");
194 	GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
195 
196 	generic_response_start(data);
197 
198 	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
199 		while (group) {
200 			if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) {
201 				((PurpleRequestChoiceCb)data->cbs[id])(data->user_data, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(group->data), "choice_id")));
202 				break;
203 			}
204 			group = group->next;
205 		}
206 	purple_request_close(PURPLE_REQUEST_INPUT, data);
207 }
208 
209 static gboolean
field_string_focus_out_cb(GtkWidget * entry,GdkEventFocus * event,PurpleRequestField * field)210 field_string_focus_out_cb(GtkWidget *entry, GdkEventFocus *event,
211 						  PurpleRequestField *field)
212 {
213 	const char *value;
214 
215 	if (purple_request_field_string_is_multiline(field))
216 	{
217 		GtkTextBuffer *buffer;
218 		GtkTextIter start_iter, end_iter;
219 
220 		buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
221 
222 		gtk_text_buffer_get_start_iter(buffer, &start_iter);
223 		gtk_text_buffer_get_end_iter(buffer, &end_iter);
224 
225 		value = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
226 	}
227 	else
228 		value = gtk_entry_get_text(GTK_ENTRY(entry));
229 
230 	purple_request_field_string_set_value(field,
231 			(*value == '\0' ? NULL : value));
232 
233 	return FALSE;
234 }
235 
236 static gboolean
field_int_focus_out_cb(GtkEntry * entry,GdkEventFocus * event,PurpleRequestField * field)237 field_int_focus_out_cb(GtkEntry *entry, GdkEventFocus *event,
238 					   PurpleRequestField *field)
239 {
240 	purple_request_field_int_set_value(field,
241 			atoi(gtk_entry_get_text(entry)));
242 
243 	return FALSE;
244 }
245 
246 static void
field_bool_cb(GtkToggleButton * button,PurpleRequestField * field)247 field_bool_cb(GtkToggleButton *button, PurpleRequestField *field)
248 {
249 	purple_request_field_bool_set_value(field,
250 			gtk_toggle_button_get_active(button));
251 }
252 
253 static void
field_choice_menu_cb(GtkComboBox * menu,PurpleRequestField * field)254 field_choice_menu_cb(GtkComboBox *menu, PurpleRequestField *field)
255 {
256 	purple_request_field_choice_set_value(field,
257 			gtk_combo_box_get_active(menu));
258 }
259 
260 static void
field_choice_option_cb(GtkRadioButton * button,PurpleRequestField * field)261 field_choice_option_cb(GtkRadioButton *button, PurpleRequestField *field)
262 {
263 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
264 		purple_request_field_choice_set_value(field,
265 				(g_slist_length(gtk_radio_button_get_group(button)) -
266 				 g_slist_index(gtk_radio_button_get_group(button), button)) - 1);
267 }
268 
269 static void
field_account_cb(GObject * w,PurpleAccount * account,PurpleRequestField * field)270 field_account_cb(GObject *w, PurpleAccount *account, PurpleRequestField *field)
271 {
272 	purple_request_field_account_set_value(field, account);
273 }
274 
275 static void
multifield_ok_cb(GtkWidget * button,PidginRequestData * data)276 multifield_ok_cb(GtkWidget *button, PidginRequestData *data)
277 {
278 	generic_response_start(data);
279 
280 	if (!GTK_WIDGET_HAS_FOCUS(button))
281 		gtk_widget_grab_focus(button);
282 
283 	if (data->cbs[0] != NULL)
284 		((PurpleRequestFieldsCb)data->cbs[0])(data->user_data,
285 											data->u.multifield.fields);
286 
287 	purple_request_close(PURPLE_REQUEST_FIELDS, data);
288 }
289 
290 static void
multifield_cancel_cb(GtkWidget * button,PidginRequestData * data)291 multifield_cancel_cb(GtkWidget *button, PidginRequestData *data)
292 {
293 	generic_response_start(data);
294 
295 	if (data->cbs[1] != NULL)
296 		((PurpleRequestFieldsCb)data->cbs[1])(data->user_data,
297 											data->u.multifield.fields);
298 
299 	purple_request_close(PURPLE_REQUEST_FIELDS, data);
300 }
301 
302 static gboolean
destroy_multifield_cb(GtkWidget * dialog,GdkEvent * event,PidginRequestData * data)303 destroy_multifield_cb(GtkWidget *dialog, GdkEvent *event,
304 					  PidginRequestData *data)
305 {
306 	multifield_cancel_cb(NULL, data);
307 	return FALSE;
308 }
309 
310 
311 #define STOCK_ITEMIZE(r, l) \
312 	if (purple_strequal((r), text)) \
313 		return (l);
314 
315 static const char *
text_to_stock(const char * text)316 text_to_stock(const char *text)
317 {
318 	STOCK_ITEMIZE(_("Yes"),     GTK_STOCK_YES);
319 	STOCK_ITEMIZE(_("No"),      GTK_STOCK_NO);
320 	STOCK_ITEMIZE(_("OK"),      GTK_STOCK_OK);
321 	STOCK_ITEMIZE(_("Cancel"),  GTK_STOCK_CANCEL);
322 	STOCK_ITEMIZE(_("Apply"),   GTK_STOCK_APPLY);
323 	STOCK_ITEMIZE(_("Close"),   GTK_STOCK_CLOSE);
324 	STOCK_ITEMIZE(_("Delete"),  GTK_STOCK_DELETE);
325 	STOCK_ITEMIZE(_("Add"),     GTK_STOCK_ADD);
326 	STOCK_ITEMIZE(_("Remove"),  GTK_STOCK_REMOVE);
327 	STOCK_ITEMIZE(_("Save"),    GTK_STOCK_SAVE);
328 	STOCK_ITEMIZE(_("Alias"),   PIDGIN_STOCK_ALIAS);
329 
330 	return text;
331 }
332 
333 static void *
pidgin_request_input(const char * title,const char * primary,const char * secondary,const char * default_value,gboolean multiline,gboolean masked,gchar * hint,const char * ok_text,GCallback ok_cb,const char * cancel_text,GCallback cancel_cb,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data)334 pidgin_request_input(const char *title, const char *primary,
335 					   const char *secondary, const char *default_value,
336 					   gboolean multiline, gboolean masked, gchar *hint,
337 					   const char *ok_text, GCallback ok_cb,
338 					   const char *cancel_text, GCallback cancel_cb,
339 					   PurpleAccount *account, const char *who, PurpleConversation *conv,
340 					   void *user_data)
341 {
342 	PidginRequestData *data;
343 	GtkWidget *dialog;
344 	GtkWidget *vbox;
345 	GtkWidget *hbox;
346 	GtkWidget *label;
347 	GtkWidget *entry;
348 	GtkWidget *img;
349 	GtkWidget *toolbar;
350 	char *label_text;
351 	char *primary_esc, *secondary_esc;
352 
353 	data            = g_new0(PidginRequestData, 1);
354 	data->type      = PURPLE_REQUEST_INPUT;
355 	data->user_data = user_data;
356 
357 	data->cb_count = 2;
358 	data->cbs = g_new0(GCallback, 2);
359 
360 	data->cbs[0] = ok_cb;
361 	data->cbs[1] = cancel_cb;
362 
363 	/* Create the dialog. */
364 	dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
365 					     NULL, 0,
366 					     text_to_stock(cancel_text), 1,
367 					     text_to_stock(ok_text),     0,
368 					     NULL);
369 	data->dialog = dialog;
370 
371 	g_signal_connect(G_OBJECT(dialog), "response",
372 					 G_CALLBACK(input_response_cb), data);
373 
374 	/* Setup the dialog */
375 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
376 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
377 	if (!multiline)
378 		gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
379 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
380 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0);
381 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
382 
383 	/* Setup the main horizontal box */
384 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
385 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
386 
387 	/* Dialog icon. */
388 	img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
389 					gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
390 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
391 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
392 
393 	/* Vertical box */
394 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
395 
396 	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
397 
398 	pidgin_widget_decorate_account(hbox, account);
399 
400 	/* Descriptive label */
401 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
402 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
403 	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
404 								 "%s</span>%s%s" : "%s%s%s"),
405 								 (primary ? primary_esc : ""),
406 								 ((primary && secondary) ? "\n\n" : ""),
407 								 (secondary ? secondary_esc : ""));
408 	g_free(primary_esc);
409 	g_free(secondary_esc);
410 
411 	label = gtk_label_new(NULL);
412 
413 	gtk_label_set_markup(GTK_LABEL(label), label_text);
414 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
415 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
416 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
417 
418 	g_free(label_text);
419 
420 	/* Entry field. */
421 	data->u.input.multiline = multiline;
422 	data->u.input.hint = g_strdup(hint);
423 
424 	gtk_widget_show_all(hbox);
425 
426 	if (purple_strequal(data->u.input.hint, "html")) {
427 		GtkWidget *frame;
428 
429 		/* imhtml */
430 		frame = pidgin_create_imhtml(TRUE, &entry, &toolbar, NULL);
431 		gtk_widget_set_size_request(entry, 320, 130);
432 		gtk_widget_set_name(entry, "pidgin_request_imhtml");
433 		if (default_value != NULL)
434 			gtk_imhtml_append_text(GTK_IMHTML(entry), default_value, GTK_IMHTML_NO_SCROLL);
435 		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
436 		gtk_widget_show(frame);
437 
438 		gtk_imhtml_set_return_inserts_newline(GTK_IMHTML(entry));
439 	}
440 	else {
441 		if (multiline) {
442 			/* GtkTextView */
443 			entry = gtk_text_view_new();
444 			gtk_text_view_set_editable(GTK_TEXT_VIEW(entry), TRUE);
445 
446 			if (default_value != NULL) {
447 				GtkTextBuffer *buffer;
448 
449 				buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
450 				gtk_text_buffer_set_text(buffer, default_value, -1);
451 			}
452 
453 			gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(entry), GTK_WRAP_WORD_CHAR);
454 
455 			if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
456 				pidgin_setup_gtkspell(GTK_TEXT_VIEW(entry));
457 
458 			gtk_box_pack_start(GTK_BOX(vbox),
459 				pidgin_make_scrollable(entry, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, 320, 130),
460 				TRUE, TRUE, 0);
461 		}
462 		else {
463 			entry = gtk_entry_new();
464 
465 			gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
466 
467 			gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
468 
469 			if (default_value != NULL)
470 				gtk_entry_set_text(GTK_ENTRY(entry), default_value);
471 
472 			if (masked)
473 			{
474 				gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
475 #if !GTK_CHECK_VERSION(2,16,0)
476 				if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
477 					gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
478 #endif /* Less than GTK+ 2.16 */
479 			}
480 		}
481 		gtk_widget_show_all(vbox);
482 	}
483 
484 	pidgin_set_accessible_label (entry, label);
485 	data->u.input.entry = entry;
486 
487 	pidgin_auto_parent_window(dialog);
488 
489 	/* Show everything. */
490 	gtk_widget_show(dialog);
491 
492 	return data;
493 }
494 
495 static void *
pidgin_request_choice(const char * title,const char * primary,const char * secondary,int default_value,const char * ok_text,GCallback ok_cb,const char * cancel_text,GCallback cancel_cb,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data,va_list args)496 pidgin_request_choice(const char *title, const char *primary,
497 			const char *secondary, int default_value,
498 			const char *ok_text, GCallback ok_cb,
499 			const char *cancel_text, GCallback cancel_cb,
500 			PurpleAccount *account, const char *who, PurpleConversation *conv,
501 			void *user_data, va_list args)
502 {
503 	PidginRequestData *data;
504 	GtkWidget *dialog;
505 	GtkWidget *vbox, *vbox2;
506 	GtkWidget *hbox;
507 	GtkWidget *label;
508 	GtkWidget *img;
509 	GtkWidget *radio = NULL;
510 	char *label_text;
511 	char *radio_text;
512 	char *primary_esc, *secondary_esc;
513 
514 	data            = g_new0(PidginRequestData, 1);
515 	data->type      = PURPLE_REQUEST_ACTION;
516 	data->user_data = user_data;
517 
518 	data->cb_count = 2;
519 	data->cbs = g_new0(GCallback, 2);
520 	data->cbs[0] = cancel_cb;
521 	data->cbs[1] = ok_cb;
522 
523 	/* Create the dialog. */
524 	data->dialog = dialog = gtk_dialog_new();
525 
526 	if (title != NULL)
527 		gtk_window_set_title(GTK_WINDOW(dialog), title);
528 #ifdef _WIN32
529 		gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
530 #endif
531 
532 	gtk_dialog_add_button(GTK_DIALOG(dialog),
533 			      text_to_stock(cancel_text), 0);
534 
535 	gtk_dialog_add_button(GTK_DIALOG(dialog),
536 			      text_to_stock(ok_text), 1);
537 
538 	g_signal_connect(G_OBJECT(dialog), "response",
539 			 G_CALLBACK(choice_response_cb), data);
540 
541 	/* Setup the dialog */
542 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
543 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
544 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
545 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
546 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
547 
548 	/* Setup the main horizontal box */
549 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
550 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
551 
552 	/* Dialog icon. */
553 	img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
554 				       gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
555 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
556 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
557 
558 	pidgin_widget_decorate_account(hbox, account);
559 
560 	/* Vertical box */
561 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
562 	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
563 
564 	/* Descriptive label */
565 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
566 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
567 	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
568 				      "%s</span>%s%s" : "%s%s%s"),
569 				     (primary ? primary_esc : ""),
570 				     ((primary && secondary) ? "\n\n" : ""),
571 				     (secondary ? secondary_esc : ""));
572 	g_free(primary_esc);
573 	g_free(secondary_esc);
574 
575 	label = gtk_label_new(NULL);
576 
577 	gtk_label_set_markup(GTK_LABEL(label), label_text);
578 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
579 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
580 	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
581 
582 	g_free(label_text);
583 
584 	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
585 	gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);
586 	while ((radio_text = va_arg(args, char*))) {
587 		       int resp = va_arg(args, int);
588 		       radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), radio_text);
589 		       gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0);
590 		       g_object_set_data(G_OBJECT(radio), "choice_id", GINT_TO_POINTER(resp));
591 		       if (resp == default_value)
592 			       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
593 	}
594 
595 	g_object_set_data(G_OBJECT(dialog), "radio", radio);
596 
597 	/* Show everything. */
598 	pidgin_auto_parent_window(dialog);
599 
600 	gtk_widget_show_all(dialog);
601 
602 	return data;
603 }
604 
605 static void *
pidgin_request_action_with_icon(const char * title,const char * primary,const char * secondary,int default_action,PurpleAccount * account,const char * who,PurpleConversation * conv,gconstpointer icon_data,gsize icon_size,void * user_data,size_t action_count,va_list actions)606 pidgin_request_action_with_icon(const char *title, const char *primary,
607 						const char *secondary, int default_action,
608 					    PurpleAccount *account, const char *who,
609 						PurpleConversation *conv, gconstpointer icon_data,
610 						gsize icon_size,
611 						void *user_data, size_t action_count, va_list actions)
612 {
613 	PidginRequestData *data;
614 	GtkWidget *dialog;
615 	GtkWidget *vbox;
616 	GtkWidget *hbox;
617 	GtkWidget *label;
618 	GtkWidget *img = NULL;
619 	void **buttons;
620 	char *label_text;
621 	char *primary_esc, *secondary_esc;
622 	gsize i;
623 
624 	data            = g_new0(PidginRequestData, 1);
625 	data->type      = PURPLE_REQUEST_ACTION;
626 	data->user_data = user_data;
627 
628 	data->cb_count = action_count;
629 	data->cbs = g_new0(GCallback, action_count);
630 
631 	/* Reverse the buttons */
632 	buttons = g_new0(void *, action_count * 2);
633 
634 	for (i = 0; i < action_count * 2; i += 2) {
635 		buttons[(action_count * 2) - i - 2] = va_arg(actions, char *);
636 		buttons[(action_count * 2) - i - 1] = va_arg(actions, GCallback);
637 	}
638 
639 	/* Create the dialog. */
640 	data->dialog = dialog = gtk_dialog_new();
641 
642 	gtk_window_set_deletable(GTK_WINDOW(data->dialog), FALSE);
643 
644 	if (title != NULL)
645 		gtk_window_set_title(GTK_WINDOW(dialog), title);
646 #ifdef _WIN32
647 	else
648 		gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
649 #endif
650 
651 	for (i = 0; i < action_count; i++) {
652 		gtk_dialog_add_button(GTK_DIALOG(dialog),
653 							  text_to_stock(buttons[2 * i]), i);
654 
655 		data->cbs[i] = buttons[2 * i + 1];
656 	}
657 
658 	g_free(buttons);
659 
660 	g_signal_connect(G_OBJECT(dialog), "response",
661 					 G_CALLBACK(action_response_cb), data);
662 
663 	/* Setup the dialog */
664 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
665 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
666 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
667 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
668 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
669 
670 	/* Setup the main horizontal box */
671 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
672 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
673 
674 	/* Dialog icon. */
675 	if (icon_data) {
676 		GdkPixbuf *pixbuf = pidgin_pixbuf_from_data(icon_data, icon_size);
677 		if (pixbuf) {
678 			/* scale the image if it is too large */
679 			int width = gdk_pixbuf_get_width(pixbuf);
680 			int height = gdk_pixbuf_get_height(pixbuf);
681 			if (width > 128 || height > 128) {
682 				int scaled_width = width > height ? 128 : (128 * width) / height;
683 				int scaled_height = height > width ? 128 : (128 * height) / width;
684 				GdkPixbuf *scaled =
685 						gdk_pixbuf_scale_simple(pixbuf, scaled_width, scaled_height,
686 						    GDK_INTERP_BILINEAR);
687 
688 				purple_debug_info("pidgin",
689 				    "dialog icon was too large, scaled it down\n");
690 				if (scaled) {
691 					g_object_unref(pixbuf);
692 					pixbuf = scaled;
693 				}
694 			}
695 			img = gtk_image_new_from_pixbuf(pixbuf);
696 			g_object_unref(pixbuf);
697 		} else {
698 			purple_debug_info("pidgin", "failed to parse dialog icon\n");
699 		}
700 	}
701 
702 	if (!img) {
703 		img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
704 				       gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
705 	}
706 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
707 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
708 
709 	/* Vertical box */
710 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
711 	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
712 
713 	pidgin_widget_decorate_account(hbox, account);
714 
715 	/* Descriptive label */
716 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
717 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
718 	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
719 								 "%s</span>%s%s" : "%s%s%s"),
720 								 (primary ? primary_esc : ""),
721 								 ((primary && secondary) ? "\n\n" : ""),
722 								 (secondary ? secondary_esc : ""));
723 	g_free(primary_esc);
724 	g_free(secondary_esc);
725 
726 	label = gtk_label_new(NULL);
727 
728 	gtk_label_set_markup(GTK_LABEL(label), label_text);
729 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
730 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
731 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
732 	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
733 
734 	g_free(label_text);
735 
736 
737 	if (default_action == PURPLE_DEFAULT_ACTION_NONE) {
738 		GTK_WIDGET_SET_FLAGS(img, GTK_CAN_DEFAULT);
739 		GTK_WIDGET_SET_FLAGS(img, GTK_CAN_FOCUS);
740 		gtk_widget_grab_focus(img);
741 		gtk_widget_grab_default(img);
742 	} else
743 		/*
744 		 * Need to invert the default_action number because the
745 		 * buttons are added to the dialog in reverse order.
746 		 */
747 		gtk_dialog_set_default_response(GTK_DIALOG(dialog), action_count - 1 - default_action);
748 
749 	/* Show everything. */
750 	pidgin_auto_parent_window(dialog);
751 
752 	gtk_widget_show_all(dialog);
753 
754 	return data;
755 }
756 
757 static void *
pidgin_request_action(const char * title,const char * primary,const char * secondary,int default_action,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data,size_t action_count,va_list actions)758 pidgin_request_action(const char *title, const char *primary,
759 						const char *secondary, int default_action,
760 					    PurpleAccount *account, const char *who, PurpleConversation *conv,
761 						void *user_data, size_t action_count, va_list actions)
762 {
763 	return pidgin_request_action_with_icon(title, primary, secondary,
764 		default_action, account, who, conv, NULL, 0, user_data, action_count,
765 		actions);
766 }
767 
768 static void
req_entry_field_changed_cb(GtkWidget * entry,PurpleRequestField * field)769 req_entry_field_changed_cb(GtkWidget *entry, PurpleRequestField *field)
770 {
771 	PurpleRequestFieldGroup *group;
772 	PidginRequestData *req_data;
773 
774 	if (purple_request_field_string_is_multiline(field))
775 	{
776 		char *text;
777 		GtkTextIter start_iter, end_iter;
778 
779 		gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(entry), &start_iter);
780 		gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(entry), &end_iter);
781 
782 		text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(entry), &start_iter, &end_iter, FALSE);
783 		purple_request_field_string_set_value(field, (!text || !*text) ? NULL : text);
784 		g_free(text);
785 	}
786 	else
787 	{
788 		const char *text = NULL;
789 		text = gtk_entry_get_text(GTK_ENTRY(entry));
790 		purple_request_field_string_set_value(field, (*text == '\0') ? NULL : text);
791 	}
792 
793 	group = purple_request_field_get_group(field);
794 	req_data = (PidginRequestData *)group->fields_list->ui_data;
795 
796 	gtk_widget_set_sensitive(req_data->ok_button,
797 		purple_request_fields_all_required_filled(group->fields_list));
798 }
799 
800 static void
setup_entry_field(GtkWidget * entry,PurpleRequestField * field)801 setup_entry_field(GtkWidget *entry, PurpleRequestField *field)
802 {
803 	const char *type_hint;
804 
805 	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
806 
807 	if (purple_request_field_is_required(field))
808 	{
809 		g_signal_connect(G_OBJECT(entry), "changed",
810 						 G_CALLBACK(req_entry_field_changed_cb), field);
811 	}
812 
813 	if ((type_hint = purple_request_field_get_type_hint(field)) != NULL)
814 	{
815 		if (purple_str_has_prefix(type_hint, "screenname"))
816 		{
817 			GtkWidget *optmenu = NULL;
818 			PurpleRequestFieldGroup *group = purple_request_field_get_group(field);
819 			GList *fields = group->fields;
820 
821 			/* Ensure the account option menu is created (if the widget hasn't
822 			 * been initialized already) for username auto-completion. */
823 			while (fields)
824 			{
825 				PurpleRequestField *fld = fields->data;
826 				fields = fields->next;
827 
828 				if (purple_request_field_get_type(fld) == PURPLE_REQUEST_FIELD_ACCOUNT &&
829 						purple_request_field_is_visible(fld))
830 				{
831 					const char *type_hint = purple_request_field_get_type_hint(fld);
832 					if (purple_strequal(type_hint, "account"))
833 					{
834 						optmenu = GTK_WIDGET(purple_request_field_get_ui_data(fld));
835 						if (optmenu == NULL) {
836 							optmenu = GTK_WIDGET(create_account_field(fld));
837 							purple_request_field_set_ui_data(fld, optmenu);
838 						}
839 						break;
840 					}
841 				}
842 			}
843 			pidgin_setup_screenname_autocomplete_with_filter(entry, optmenu, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(purple_strequal(type_hint, "screenname-all")));
844 		}
845 	}
846 }
847 
848 static GtkWidget *
create_string_field(PurpleRequestField * field)849 create_string_field(PurpleRequestField *field)
850 {
851 	const char *value;
852 	GtkWidget *widget;
853 
854 	value = purple_request_field_string_get_default_value(field);
855 
856 	if (purple_request_field_string_is_multiline(field))
857 	{
858 		GtkWidget *textview;
859 
860 		textview = gtk_text_view_new();
861 		gtk_text_view_set_editable(GTK_TEXT_VIEW(textview),
862 								   TRUE);
863 		gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview),
864 									GTK_WRAP_WORD_CHAR);
865 
866 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
867 			pidgin_setup_gtkspell(GTK_TEXT_VIEW(textview));
868 
869 		gtk_widget_show(textview);
870 
871 		if (value != NULL)
872 		{
873 			GtkTextBuffer *buffer;
874 
875 			buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
876 
877 			gtk_text_buffer_set_text(buffer, value, -1);
878 		}
879 
880 		gtk_text_view_set_editable(GTK_TEXT_VIEW(textview),
881 			purple_request_field_string_is_editable(field));
882 
883 		g_signal_connect(G_OBJECT(textview), "focus-out-event",
884 						 G_CALLBACK(field_string_focus_out_cb), field);
885 
886 	    if (purple_request_field_is_required(field))
887 	    {
888 			GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
889 			g_signal_connect(G_OBJECT(buffer), "changed",
890 							 G_CALLBACK(req_entry_field_changed_cb), field);
891 	    }
892 
893 		widget = pidgin_make_scrollable(textview, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, 75);
894 	}
895 	else
896 	{
897 		widget = gtk_entry_new();
898 
899 		setup_entry_field(widget, field);
900 
901 		if (value != NULL)
902 			gtk_entry_set_text(GTK_ENTRY(widget), value);
903 
904 		if (purple_request_field_string_is_masked(field))
905 		{
906 			gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
907 #if !GTK_CHECK_VERSION(2,16,0)
908 			if (gtk_entry_get_invisible_char(GTK_ENTRY(widget)) == '*')
909 				gtk_entry_set_invisible_char(GTK_ENTRY(widget),	PIDGIN_INVISIBLE_CHAR);
910 #endif /* Less than GTK+ 2.16 */
911 		}
912 
913 		gtk_editable_set_editable(GTK_EDITABLE(widget),
914 			purple_request_field_string_is_editable(field));
915 
916 		g_signal_connect(G_OBJECT(widget), "focus-out-event",
917 						 G_CALLBACK(field_string_focus_out_cb), field);
918 	}
919 
920 	return widget;
921 }
922 
923 static GtkWidget *
create_int_field(PurpleRequestField * field)924 create_int_field(PurpleRequestField *field)
925 {
926 	int value;
927 	GtkWidget *widget;
928 
929 	widget = gtk_entry_new();
930 
931 	setup_entry_field(widget, field);
932 
933 	value = purple_request_field_int_get_default_value(field);
934 
935 	if (value != 0)
936 	{
937 		char buf[32];
938 
939 		g_snprintf(buf, sizeof(buf), "%d", value);
940 
941 		gtk_entry_set_text(GTK_ENTRY(widget), buf);
942 	}
943 
944 	g_signal_connect(G_OBJECT(widget), "focus-out-event",
945 					 G_CALLBACK(field_int_focus_out_cb), field);
946 
947 	return widget;
948 }
949 
950 static GtkWidget *
create_bool_field(PurpleRequestField * field)951 create_bool_field(PurpleRequestField *field)
952 {
953 	GtkWidget *widget;
954 
955 	widget = gtk_check_button_new_with_label(
956 		purple_request_field_get_label(field));
957 
958 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
959 		purple_request_field_bool_get_default_value(field));
960 
961 	g_signal_connect(G_OBJECT(widget), "toggled",
962 					 G_CALLBACK(field_bool_cb), field);
963 
964 	return widget;
965 }
966 
967 static GtkWidget *
create_choice_field(PurpleRequestField * field)968 create_choice_field(PurpleRequestField *field)
969 {
970 	GtkWidget *widget;
971 	GList *labels = purple_request_field_choice_get_labels(field);
972 	int num_labels = g_list_length(labels);
973 	GList *l;
974 
975 	if (num_labels > 5)
976 	{
977 		widget = gtk_combo_box_new_text();
978 
979 		for (l = labels; l != NULL; l = l->next)
980 		{
981 			const char *text = l->data;
982 			gtk_combo_box_append_text(GTK_COMBO_BOX(widget), text);
983 		}
984 
985 		gtk_combo_box_set_active(GTK_COMBO_BOX(widget),
986 						purple_request_field_choice_get_default_value(field));
987 
988 		g_signal_connect(G_OBJECT(widget), "changed",
989 						 G_CALLBACK(field_choice_menu_cb), field);
990 	}
991 	else
992 	{
993 		GtkWidget *box;
994 		GtkWidget *first_radio = NULL;
995 		GtkWidget *radio;
996 		gint i;
997 
998 		if (num_labels == 2)
999 			box = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1000 		else
1001 			box = gtk_vbox_new(FALSE, 0);
1002 
1003 		widget = box;
1004 
1005 		for (l = labels, i = 0; l != NULL; l = l->next, i++)
1006 		{
1007 			const char *text = l->data;
1008 
1009 			radio = gtk_radio_button_new_with_label_from_widget(
1010 				GTK_RADIO_BUTTON(first_radio), text);
1011 
1012 			if (first_radio == NULL)
1013 				first_radio = radio;
1014 
1015 			if (i == purple_request_field_choice_get_default_value(field))
1016 				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
1017 
1018 			gtk_box_pack_start(GTK_BOX(box), radio, TRUE, TRUE, 0);
1019 			gtk_widget_show(radio);
1020 
1021 			g_signal_connect(G_OBJECT(radio), "toggled",
1022 							 G_CALLBACK(field_choice_option_cb), field);
1023 		}
1024 	}
1025 
1026 	return widget;
1027 }
1028 
1029 static GtkWidget *
create_image_field(PurpleRequestField * field)1030 create_image_field(PurpleRequestField *field)
1031 {
1032 	GtkWidget *widget;
1033 	GdkPixbuf *buf, *scale;
1034 
1035 	buf = pidgin_pixbuf_from_data(
1036 			(const guchar *)purple_request_field_image_get_buffer(field),
1037 			purple_request_field_image_get_size(field));
1038 
1039 	scale = gdk_pixbuf_scale_simple(buf,
1040 			purple_request_field_image_get_scale_x(field) * gdk_pixbuf_get_width(buf),
1041 			purple_request_field_image_get_scale_y(field) * gdk_pixbuf_get_height(buf),
1042 			GDK_INTERP_BILINEAR);
1043 	widget = gtk_image_new_from_pixbuf(scale);
1044 	g_object_unref(G_OBJECT(buf));
1045 	g_object_unref(G_OBJECT(scale));
1046 
1047 	return widget;
1048 }
1049 
1050 static GtkWidget *
create_account_field(PurpleRequestField * field)1051 create_account_field(PurpleRequestField *field)
1052 {
1053 	GtkWidget *widget;
1054 
1055 	widget = pidgin_account_option_menu_new(
1056 		purple_request_field_account_get_default_value(field),
1057 		purple_request_field_account_get_show_all(field),
1058 		G_CALLBACK(field_account_cb),
1059 		purple_request_field_account_get_filter(field),
1060 		field);
1061 
1062 	return widget;
1063 }
1064 
1065 static void
select_field_list_item(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1066 select_field_list_item(GtkTreeModel *model, GtkTreePath *path,
1067 					   GtkTreeIter *iter, gpointer data)
1068 {
1069 	PurpleRequestField *field = (PurpleRequestField *)data;
1070 	char *text;
1071 
1072 	gtk_tree_model_get(model, iter, 1, &text, -1);
1073 
1074 	purple_request_field_list_add_selected(field, text);
1075 	g_free(text);
1076 }
1077 
1078 static void
list_field_select_changed_cb(GtkTreeSelection * sel,PurpleRequestField * field)1079 list_field_select_changed_cb(GtkTreeSelection *sel, PurpleRequestField *field)
1080 {
1081 	purple_request_field_list_clear_selected(field);
1082 
1083 	gtk_tree_selection_selected_foreach(sel, select_field_list_item, field);
1084 }
1085 
1086 static GtkWidget *
create_list_field(PurpleRequestField * field)1087 create_list_field(PurpleRequestField *field)
1088 {
1089 	GtkWidget *treeview;
1090 	GtkListStore *store;
1091 	GtkCellRenderer *renderer;
1092 	GtkTreeSelection *sel;
1093 	GtkTreeViewColumn *column;
1094 	GtkTreeIter iter;
1095 	GList *l;
1096 	GList *icons = NULL;
1097 
1098 	icons = purple_request_field_list_get_icons(field);
1099 
1100 
1101 	/* Create the list store */
1102 	if (icons)
1103 		store = gtk_list_store_new(3, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF);
1104 	else
1105 		store = gtk_list_store_new(2, G_TYPE_POINTER, G_TYPE_STRING);
1106 
1107 	/* Create the tree view */
1108 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1109 	g_object_unref(G_OBJECT(store));
1110 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
1111 
1112 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1113 
1114 	if (purple_request_field_list_get_multi_select(field))
1115 		gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1116 
1117 	column = gtk_tree_view_column_new();
1118 	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
1119 
1120 	renderer = gtk_cell_renderer_text_new();
1121 	gtk_tree_view_column_pack_start(column, renderer, TRUE);
1122 	gtk_tree_view_column_add_attribute(column, renderer, "text", 1);
1123 
1124 	if (icons)
1125 	{
1126 		renderer = gtk_cell_renderer_pixbuf_new();
1127 		gtk_tree_view_column_pack_start(column, renderer, TRUE);
1128 		gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", 2);
1129 
1130 		gtk_widget_set_size_request(treeview, 200, 400);
1131 	}
1132 
1133 	for (l = purple_request_field_list_get_items(field); l != NULL; l = l->next)
1134 	{
1135 		const char *text = (const char *)l->data;
1136 
1137 		gtk_list_store_append(store, &iter);
1138 
1139 		if (icons)
1140 		{
1141 			const char *icon_path = (const char *)icons->data;
1142 			GdkPixbuf* pixbuf = NULL;
1143 
1144 			if (icon_path)
1145 				pixbuf = pidgin_pixbuf_new_from_file(icon_path);
1146 
1147 			gtk_list_store_set(store, &iter,
1148 						   0, purple_request_field_list_get_data(field, text),
1149 						   1, text,
1150 						   2, pixbuf,
1151 						   -1);
1152 			icons = icons->next;
1153 		}
1154 		else
1155 			gtk_list_store_set(store, &iter,
1156 						   0, purple_request_field_list_get_data(field, text),
1157 						   1, text,
1158 						   -1);
1159 
1160 		if (purple_request_field_list_is_selected(field, text))
1161 			gtk_tree_selection_select_iter(sel, &iter);
1162 	}
1163 
1164 	/*
1165 	 * We only want to catch changes made by the user, so it's important
1166 	 * that we wait until after the list is created to connect this
1167 	 * handler.  If we connect the handler before the loop above and
1168 	 * there are multiple items selected, then selecting the first iter
1169 	 * in the tree causes list_field_select_changed_cb to be triggered
1170 	 * which clears out the rest of the list of selected items.
1171 	 */
1172 	g_signal_connect(G_OBJECT(sel), "changed",
1173 					 G_CALLBACK(list_field_select_changed_cb), field);
1174 
1175 	gtk_widget_show(treeview);
1176 
1177 	return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, -1);
1178 }
1179 
1180 static void *
pidgin_request_fields(const char * title,const char * primary,const char * secondary,PurpleRequestFields * fields,const char * ok_text,GCallback ok_cb,const char * cancel_text,GCallback cancel_cb,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data)1181 pidgin_request_fields(const char *title, const char *primary,
1182 						const char *secondary, PurpleRequestFields *fields,
1183 						const char *ok_text, GCallback ok_cb,
1184 						const char *cancel_text, GCallback cancel_cb,
1185 					    PurpleAccount *account, const char *who, PurpleConversation *conv,
1186 						void *user_data)
1187 {
1188 	PidginRequestData *data;
1189 	GtkWidget *win;
1190 	GtkWidget *vbox;
1191 	GtkWidget *vbox2;
1192 	GtkWidget *hbox;
1193 	GtkWidget *frame;
1194 	GtkWidget *label;
1195 	GtkWidget *table;
1196 	GtkWidget *button;
1197 	GtkWidget *img;
1198 	GtkSizeGroup *sg;
1199 	GList *gl, *fl;
1200 	PurpleRequestFieldGroup *group;
1201 	PurpleRequestField *field;
1202 	char *label_text;
1203 	char *primary_esc, *secondary_esc;
1204 	int total_fields = 0;
1205 
1206 	data            = g_new0(PidginRequestData, 1);
1207 	data->type      = PURPLE_REQUEST_FIELDS;
1208 	data->user_data = user_data;
1209 	data->u.multifield.fields = fields;
1210 
1211 	fields->ui_data = data;
1212 
1213 	data->cb_count = 2;
1214 	data->cbs = g_new0(GCallback, 2);
1215 
1216 	data->cbs[0] = ok_cb;
1217 	data->cbs[1] = cancel_cb;
1218 
1219 
1220 #ifdef _WIN32
1221 	data->dialog = win = pidgin_create_dialog(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
1222 #else /* !_WIN32 */
1223 	data->dialog = win = pidgin_create_dialog(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
1224 #endif /* _WIN32 */
1225 
1226 	g_signal_connect(G_OBJECT(win), "delete_event",
1227 					 G_CALLBACK(destroy_multifield_cb), data);
1228 
1229 	/* Setup the main horizontal box */
1230 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
1231 	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(win))), hbox);
1232 	gtk_widget_show(hbox);
1233 
1234 	/* Dialog icon. */
1235 	img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
1236 					gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
1237 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
1238 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
1239 	gtk_widget_show(img);
1240 
1241 	/* Cancel button */
1242 	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(cancel_text), G_CALLBACK(multifield_cancel_cb), data);
1243 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1244 
1245 	/* OK button */
1246 	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(ok_text), G_CALLBACK(multifield_ok_cb), data);
1247 	data->ok_button = button;
1248 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1249 	gtk_window_set_default(GTK_WINDOW(win), button);
1250 
1251 	pidgin_widget_decorate_account(hbox, account);
1252 
1253 	/* Setup the vbox */
1254 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
1255 	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1256 	gtk_widget_show(vbox);
1257 
1258 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1259 
1260 	if(primary) {
1261 		primary_esc = g_markup_escape_text(primary, -1);
1262 		label_text = g_strdup_printf(
1263 				"<span weight=\"bold\" size=\"larger\">%s</span>", primary_esc);
1264 		g_free(primary_esc);
1265 		label = gtk_label_new(NULL);
1266 
1267 		gtk_label_set_markup(GTK_LABEL(label), label_text);
1268 		gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1269 		gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1270 		gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1271 		gtk_widget_show(label);
1272 		g_free(label_text);
1273 	}
1274 
1275 	for (gl = purple_request_fields_get_groups(fields); gl != NULL;
1276 			gl = gl->next)
1277 		total_fields += g_list_length(purple_request_field_group_get_fields(gl->data));
1278 
1279 	if(total_fields > 9) {
1280 		GtkWidget *hbox_for_spacing, *vbox_for_spacing;
1281 
1282 		hbox_for_spacing = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
1283 		gtk_box_pack_start(GTK_BOX(vbox),
1284 			pidgin_make_scrollable(hbox_for_spacing, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, 200),
1285 			TRUE, TRUE, 0);
1286 		gtk_widget_show(hbox_for_spacing);
1287 
1288 		vbox_for_spacing = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
1289 		gtk_box_pack_start(GTK_BOX(hbox_for_spacing),
1290 				vbox_for_spacing, TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
1291 		gtk_widget_show(vbox_for_spacing);
1292 
1293 		vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
1294 		gtk_box_pack_start(GTK_BOX(vbox_for_spacing),
1295 				vbox2, TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
1296 		gtk_widget_show(vbox2);
1297 	} else {
1298 		vbox2 = vbox;
1299 	}
1300 
1301 	if (secondary) {
1302 		secondary_esc = g_markup_escape_text(secondary, -1);
1303 		label = gtk_label_new(NULL);
1304 
1305 		gtk_label_set_markup(GTK_LABEL(label), secondary_esc);
1306 		g_free(secondary_esc);
1307 		gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1308 		gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1309 		gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0);
1310 		gtk_widget_show(label);
1311 	}
1312 
1313 	for (gl = purple_request_fields_get_groups(fields);
1314 		 gl != NULL;
1315 		 gl = gl->next)
1316 	{
1317 		GList *field_list;
1318 		size_t field_count = 0;
1319 		size_t cols = 1;
1320 		size_t rows;
1321 #if 0
1322 		size_t col_num;
1323 #endif
1324 		size_t row_num = 0;
1325 
1326 		group      = gl->data;
1327 		field_list = purple_request_field_group_get_fields(group);
1328 
1329 		if (purple_request_field_group_get_title(group) != NULL)
1330 		{
1331 			frame = pidgin_make_frame(vbox2,
1332 				purple_request_field_group_get_title(group));
1333 		}
1334 		else
1335 			frame = vbox2;
1336 
1337 		field_count = g_list_length(field_list);
1338 #if 0
1339 		if (field_count > 9)
1340 		{
1341 			rows = field_count / 2;
1342 			cols++;
1343 		}
1344 		else
1345 #endif
1346 			rows = field_count;
1347 
1348 #if 0
1349 		col_num = 0;
1350 #endif
1351 
1352 		for (fl = field_list; fl != NULL; fl = fl->next)
1353 		{
1354 			PurpleRequestFieldType type;
1355 
1356 			field = (PurpleRequestField *)fl->data;
1357 
1358 			type = purple_request_field_get_type(field);
1359 
1360 			if (type == PURPLE_REQUEST_FIELD_LABEL)
1361 			{
1362 #if 0
1363 				if (col_num > 0)
1364 					rows++;
1365 #endif
1366 
1367 				rows++;
1368 			}
1369 			else if ((type == PURPLE_REQUEST_FIELD_LIST) ||
1370 				 (type == PURPLE_REQUEST_FIELD_STRING &&
1371 				  purple_request_field_string_is_multiline(field)))
1372 			{
1373 #if 0
1374 				if (col_num > 0)
1375 					rows++;
1376 #endif
1377 
1378 				rows += 2;
1379 			}
1380 
1381 #if 0
1382 			col_num++;
1383 
1384 			if (col_num >= cols)
1385 				col_num = 0;
1386 #endif
1387 		}
1388 
1389 		table = gtk_table_new(rows, 2 * cols, FALSE);
1390 		gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE);
1391 		gtk_table_set_col_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE);
1392 
1393 		gtk_container_add(GTK_CONTAINER(frame), table);
1394 		gtk_widget_show(table);
1395 
1396 		for (row_num = 0, fl = field_list;
1397 			 row_num < rows && fl != NULL;
1398 			 row_num++)
1399 		{
1400 #if 0
1401 			for (col_num = 0;
1402 				 col_num < cols && fl != NULL;
1403 				 col_num++, fl = fl->next)
1404 #else
1405 			gboolean dummy_counter = TRUE;
1406 			/* it's the same as loop above */
1407 			for (; dummy_counter && fl != NULL; dummy_counter = FALSE, fl = fl->next)
1408 #endif
1409 			{
1410 #if 0
1411 				size_t col_offset = col_num * 2;
1412 #else
1413 				size_t col_offset = 0;
1414 #endif
1415 				PurpleRequestFieldType type;
1416 				GtkWidget *widget = NULL;
1417 				const char *field_label;
1418 
1419 				label = NULL;
1420 				field = fl->data;
1421 
1422 				if (!purple_request_field_is_visible(field)) {
1423 #if 0
1424 					col_num--;
1425 #endif
1426 					continue;
1427 				}
1428 
1429 				type = purple_request_field_get_type(field);
1430 				field_label = purple_request_field_get_label(field);
1431 
1432 				if (type != PURPLE_REQUEST_FIELD_BOOLEAN && field_label)
1433 				{
1434 					char *text = NULL;
1435 
1436 					if (field_label[strlen(field_label) - 1] != ':')
1437 						text = g_strdup_printf("%s:", field_label);
1438 
1439 					label = gtk_label_new(NULL);
1440 					gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), text ? text : field_label);
1441 					g_free(text);
1442 
1443 					gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1444 
1445 					gtk_size_group_add_widget(sg, label);
1446 
1447 					if (type == PURPLE_REQUEST_FIELD_LABEL ||
1448 					    type == PURPLE_REQUEST_FIELD_LIST ||
1449 						(type == PURPLE_REQUEST_FIELD_STRING &&
1450 						 purple_request_field_string_is_multiline(field)))
1451 					{
1452 #if 0
1453 						if(col_num > 0)
1454 							row_num++;
1455 #endif
1456 
1457 						gtk_table_attach_defaults(GTK_TABLE(table), label,
1458 												  0, 2 * cols,
1459 												  row_num, row_num + 1);
1460 
1461 						row_num++;
1462 #if 0
1463 						col_num=cols;
1464 #endif
1465 					}
1466 					else
1467 					{
1468 						gtk_table_attach_defaults(GTK_TABLE(table), label,
1469 												  col_offset, col_offset + 1,
1470 												  row_num, row_num + 1);
1471 					}
1472 
1473 					gtk_widget_show(label);
1474 				}
1475 
1476 				widget = GTK_WIDGET(purple_request_field_get_ui_data(field));
1477 				if (widget == NULL)
1478 				{
1479 					if (type == PURPLE_REQUEST_FIELD_STRING)
1480 						widget = create_string_field(field);
1481 					else if (type == PURPLE_REQUEST_FIELD_INTEGER)
1482 						widget = create_int_field(field);
1483 					else if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
1484 						widget = create_bool_field(field);
1485 					else if (type == PURPLE_REQUEST_FIELD_CHOICE)
1486 						widget = create_choice_field(field);
1487 					else if (type == PURPLE_REQUEST_FIELD_LIST)
1488 						widget = create_list_field(field);
1489 					else if (type == PURPLE_REQUEST_FIELD_IMAGE)
1490 						widget = create_image_field(field);
1491 					else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
1492 						widget = create_account_field(field);
1493 					else
1494 						continue;
1495 				}
1496 
1497 				if (label)
1498 					gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
1499 
1500 				if (type == PURPLE_REQUEST_FIELD_STRING &&
1501 					purple_request_field_string_is_multiline(field))
1502 				{
1503 					gtk_table_attach(GTK_TABLE(table), widget,
1504 									 0, 2 * cols,
1505 									 row_num, row_num + 1,
1506 									 GTK_FILL | GTK_EXPAND,
1507 									 GTK_FILL | GTK_EXPAND,
1508 									 5, 0);
1509 				}
1510 				else if (type == PURPLE_REQUEST_FIELD_LIST)
1511 				{
1512 									gtk_table_attach(GTK_TABLE(table), widget,
1513 									0, 2 * cols,
1514 									row_num, row_num + 1,
1515 									GTK_FILL | GTK_EXPAND,
1516 									GTK_FILL | GTK_EXPAND,
1517 									5, 0);
1518 				}
1519 				else if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
1520 				{
1521 					gtk_table_attach(GTK_TABLE(table), widget,
1522 									 col_offset, col_offset + 1,
1523 									 row_num, row_num + 1,
1524 									 GTK_FILL | GTK_EXPAND,
1525 									 GTK_FILL | GTK_EXPAND,
1526 									 5, 0);
1527 				}
1528 				else
1529 				{
1530 					gtk_table_attach(GTK_TABLE(table), widget,
1531 							 		 1, 2 * cols,
1532 									 row_num, row_num + 1,
1533 									 GTK_FILL | GTK_EXPAND,
1534 									 GTK_FILL | GTK_EXPAND,
1535 									 5, 0);
1536 				}
1537 
1538 				gtk_widget_show(widget);
1539 
1540 				purple_request_field_set_ui_data(field, widget);
1541 			}
1542 		}
1543 	}
1544 
1545 	g_object_unref(sg);
1546 
1547 	if (!purple_request_fields_all_required_filled(fields))
1548 		gtk_widget_set_sensitive(data->ok_button, FALSE);
1549 
1550 	pidgin_auto_parent_window(win);
1551 
1552 	gtk_widget_show(win);
1553 
1554 	return data;
1555 }
1556 
1557 static void
file_yes_no_cb(PidginRequestData * data,gint id)1558 file_yes_no_cb(PidginRequestData *data, gint id)
1559 {
1560 	/* Only call the callback if yes was selected, otherwise the request
1561 	 * (eg. file transfer) will be cancelled, then when a new filename is chosen
1562 	 * things go BOOM */
1563 	if (id == 1) {
1564 		if (data->cbs[1] != NULL)
1565 			((PurpleRequestFileCb)data->cbs[1])(data->user_data, data->u.file.name);
1566 		purple_request_close(data->type, data);
1567 	} else {
1568 		pidgin_clear_cursor(GTK_WIDGET(data->dialog));
1569 	}
1570 }
1571 
1572 static void
file_ok_check_if_exists_cb(GtkWidget * widget,gint response,PidginRequestData * data)1573 file_ok_check_if_exists_cb(GtkWidget *widget, gint response, PidginRequestData *data)
1574 {
1575 	gchar *current_folder;
1576 
1577 	generic_response_start(data);
1578 
1579 	if (response != GTK_RESPONSE_ACCEPT) {
1580 		if (data->cbs[0] != NULL)
1581 			((PurpleRequestFileCb)data->cbs[0])(data->user_data, NULL);
1582 		purple_request_close(data->type, data);
1583 		return;
1584 	}
1585 
1586 	data->u.file.name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(data->dialog));
1587 	current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(data->dialog));
1588 	if (current_folder != NULL) {
1589 		if (data->u.file.savedialog) {
1590 			purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_save_folder", current_folder);
1591 		} else {
1592 			purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_open_folder", current_folder);
1593 		}
1594 		g_free(current_folder);
1595 	}
1596 	if ((data->u.file.savedialog == TRUE) &&
1597 		(g_file_test(data->u.file.name, G_FILE_TEST_EXISTS))) {
1598 		purple_request_action(data, NULL, _("That file already exists"),
1599 							_("Would you like to overwrite it?"), 0,
1600 							NULL, NULL, NULL,
1601 							data, 2,
1602 							_("Overwrite"), G_CALLBACK(file_yes_no_cb),
1603 							_("Choose New Name"), G_CALLBACK(file_yes_no_cb));
1604 	} else
1605 		file_yes_no_cb(data, 1);
1606 }
1607 
1608 static void *
pidgin_request_file(const char * title,const char * filename,gboolean savedialog,GCallback ok_cb,GCallback cancel_cb,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data)1609 pidgin_request_file(const char *title, const char *filename,
1610 					  gboolean savedialog,
1611 					  GCallback ok_cb, GCallback cancel_cb,
1612 					  PurpleAccount *account, const char *who, PurpleConversation *conv,
1613 					  void *user_data)
1614 {
1615 	PidginRequestData *data;
1616 	GtkWidget *filesel;
1617 	const gchar *current_folder;
1618 	gboolean folder_set = FALSE;
1619 
1620 	data = g_new0(PidginRequestData, 1);
1621 	data->type = PURPLE_REQUEST_FILE;
1622 	data->user_data = user_data;
1623 	data->cb_count = 2;
1624 	data->cbs = g_new0(GCallback, 2);
1625 	data->cbs[0] = cancel_cb;
1626 	data->cbs[1] = ok_cb;
1627 	data->u.file.savedialog = savedialog;
1628 
1629 	filesel = gtk_file_chooser_dialog_new(
1630 						title ? title : (savedialog ? _("Save File...")
1631 													: _("Open File...")),
1632 						NULL,
1633 						savedialog ? GTK_FILE_CHOOSER_ACTION_SAVE
1634 								   : GTK_FILE_CHOOSER_ACTION_OPEN,
1635 						GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1636 						savedialog ? GTK_STOCK_SAVE
1637 								   : GTK_STOCK_OPEN,
1638 						GTK_RESPONSE_ACCEPT,
1639 						NULL);
1640 	gtk_dialog_set_default_response(GTK_DIALOG(filesel), GTK_RESPONSE_ACCEPT);
1641 
1642 	if (savedialog) {
1643 		current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_save_folder");
1644 	} else {
1645 		current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_open_folder");
1646 	}
1647 
1648 	if ((filename != NULL) && (*filename != '\0')) {
1649 		if (savedialog)
1650 			gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filesel), filename);
1651 		else if (g_file_test(filename, G_FILE_TEST_EXISTS))
1652 			gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filesel), filename);
1653 	}
1654 	if ((filename == NULL || *filename == '\0' || !g_file_test(filename, G_FILE_TEST_EXISTS)) &&
1655 				(current_folder != NULL) && (*current_folder != '\0')) {
1656 		folder_set = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel), current_folder);
1657 	}
1658 
1659 #ifdef _WIN32
1660 	if (!folder_set && (filename == NULL || *filename == '\0' || !g_file_test(filename, G_FILE_TEST_EXISTS))) {
1661 		char *my_documents = wpurple_get_special_folder(CSIDL_PERSONAL);
1662 
1663 		if (my_documents != NULL) {
1664 			gtk_file_chooser_set_current_folder(
1665 					GTK_FILE_CHOOSER(filesel), my_documents);
1666 
1667 			g_free(my_documents);
1668 		}
1669 	}
1670 #else
1671 	(void)folder_set;
1672 #endif
1673 
1674 	g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(filesel)), "response",
1675 					 G_CALLBACK(file_ok_check_if_exists_cb), data);
1676 
1677 	pidgin_auto_parent_window(filesel);
1678 
1679 	data->dialog = filesel;
1680 	gtk_widget_show(filesel);
1681 
1682 	return (void *)data;
1683 }
1684 
1685 static void *
pidgin_request_folder(const char * title,const char * dirname,GCallback ok_cb,GCallback cancel_cb,PurpleAccount * account,const char * who,PurpleConversation * conv,void * user_data)1686 pidgin_request_folder(const char *title, const char *dirname,
1687 					  GCallback ok_cb, GCallback cancel_cb,
1688 					  PurpleAccount *account, const char *who, PurpleConversation *conv,
1689 					  void *user_data)
1690 {
1691 	PidginRequestData *data;
1692 	GtkWidget *dirsel;
1693 
1694 	data = g_new0(PidginRequestData, 1);
1695 	data->type = PURPLE_REQUEST_FOLDER;
1696 	data->user_data = user_data;
1697 	data->cb_count = 2;
1698 	data->cbs = g_new0(GCallback, 2);
1699 	data->cbs[0] = cancel_cb;
1700 	data->cbs[1] = ok_cb;
1701 	data->u.file.savedialog = FALSE;
1702 
1703 	dirsel = gtk_file_chooser_dialog_new(
1704 						title ? title : _("Select Folder..."),
1705 						NULL,
1706 						GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1707 						GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1708 						GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1709 						NULL);
1710 	gtk_dialog_set_default_response(GTK_DIALOG(dirsel), GTK_RESPONSE_ACCEPT);
1711 
1712 	if ((dirname != NULL) && (*dirname != '\0'))
1713 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirsel), dirname);
1714 
1715 	g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(dirsel)), "response",
1716 						G_CALLBACK(file_ok_check_if_exists_cb), data);
1717 
1718 	data->dialog = dirsel;
1719 	pidgin_auto_parent_window(dirsel);
1720 
1721 	gtk_widget_show(dirsel);
1722 
1723 	return (void *)data;
1724 }
1725 
1726 #ifdef USE_VV
1727 
1728 #ifdef HAVE_GIOUNIX
1729 
1730 static gboolean portal_failed;
1731 
1732 static void screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data);
1733 
portal_fallback(PidginRequestData * data)1734 static void portal_fallback(PidginRequestData *data)
1735 {
1736 	purple_debug_info("pidgin", "Fallback from XDP portal screenshare\n");
1737 	portal_failed = TRUE;
1738 
1739 	if (data->dialog) {
1740 		pidgin_auto_parent_window(data->dialog);
1741 		gtk_widget_show_all(data->dialog);
1742 	} else {
1743 		screenshare_cancel_cb(NULL, data);
1744 	}
1745 }
1746 
request_completed_cb(GObject * object,GAsyncResult * res,gpointer _data)1747 static void request_completed_cb(GObject *object, GAsyncResult *res, gpointer _data)
1748 {
1749 	PidginRequestData *data = _data;
1750 	GError *error = NULL;
1751 	GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
1752 									     res,
1753 									     &error);
1754 	if (!msg || g_dbus_message_to_gerror(msg, &error)) {
1755 		/* This is the expected failure mode when XDP screencast isn't available.
1756 		 * Don't be too noisy about it; just fall back to direct mode. */
1757 		purple_debug_info("pidgin",
1758 				  "ScreenCast call failed: %s\n", error->message);
1759 		portal_fallback(data);
1760 	}
1761 }
1762 
portal_request_path(PidginRequestData * data,GVariantBuilder * b)1763 static gchar *portal_request_path(PidginRequestData *data, GVariantBuilder *b)
1764 {
1765 	const gchar *bus_name;
1766 	gchar *dot, *request_str;
1767 	gchar *request_path;
1768 
1769 	bus_name = g_dbus_connection_get_unique_name(data->u.screenshare.dbus_connection);
1770 
1771 	request_str = g_strdup_printf("u%u", data->u.screenshare.portal_request_nr++);
1772 	if (!request_str) {
1773 		return NULL;
1774 	}
1775 
1776 	request_path = g_strdup_printf("/org/freedesktop/portal/desktop/request/%s/%s",
1777 				       bus_name + 1, request_str);
1778 	if (!request_path) {
1779 		g_free(request_str);
1780 		return NULL;
1781 	}
1782 
1783 	g_variant_builder_add(b, "{sv}", "handle_token", g_variant_new_take_string(request_str));
1784 
1785 	dot = request_path;
1786 	while ((dot = strchr(dot, '.'))) {
1787 		*dot = '_';
1788 	}
1789 
1790 	return request_path;
1791 }
1792 
screen_cast_call(PidginRequestData * data,const gchar * method,const gchar * str_arg,GVariantBuilder * opts,GDBusSignalCallback cb)1793 static void screen_cast_call(PidginRequestData *data, const gchar *method, const gchar *str_arg,
1794 			     GVariantBuilder *opts, GDBusSignalCallback cb)
1795 {
1796 	GDBusMessage *msg;
1797 	GVariantBuilder b;
1798 	gchar *request_path;
1799 
1800 	if (data->u.screenshare.signal_id) {
1801 		g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
1802 						     data->u.screenshare.signal_id);
1803 	}
1804 
1805 	if (!opts) {
1806 		opts = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
1807 	}
1808 
1809 	request_path = portal_request_path(data, opts);
1810 	if (!request_path) {
1811 		g_variant_builder_unref(opts);
1812 		purple_notify_error(NULL, _("Screen share error"),
1813 				    _("Error creating screencast request"), NULL);
1814 		screenshare_cancel_cb(NULL, data);
1815 	}
1816 
1817 	data->u.screenshare.signal_id = g_dbus_connection_signal_subscribe(data->u.screenshare.dbus_connection,
1818 									   "org.freedesktop.portal.Desktop",
1819 									   "org.freedesktop.portal.Request",
1820 									   "Response", request_path, NULL, 0,
1821 									   cb, data, NULL);
1822 	g_free(request_path);
1823 
1824 	msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
1825 					     "/org/freedesktop/portal/desktop",
1826 					     "org.freedesktop.portal.ScreenCast",
1827 					     method);
1828 
1829 	g_variant_builder_init(&b, G_VARIANT_TYPE_TUPLE);
1830 	if (data->u.screenshare.session_path) {
1831 		g_variant_builder_add(&b, "o", data->u.screenshare.session_path);
1832 	}
1833 	if (str_arg) {
1834 		g_variant_builder_add(&b, "s", str_arg);
1835 	}
1836 	g_variant_builder_add(&b, "a{sv}", opts);
1837 
1838 	g_dbus_message_set_body(msg, g_variant_builder_end(&b));
1839 
1840 	g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
1841 						  0, 2000, NULL, NULL, request_completed_cb, data);
1842 }
1843 
create_pipewiresrc_cb(PurpleMedia * media,const gchar * session_id,const gchar * participant)1844 static GstElement *create_pipewiresrc_cb(PurpleMedia *media, const gchar *session_id,
1845 					 const gchar *participant)
1846 {
1847 	GstElement *ret;
1848 	GObject *info;
1849 	gchar *node_id;
1850 
1851 	info = g_object_get_data(G_OBJECT(media), "src-element");
1852 	if (!info) {
1853 		return NULL;
1854 	}
1855 
1856 	ret = gst_element_factory_make("pipewiresrc", NULL);
1857 	if (ret == NULL) {
1858 		return NULL;
1859 	}
1860 
1861 	/* Take the node-id and fd from the PurpleMediaElementInfo
1862 	 * and apply them to the pipewiresrc */
1863 	node_id = g_strdup_printf("%u",
1864 				  GPOINTER_TO_UINT(g_object_get_data(info, "node-id")));
1865 	g_object_set(ret,"path", node_id, "do-timestamp", TRUE,
1866 		     "fd", GPOINTER_TO_INT(g_object_get_data(info, "fd")),
1867 		     NULL);
1868 	g_free(node_id);
1869 
1870 	return ret;
1871 }
1872 
close_pipewire_fd(gpointer _fd)1873 static void close_pipewire_fd(gpointer _fd)
1874 {
1875 	int fd = GPOINTER_TO_INT(_fd);
1876 
1877 	close(fd);
1878 }
1879 
pipewire_fd_cb(GObject * object,GAsyncResult * res,gpointer _data)1880 static void pipewire_fd_cb(GObject *object, GAsyncResult *res, gpointer _data)
1881 {
1882 	PidginRequestData *data = _data;
1883 	GError *error = NULL;
1884 	GUnixFDList *l;
1885 	int pipewire_fd;
1886 	GDBusMessage *msg = g_dbus_connection_send_message_with_reply_finish(data->u.screenshare.dbus_connection,
1887 									     res,
1888 									     &error);
1889 	if (!msg || g_dbus_message_to_gerror(msg, &error)) {
1890 		purple_debug_info("pidgin", "OpenPipeWireRemote request failed: %s\n", error->message);
1891 		purple_notify_error(NULL, _("Screen share error"),
1892 				    _("OpenPipeWireRemote request failed"), error->message);
1893 		g_clear_error(&error);
1894 		screenshare_cancel_cb(NULL, data);
1895 		return;
1896 	}
1897 	l = g_dbus_message_get_unix_fd_list(msg);
1898 	if (!l) {
1899 		purple_debug_info("pidgin", "OpenPipeWireRemote request failed to yield a file descriptor\n");
1900 		purple_notify_error(NULL, _("Screen share error"), _("OpenPipeWireRemote request failed"),
1901 				    _("No file descriptor found"));
1902 		screenshare_cancel_cb(NULL, data);
1903 		return;
1904 	}
1905 	pipewire_fd = g_unix_fd_list_get(l, 0, NULL);
1906 
1907 	if (data->cbs[0] != NULL) {
1908 		GObject *info;
1909 		info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
1910 				    "id", "screenshare-window",
1911 				    "name", "Screen share single window",
1912 				    "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
1913 				    PURPLE_MEDIA_ELEMENT_ONE_SRC,
1914 				    "create-cb", create_pipewiresrc_cb, NULL);
1915 		g_object_set_data_full(info, "fd", GINT_TO_POINTER(pipewire_fd), close_pipewire_fd);
1916 		g_object_set_data(info, "node-id", GUINT_TO_POINTER(data->u.screenshare.node_id));
1917 		/* When the DBus connection closes, the session ends. So keep it attached
1918 		   to the PurpleMediaElementInfo, which in turn should be attached to
1919 		   the owning PurpleMedia for the lifetime of the session. */
1920 		g_object_set_data_full(info, "dbus-connection",
1921 				       g_object_ref(data->u.screenshare.dbus_connection),
1922 				       g_object_unref);
1923 		((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
1924 	}
1925 
1926 	purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
1927 }
1928 
1929 
get_pipewire_fd(PidginRequestData * data)1930 static void get_pipewire_fd(PidginRequestData *data)
1931 {
1932 	GDBusMessage *msg;
1933 	GVariant *args;
1934 
1935 	if (data->u.screenshare.signal_id) {
1936 		g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
1937 						     data->u.screenshare.signal_id);
1938 	}
1939 	data->u.screenshare.signal_id = 0;
1940 
1941 	msg = g_dbus_message_new_method_call("org.freedesktop.portal.Desktop",
1942 					     "/org/freedesktop/portal/desktop",
1943 					     "org.freedesktop.portal.ScreenCast",
1944 					     "OpenPipeWireRemote");
1945 
1946 	args = g_variant_new("(oa{sv})", data->u.screenshare.session_path, NULL);
1947 	g_dbus_message_set_body(msg, args);
1948 
1949 	g_dbus_connection_send_message_with_reply(data->u.screenshare.dbus_connection, msg,
1950 						  0, 200, NULL, NULL, pipewire_fd_cb, data);
1951 }
1952 
started_cb(GDBusConnection * dc,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * params,gpointer _data)1953 static void started_cb(GDBusConnection *dc, const gchar *sender_name,
1954 		       const gchar *object_path, const gchar *interface_name,
1955 		       const gchar *signal_name, GVariant *params, gpointer _data)
1956 {
1957 	PidginRequestData *data = _data;
1958 	GVariant *args, *streams;
1959 	guint code;
1960 
1961 	g_variant_get(params, "(u@a{sv})", &code, &args);
1962 	if (code || !g_variant_lookup(args, "streams", "@a(ua{sv})", &streams) ||
1963 	    g_variant_n_children(streams) != 1) {
1964 		purple_debug_info("pidgin", "Screencast Start call returned %d\n", code);
1965 		purple_notify_error(NULL, _("Screen share error"),
1966 				    _("Screencast \"Start\" failed"), NULL);
1967 		screenshare_cancel_cb(NULL, data);
1968 		return;
1969 	}
1970 
1971 	g_variant_get_child(streams, 0, "(u@a{sv})", &data->u.screenshare.node_id, NULL);
1972 
1973 	get_pipewire_fd(data);
1974 }
1975 
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)1976 static void source_selected_cb(GDBusConnection *dc, const gchar *sender_name,
1977 			       const gchar *object_path, const gchar *interface_name,
1978 			       const gchar *signal_name, GVariant *params, gpointer _data)
1979 {
1980 	PidginRequestData *data = _data;
1981 	guint code;
1982 
1983 	g_variant_get(params, "(u@a{sv})", &code, NULL);
1984 	if (code) {
1985 		purple_debug_info("pidgin", "Screencast SelectSources call returned %d\n", code);
1986 		purple_notify_error(NULL, _("Screen share error"),
1987 				    _("Screencast \"SelectSources\" failed"), NULL);
1988 		screenshare_cancel_cb(NULL, data);
1989 		return;
1990 	}
1991 
1992 	screen_cast_call(data, "Start", "", NULL, started_cb);
1993 }
1994 
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)1995 static void sess_created_cb(GDBusConnection *dc, const gchar *sender_name,
1996 			    const gchar *object_path, const gchar *interface_name,
1997 			    const gchar *signal_name, GVariant *params, gpointer _data)
1998 {
1999 	PidginRequestData *data = _data;
2000 	GVariantBuilder opts;
2001 	GVariant *args;
2002 	guint code;
2003 
2004 	g_variant_get(params, "(u@a{sv})", &code, &args);
2005 	if (code || !g_variant_lookup(args, "session_handle", "s",
2006 				      &data->u.screenshare.session_path)) {
2007 		purple_debug_info("pidgin", "Screencast CreateSession call returned %d\n", code);
2008 		purple_notify_error(NULL, _("Screen share error"),
2009 				    _("Screencast \"CreateSession\" failed."), NULL);
2010 		screenshare_cancel_cb(NULL, data);
2011 		return;
2012 	}
2013 
2014 	g_variant_builder_init(&opts, G_VARIANT_TYPE("a{sv}"));
2015 	g_variant_builder_add(&opts, "{sv}", "multiple", g_variant_new_boolean(FALSE));
2016 	g_variant_builder_add(&opts, "{sv}", "types", g_variant_new_uint32(3));
2017 
2018 	screen_cast_call(data, "SelectSources", NULL, &opts, source_selected_cb);
2019 }
2020 
portal_conn_cb(GObject * object,GAsyncResult * res,gpointer _data)2021 static void portal_conn_cb(GObject *object, GAsyncResult *res, gpointer _data)
2022 {
2023 	PidginRequestData *data = _data;
2024 	GVariantBuilder opts;
2025 	GError *error = NULL;
2026 	gchar *session_token;
2027 
2028 	data->u.screenshare.dbus_connection = g_dbus_connection_new_for_address_finish(res, &error);
2029 	if (!data->u.screenshare.dbus_connection) {
2030 		purple_debug_info("pidgin", "Connection to XDP portal failed: %s\n", error->message);
2031 		portal_fallback(data);
2032 		return;
2033 	}
2034 
2035 	session_token = g_strdup_printf("u%u", data->u.screenshare.portal_session_nr++);
2036 
2037 	g_variant_builder_init(&opts, G_VARIANT_TYPE("a{sv}"));
2038 	g_variant_builder_add(&opts, "{sv}", "session_handle_token",
2039 			      g_variant_new_take_string(session_token));
2040 
2041 	screen_cast_call(data, "CreateSession", NULL, &opts, sess_created_cb);
2042 }
2043 
request_xdp_portal_screenshare(PidginRequestData * data)2044 static gboolean request_xdp_portal_screenshare(PidginRequestData *data)
2045 {
2046 	gchar *addr;
2047 
2048 	if (portal_failed) {
2049 		return FALSE;
2050 	}
2051 
2052 	data->u.screenshare.cancellable = g_cancellable_new();
2053 
2054 	/* We create a new connection instead of using g_bus_get() because it
2055 	 * makes cleanup a *lot* easier. Just kill the connection. */
2056 	addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2057 	if (!addr) {
2058 		portal_failed = TRUE;
2059 		return FALSE;
2060 	}
2061 
2062 	g_dbus_connection_new_for_address(addr,
2063 					  G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
2064 					  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL,
2065 					  data->u.screenshare.cancellable, portal_conn_cb, data);
2066 	g_free(addr);
2067 	return TRUE;
2068 }
2069 
2070 #endif
2071 
2072 static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id,
2073 				       const gchar *participant);
2074 
2075 #ifdef HAVE_X11
2076 static gboolean
grab_event(GtkWidget * child,GdkEvent * event,PidginRequestData * data)2077 grab_event (GtkWidget *child, GdkEvent *event, PidginRequestData *data)
2078 {
2079 	GdkScreen *screen = gdk_screen_get_default();
2080 	GObject *info;
2081 	GdkWindow *gdkroot = gdk_get_default_root_window();
2082 	Window xroot = GDK_WINDOW_XID(gdkroot), xwindow, parent, *children;
2083 	unsigned int nchildren, xmask;
2084 	Display *xdisplay = GDK_SCREEN_XDISPLAY(screen);
2085 	int rootx, rooty, winx, winy;
2086 
2087 	if (event->type != GDK_BUTTON_PRESS)
2088 		return FALSE;
2089 
2090 	XQueryPointer(xdisplay, xroot, &xroot, &xwindow, &rootx, &rooty, &winx, &winy, &xmask);
2091 
2092 	gdk_pointer_ungrab(GDK_CURRENT_TIME);
2093 
2094 	/* Find WM window (direct child of root) */
2095 	while (1) {
2096 		if (!XQueryTree(xdisplay, xwindow, &xroot, &parent, &children, &nchildren))
2097 			break;
2098 
2099 		if (nchildren)
2100 			XFree(children);
2101 
2102 		if (xroot == parent)
2103 			break;
2104 
2105 		xwindow = parent;
2106 	}
2107 
2108 	generic_response_start(data);
2109 
2110 	if (data->cbs[0] != NULL) {
2111 		info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2112 				    "id", "screenshare-window",
2113 				    "name", "Screen share single window",
2114 				    "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
2115 				    PURPLE_MEDIA_ELEMENT_ONE_SRC,
2116 				    "create-cb", create_screensrc_cb, NULL);
2117 		g_object_set_data(info, "window-id", GUINT_TO_POINTER(xwindow));
2118 		((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
2119 	}
2120 
2121 	purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
2122 
2123 	return FALSE;
2124 }
2125 
2126 static void
screenshare_window_cb(GtkWidget * button,PidginRequestData * data)2127 screenshare_window_cb(GtkWidget *button, PidginRequestData *data)
2128 {
2129 	GdkCursor *cursor;
2130 	GdkWindow *gdkwin = gtk_widget_get_window(GTK_WIDGET(data->dialog));
2131 
2132 	if (!GTK_WIDGET_HAS_FOCUS(button))
2133 		gtk_widget_grab_focus(button);
2134 
2135 	gtk_widget_add_events(GTK_WIDGET(data->dialog),
2136 			      GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
2137 	g_signal_connect(data->dialog, "event", G_CALLBACK(grab_event), data);
2138 
2139 	cursor = gdk_cursor_new(GDK_CROSSHAIR);
2140 	gdk_pointer_grab(gdkwin, FALSE,
2141 			 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK,
2142 			 NULL, cursor, GDK_CURRENT_TIME);
2143 }
2144 
create_screensrc_cb(PurpleMedia * media,const gchar * session_id,const gchar * participant)2145 static GstElement *create_screensrc_cb(PurpleMedia *media, const gchar *session_id,
2146 				       const gchar *participant)
2147 {
2148 	GObject *info;
2149 	GstElement *ret;
2150 
2151 	ret = gst_element_factory_make("ximagesrc", NULL);
2152 	if (ret == NULL)
2153 		return NULL;
2154 
2155 	g_object_set(ret, "use-damage", 0, NULL);
2156 
2157 	info = g_object_get_data(G_OBJECT(media), "src-element");
2158 	if (info) {
2159 		Window xid = GPOINTER_TO_UINT(g_object_get_data(info, "window-id"));
2160 		int monitor_no = GPOINTER_TO_INT(g_object_get_data(info, "monitor-no"));
2161 		if (xid) {
2162 			g_object_set(ret, "xid", xid, NULL);
2163 		} else if (monitor_no >= 0) {
2164 			GdkScreen *screen = gdk_screen_get_default();
2165 			GdkRectangle geom;
2166 
2167 			gdk_screen_get_monitor_geometry(screen, monitor_no, &geom);
2168 			g_object_set(ret, "startx", geom.x, "starty", geom.y,
2169 				     "endx", geom.x + geom.width - 1,
2170 				     "endy", geom.y + geom.height - 1, NULL);
2171 		}
2172 	}
2173 
2174 	return ret;
2175 }
2176 #elif defined (_WIN32)
create_screensrc_cb(PPurpleMedia * media,const gchar * session_id,const gchar * participant)2177 static GstElement *create_screensrc_cb(PPurpleMedia *media, const gchar *session_id,
2178 				       const gchar *participant)
2179 {
2180 	GObject *info;
2181 	GstElement *ret;
2182 
2183 	ret = gst_element_factory_make("gdiscreencapsrc", NULL);
2184 	if (ret == NULL)
2185 		return NULL;
2186 
2187 	g_object_set(ret, "cursor", TRUE);
2188 
2189 	info = g_object_get_data(G_OBJECT(media), "src-element");
2190 	if (info) {
2191 		int monitor_no = GPOINTER_TO_INT(g_object_get_data(info, "monitor-no"));
2192 		if (monitor_no >= 0)
2193 			g_object_set(ret, "monitor", monitor_no);
2194 	}
2195 
2196 	return ret;
2197 }
2198 #else
2199 /* We don't actually need to break the build just because we can't do
2200  * screencap, but gtkmedia.c is going to break the USE_VV build if it
2201  * isn't WIN32 or X11 anyway, so we might as well. */
2202 #error "Unsupported windowing system"
2203 #endif
2204 
2205 static void
screenshare_monitor_cb(GtkWidget * button,PidginRequestData * data)2206 screenshare_monitor_cb(GtkWidget *button, PidginRequestData *data)
2207 {
2208 	GtkWidget *radio;
2209 	GObject *info;
2210 	int monitor_no = -1;
2211 
2212 	generic_response_start(data);
2213 
2214 	if (!GTK_WIDGET_HAS_FOCUS(button))
2215 		gtk_widget_grab_focus(button);
2216 
2217 	radio = g_object_get_data(G_OBJECT(data->dialog), "radio");
2218 	if (radio) {
2219 		GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
2220 
2221 		while (group) {
2222 			if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) {
2223 				monitor_no = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(group->data),
2224 									       "monitor-no"));
2225 				break;
2226 			}
2227 			group = group->next;
2228 		}
2229 	}
2230 	if (data->cbs[0] != NULL) {
2231 		info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2232 				    "id", "screenshare-monitor",
2233 				    "name", "Screen share monitor",
2234 				    "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
2235 				    PURPLE_MEDIA_ELEMENT_ONE_SRC,
2236 				    "create-cb", create_screensrc_cb, NULL);
2237 		g_object_set_data(info, "monitor-no", GINT_TO_POINTER(monitor_no));
2238 		((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
2239 	}
2240 
2241 	purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
2242 }
2243 
create_videotest_cb(PurpleMedia * media,const gchar * session_id,const gchar * participant)2244 static GstElement *create_videotest_cb(PurpleMedia *media, const gchar *session_id,
2245 				       const gchar *participant)
2246 {
2247 	return gst_element_factory_make("videotestsrc", NULL);
2248 }
2249 
2250 static void
screenshare_videotest_cb(GtkWidget * button,PidginRequestData * data)2251 screenshare_videotest_cb(GtkWidget *button, PidginRequestData *data)
2252 {
2253 	GObject *info;
2254 
2255 	generic_response_start(data);
2256 
2257 	if (!GTK_WIDGET_HAS_FOCUS(button))
2258 		gtk_widget_grab_focus(button);
2259 
2260 	if (data->cbs[0] != NULL) {
2261 		info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
2262 				    "id", "screenshare-videotestsrc",
2263 				    "name", "Screen share test source",
2264 				    "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC |
2265 				    PURPLE_MEDIA_ELEMENT_ONE_SRC,
2266 				    "create-cb", create_videotest_cb, NULL);
2267 		((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, info);
2268 	}
2269 
2270 	purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
2271 }
2272 
2273 static void
screenshare_cancel_cb(GtkWidget * button,PidginRequestData * data)2274 screenshare_cancel_cb(GtkWidget *button, PidginRequestData *data)
2275 {
2276 	if (data->dialog) {
2277 		generic_response_start(data);
2278 	}
2279 
2280 	if (data->cbs[0] != NULL)
2281 		((PurpleRequestScreenshareCb)data->cbs[0])(data->user_data, NULL);
2282 
2283 	purple_request_close(PURPLE_REQUEST_SCREENSHARE, data);
2284 }
2285 
2286 static gboolean
destroy_screenshare_cb(GtkWidget * dialog,GdkEvent * event,PidginRequestData * data)2287 destroy_screenshare_cb(GtkWidget *dialog, GdkEvent *event,
2288 		       PidginRequestData *data)
2289 {
2290 	screenshare_cancel_cb(NULL, data);
2291 	return FALSE;
2292 }
2293 
pidgin_request_screenshare_media(const char * title,const char * primary,const char * secondary,PurpleAccount * account,GCallback cb,void * user_data)2294 static void *pidgin_request_screenshare_media(const char *title, const char *primary,
2295 					      const char *secondary, PurpleAccount *account,
2296 					      GCallback cb, void *user_data)
2297 {
2298 	PidginRequestData *data;
2299 	GtkWidget *dialog;
2300 	GtkWidget *vbox;
2301 	GtkWidget *hbox;
2302 	GtkWidget *label;
2303 	GtkWidget *button;
2304 	GtkWidget *radio = NULL;
2305 	GdkScreen *screen;
2306 	char *label_text;
2307 	char *primary_esc, *secondary_esc;
2308 
2309 	data            = g_new0(PidginRequestData, 1);
2310 	data->type      = PURPLE_REQUEST_SCREENSHARE;
2311 	data->user_data = user_data;
2312 
2313 	data->cb_count = 1;
2314 	data->cbs = g_new0(GCallback, 1);
2315 	data->cbs[0] = cb;
2316 
2317 	/* Create the dialog. */
2318 	data->dialog = dialog = gtk_dialog_new();
2319 
2320 	if (title != NULL)
2321 		gtk_window_set_title(GTK_WINDOW(dialog), title);
2322 #ifdef _WIN32
2323 	else
2324 		gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
2325 #endif
2326 
2327 	button = pidgin_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
2328 					  G_CALLBACK(screenshare_cancel_cb), data);
2329 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
2330 
2331 	if (g_getenv("PIDGIN_SHARE_VIDEOTEST") != NULL) {
2332 		button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Test image"),
2333 						  G_CALLBACK(screenshare_videotest_cb), data);
2334 		GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
2335 		gtk_window_set_default(GTK_WINDOW(dialog), button);
2336 	}
2337 
2338 #ifdef HAVE_X11
2339 	button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Select window"),
2340 					  G_CALLBACK(screenshare_window_cb), data);
2341 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
2342 	gtk_window_set_default(GTK_WINDOW(dialog), button);
2343 #endif
2344 
2345 	button = pidgin_dialog_add_button(GTK_DIALOG(dialog), _("Use monitor"),
2346 					  G_CALLBACK(screenshare_monitor_cb), data);
2347 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
2348 	gtk_window_set_default(GTK_WINDOW(dialog), button);
2349 
2350 	g_signal_connect(G_OBJECT(dialog), "delete_event",
2351 					 G_CALLBACK(destroy_screenshare_cb), data);
2352 
2353 	/* Setup the dialog */
2354 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
2355 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
2356 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
2357 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
2358 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
2359 
2360 	/* Setup the main horizontal box */
2361 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
2362 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
2363 
2364 
2365 	/* Vertical box */
2366 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
2367 	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
2368 
2369 	pidgin_widget_decorate_account(hbox, account);
2370 
2371 	/* Descriptive label */
2372 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
2373 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
2374 	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
2375 								 "%s</span>%s%s" : "%s%s%s"),
2376 								 (primary ? primary_esc : ""),
2377 								 ((primary && secondary) ? "\n\n" : ""),
2378 								 (secondary ? secondary_esc : ""));
2379 	g_free(primary_esc);
2380 	g_free(secondary_esc);
2381 
2382 	label = gtk_label_new(NULL);
2383 
2384 	gtk_label_set_markup(GTK_LABEL(label), label_text);
2385 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
2386 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
2387 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
2388 	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
2389 
2390 	g_free(label_text);
2391 
2392 	screen = gdk_screen_get_default();
2393 	if (screen) {
2394 		int nr_monitors = gdk_screen_get_n_monitors(screen);
2395 		int primary = gdk_screen_get_primary_monitor(screen);
2396 		int i;
2397 
2398 		for (i = 0; i < nr_monitors; i++) {
2399 			GdkRectangle geom;
2400 			gchar *name;
2401 			gchar *label;
2402 
2403 			name = gdk_screen_get_monitor_plug_name(screen, i);
2404 			gdk_screen_get_monitor_geometry(screen, i, &geom);
2405 
2406 			label = g_strdup_printf(_("%s (%d✕%d @ %d,%d)"),
2407 						name ? name : _("Unknown output"),
2408 						geom.width, geom.height,
2409 						geom.x, geom.y);
2410 			radio = gtk_radio_button_new_with_label_from_widget((GtkRadioButton *)radio, label);
2411 			g_object_set_data(G_OBJECT(radio), "monitor-no", GINT_TO_POINTER(i));
2412 			gtk_box_pack_start(GTK_BOX(vbox), radio, FALSE, FALSE, 0);
2413 			if (i == primary)
2414 			       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
2415 
2416 			g_free(label);
2417 			g_free(name);
2418 		}
2419 		g_object_set_data(G_OBJECT(dialog), "radio", radio);
2420 	}
2421 
2422 #ifdef HAVE_GIOUNIX
2423 	/*
2424 	 * We create the dialog for direct x11/win share here anyway, because
2425 	 * it's simpler than storing everything we need to create it, including
2426 	 * the PurpleAccount which we can't just take a ref on because it isn't
2427 	 * just a GObject yet. On fallback, the dialog can be used immediately.
2428 	 */
2429 	if (request_xdp_portal_screenshare(data)) {
2430 		purple_debug_info("pidgin", "Attempt XDP portal screenshare\n");
2431 		return data;
2432 	}
2433 #endif
2434 
2435 	purple_debug_info("pidgin", "Using direct screenshare\n");
2436 
2437 	/* Show everything. */
2438 	pidgin_auto_parent_window(dialog);
2439 
2440 	gtk_widget_show_all(dialog);
2441 
2442 	return data;
2443 
2444 }
2445 #endif /* USE_VV */
2446 
2447 static void
pidgin_close_request(PurpleRequestType type,void * ui_handle)2448 pidgin_close_request(PurpleRequestType type, void *ui_handle)
2449 {
2450 	PidginRequestData *data = (PidginRequestData *)ui_handle;
2451 
2452 	g_free(data->cbs);
2453 
2454 	if (data->dialog) {
2455 		gtk_widget_destroy(data->dialog);
2456 	}
2457 
2458 	if (type == PURPLE_REQUEST_FIELDS) {
2459 		purple_request_fields_destroy(data->u.multifield.fields);
2460 	} else if (type == PURPLE_REQUEST_FILE) {
2461 		g_free(data->u.file.name);
2462 	} else if (type == PURPLE_REQUEST_SCREENSHARE) {
2463 #ifdef HAVE_GIOUNIX
2464 		g_cancellable_cancel(data->u.screenshare.cancellable);
2465 		if (data->u.screenshare.signal_id)
2466 			g_dbus_connection_signal_unsubscribe(data->u.screenshare.dbus_connection,
2467 							     data->u.screenshare.signal_id);
2468 		g_clear_object(&data->u.screenshare.dbus_connection);
2469 		g_free(data->u.screenshare.session_path);
2470 		g_clear_object(&data->u.screenshare.cancellable);
2471 #endif
2472 	}
2473 
2474 	g_free(data);
2475 }
2476 
2477 static PurpleRequestUiOps ops =
2478 {
2479 	pidgin_request_input,
2480 	pidgin_request_choice,
2481 	pidgin_request_action,
2482 	pidgin_request_fields,
2483 	pidgin_request_file,
2484 	pidgin_close_request,
2485 	pidgin_request_folder,
2486 	pidgin_request_action_with_icon,
2487 #ifdef USE_VV
2488 	pidgin_request_screenshare_media,
2489 #else
2490 	NULL,
2491 #endif
2492 	NULL,
2493 	NULL
2494 };
2495 
2496 PurpleRequestUiOps *
pidgin_request_get_ui_ops(void)2497 pidgin_request_get_ui_ops(void)
2498 {
2499 	return &ops;
2500 }
2501