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