1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* GTK - The GIMP Toolkit
3 * Copyright (C) Christian Kellner <gicmo@gnome.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/*
20 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
21 * file for a list of people on the GTK+ Team. See the ChangeLog
22 * files for a list of changes. These files are distributed with
23 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24 */
25
26#include "config.h"
27
28#include <errno.h>
29#include <string.h>
30
31#include "gtkmountoperationprivate.h"
32#include "gtkbox.h"
33#include "gtkdbusgenerated.h"
34#include "gtkentry.h"
35#include "gtkbox.h"
36#include "gtkintl.h"
37#include "gtklabel.h"
38#include "gtkmessagedialog.h"
39#include "gtkmountoperation.h"
40#include "gtkprivate.h"
41#include "gtkcheckbutton.h"
42#include "gtkgrid.h"
43#include "gtkwindow.h"
44#include "gtktreeview.h"
45#include "gtktreeselection.h"
46#include "gtkcellrenderertext.h"
47#include "gtkcellrendererpixbuf.h"
48#include "gtkscrolledwindow.h"
49#include "gtkicontheme.h"
50#include "gtkmain.h"
51#include "gtksettings.h"
52#include "gtkdialogprivate.h"
53#include "gtkgestureclick.h"
54#include "gtkmodelbuttonprivate.h"
55#include "gtkpopover.h"
56#include "gtksnapshot.h"
57#include "gdktextureprivate.h"
58#include "gtkshortcutcontroller.h"
59#include "gtkshortcuttrigger.h"
60#include "gtkshortcutaction.h"
61#include "gtkshortcut.h"
62#include "gtkliststore.h"
63#include <glib/gprintf.h>
64
65/**
66 * GtkMountOperation:
67 *
68 * `GtkMountOperation` is an implementation of `GMountOperation`.
69 *
70 * The functions and objects described here make working with GTK and
71 * GIO more convenient.
72 *
73 * `GtkMountOperation` is needed when mounting volumes:
74 * It is an implementation of `GMountOperation` that can be used with
75 * GIO functions for mounting volumes such as
76 * g_file_mount_enclosing_volume(), g_file_mount_mountable(),
77 * g_volume_mount(), g_mount_unmount_with_operation() and others.
78 *
79 * When necessary, `GtkMountOperation` shows dialogs to let the user
80 * enter passwords, ask questions or show processes blocking unmount.
81 */
82
83static void gtk_mount_operation_finalize (GObject *object);
84static void gtk_mount_operation_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec);
88static void gtk_mount_operation_get_property (GObject *object,
89 guint prop_id,
90 GValue *value,
91 GParamSpec *pspec);
92
93static void gtk_mount_operation_ask_password (GMountOperation *op,
94 const char *message,
95 const char *default_user,
96 const char *default_domain,
97 GAskPasswordFlags flags);
98
99static void gtk_mount_operation_ask_question (GMountOperation *op,
100 const char *message,
101 const char *choices[]);
102
103static void gtk_mount_operation_show_processes (GMountOperation *op,
104 const char *message,
105 GArray *processes,
106 const char *choices[]);
107
108static void gtk_mount_operation_aborted (GMountOperation *op);
109
110struct _GtkMountOperationPrivate {
111 GtkWindow *parent_window;
112 GtkDialog *dialog;
113 GdkDisplay *display;
114
115 /* bus proxy */
116 _GtkMountOperationHandler *handler;
117 GCancellable *cancellable;
118 gboolean handler_showing;
119
120 /* for the ask-password dialog */
121 GtkWidget *grid;
122 GtkWidget *username_entry;
123 GtkWidget *domain_entry;
124 GtkWidget *password_entry;
125 GtkWidget *pim_entry;
126 GtkWidget *anonymous_toggle;
127 GtkWidget *tcrypt_hidden_toggle;
128 GtkWidget *tcrypt_system_toggle;
129 GList *user_widgets;
130
131 GAskPasswordFlags ask_flags;
132 GPasswordSave password_save;
133 gboolean anonymous;
134
135 /* for the show-processes dialog */
136 GtkWidget *process_tree_view;
137 GtkListStore *process_list_store;
138};
139
140enum {
141 PROP_0,
142 PROP_PARENT,
143 PROP_IS_SHOWING,
144 PROP_DISPLAY
145
146};
147
148G_DEFINE_TYPE_WITH_PRIVATE (GtkMountOperation, gtk_mount_operation, G_TYPE_MOUNT_OPERATION)
149
150static void
151gtk_mount_operation_class_init (GtkMountOperationClass *klass)
152{
153 GObjectClass *object_class = G_OBJECT_CLASS (klass);
154 GMountOperationClass *mount_op_class = G_MOUNT_OPERATION_CLASS (klass);
155
156 object_class->finalize = gtk_mount_operation_finalize;
157 object_class->get_property = gtk_mount_operation_get_property;
158 object_class->set_property = gtk_mount_operation_set_property;
159
160 mount_op_class->ask_password = gtk_mount_operation_ask_password;
161 mount_op_class->ask_question = gtk_mount_operation_ask_question;
162 mount_op_class->show_processes = gtk_mount_operation_show_processes;
163 mount_op_class->aborted = gtk_mount_operation_aborted;
164
165 /**
166 * GtkMountOperation:parent: (attributes org.gtk.Property.get=gtk_mount_operation_get_parent org.gtk.Property.set=gtk_mount_operation_set_parent)
167 *
168 * The parent window.
169 */
170 g_object_class_install_property (oclass: object_class,
171 property_id: PROP_PARENT,
172 pspec: g_param_spec_object (name: "parent",
173 P_("Parent"),
174 P_("The parent window"),
175 GTK_TYPE_WINDOW,
176 GTK_PARAM_READWRITE));
177
178 /**
179 * GtkMountOperation:is-showing: (attributes org.gtk.Property.get=gtk_mount_operation_is_showing)
180 *
181 * Whether a dialog is currently shown.
182 */
183 g_object_class_install_property (oclass: object_class,
184 property_id: PROP_IS_SHOWING,
185 pspec: g_param_spec_boolean (name: "is-showing",
186 P_("Is Showing"),
187 P_("Are we showing a dialog"),
188 FALSE,
189 GTK_PARAM_READABLE));
190
191 /**
192 * GtkMountOperation:display: (attributes org.gtk.Property.get=gtk_mount_operation_get_display org.gtk.Property.set=gtk_mount_operation_set_display)
193 *
194 * The display where dialogs will be shown.
195 */
196 g_object_class_install_property (oclass: object_class,
197 property_id: PROP_DISPLAY,
198 pspec: g_param_spec_object (name: "display",
199 P_("Display"),
200 P_("The display where this window will be displayed."),
201 GDK_TYPE_DISPLAY,
202 GTK_PARAM_READWRITE));
203}
204
205static void
206gtk_mount_operation_init (GtkMountOperation *operation)
207{
208 char *name_owner;
209
210 operation->priv = gtk_mount_operation_get_instance_private (self: operation);
211
212 operation->priv->handler =
213 _gtk_mount_operation_handler_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
214 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
215 "org.gtk.MountOperationHandler",
216 "/org/gtk/MountOperationHandler",
217 NULL, NULL);
218 if (!operation->priv->handler)
219 return;
220
221 name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (operation->priv->handler));
222 if (!name_owner)
223 g_clear_object (&operation->priv->handler);
224 g_free (mem: name_owner);
225
226 if (operation->priv->handler)
227 g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (operation->priv->handler), G_MAXINT);
228}
229
230static void
231parent_destroyed (GtkWidget *parent,
232 gpointer **pointer)
233{
234 *pointer = NULL;
235}
236
237static void
238gtk_mount_operation_finalize (GObject *object)
239{
240 GtkMountOperation *operation = GTK_MOUNT_OPERATION (object);
241 GtkMountOperationPrivate *priv = operation->priv;
242
243 if (priv->user_widgets)
244 g_list_free (list: priv->user_widgets);
245
246 if (priv->parent_window)
247 {
248 g_signal_handlers_disconnect_by_func (priv->parent_window,
249 parent_destroyed,
250 &priv->parent_window);
251 g_object_unref (object: priv->parent_window);
252 }
253
254 if (priv->display)
255 g_object_unref (object: priv->display);
256
257 if (priv->handler)
258 g_object_unref (object: priv->handler);
259
260 G_OBJECT_CLASS (gtk_mount_operation_parent_class)->finalize (object);
261}
262
263static void
264gtk_mount_operation_set_property (GObject *object,
265 guint prop_id,
266 const GValue *value,
267 GParamSpec *pspec)
268{
269 GtkMountOperation *operation = GTK_MOUNT_OPERATION (object);
270
271 switch (prop_id)
272 {
273 case PROP_PARENT:
274 gtk_mount_operation_set_parent (op: operation, parent: g_value_get_object (value));
275 break;
276
277 case PROP_DISPLAY:
278 gtk_mount_operation_set_display (op: operation, display: g_value_get_object (value));
279 break;
280
281 case PROP_IS_SHOWING:
282 default:
283 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
284 break;
285 }
286}
287
288static void
289gtk_mount_operation_get_property (GObject *object,
290 guint prop_id,
291 GValue *value,
292 GParamSpec *pspec)
293{
294 GtkMountOperation *operation = GTK_MOUNT_OPERATION (object);
295 GtkMountOperationPrivate *priv = operation->priv;
296
297 switch (prop_id)
298 {
299 case PROP_PARENT:
300 g_value_set_object (value, v_object: priv->parent_window);
301 break;
302
303 case PROP_IS_SHOWING:
304 g_value_set_boolean (value, v_boolean: priv->dialog != NULL || priv->handler_showing);
305 break;
306
307 case PROP_DISPLAY:
308 g_value_set_object (value, v_object: priv->display);
309 break;
310
311 default:
312 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
313 break;
314 }
315}
316
317static void
318gtk_mount_operation_proxy_finish (GtkMountOperation *op,
319 GMountOperationResult result)
320{
321 _gtk_mount_operation_handler_call_close (op->priv->handler, NULL, NULL, NULL);
322
323 op->priv->handler_showing = FALSE;
324 g_object_notify (G_OBJECT (op), property_name: "is-showing");
325
326 g_mount_operation_reply (G_MOUNT_OPERATION (op), result);
327
328 /* drop the reference acquired when calling the proxy method */
329 g_object_unref (object: op);
330}
331
332static void
333remember_button_toggled (GtkCheckButton *button,
334 GtkMountOperation *operation)
335{
336 GtkMountOperationPrivate *priv = operation->priv;
337
338 if (gtk_check_button_get_active (self: button))
339 {
340 gpointer data;
341
342 data = g_object_get_data (G_OBJECT (button), key: "password-save");
343 priv->password_save = GPOINTER_TO_INT (data);
344 }
345}
346
347static void
348pw_dialog_got_response (GtkDialog *dialog,
349 int response_id,
350 GtkMountOperation *mount_op)
351{
352 GtkMountOperationPrivate *priv = mount_op->priv;
353 GMountOperation *op = G_MOUNT_OPERATION (mount_op);
354
355 if (response_id == GTK_RESPONSE_OK)
356 {
357 const char *text;
358
359 if (priv->ask_flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED)
360 g_mount_operation_set_anonymous (op, anonymous: priv->anonymous);
361
362 if (priv->username_entry)
363 {
364 text = gtk_editable_get_text (GTK_EDITABLE (priv->username_entry));
365 g_mount_operation_set_username (op, username: text);
366 }
367
368 if (priv->domain_entry)
369 {
370 text = gtk_editable_get_text (GTK_EDITABLE (priv->domain_entry));
371 g_mount_operation_set_domain (op, domain: text);
372 }
373
374 if (priv->password_entry)
375 {
376 text = gtk_editable_get_text (GTK_EDITABLE (priv->password_entry));
377 g_mount_operation_set_password (op, password: text);
378 }
379
380 if (priv->pim_entry)
381 {
382 text = gtk_editable_get_text (GTK_EDITABLE (priv->pim_entry));
383 if (text && strlen (s: text) > 0)
384 {
385 guint64 pim;
386 char *end = NULL;
387
388 errno = 0;
389 pim = g_ascii_strtoull (nptr: text, endptr: &end, base: 10);
390 if (errno == 0 && pim <= G_MAXUINT && end != text)
391 g_mount_operation_set_pim (op, pim: (guint) pim);
392 }
393 }
394
395 if (priv->tcrypt_hidden_toggle && gtk_check_button_get_active (GTK_CHECK_BUTTON (priv->tcrypt_hidden_toggle)))
396 g_mount_operation_set_is_tcrypt_hidden_volume (op, TRUE);
397
398 if (priv->tcrypt_system_toggle && gtk_check_button_get_active (GTK_CHECK_BUTTON (priv->tcrypt_system_toggle)))
399 g_mount_operation_set_is_tcrypt_system_volume (op, TRUE);
400
401 if (priv->ask_flags & G_ASK_PASSWORD_SAVING_SUPPORTED)
402 g_mount_operation_set_password_save (op, save: priv->password_save);
403
404 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_HANDLED);
405 }
406 else
407 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_ABORTED);
408
409 priv->dialog = NULL;
410 g_object_notify (G_OBJECT (op), property_name: "is-showing");
411 gtk_window_destroy (GTK_WINDOW (dialog));
412 g_object_unref (object: op);
413}
414
415static gboolean
416entry_has_input (GtkWidget *entry_widget)
417{
418 const char *text;
419
420 if (entry_widget == NULL)
421 return TRUE;
422
423 text = gtk_editable_get_text (GTK_EDITABLE (entry_widget));
424
425 return text != NULL && text[0] != '\0';
426}
427
428static gboolean
429pim_entry_is_valid (GtkWidget *entry_widget)
430{
431 const char *text;
432 char *end = NULL;
433 guint64 pim;
434
435 if (entry_widget == NULL)
436 return TRUE;
437
438 text = gtk_editable_get_text (GTK_EDITABLE (entry_widget));
439 /* An empty PIM entry is OK */
440 if (text == NULL || text[0] == '\0')
441 return TRUE;
442
443 errno = 0;
444 pim = g_ascii_strtoull (nptr: text, endptr: &end, base: 10);
445 if (errno || pim > G_MAXUINT || end == text)
446 return FALSE;
447 else
448 return TRUE;
449}
450
451static gboolean
452pw_dialog_input_is_valid (GtkMountOperation *operation)
453{
454 GtkMountOperationPrivate *priv = operation->priv;
455 gboolean is_valid = TRUE;
456
457 /* We don't require password to be non-empty here
458 * since there are situations where it is not needed,
459 * see bug 578365.
460 * We may add a way for the backend to specify that it
461 * definitively needs a password.
462 */
463 is_valid = entry_has_input (entry_widget: priv->username_entry) &&
464 entry_has_input (entry_widget: priv->domain_entry) &&
465 pim_entry_is_valid (entry_widget: priv->pim_entry);
466
467 return is_valid;
468}
469
470static void
471pw_dialog_verify_input (GtkEditable *editable,
472 GtkMountOperation *operation)
473{
474 GtkMountOperationPrivate *priv = operation->priv;
475 gboolean is_valid;
476
477 is_valid = pw_dialog_input_is_valid (operation);
478 gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog),
479 response_id: GTK_RESPONSE_OK,
480 setting: is_valid);
481}
482
483static void
484pw_dialog_anonymous_toggled (GtkWidget *widget,
485 GtkMountOperation *operation)
486{
487 GtkMountOperationPrivate *priv = operation->priv;
488 gboolean is_valid;
489 GList *l;
490
491 priv->anonymous = widget == priv->anonymous_toggle;
492
493 if (priv->anonymous)
494 is_valid = TRUE;
495 else
496 is_valid = pw_dialog_input_is_valid (operation);
497
498 for (l = priv->user_widgets; l != NULL; l = l->next)
499 {
500 gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive: !priv->anonymous);
501 }
502
503 gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog),
504 response_id: GTK_RESPONSE_OK,
505 setting: is_valid);
506}
507
508
509static void
510pw_dialog_cycle_focus (GtkWidget *widget,
511 GtkMountOperation *operation)
512{
513 GtkMountOperationPrivate *priv;
514 GtkWidget *next_widget = NULL;
515
516 priv = operation->priv;
517
518 if (widget == priv->username_entry)
519 {
520 if (priv->domain_entry != NULL)
521 next_widget = priv->domain_entry;
522 else if (priv->password_entry != NULL)
523 next_widget = priv->password_entry;
524 }
525 else if (widget == priv->domain_entry && priv->password_entry)
526 next_widget = priv->password_entry;
527
528 if (next_widget)
529 gtk_widget_grab_focus (widget: next_widget);
530 else if (pw_dialog_input_is_valid (operation))
531 gtk_widget_activate_default (widget);
532}
533
534static GtkWidget *
535table_add_entry (GtkMountOperation *operation,
536 int row,
537 const char *label_text,
538 const char *value,
539 gpointer user_data)
540{
541 GtkWidget *entry;
542 GtkWidget *label;
543
544 label = gtk_label_new_with_mnemonic (str: label_text);
545 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_END);
546 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_CENTER);
547 gtk_widget_set_hexpand (widget: label, FALSE);
548 operation->priv->user_widgets = g_list_prepend (list: operation->priv->user_widgets, data: label);
549
550 entry = gtk_entry_new ();
551 gtk_widget_set_hexpand (widget: entry, TRUE);
552
553 if (value)
554 gtk_editable_set_text (GTK_EDITABLE (entry), text: value);
555
556 gtk_grid_attach (GTK_GRID (operation->priv->grid), child: label, column: 0, row, width: 1, height: 1);
557 gtk_grid_attach (GTK_GRID (operation->priv->grid), child: entry, column: 1, row, width: 1, height: 1);
558 gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget: entry);
559 operation->priv->user_widgets = g_list_prepend (list: operation->priv->user_widgets, data: entry);
560
561 g_signal_connect (entry, "changed",
562 G_CALLBACK (pw_dialog_verify_input), user_data);
563
564 g_signal_connect (entry, "activate",
565 G_CALLBACK (pw_dialog_cycle_focus), user_data);
566
567 return entry;
568}
569
570static void
571gtk_mount_operation_ask_password_do_gtk (GtkMountOperation *operation,
572 const char *message,
573 const char *default_user,
574 const char *default_domain)
575{
576 GtkMountOperationPrivate *priv;
577 GtkWidget *widget;
578 GtkDialog *dialog;
579 GtkWindow *window;
580 GtkWidget *hbox, *main_vbox, *icon;
581 GtkWidget *grid;
582 GtkWidget *label;
583 GtkWidget *content_area;
584 gboolean can_anonymous;
585 guint rows;
586 char *primary;
587 const char *secondary = NULL;
588 gboolean use_header;
589
590 priv = operation->priv;
591
592 g_object_get (object: gtk_settings_get_default (),
593 first_property_name: "gtk-dialogs-use-header", &use_header,
594 NULL);
595 widget = g_object_new (GTK_TYPE_DIALOG,
596 first_property_name: "use-header-bar", use_header,
597 NULL);
598 dialog = GTK_DIALOG (widget);
599 window = GTK_WINDOW (widget);
600
601 priv->dialog = dialog;
602
603 content_area = gtk_dialog_get_content_area (dialog);
604
605 gtk_window_set_resizable (window, FALSE);
606 gtk_window_set_title (window, title: "");
607 gtk_window_set_icon_name (window, name: "dialog-password");
608
609 gtk_dialog_add_buttons (dialog,
610 _("_Cancel"), GTK_RESPONSE_CANCEL,
611 _("Co_nnect"), GTK_RESPONSE_OK,
612 NULL);
613 gtk_dialog_set_default_response (dialog, response_id: GTK_RESPONSE_OK);
614
615
616 /* Build contents */
617 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 12);
618 g_object_set (object: hbox,
619 first_property_name: "margin-start", 12,
620 "margin-end", 12,
621 "margin-top", 12,
622 "margin-bottom", 12,
623 NULL);
624 gtk_box_append (GTK_BOX (content_area), child: hbox);
625
626 icon = gtk_image_new_from_icon_name (icon_name: "dialog-password");
627 gtk_image_set_icon_size (GTK_IMAGE (icon), icon_size: GTK_ICON_SIZE_LARGE);
628
629 gtk_widget_set_halign (widget: icon, align: GTK_ALIGN_CENTER);
630 gtk_widget_set_valign (widget: icon, align: GTK_ALIGN_START);
631 gtk_box_append (GTK_BOX (hbox), child: icon);
632
633 main_vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 18);
634 gtk_box_append (GTK_BOX (hbox), child: main_vbox);
635
636 primary = strstr (haystack: message, needle: "\n");
637 if (primary)
638 {
639 secondary = primary + 1;
640 primary = g_strndup (str: message, n: primary - message);
641 }
642
643 label = gtk_label_new (str: primary != NULL ? primary : message);
644 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
645 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_CENTER);
646 gtk_label_set_wrap (GTK_LABEL (label), TRUE);
647 gtk_box_append (GTK_BOX (main_vbox), GTK_WIDGET (label));
648 g_free (mem: primary);
649 gtk_widget_add_css_class (widget: label, css_class: "title-3");
650
651 if (secondary != NULL)
652 {
653 label = gtk_label_new (str: secondary);
654 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
655 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_CENTER);
656 gtk_label_set_wrap (GTK_LABEL (label), TRUE);
657 gtk_box_append (GTK_BOX (main_vbox), GTK_WIDGET (label));
658 }
659
660 grid = gtk_grid_new ();
661 operation->priv->grid = grid;
662 gtk_grid_set_row_spacing (GTK_GRID (grid), spacing: 12);
663 gtk_grid_set_column_spacing (GTK_GRID (grid), spacing: 12);
664 gtk_widget_set_margin_bottom (widget: grid, margin: 12);
665 gtk_box_append (GTK_BOX (main_vbox), child: grid);
666
667 can_anonymous = priv->ask_flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED;
668
669 rows = 0;
670
671 priv->anonymous_toggle = NULL;
672 if (can_anonymous)
673 {
674 GtkWidget *anon_box;
675 GtkWidget *choice;
676
677 label = gtk_label_new (_("Connect As"));
678 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_END);
679 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_START);
680 gtk_widget_set_hexpand (widget: label, FALSE);
681 gtk_grid_attach (GTK_GRID (grid), child: label, column: 0, row: rows, width: 1, height: 1);
682
683 anon_box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
684 gtk_grid_attach (GTK_GRID (grid), child: anon_box, column: 1, row: rows++, width: 1, height: 1);
685
686 choice = gtk_check_button_new_with_mnemonic (_("_Anonymous"));
687 gtk_box_append (GTK_BOX (anon_box), child: choice);
688 g_signal_connect (choice, "toggled",
689 G_CALLBACK (pw_dialog_anonymous_toggled), operation);
690 priv->anonymous_toggle = choice;
691
692 choice = gtk_check_button_new_with_mnemonic (_("Registered U_ser"));
693 gtk_check_button_set_group (GTK_CHECK_BUTTON (choice), GTK_CHECK_BUTTON (priv->anonymous_toggle));
694 gtk_box_append (GTK_BOX (anon_box), child: choice);
695 g_signal_connect (choice, "toggled",
696 G_CALLBACK (pw_dialog_anonymous_toggled), operation);
697 }
698
699 priv->username_entry = NULL;
700
701 if (priv->ask_flags & G_ASK_PASSWORD_NEED_USERNAME)
702 priv->username_entry = table_add_entry (operation, row: rows++, _("_Username"),
703 value: default_user, user_data: operation);
704
705 priv->domain_entry = NULL;
706 if (priv->ask_flags & G_ASK_PASSWORD_NEED_DOMAIN)
707 priv->domain_entry = table_add_entry (operation, row: rows++, _("_Domain"),
708 value: default_domain, user_data: operation);
709
710 priv->pim_entry = NULL;
711 if (priv->ask_flags & G_ASK_PASSWORD_TCRYPT)
712 {
713 GtkWidget *volume_type_label;
714 GtkWidget *volume_type_box;
715
716 volume_type_label = gtk_label_new (_("Volume type"));
717 gtk_widget_set_halign (widget: volume_type_label, align: GTK_ALIGN_END);
718 gtk_widget_set_hexpand (widget: volume_type_label, FALSE);
719 gtk_grid_attach (GTK_GRID (grid), child: volume_type_label, column: 0, row: rows, width: 1, height: 1);
720 priv->user_widgets = g_list_prepend (list: priv->user_widgets, data: volume_type_label);
721
722 volume_type_box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
723 gtk_grid_attach (GTK_GRID (grid), child: volume_type_box, column: 1, row: rows++, width: 1, height: 1);
724 priv->user_widgets = g_list_prepend (list: priv->user_widgets, data: volume_type_box);
725
726 priv->tcrypt_hidden_toggle = gtk_check_button_new_with_mnemonic (_("_Hidden"));
727 gtk_box_append (GTK_BOX (volume_type_box), child: priv->tcrypt_hidden_toggle);
728
729 priv->tcrypt_system_toggle = gtk_check_button_new_with_mnemonic (_("_Windows system"));
730 gtk_box_append (GTK_BOX (volume_type_box), child: priv->tcrypt_system_toggle);
731
732 priv->pim_entry = table_add_entry (operation, row: rows++, _("_PIM"), NULL, user_data: operation);
733 }
734
735 priv->password_entry = NULL;
736 if (priv->ask_flags & G_ASK_PASSWORD_NEED_PASSWORD)
737 {
738 priv->password_entry = table_add_entry (operation, row: rows++, _("_Password"),
739 NULL, user_data: operation);
740 gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry), FALSE);
741 }
742
743 if (priv->ask_flags & G_ASK_PASSWORD_SAVING_SUPPORTED)
744 {
745 GtkWidget *remember_box;
746 GtkWidget *choice;
747 GtkWidget *group;
748 GPasswordSave password_save;
749
750 remember_box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
751 gtk_grid_attach (GTK_GRID (grid), child: remember_box, column: 0, row: rows++, width: 2, height: 1);
752 priv->user_widgets = g_list_prepend (list: priv->user_widgets, data: remember_box);
753
754 label = gtk_label_new (str: "");
755 gtk_box_append (GTK_BOX (remember_box), child: label);
756
757 password_save = g_mount_operation_get_password_save (G_MOUNT_OPERATION (operation));
758 priv->password_save = password_save;
759
760 choice = gtk_check_button_new_with_mnemonic (_("Forget password _immediately"));
761 gtk_check_button_set_active (GTK_CHECK_BUTTON (choice),
762 setting: password_save == G_PASSWORD_SAVE_NEVER);
763 g_object_set_data (G_OBJECT (choice), key: "password-save",
764 GINT_TO_POINTER (G_PASSWORD_SAVE_NEVER));
765 g_signal_connect (choice, "toggled",
766 G_CALLBACK (remember_button_toggled), operation);
767 gtk_box_append (GTK_BOX (remember_box), child: choice);
768 group = choice;
769
770 choice = gtk_check_button_new_with_mnemonic (_("Remember password until you _logout"));
771 gtk_check_button_set_group (GTK_CHECK_BUTTON (choice), GTK_CHECK_BUTTON (group));
772 gtk_check_button_set_active (GTK_CHECK_BUTTON (choice),
773 setting: password_save == G_PASSWORD_SAVE_FOR_SESSION);
774 g_object_set_data (G_OBJECT (choice), key: "password-save",
775 GINT_TO_POINTER (G_PASSWORD_SAVE_FOR_SESSION));
776 g_signal_connect (choice, "toggled",
777 G_CALLBACK (remember_button_toggled), operation);
778 gtk_box_append (GTK_BOX (remember_box), child: choice);
779 group = choice;
780
781 choice = gtk_check_button_new_with_mnemonic (_("Remember _forever"));
782 gtk_check_button_set_group (GTK_CHECK_BUTTON (choice), GTK_CHECK_BUTTON (group));
783 gtk_check_button_set_active (GTK_CHECK_BUTTON (choice),
784 setting: password_save == G_PASSWORD_SAVE_PERMANENTLY);
785 g_object_set_data (G_OBJECT (choice), key: "password-save",
786 GINT_TO_POINTER (G_PASSWORD_SAVE_PERMANENTLY));
787 g_signal_connect (choice, "toggled",
788 G_CALLBACK (remember_button_toggled), operation);
789 gtk_box_append (GTK_BOX (remember_box), child: choice);
790 }
791
792 g_signal_connect (G_OBJECT (dialog), "response",
793 G_CALLBACK (pw_dialog_got_response), operation);
794
795 if (can_anonymous)
796 {
797 /* The anonymous option will be active by default,
798 * ensure the toggled signal is emitted for it.
799 */
800 g_signal_emit_by_name (instance: priv->anonymous_toggle, detailed_signal: "toggled");
801 }
802 else if (! pw_dialog_input_is_valid (operation))
803 gtk_dialog_set_response_sensitive (dialog, response_id: GTK_RESPONSE_OK, FALSE);
804
805 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
806
807 if (priv->parent_window)
808 {
809 gtk_window_set_transient_for (window, parent: priv->parent_window);
810 gtk_window_set_modal (window, TRUE);
811 }
812 else if (priv->display)
813 gtk_window_set_display (GTK_WINDOW (dialog), display: priv->display);
814
815 gtk_widget_show (GTK_WIDGET (dialog));
816
817 g_object_ref (operation);
818}
819
820static void
821call_password_proxy_cb (GObject *source,
822 GAsyncResult *res,
823 gpointer user_data)
824{
825 _GtkMountOperationHandler *proxy = _GTK_MOUNT_OPERATION_HANDLER (source);
826 GMountOperation *op = user_data;
827 GMountOperationResult result;
828 GVariant *result_details;
829 GVariantIter iter;
830 const char *key;
831 GVariant *value;
832 GError *error = NULL;
833
834 if (!_gtk_mount_operation_handler_call_ask_password_finish (proxy,
835 &result,
836 &result_details,
837 res,
838 &error))
839 {
840 result = G_MOUNT_OPERATION_ABORTED;
841 g_warning ("Shell mount operation error: %s", error->message);
842 g_error_free (error);
843 goto out;
844 }
845
846 g_variant_iter_init (iter: &iter, value: result_details);
847 while (g_variant_iter_loop (iter: &iter, format_string: "{&sv}", &key, &value))
848 {
849 if (strcmp (s1: key, s2: "password") == 0)
850 g_mount_operation_set_password (op, password: g_variant_get_string (value, NULL));
851 else if (strcmp (s1: key, s2: "password_save") == 0)
852 g_mount_operation_set_password_save (op, save: g_variant_get_uint32 (value));
853 else if (strcmp (s1: key, s2: "hidden_volume") == 0)
854 g_mount_operation_set_is_tcrypt_hidden_volume (op, hidden_volume: g_variant_get_boolean (value));
855 else if (strcmp (s1: key, s2: "system_volume") == 0)
856 g_mount_operation_set_is_tcrypt_system_volume (op, system_volume: g_variant_get_boolean (value));
857 else if (strcmp (s1: key, s2: "pim") == 0)
858 g_mount_operation_set_pim (op, pim: g_variant_get_uint32 (value));
859 }
860
861 out:
862 gtk_mount_operation_proxy_finish (GTK_MOUNT_OPERATION (op), result);
863}
864
865static void
866gtk_mount_operation_ask_password_do_proxy (GtkMountOperation *operation,
867 const char *message,
868 const char *default_user,
869 const char *default_domain)
870{
871 char id[255];
872 g_sprintf(string: id, format: "GtkMountOperation%p", operation);
873
874 operation->priv->handler_showing = TRUE;
875 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
876
877 /* keep a ref to the operation while the handler is showing */
878 g_object_ref (operation);
879
880 _gtk_mount_operation_handler_call_ask_password (operation->priv->handler, id,
881 message, "drive-harddisk",
882 default_user, default_domain,
883 operation->priv->ask_flags, NULL,
884 call_password_proxy_cb, operation);
885}
886
887static void
888gtk_mount_operation_ask_password (GMountOperation *mount_op,
889 const char *message,
890 const char *default_user,
891 const char *default_domain,
892 GAskPasswordFlags flags)
893{
894 GtkMountOperation *operation;
895 GtkMountOperationPrivate *priv;
896 gboolean use_gtk;
897
898 operation = GTK_MOUNT_OPERATION (mount_op);
899 priv = operation->priv;
900 priv->ask_flags = flags;
901
902 use_gtk = (operation->priv->handler == NULL) ||
903 (priv->ask_flags & G_ASK_PASSWORD_NEED_DOMAIN) ||
904 (priv->ask_flags & G_ASK_PASSWORD_NEED_USERNAME);
905
906 if (use_gtk)
907 gtk_mount_operation_ask_password_do_gtk (operation, message, default_user, default_domain);
908 else
909 gtk_mount_operation_ask_password_do_proxy (operation, message, default_user, default_domain);
910}
911
912static void
913question_dialog_button_clicked (GtkDialog *dialog,
914 int button_number,
915 GMountOperation *op)
916{
917 GtkMountOperationPrivate *priv;
918 GtkMountOperation *operation;
919
920 operation = GTK_MOUNT_OPERATION (op);
921 priv = operation->priv;
922
923 if (button_number >= 0)
924 {
925 g_mount_operation_set_choice (op, choice: button_number);
926 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_HANDLED);
927 }
928 else
929 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_ABORTED);
930
931 priv->dialog = NULL;
932 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
933 gtk_window_destroy (GTK_WINDOW (dialog));
934 g_object_unref (object: op);
935}
936
937static void
938gtk_mount_operation_ask_question_do_gtk (GtkMountOperation *op,
939 const char *message,
940 const char *choices[])
941{
942 GtkMountOperationPrivate *priv;
943 GtkWidget *dialog;
944 const char *secondary = NULL;
945 char *primary;
946 int count, len = 0;
947
948 g_return_if_fail (GTK_IS_MOUNT_OPERATION (op));
949 g_return_if_fail (message != NULL);
950 g_return_if_fail (choices != NULL);
951
952 priv = op->priv;
953
954 primary = strstr (haystack: message, needle: "\n");
955 if (primary)
956 {
957 secondary = primary + 1;
958 primary = g_strndup (str: message, n: primary - message);
959 }
960
961 dialog = gtk_message_dialog_new (parent: priv->parent_window, flags: 0,
962 type: GTK_MESSAGE_QUESTION,
963 buttons: GTK_BUTTONS_NONE, message_format: "%s",
964 primary != NULL ? primary : message);
965 g_free (mem: primary);
966
967 if (secondary)
968 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
969 message_format: "%s", secondary);
970
971 /* First count the items in the list then
972 * add the buttons in reverse order */
973
974 while (choices[len] != NULL)
975 len++;
976
977 for (count = len - 1; count >= 0; count--)
978 gtk_dialog_add_button (GTK_DIALOG (dialog), button_text: choices[count], response_id: count);
979
980 g_signal_connect (G_OBJECT (dialog), "response",
981 G_CALLBACK (question_dialog_button_clicked), op);
982
983 priv->dialog = GTK_DIALOG (dialog);
984 g_object_notify (G_OBJECT (op), property_name: "is-showing");
985
986 if (priv->parent_window == NULL && priv->display)
987 gtk_window_set_display (GTK_WINDOW (dialog), display: priv->display);
988
989 gtk_widget_show (widget: dialog);
990 g_object_ref (op);
991}
992
993static void
994call_question_proxy_cb (GObject *source,
995 GAsyncResult *res,
996 gpointer user_data)
997{
998 _GtkMountOperationHandler *proxy = _GTK_MOUNT_OPERATION_HANDLER (source);
999 GMountOperation *op = user_data;
1000 GMountOperationResult result;
1001 GVariant *result_details;
1002 GVariantIter iter;
1003 const char *key;
1004 GVariant *value;
1005 GError *error = NULL;
1006
1007 if (!_gtk_mount_operation_handler_call_ask_question_finish (proxy,
1008 &result,
1009 &result_details,
1010 res,
1011 &error))
1012 {
1013 result = G_MOUNT_OPERATION_ABORTED;
1014 g_warning ("Shell mount operation error: %s", error->message);
1015 g_error_free (error);
1016 goto out;
1017 }
1018
1019 g_variant_iter_init (iter: &iter, value: result_details);
1020 while (g_variant_iter_loop (iter: &iter, format_string: "{&sv}", &key, &value))
1021 {
1022 if (strcmp (s1: key, s2: "choice") == 0)
1023 g_mount_operation_set_choice (op, choice: g_variant_get_int32 (value));
1024 }
1025
1026 out:
1027 gtk_mount_operation_proxy_finish (GTK_MOUNT_OPERATION (op), result);
1028}
1029
1030static void
1031gtk_mount_operation_ask_question_do_proxy (GtkMountOperation *operation,
1032 const char *message,
1033 const char *choices[])
1034{
1035 char id[255];
1036 g_sprintf(string: id, format: "GtkMountOperation%p", operation);
1037
1038 operation->priv->handler_showing = TRUE;
1039 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
1040
1041 /* keep a ref to the operation while the handler is showing */
1042 g_object_ref (operation);
1043
1044 _gtk_mount_operation_handler_call_ask_question (operation->priv->handler, id,
1045 message, "drive-harddisk",
1046 choices, NULL,
1047 call_question_proxy_cb, operation);
1048}
1049
1050static void
1051gtk_mount_operation_ask_question (GMountOperation *op,
1052 const char *message,
1053 const char *choices[])
1054{
1055 GtkMountOperation *operation;
1056 gboolean use_gtk;
1057
1058 operation = GTK_MOUNT_OPERATION (op);
1059 use_gtk = (operation->priv->handler == NULL);
1060
1061 if (use_gtk)
1062 gtk_mount_operation_ask_question_do_gtk (op: operation, message, choices);
1063 else
1064 gtk_mount_operation_ask_question_do_proxy (operation, message, choices);
1065}
1066
1067static void
1068show_processes_button_clicked (GtkDialog *dialog,
1069 int button_number,
1070 GMountOperation *op)
1071{
1072 GtkMountOperationPrivate *priv;
1073 GtkMountOperation *operation;
1074
1075 operation = GTK_MOUNT_OPERATION (op);
1076 priv = operation->priv;
1077
1078 if (button_number >= 0)
1079 {
1080 g_mount_operation_set_choice (op, choice: button_number);
1081 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_HANDLED);
1082 }
1083 else
1084 g_mount_operation_reply (op, result: G_MOUNT_OPERATION_ABORTED);
1085
1086 priv->dialog = NULL;
1087 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
1088 gtk_window_destroy (GTK_WINDOW (dialog));
1089 g_object_unref (object: op);
1090}
1091
1092static int
1093pid_equal (gconstpointer a,
1094 gconstpointer b)
1095{
1096 GPid pa, pb;
1097
1098 pa = *((GPid *) a);
1099 pb = *((GPid *) b);
1100
1101 return GPOINTER_TO_INT(pb) - GPOINTER_TO_INT(pa);
1102}
1103
1104static void
1105diff_sorted_arrays (GArray *array1,
1106 GArray *array2,
1107 GCompareFunc compare,
1108 GArray *added_indices,
1109 GArray *removed_indices)
1110{
1111 int order;
1112 guint n1, n2;
1113 guint elem_size;
1114
1115 n1 = n2 = 0;
1116
1117 elem_size = g_array_get_element_size (array: array1);
1118 g_assert (elem_size == g_array_get_element_size (array2));
1119
1120 while (n1 < array1->len && n2 < array2->len)
1121 {
1122 order = (*compare) (((const char*) array1->data) + n1 * elem_size,
1123 ((const char*) array2->data) + n2 * elem_size);
1124 if (order < 0)
1125 {
1126 g_array_append_val (removed_indices, n1);
1127 n1++;
1128 }
1129 else if (order > 0)
1130 {
1131 g_array_append_val (added_indices, n2);
1132 n2++;
1133 }
1134 else
1135 { /* same item */
1136 n1++;
1137 n2++;
1138 }
1139 }
1140
1141 while (n1 < array1->len)
1142 {
1143 g_array_append_val (removed_indices, n1);
1144 n1++;
1145 }
1146 while (n2 < array2->len)
1147 {
1148 g_array_append_val (added_indices, n2);
1149 n2++;
1150 }
1151}
1152
1153static GdkTexture *
1154render_paintable_to_texture (GdkPaintable *paintable)
1155{
1156 GtkSnapshot *snapshot;
1157 GskRenderNode *node;
1158 int width, height;
1159 cairo_surface_t *surface;
1160 cairo_t *cr;
1161 GdkTexture *texture;
1162
1163 width = gdk_paintable_get_intrinsic_width (paintable);
1164 height = gdk_paintable_get_intrinsic_height (paintable);
1165
1166 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width, height);
1167
1168 snapshot = gtk_snapshot_new ();
1169 gdk_paintable_snapshot (paintable, snapshot, width, height);
1170 node = gtk_snapshot_free_to_node (snapshot);
1171
1172 cr = cairo_create (target: surface);
1173 gsk_render_node_draw (node, cr);
1174 cairo_destroy (cr);
1175
1176 gsk_render_node_unref (node);
1177
1178 texture = gdk_texture_new_for_surface (surface);
1179 cairo_surface_destroy (surface);
1180
1181 return texture;
1182}
1183
1184static void
1185add_pid_to_process_list_store (GtkMountOperation *mount_operation,
1186 GtkMountOperationLookupContext *lookup_context,
1187 GtkListStore *list_store,
1188 GPid pid)
1189{
1190 char *command_line;
1191 char *name;
1192 GdkTexture *texture;
1193 char *markup;
1194 GtkTreeIter iter;
1195
1196 name = NULL;
1197 texture = NULL;
1198 command_line = NULL;
1199 _gtk_mount_operation_lookup_info (context: lookup_context,
1200 pid,
1201 size_pixels: 24,
1202 out_name: &name,
1203 out_command_line: &command_line,
1204 out_texture: &texture);
1205
1206 if (name == NULL)
1207 name = g_strdup_printf (_("Unknown Application (PID %d)"), (int) (gssize) pid);
1208
1209 if (command_line == NULL)
1210 command_line = g_strdup (str: "");
1211
1212 if (texture == NULL)
1213 {
1214 GtkIconTheme *theme;
1215 GtkIconPaintable *icon;
1216
1217 theme = gtk_icon_theme_get_for_display (display: gtk_widget_get_display (GTK_WIDGET (mount_operation->priv->dialog)));
1218 icon = gtk_icon_theme_lookup_icon (self: theme,
1219 icon_name: "application-x-executable",
1220 NULL,
1221 size: 24, scale: 1,
1222 direction: gtk_widget_get_direction (GTK_WIDGET (mount_operation->priv->dialog)),
1223 flags: 0);
1224 texture = render_paintable_to_texture (paintable: GDK_PAINTABLE (ptr: icon));
1225 g_object_unref (object: icon);
1226 }
1227
1228 markup = g_strdup_printf (format: "<b>%s</b>\n"
1229 "<small>%s</small>",
1230 name,
1231 command_line);
1232
1233 gtk_list_store_append (list_store, iter: &iter);
1234 gtk_list_store_set (list_store, iter: &iter,
1235 0, texture,
1236 1, markup,
1237 2, pid,
1238 -1);
1239
1240 if (texture != NULL)
1241 g_object_unref (object: texture);
1242 g_free (mem: markup);
1243 g_free (mem: name);
1244 g_free (mem: command_line);
1245}
1246
1247static void
1248remove_pid_from_process_list_store (GtkMountOperation *mount_operation,
1249 GtkListStore *list_store,
1250 GPid pid)
1251{
1252 GtkTreeIter iter;
1253 GPid pid_of_item;
1254
1255 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), iter: &iter))
1256 {
1257 do
1258 {
1259 gtk_tree_model_get (GTK_TREE_MODEL (list_store),
1260 iter: &iter,
1261 2, &pid_of_item,
1262 -1);
1263
1264 if (pid_of_item == pid)
1265 {
1266 gtk_list_store_remove (list_store, iter: &iter);
1267 break;
1268 }
1269 }
1270 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list_store), iter: &iter));
1271 }
1272}
1273
1274
1275static void
1276update_process_list_store (GtkMountOperation *mount_operation,
1277 GtkListStore *list_store,
1278 GArray *processes)
1279{
1280 guint n;
1281 GtkMountOperationLookupContext *lookup_context;
1282 GArray *current_pids;
1283 GArray *pid_indices_to_add;
1284 GArray *pid_indices_to_remove;
1285 GtkTreeIter iter;
1286 GPid pid;
1287
1288 /* Just removing all items and adding new ones will screw up the
1289 * focus handling in the treeview - so compute the delta, and add/remove
1290 * items as appropriate
1291 */
1292 current_pids = g_array_new (FALSE, FALSE, element_size: sizeof (GPid));
1293 pid_indices_to_add = g_array_new (FALSE, FALSE, element_size: sizeof (int));
1294 pid_indices_to_remove = g_array_new (FALSE, FALSE, element_size: sizeof (int));
1295
1296 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), iter: &iter))
1297 {
1298 do
1299 {
1300 gtk_tree_model_get (GTK_TREE_MODEL (list_store),
1301 iter: &iter,
1302 2, &pid,
1303 -1);
1304
1305 g_array_append_val (current_pids, pid);
1306 }
1307 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list_store), iter: &iter));
1308 }
1309
1310 g_array_sort (array: current_pids, compare_func: pid_equal);
1311 g_array_sort (array: processes, compare_func: pid_equal);
1312
1313 diff_sorted_arrays (array1: current_pids, array2: processes, compare: pid_equal, added_indices: pid_indices_to_add, removed_indices: pid_indices_to_remove);
1314
1315 for (n = 0; n < pid_indices_to_remove->len; n++)
1316 {
1317 pid = g_array_index (current_pids, GPid, n);
1318 remove_pid_from_process_list_store (mount_operation, list_store, pid);
1319 }
1320
1321 if (pid_indices_to_add->len > 0)
1322 {
1323 lookup_context = _gtk_mount_operation_lookup_context_get (display: gtk_widget_get_display (widget: mount_operation->priv->process_tree_view));
1324 for (n = 0; n < pid_indices_to_add->len; n++)
1325 {
1326 pid = g_array_index (processes, GPid, n);
1327 add_pid_to_process_list_store (mount_operation, lookup_context, list_store, pid);
1328 }
1329 _gtk_mount_operation_lookup_context_free (context: lookup_context);
1330 }
1331
1332 /* select the first item, if we went from a zero to a non-zero amount of processes */
1333 if (current_pids->len == 0 && pid_indices_to_add->len > 0)
1334 {
1335 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), iter: &iter))
1336 {
1337 GtkTreeSelection *tree_selection;
1338 tree_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (mount_operation->priv->process_tree_view));
1339 gtk_tree_selection_select_iter (selection: tree_selection, iter: &iter);
1340 }
1341 }
1342
1343 g_array_unref (array: current_pids);
1344 g_array_unref (array: pid_indices_to_add);
1345 g_array_unref (array: pid_indices_to_remove);
1346}
1347
1348static void
1349on_dialog_response (GtkDialog *dialog,
1350 int response)
1351{
1352 /* GTK_RESPONSE_NONE means the dialog were programmatically destroy, e.g. that
1353 * GTK_DIALOG_DESTROY_WITH_PARENT kicked in - so it would trigger a warning to
1354 * destroy the dialog in that case
1355 */
1356 if (response != GTK_RESPONSE_NONE)
1357 gtk_window_destroy (GTK_WINDOW (dialog));
1358}
1359
1360static void
1361on_end_process_activated (GtkModelButton *button,
1362 gpointer user_data)
1363{
1364 GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data);
1365 GtkTreeSelection *selection;
1366 GtkTreeIter iter;
1367 GPid pid_to_kill;
1368 GError *error;
1369
1370 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (op->priv->process_tree_view));
1371
1372 if (!gtk_tree_selection_get_selected (selection,
1373 NULL,
1374 iter: &iter))
1375 goto out;
1376
1377 gtk_tree_model_get (GTK_TREE_MODEL (op->priv->process_list_store),
1378 iter: &iter,
1379 2, &pid_to_kill,
1380 -1);
1381
1382 /* TODO: We might want to either
1383 *
1384 * - Be smart about things and send SIGKILL rather than SIGTERM if
1385 * this is the second time the user requests killing a process
1386 *
1387 * - Or, easier (but worse user experience), offer both "End Process"
1388 * and "Terminate Process" options
1389 *
1390 * But that's not how things work right now....
1391 */
1392 error = NULL;
1393 if (!_gtk_mount_operation_kill_process (pid: pid_to_kill, error: &error))
1394 {
1395 GtkWidget *dialog;
1396
1397 /* Use GTK_DIALOG_DESTROY_WITH_PARENT here since the parent dialog can be
1398 * indeed be destroyed via the GMountOperation::abort signal... for example,
1399 * this is triggered if the user yanks the device while we are showing
1400 * the dialog...
1401 */
1402 dialog = gtk_message_dialog_new (GTK_WINDOW (op->priv->dialog),
1403 flags: GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1404 type: GTK_MESSAGE_ERROR,
1405 buttons: GTK_BUTTONS_CLOSE,
1406 _("Unable to end process"));
1407 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1408 message_format: "%s",
1409 error->message);
1410
1411 gtk_widget_show (widget: dialog);
1412
1413 g_signal_connect (dialog, "response", G_CALLBACK (on_dialog_response), NULL);
1414
1415 g_error_free (error);
1416 }
1417
1418 out:
1419 ;
1420}
1421
1422static gboolean
1423do_popup_menu_for_process_tree_view (GtkWidget *widget,
1424 GdkEvent *event,
1425 GtkMountOperation *op)
1426{
1427 GtkWidget *menu;
1428 GtkWidget *item;
1429 double x, y;
1430
1431 menu = gtk_popover_new ();
1432 gtk_widget_set_parent (widget: menu, parent: widget);
1433 gtk_widget_add_css_class (widget: menu, css_class: "context-menu");
1434
1435 item = gtk_model_button_new ();
1436 g_object_set (object: item, first_property_name: "text", _("_End Process"), NULL);
1437 g_signal_connect (item, "clicked",
1438 G_CALLBACK (on_end_process_activated),
1439 op);
1440 gtk_box_append (GTK_BOX (menu), child: item);
1441
1442 if (event && gdk_event_triggers_context_menu (event))
1443 {
1444 GtkTreePath *path;
1445 GtkTreeSelection *selection;
1446
1447 gdk_event_get_position (event, x: &x, y: &y);
1448 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (op->priv->process_tree_view),
1449 x: (int) x,
1450 y: (int) y,
1451 path: &path,
1452 NULL,
1453 NULL,
1454 NULL))
1455 {
1456 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (op->priv->process_tree_view));
1457 gtk_tree_selection_select_path (selection, path);
1458 gtk_tree_path_free (path);
1459 }
1460 else
1461 {
1462 /* don't popup a menu if the user right-clicked in an area with no rows */
1463 return FALSE;
1464 }
1465
1466 gtk_popover_set_pointing_to (GTK_POPOVER (menu), rect: &(GdkRectangle){ x, y, 1, 1});
1467 }
1468
1469 gtk_popover_popup (GTK_POPOVER (menu));
1470 return TRUE;
1471}
1472
1473static gboolean
1474on_popup_menu_for_process_tree_view (GtkWidget *widget,
1475 GVariant *args,
1476 gpointer user_data)
1477{
1478 GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data);
1479 return do_popup_menu_for_process_tree_view (widget, NULL, op);
1480}
1481
1482static void
1483click_cb (GtkGesture *gesture,
1484 int n_press,
1485 double x,
1486 double y,
1487 gpointer user_data)
1488{
1489 GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data);
1490 GdkEvent *event;
1491 GdkEventSequence *sequence;
1492 GtkWidget *widget;
1493
1494 sequence = gtk_gesture_get_last_updated_sequence (gesture);
1495 event = gtk_gesture_get_last_event (gesture, sequence);
1496 widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
1497 do_popup_menu_for_process_tree_view (widget, event, op);
1498}
1499
1500static GtkWidget *
1501create_show_processes_dialog (GtkMountOperation *op,
1502 const char *message,
1503 const char *choices[])
1504{
1505 GtkMountOperationPrivate *priv;
1506 GtkWidget *dialog;
1507 const char *secondary = NULL;
1508 char *primary;
1509 int count, len = 0;
1510 GtkWidget *label;
1511 GtkWidget *tree_view;
1512 GtkWidget *scrolled_window;
1513 GtkWidget *vbox;
1514 GtkWidget *content_area;
1515 GtkTreeViewColumn *column;
1516 GtkCellRenderer *renderer;
1517 GtkListStore *list_store;
1518 char *s;
1519 gboolean use_header;
1520 GtkGesture *gesture;
1521 GtkEventController *controller;
1522 GtkShortcutTrigger *trigger;
1523 GtkShortcutAction *action;
1524 GtkShortcut *shortcut;
1525
1526 priv = op->priv;
1527
1528 primary = strstr (haystack: message, needle: "\n");
1529 if (primary)
1530 {
1531 secondary = primary + 1;
1532 primary = g_strndup (str: message, n: primary - message);
1533 }
1534
1535 g_object_get (object: gtk_settings_get_default (),
1536 first_property_name: "gtk-dialogs-use-header", &use_header,
1537 NULL);
1538 dialog = g_object_new (GTK_TYPE_DIALOG,
1539 first_property_name: "use-header-bar", use_header,
1540 NULL);
1541
1542 if (priv->parent_window != NULL)
1543 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent: priv->parent_window);
1544 gtk_window_set_title (GTK_WINDOW (dialog), title: "");
1545
1546 content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
1547 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 12);
1548 gtk_box_append (GTK_BOX (content_area), child: vbox);
1549
1550 if (secondary != NULL)
1551 s = g_strdup_printf (format: "<big><b>%s</b></big>\n\n%s", primary, secondary);
1552 else
1553 s = g_strdup_printf (format: "%s", primary);
1554
1555 g_free (mem: primary);
1556 label = gtk_label_new (NULL);
1557 gtk_label_set_markup (GTK_LABEL (label), str: s);
1558 g_free (mem: s);
1559 gtk_box_append (GTK_BOX (vbox), child: label);
1560
1561 /* First count the items in the list then
1562 * add the buttons in reverse order
1563 */
1564
1565 while (choices[len] != NULL)
1566 len++;
1567
1568 for (count = len - 1; count >= 0; count--)
1569 gtk_dialog_add_button (GTK_DIALOG (dialog), button_text: choices[count], response_id: count);
1570
1571 g_signal_connect (G_OBJECT (dialog), "response",
1572 G_CALLBACK (show_processes_button_clicked), op);
1573
1574 priv->dialog = GTK_DIALOG (dialog);
1575 g_object_notify (G_OBJECT (op), property_name: "is-showing");
1576
1577 if (priv->parent_window == NULL && priv->display)
1578 gtk_window_set_display (GTK_WINDOW (dialog), display: priv->display);
1579
1580 tree_view = gtk_tree_view_new ();
1581 gtk_widget_set_size_request (widget: tree_view, width: 300, height: 120);
1582
1583 column = gtk_tree_view_column_new ();
1584 renderer = gtk_cell_renderer_pixbuf_new ();
1585 gtk_tree_view_column_pack_start (tree_column: column, cell: renderer, FALSE);
1586 gtk_tree_view_column_set_attributes (tree_column: column, cell_renderer: renderer,
1587 "texture", 0,
1588 NULL);
1589 renderer = gtk_cell_renderer_text_new ();
1590 g_object_set (object: renderer,
1591 first_property_name: "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
1592 "ellipsize-set", TRUE,
1593 NULL);
1594 gtk_tree_view_column_pack_start (tree_column: column, cell: renderer, TRUE);
1595 gtk_tree_view_column_set_attributes (tree_column: column, cell_renderer: renderer,
1596 "markup", 1,
1597 NULL);
1598 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
1599 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
1600
1601
1602 scrolled_window = gtk_scrolled_window_new ();
1603 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1604 hscrollbar_policy: GTK_POLICY_NEVER,
1605 vscrollbar_policy: GTK_POLICY_AUTOMATIC);
1606 gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (scrolled_window), TRUE);
1607
1608 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), child: tree_view);
1609 gtk_box_append (GTK_BOX (vbox), child: scrolled_window);
1610
1611 controller = gtk_shortcut_controller_new ();
1612 trigger = gtk_alternative_trigger_new (first: gtk_keyval_trigger_new (GDK_KEY_F10, modifiers: GDK_SHIFT_MASK),
1613 second: gtk_keyval_trigger_new (GDK_KEY_Menu, modifiers: 0));
1614 action = gtk_callback_action_new (callback: on_popup_menu_for_process_tree_view,
1615 data: op,
1616 NULL);
1617 shortcut = gtk_shortcut_new_with_arguments (trigger, action, format_string: "s", "sv");
1618 gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
1619 gtk_widget_add_controller (GTK_WIDGET (tree_view), controller);
1620
1621 gesture = gtk_gesture_click_new ();
1622 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY);
1623 g_signal_connect (gesture, "pressed",
1624 G_CALLBACK (click_cb), op);
1625 gtk_widget_add_controller (widget: tree_view, GTK_EVENT_CONTROLLER (gesture));
1626
1627 list_store = gtk_list_store_new (n_columns: 3,
1628 GDK_TYPE_TEXTURE,
1629 G_TYPE_STRING,
1630 G_TYPE_INT);
1631
1632 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (list_store));
1633
1634 priv->process_list_store = list_store;
1635 priv->process_tree_view = tree_view;
1636 /* set pointers to NULL when dialog goes away */
1637 g_object_add_weak_pointer (G_OBJECT (priv->process_list_store), weak_pointer_location: (gpointer *) &priv->process_list_store);
1638 g_object_add_weak_pointer (G_OBJECT (priv->process_tree_view), weak_pointer_location: (gpointer *) &priv->process_tree_view);
1639
1640 g_object_unref (object: list_store);
1641 g_object_ref (op);
1642
1643 return dialog;
1644}
1645
1646static void
1647call_processes_proxy_cb (GObject *source,
1648 GAsyncResult *res,
1649 gpointer user_data)
1650{
1651 _GtkMountOperationHandler *proxy = _GTK_MOUNT_OPERATION_HANDLER (source);
1652 GMountOperation *op = user_data;
1653 GMountOperationResult result;
1654 GVariant *result_details;
1655 GVariantIter iter;
1656 const char *key;
1657 GVariant *value;
1658 GError *error = NULL;
1659
1660 if (!_gtk_mount_operation_handler_call_show_processes_finish (proxy,
1661 &result,
1662 &result_details,
1663 res,
1664 &error))
1665 {
1666 result = G_MOUNT_OPERATION_ABORTED;
1667 g_warning ("Shell mount operation error: %s", error->message);
1668 g_error_free (error);
1669 goto out;
1670 }
1671
1672 /* If the request was unhandled it means we called the method again;
1673 * in this case, just return and wait for the next response.
1674 */
1675 if (result == G_MOUNT_OPERATION_UNHANDLED)
1676 return;
1677
1678 g_variant_iter_init (iter: &iter, value: result_details);
1679 while (g_variant_iter_loop (iter: &iter, format_string: "{&sv}", &key, &value))
1680 {
1681 if (strcmp (s1: key, s2: "choice") == 0)
1682 g_mount_operation_set_choice (op, choice: g_variant_get_int32 (value));
1683 }
1684
1685 out:
1686 gtk_mount_operation_proxy_finish (GTK_MOUNT_OPERATION (op), result);
1687}
1688
1689static void
1690gtk_mount_operation_show_processes_do_proxy (GtkMountOperation *operation,
1691 const char *message,
1692 GArray *processes,
1693 const char *choices[])
1694{
1695 char id[255];
1696 g_sprintf(string: id, format: "GtkMountOperation%p", operation);
1697
1698 operation->priv->handler_showing = TRUE;
1699 g_object_notify (G_OBJECT (operation), property_name: "is-showing");
1700
1701 /* keep a ref to the operation while the handler is showing */
1702 g_object_ref (operation);
1703
1704 _gtk_mount_operation_handler_call_show_processes (operation->priv->handler, id,
1705 message, "drive-harddisk",
1706 g_variant_new_fixed_array (G_VARIANT_TYPE_INT32,
1707 elements: processes->data, n_elements: processes->len,
1708 element_size: sizeof (GPid)),
1709 choices, NULL,
1710 call_processes_proxy_cb, operation);
1711}
1712
1713static void
1714gtk_mount_operation_show_processes_do_gtk (GtkMountOperation *op,
1715 const char *message,
1716 GArray *processes,
1717 const char *choices[])
1718{
1719 GtkMountOperationPrivate *priv;
1720 GtkWidget *dialog = NULL;
1721
1722 g_return_if_fail (GTK_IS_MOUNT_OPERATION (op));
1723 g_return_if_fail (message != NULL);
1724 g_return_if_fail (processes != NULL);
1725 g_return_if_fail (choices != NULL);
1726
1727 priv = op->priv;
1728
1729 if (priv->process_list_store == NULL)
1730 {
1731 /* need to create the dialog */
1732 dialog = create_show_processes_dialog (op, message, choices);
1733 }
1734
1735 /* otherwise, we're showing the dialog, assume messages+choices hasn't changed */
1736
1737 update_process_list_store (mount_operation: op,
1738 list_store: priv->process_list_store,
1739 processes);
1740
1741 if (dialog != NULL)
1742 {
1743 gtk_widget_show (widget: dialog);
1744 }
1745}
1746
1747
1748static void
1749gtk_mount_operation_show_processes (GMountOperation *op,
1750 const char *message,
1751 GArray *processes,
1752 const char *choices[])
1753{
1754
1755 GtkMountOperation *operation;
1756 gboolean use_gtk;
1757
1758 operation = GTK_MOUNT_OPERATION (op);
1759 use_gtk = (operation->priv->handler == NULL);
1760
1761 if (use_gtk)
1762 gtk_mount_operation_show_processes_do_gtk (op: operation, message, processes, choices);
1763 else
1764 gtk_mount_operation_show_processes_do_proxy (operation, message, processes, choices);
1765}
1766
1767static void
1768gtk_mount_operation_aborted (GMountOperation *op)
1769{
1770 GtkMountOperationPrivate *priv;
1771
1772 priv = GTK_MOUNT_OPERATION (op)->priv;
1773
1774 if (priv->dialog != NULL)
1775 {
1776 gtk_window_destroy (GTK_WINDOW (priv->dialog));
1777 priv->dialog = NULL;
1778 g_object_notify (G_OBJECT (op), property_name: "is-showing");
1779 g_object_unref (object: op);
1780 }
1781
1782 if (priv->handler != NULL)
1783 {
1784 _gtk_mount_operation_handler_call_close (priv->handler, NULL, NULL, NULL);
1785
1786 priv->handler_showing = FALSE;
1787 g_object_notify (G_OBJECT (op), property_name: "is-showing");
1788 }
1789}
1790
1791/**
1792 * gtk_mount_operation_new:
1793 * @parent: (nullable): transient parent of the window
1794 *
1795 * Creates a new `GtkMountOperation`.
1796 *
1797 * Returns: a new `GtkMountOperation`
1798 */
1799GMountOperation *
1800gtk_mount_operation_new (GtkWindow *parent)
1801{
1802 GMountOperation *mount_operation;
1803
1804 mount_operation = g_object_new (GTK_TYPE_MOUNT_OPERATION,
1805 first_property_name: "parent", parent, NULL);
1806
1807 return mount_operation;
1808}
1809
1810/**
1811 * gtk_mount_operation_is_showing: (attributes org.gtk.Method.get_property=is-showing)
1812 * @op: a `GtkMountOperation`
1813 *
1814 * Returns whether the `GtkMountOperation` is currently displaying
1815 * a window.
1816 *
1817 * Returns: %TRUE if @op is currently displaying a window
1818 */
1819gboolean
1820gtk_mount_operation_is_showing (GtkMountOperation *op)
1821{
1822 g_return_val_if_fail (GTK_IS_MOUNT_OPERATION (op), FALSE);
1823
1824 return op->priv->dialog != NULL;
1825}
1826
1827/**
1828 * gtk_mount_operation_set_parent: (attributes org.gtk.Method.set_property=parent)
1829 * @op: a `GtkMountOperation`
1830 * @parent: (nullable): transient parent of the window
1831 *
1832 * Sets the transient parent for windows shown by the
1833 * `GtkMountOperation`.
1834 */
1835void
1836gtk_mount_operation_set_parent (GtkMountOperation *op,
1837 GtkWindow *parent)
1838{
1839 GtkMountOperationPrivate *priv;
1840
1841 g_return_if_fail (GTK_IS_MOUNT_OPERATION (op));
1842 g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
1843
1844 priv = op->priv;
1845
1846 if (priv->parent_window == parent)
1847 return;
1848
1849 if (priv->parent_window)
1850 {
1851 g_signal_handlers_disconnect_by_func (priv->parent_window,
1852 parent_destroyed,
1853 &priv->parent_window);
1854 g_object_unref (object: priv->parent_window);
1855 }
1856 priv->parent_window = parent;
1857 if (priv->parent_window)
1858 {
1859 g_object_ref (priv->parent_window);
1860 g_signal_connect (priv->parent_window, "destroy",
1861 G_CALLBACK (parent_destroyed), &priv->parent_window);
1862 }
1863
1864 if (priv->dialog)
1865 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), parent: priv->parent_window);
1866
1867 g_object_notify (G_OBJECT (op), property_name: "parent");
1868}
1869
1870/**
1871 * gtk_mount_operation_get_parent: (attributes org.gtk.Method.get_property=parent)
1872 * @op: a `GtkMountOperation`
1873 *
1874 * Gets the transient parent used by the `GtkMountOperation`.
1875 *
1876 * Returns: (transfer none) (nullable): the transient parent for windows shown by @op
1877 */
1878GtkWindow *
1879gtk_mount_operation_get_parent (GtkMountOperation *op)
1880{
1881 g_return_val_if_fail (GTK_IS_MOUNT_OPERATION (op), NULL);
1882
1883 return op->priv->parent_window;
1884}
1885
1886/**
1887 * gtk_mount_operation_set_display: (attributes org.gtk.Method.set_property=display)
1888 * @op: a `GtkMountOperation`
1889 * @display: a `GdkDisplay`
1890 *
1891 * Sets the display to show windows of the `GtkMountOperation` on.
1892 */
1893void
1894gtk_mount_operation_set_display (GtkMountOperation *op,
1895 GdkDisplay *display)
1896{
1897 GtkMountOperationPrivate *priv;
1898
1899 g_return_if_fail (GTK_IS_MOUNT_OPERATION (op));
1900 g_return_if_fail (GDK_IS_DISPLAY (display));
1901
1902 priv = op->priv;
1903
1904 if (priv->display == display)
1905 return;
1906
1907 if (priv->display)
1908 g_object_unref (object: priv->display);
1909
1910 priv->display = g_object_ref (display);
1911
1912 if (priv->dialog)
1913 gtk_window_set_display (GTK_WINDOW (priv->dialog), display);
1914
1915 g_object_notify (G_OBJECT (op), property_name: "display");
1916}
1917
1918/**
1919 * gtk_mount_operation_get_display: (attributes org.gtk.Method.get_property=display)
1920 * @op: a `GtkMountOperation`
1921 *
1922 * Gets the display on which windows of the `GtkMountOperation`
1923 * will be shown.
1924 *
1925 * Returns: (transfer none): the display on which windows of @op are shown
1926 */
1927GdkDisplay *
1928gtk_mount_operation_get_display (GtkMountOperation *op)
1929{
1930 GtkMountOperationPrivate *priv;
1931
1932 g_return_val_if_fail (GTK_IS_MOUNT_OPERATION (op), NULL);
1933
1934 priv = op->priv;
1935
1936 if (priv->dialog)
1937 return gtk_widget_get_display (GTK_WIDGET (priv->dialog));
1938 else if (priv->parent_window)
1939 return gtk_widget_get_display (GTK_WIDGET (priv->parent_window));
1940 else if (priv->display)
1941 return priv->display;
1942 else
1943 return gdk_display_get_default ();
1944}
1945

source code of gtk/gtk/gtkmountoperation.c