diff options
author | David Zeuthen <davidz@redhat.com> | 2009-06-30 23:46:02 -0400 |
---|---|---|
committer | David Zeuthen <davidz@redhat.com> | 2009-07-06 14:23:03 -0400 |
commit | 200d5dde044bcfeb9fe9db0eb91f59f8ee6f2f8c (patch) | |
tree | ca8c4b59b9d304122ef2de47dde631d7df740279 /gtk/gtkmountoperation.c | |
parent | dab552dc2249fc1b569ffaf93f039c9f3382f67e (diff) | |
download | gtk+-200d5dde044bcfeb9fe9db0eb91f59f8ee6f2f8c.tar.gz |
Bug 587485 – GMountOperation::show-processes support
- Add support for GMountOperation::show-processes
- Implement process look up for the X11 target
Diffstat (limited to 'gtk/gtkmountoperation.c')
-rw-r--r-- | gtk/gtkmountoperation.c | 601 |
1 files changed, 597 insertions, 4 deletions
diff --git a/gtk/gtkmountoperation.c b/gtk/gtkmountoperation.c index ace58ed90a..318e4aeb5b 100644 --- a/gtk/gtkmountoperation.c +++ b/gtk/gtkmountoperation.c @@ -29,6 +29,7 @@ #include <string.h> +#include "gtkmountoperationprivate.h" #include "gtkalignment.h" #include "gtkbox.h" #include "gtkentry.h" @@ -44,6 +45,14 @@ #include "gtkstock.h" #include "gtktable.h" #include "gtkwindow.h" +#include "gtktreeview.h" +#include "gtktreeselection.h" +#include "gtkcellrenderertext.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkscrolledwindow.h" +#include "gtkicontheme.h" +#include "gtkimagemenuitem.h" +#include "gtkmain.h" #include "gtkalias.h" /** @@ -58,14 +67,16 @@ * applications. */ -/** +/** * GtkMountOperation: * - * #GtkMountOperation is an implementation of #GMountOperation that + * #GtkMountOperation is an implementation of #GMountOperation that * can be used with GIO functions for mounting volumes such as - * g_file_mount_enclosing_volume() or g_file_mount_mountable(). + * g_file_mount_enclosing_volume(), g_file_mount_mountable(), + * g_volume_mount(), g_mount_unmount() and others. * - * When necessary, #GtkMountOperation shows dialogs to ask for passwords. + * When necessary, #GtkMountOperation shows dialogs to ask for + * passwords, questions or show processes blocking unmount. */ static void gtk_mount_operation_finalize (GObject *object); @@ -88,6 +99,11 @@ static void gtk_mount_operation_ask_question (GMountOperation *op, const char *message, const char *choices[]); +static void gtk_mount_operation_show_processes (GMountOperation *op, + const char *message, + GArray *processes, + const char *choices[]); + static void gtk_mount_operation_aborted (GMountOperation *op); G_DEFINE_TYPE (GtkMountOperation, gtk_mount_operation, G_TYPE_MOUNT_OPERATION); @@ -115,6 +131,10 @@ struct _GtkMountOperationPrivate { GAskPasswordFlags ask_flags; GPasswordSave password_save; gboolean anonymous; + + /* for the show-processes dialog */ + GtkWidget *process_tree_view; + GtkListStore *process_list_store; }; static void @@ -131,6 +151,7 @@ gtk_mount_operation_class_init (GtkMountOperationClass *klass) mount_op_class->ask_password = gtk_mount_operation_ask_password; mount_op_class->ask_question = gtk_mount_operation_ask_question; + mount_op_class->show_processes = gtk_mount_operation_show_processes; mount_op_class->aborted = gtk_mount_operation_aborted; g_object_class_install_property (object_class, @@ -748,6 +769,578 @@ gtk_mount_operation_ask_question (GMountOperation *op, } static void +show_processes_button_clicked (GtkDialog *dialog, + gint button_number, + GMountOperation *op) +{ + GtkMountOperationPrivate *priv; + GtkMountOperation *operation; + + operation = GTK_MOUNT_OPERATION (op); + priv = operation->priv; + + if (button_number >= 0) + { + g_mount_operation_set_choice (op, button_number); + g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED); + } + else + g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED); + + priv->dialog = NULL; + g_object_notify (G_OBJECT (operation), "is-showing"); + gtk_widget_destroy (GTK_WIDGET (dialog)); + g_object_unref (op); +} + +static gint +pid_equal (gconstpointer a, + gconstpointer b) +{ + GPid pa, pb; + + pa = *((GPid *) a); + pb = *((GPid *) b); + + return pb - pa; +} + +static void +diff_sorted_arrays (GArray *array1, + GArray *array2, + GCompareFunc compare, + GArray *added_indices, + GArray *removed_indices) +{ + gint order; + guint n1, n2; + guint elem_size; + + n1 = n2 = 0; + + elem_size = g_array_get_element_size (array1); + g_assert (elem_size == g_array_get_element_size (array2)); + + while (n1 < array1->len && n2 < array2->len) + { + order = (*compare) (((gconstpointer) array1->data) + n1 * elem_size, + ((gconstpointer) array2->data) + n2 * elem_size); + if (order < 0) + { + g_array_append_val (removed_indices, n1); + n1++; + } + else if (order > 0) + { + g_array_append_val (added_indices, n2); + n2++; + } + else + { /* same item */ + n1++; + n2++; + } + } + + while (n1 < array1->len) + { + g_array_append_val (removed_indices, n1); + n1++; + } + while (n2 < array2->len) + { + g_array_append_val (added_indices, n2); + n2++; + } +} + + +static void +add_pid_to_process_list_store (GtkMountOperation *mount_operation, + GtkMountOperationLookupContext *lookup_context, + GtkListStore *list_store, + GPid pid) +{ + gchar *command_line; + gchar *name; + GdkPixbuf *pixbuf; + gchar *markup; + GtkTreeIter iter; + + name = NULL; + pixbuf = NULL; + command_line = NULL; + _gtk_mount_operation_lookup_info (lookup_context, + pid, + 24, + &name, + &command_line, + &pixbuf); + + if (name == NULL) + name = g_strdup_printf (_("Unknown Application (pid %d)"), pid); + + if (command_line == NULL) + command_line = g_strdup (""); + + if (pixbuf == NULL) + { + GtkIconTheme *theme; + theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (mount_operation->priv->dialog))); + pixbuf = gtk_icon_theme_load_icon (theme, + "application-x-executable", + 24, + 0, + NULL); + } + + markup = g_strdup_printf ("<b>%s</b>\n" + "<small>%s</small>", + name, + command_line); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + 0, pixbuf, + 1, markup, + 2, pid, + -1); + + if (pixbuf != NULL) + g_object_unref (pixbuf); + g_free (markup); + g_free (name); + g_free (command_line); +} + +static void +remove_pid_from_process_list_store (GtkMountOperation *mount_operation, + GtkListStore *list_store, + GPid pid) +{ + GtkTreeIter iter; + GPid pid_of_item; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (list_store), + &iter, + 2, &pid_of_item, + -1); + + if (pid_of_item == pid) + { + gtk_list_store_remove (list_store, &iter); + break; + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list_store), &iter)); + } +} + + +static void +update_process_list_store (GtkMountOperation *mount_operation, + GtkListStore *list_store, + GArray *processes) +{ + guint n; + GtkMountOperationLookupContext *lookup_context; + GArray *current_pids; + GArray *pid_indices_to_add; + GArray *pid_indices_to_remove; + GtkTreeIter iter; + GPid pid; + + /* Just removing all items and adding new ones will screw up the + * focus handling in the treeview - so compute the delta, and add/remove + * items as appropriate + */ + current_pids = g_array_new (FALSE, FALSE, sizeof (GPid)); + pid_indices_to_add = g_array_new (FALSE, FALSE, sizeof (gint)); + pid_indices_to_remove = g_array_new (FALSE, FALSE, sizeof (gint)); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (list_store), + &iter, + 2, &pid, + -1); + + g_array_append_val (current_pids, pid); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list_store), &iter)); + } + + g_array_sort (current_pids, pid_equal); + g_array_sort (processes, pid_equal); + + diff_sorted_arrays (current_pids, processes, pid_equal, pid_indices_to_add, pid_indices_to_remove); + + if (pid_indices_to_add->len > 0) + lookup_context = _gtk_mount_operation_lookup_context_get (gtk_widget_get_display (mount_operation->priv->process_tree_view)); + for (n = 0; n < pid_indices_to_add->len; n++) + { + pid = g_array_index (processes, GPid, n); + add_pid_to_process_list_store (mount_operation, lookup_context, list_store, pid); + } + + for (n = 0; n < pid_indices_to_remove->len; n++) + { + pid = g_array_index (current_pids, GPid, n); + remove_pid_from_process_list_store (mount_operation, list_store, pid); + } + if (pid_indices_to_add->len > 0) + _gtk_mount_operation_lookup_context_free (lookup_context); + + /* select the first item, if we went from a zero to a non-zero amount of processes */ + if (current_pids->len == 0 && pid_indices_to_add->len > 0) + { + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter)) + { + GtkTreeSelection *tree_selection; + tree_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (mount_operation->priv->process_tree_view)); + gtk_tree_selection_select_iter (tree_selection, &iter); + } + } + + g_array_unref (current_pids); + g_array_unref (pid_indices_to_add); + g_array_unref (pid_indices_to_remove); +} + +static void +on_end_process_activated (GtkMenuItem *item, + gpointer user_data) +{ + GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data); + GtkTreeSelection *selection; + GtkTreeIter iter; + GPid pid_to_kill; + GError *error; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (op->priv->process_tree_view)); + + if (!gtk_tree_selection_get_selected (selection, + NULL, + &iter)) + goto out; + + gtk_tree_model_get (GTK_TREE_MODEL (op->priv->process_list_store), + &iter, + 2, &pid_to_kill, + -1); + + /* TODO: We might want to either + * + * - Be smart about things and send SIGKILL rather than SIGTERM if + * this is the second time the user requests killing a process + * + * - Or, easier (but worse user experience), offer both "End Process" + * and "Terminate Process" options + * + * But that's not how things work right now.... + */ + error = NULL; + if (!_gtk_mount_operation_kill_process (pid_to_kill, &error)) + { + GtkWidget *dialog; + gint response; + + /* Use GTK_DIALOG_DESTROY_WITH_PARENT here since the parent dialog can be + * indeed be destroyed via the GMountOperation::abort signal... for example, + * this is triggered if the user yanks the device while we are showing + * the dialog... + */ + dialog = gtk_message_dialog_new (GTK_WINDOW (op->priv->dialog), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Unable to end process")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", + error->message); + + gtk_widget_show_all (dialog); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + /* GTK_RESPONSE_NONE means the dialog were programmatically destroy, e.g. that + * GTK_DIALOG_DESTROY_WITH_PARENT kicked in - so it would trigger a warning to + * destroy the dialog in that case + */ + if (response != GTK_RESPONSE_NONE) + gtk_widget_destroy (dialog); + + g_error_free (error); + } + + out: + ; +} + +static gboolean +do_popup_menu_for_process_tree_view (GtkWidget *widget, + GdkEventButton *event, + GtkMountOperation *op) +{ + GtkWidget *menu; + GtkWidget *item; + gint button; + gint event_time; + gboolean popped_up_menu; + + popped_up_menu = FALSE; + + menu = gtk_menu_new (); + + item = gtk_image_menu_item_new_with_mnemonic (_("_End Process")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (on_end_process_activated), + op); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show_all (menu); + + if (event != NULL) + { + GtkTreePath *path; + GtkTreeSelection *selection; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (op->priv->process_tree_view), + (gint) event->x, + (gint) event->y, + &path, + NULL, + NULL, + NULL)) + { + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (op->priv->process_tree_view)); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); + } + else + { + /* don't popup a menu if the user right-clicked in an area with no rows */ + goto out; + } + + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (menu), + NULL, + widget, + NULL, + NULL, + button, + event_time); + + popped_up_menu = TRUE; + + out: + return popped_up_menu; +} + +static gboolean +on_popup_menu_for_process_tree_view (GtkWidget *widget, + gpointer user_data) +{ + GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data); + return do_popup_menu_for_process_tree_view (widget, NULL, op); +} + +static gboolean +on_button_press_event_for_process_tree_view (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkMountOperation *op = GTK_MOUNT_OPERATION (user_data); + gboolean ret; + + ret = FALSE; + + /* Ignore double-clicks and triple-clicks */ + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + ret = do_popup_menu_for_process_tree_view (widget, event, op); + } + + return ret; +} + +static void +create_show_processes_dialog (GMountOperation *op, + const char *message, + const char *choices[]) +{ + GtkMountOperationPrivate *priv; + GtkWidget *dialog; + const char *secondary = NULL; + char *primary; + int count, len = 0; + GtkWidget *label; + GtkWidget *tree_view; + GtkWidget *scrolled_window; + GtkWidget *vbox; + GtkWidget *content_area; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkListStore *list_store; + gchar *s; + + priv = GTK_MOUNT_OPERATION (op)->priv; + + primary = strstr (message, "\n"); + if (primary) + { + secondary = primary + 1; + primary = g_strndup (message, primary - message); + } + + dialog = gtk_dialog_new (); + + if (priv->parent_window != NULL) + gtk_window_set_transient_for (GTK_WINDOW (dialog), priv->parent_window); + gtk_window_set_title (GTK_WINDOW (dialog), ""); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + vbox = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0); + + if (secondary != NULL) + { + s = g_strdup_printf ("<big><b>%s</b></big>\n\n%s", primary, secondary); + } + else + { + s = g_strdup_printf ("%s", primary); + } + g_free (primary); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), s); + g_free (s); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + + /* First count the items in the list then + * add the buttons in reverse order */ + + while (choices[len] != NULL) + len++; + + for (count = len - 1; count >= 0; count--) + gtk_dialog_add_button (GTK_DIALOG (dialog), choices[count], count); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (show_processes_button_clicked), op); + + priv->dialog = GTK_DIALOG (dialog); + g_object_notify (G_OBJECT (op), "is-showing"); + + if (priv->parent_window == NULL && priv->screen) + gtk_window_set_screen (GTK_WINDOW (dialog), priv->screen); + + tree_view = gtk_tree_view_new (); + /* TODO: should use EM's when gtk+ RI patches land */ + gtk_widget_set_size_request (tree_view, + 300, + 120); + + column = gtk_tree_view_column_new (); + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes (column, renderer, + "pixbuf", 0, + NULL); + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, + "ellipsize", PANGO_ELLIPSIZE_MIDDLE, + "ellipsize-set", TRUE, + NULL); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "markup", 1, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE); + + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN); + + gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); + + g_signal_connect (tree_view, "popup-menu", + G_CALLBACK (on_popup_menu_for_process_tree_view), + op); + g_signal_connect (tree_view, "button-press-event", + G_CALLBACK (on_button_press_event_for_process_tree_view), + op); + + list_store = gtk_list_store_new (3, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_INT); + + gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (list_store)); + + priv->process_list_store = list_store; + priv->process_tree_view = tree_view; + /* set pointers to NULL when dialog goes away */ + g_object_add_weak_pointer (G_OBJECT (list_store), (gpointer *) &priv->process_list_store); + g_object_add_weak_pointer (G_OBJECT (tree_view), (gpointer *) &priv->process_tree_view); + + g_object_unref (list_store); + + gtk_widget_show_all (dialog); + g_object_ref (op); +} + +static void +gtk_mount_operation_show_processes (GMountOperation *op, + const char *message, + GArray *processes, + const char *choices[]) +{ + GtkMountOperationPrivate *priv; + + g_return_if_fail (GTK_IS_MOUNT_OPERATION (op)); + g_return_if_fail (message != NULL); + g_return_if_fail (processes != NULL); + g_return_if_fail (choices != NULL); + + priv = GTK_MOUNT_OPERATION (op)->priv; + + if (priv->process_list_store == NULL) + { + /* need to create the dialog */ + create_show_processes_dialog (op, message, choices); + } + + /* otherwise, we're showing the dialog, assume messages+choices hasn't changed */ + + update_process_list_store (GTK_MOUNT_OPERATION (op), + priv->process_list_store, + processes); +} + +static void gtk_mount_operation_aborted (GMountOperation *op) { GtkMountOperationPrivate *priv; |