/* fm-properties-window.c - window that lets user modify file properties * * Copyright (C) 2000 Eazel, Inc. * * The Gnome Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The Gnome Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, * see . * * Authors: Darin Adler */ #include "nautilus-properties-window.h" #include #include #include #include #include #include #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #include "nautilus-application.h" #include "nautilus-dbus-launcher.h" #include "nautilus-enums.h" #include "nautilus-error-reporting.h" #include "nautilus-file-operations.h" #include "nautilus-file-utilities.h" #include "nautilus-global-preferences.h" #include "nautilus-icon-info.h" #include "nautilus-metadata.h" #include "nautilus-mime-actions.h" #include "nautilus-module.h" #include "nautilus-properties-model.h" #include "nautilus-properties-item.h" #include "nautilus-signaller.h" #include "nautilus-tag-manager.h" #include "nautilus-ui-utilities.h" static GHashTable *pending_lists; typedef struct { NautilusFile *file; char *owner; GtkWindow *window; unsigned int timeout; gboolean cancelled; } OwnerChange; typedef struct { NautilusFile *file; char *group; GtkWindow *window; unsigned int timeout; gboolean cancelled; } GroupChange; struct _NautilusPropertiesWindow { AdwWindow parent_instance; GList *original_files; GList *target_files; AdwWindowTitle *window_title; GtkStack *page_stack; /* Basic page */ GtkStack *icon_stack; GtkWidget *icon_image; GtkWidget *icon_button; GtkWidget *icon_button_image; GtkWidget *icon_chooser; GtkWidget *star_button; GtkLabel *name_value_label; GtkWidget *type_value_label; GtkLabel *type_file_system_label; GtkWidget *size_value_label; GtkWidget *contents_box; GtkWidget *contents_value_label; GtkWidget *free_space_value_label; GtkWidget *disk_list_box; GtkLevelBar *disk_space_level_bar; GtkWidget *disk_space_used_value; GtkWidget *disk_space_free_value; GtkWidget *disk_space_capacity_value; GtkWidget *locations_list_box; GtkWidget *link_target_row; GtkWidget *link_target_value_label; GtkWidget *contents_spinner; guint update_directory_contents_timeout_id; guint update_files_timeout_id; GtkWidget *parent_folder_row; GtkWidget *parent_folder_value_label; GtkWidget *trashed_list_box; GtkWidget *trashed_on_value_label; GtkWidget *original_folder_value_label; GtkWidget *times_list_box; GtkWidget *modified_row; GtkWidget *modified_value_label; GtkWidget *created_row; GtkWidget *created_value_label; GtkWidget *accessed_row; GtkWidget *accessed_value_label; GtkWidget *permissions_navigation_row; GtkWidget *permissions_value_label; GtkWidget *extension_models_list_box; /* Permissions page */ GtkWidget *permissions_stack; GtkWidget *unknown_permissions_page; GtkWidget *bottom_prompt_seperator; GtkWidget *not_the_owner_label; AdwComboRow *owner_row; AdwComboRow *owner_access_row; AdwComboRow *owner_folder_access_row; AdwComboRow *owner_file_access_row; AdwComboRow *group_row; AdwComboRow *group_access_row; AdwComboRow *group_folder_access_row; AdwComboRow *group_file_access_row; AdwComboRow *others_access_row; AdwComboRow *others_folder_access_row; AdwComboRow *others_file_access_row; AdwComboRow *execution_row; GtkSwitch *execution_switch; GtkWidget *security_context_list_box; GtkWidget *security_context_value_label; GtkWidget *change_permissions_button_box; GtkWidget *change_permissions_button; GroupChange *group_change; OwnerChange *owner_change; GList *permission_rows; GList *change_permission_combos; GHashTable *initial_permissions; gboolean has_recursive_apply; GList *value_fields; GList *mime_list; gboolean deep_count_finished; GList *deep_count_files; guint deep_count_spinner_timeout_id; guint long_operation_underway; GList *changed_files; guint64 volume_capacity; guint64 volume_free; guint64 volume_used; }; typedef enum { NO_FILES_OR_FOLDERS = (0), FILES_ONLY = (1 << 0), FOLDERS_ONLY = (1 << 1), FILES_AND_FOLDERS = FILES_ONLY | FOLDERS_ONLY, } FilterType; enum { UNIX_PERM_SUID = S_ISUID, UNIX_PERM_SGID = S_ISGID, UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */ UNIX_PERM_USER_READ = S_IRUSR, UNIX_PERM_USER_WRITE = S_IWUSR, UNIX_PERM_USER_EXEC = S_IXUSR, UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, UNIX_PERM_GROUP_READ = S_IRGRP, UNIX_PERM_GROUP_WRITE = S_IWGRP, UNIX_PERM_GROUP_EXEC = S_IXGRP, UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, UNIX_PERM_OTHER_READ = S_IROTH, UNIX_PERM_OTHER_WRITE = S_IWOTH, UNIX_PERM_OTHER_EXEC = S_IXOTH, UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH }; typedef enum { PERMISSION_NONE = (0), PERMISSION_READ = (1 << 0), PERMISSION_WRITE = (1 << 1), PERMISSION_EXEC = (1 << 2), PERMISSION_INCONSISTENT = (1 << 3) } PermissionValue; typedef enum { PERMISSION_USER, PERMISSION_GROUP, PERMISSION_OTHER, NUM_PERMISSION_TYPE } PermissionType; /** Contains permissions for files and folders for each PermissionType */ typedef struct { NautilusPropertiesWindow *window; PermissionValue folder_permissions[NUM_PERMISSION_TYPE]; PermissionValue file_permissions[NUM_PERMISSION_TYPE]; PermissionValue file_exec_permissions; gboolean has_files; gboolean has_folders; gboolean can_set_all_folder_permission; gboolean can_set_all_file_permission; gboolean can_set_any_file_permission; gboolean is_multi_file_window; } TargetPermissions; /* NautilusPermissionEntry - helper struct for permission AdwComboRow */ #define NAUTILUS_TYPE_PERMISSION_ENTRY (nautilus_permission_entry_get_type ()) G_DECLARE_FINAL_TYPE (NautilusPermissionEntry, nautilus_permission_entry, NAUTILUS, PERMISSION_ENTRY, GObject) enum { PROP_NAME = 1, NUM_PROPERTIES }; struct _NautilusPermissionEntry { GObject parent; char *name; PermissionValue permission_value; }; G_DEFINE_TYPE (NautilusPermissionEntry, nautilus_permission_entry, G_TYPE_OBJECT) static void nautilus_permission_entry_init (NautilusPermissionEntry *self) { self->name = NULL; self->permission_value = PERMISSION_NONE; } static void nautilus_permission_entry_finalize (GObject *object) { NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object); g_free (self->name); G_OBJECT_CLASS (nautilus_permission_entry_parent_class)->finalize (object); } static void nautilus_permission_entry_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object); switch (prop_id) { case PROP_NAME: { g_value_set_string (value, self->name); } break; default: { g_assert_not_reached (); } break; } } static void nautilus_permission_entry_class_init (NautilusPermissionEntryClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = nautilus_permission_entry_finalize; gobject_class->get_property = nautilus_permission_entry_get_property; g_object_class_install_property (gobject_class, PROP_NAME, g_param_spec_string ("name", "", "", NULL, G_PARAM_READABLE)); } static gchar * permission_value_to_string (PermissionValue permission_value, gboolean describes_folder) { if (permission_value & PERMISSION_INCONSISTENT) { return "---"; } else if (permission_value & PERMISSION_READ) { if (permission_value & PERMISSION_WRITE) { if (!describes_folder) { return _("Read and write"); } else if (permission_value & PERMISSION_EXEC) { return _("Create and delete files"); } else { return _("Read/write, no access"); } } else { if (!describes_folder) { return _("Read-only"); } else if (permission_value & PERMISSION_EXEC) { return _("Access files"); } else { return _("List files only"); } } } else { if (permission_value & PERMISSION_WRITE) { if (!describes_folder || permission_value & PERMISSION_EXEC) { return _("Write-only"); } else { return _("Write-only, no access"); } } else { if (describes_folder && permission_value & PERMISSION_EXEC) { return _("Access-only"); } else { /* Translators: this is referred to the permissions the user has in a directory. */ return _("None"); } } } } /* end NautilusPermissionEntry */ enum { COLUMN_NAME, COLUMN_VALUE, COLUMN_USE_ORIGINAL, COLUMN_ID, NUM_COLUMNS }; typedef struct { GList *original_files; GList *target_files; GtkWidget *parent_widget; GtkWindow *parent_window; char *startup_id; char *pending_key; GHashTable *pending_files; NautilusPropertiesWindowCallback callback; gpointer callback_data; NautilusPropertiesWindow *window; gboolean cancelled; } StartupData; #define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ #define FILES_UPDATE_INTERVAL 200 /* milliseconds */ /* * A timeout before changes through the user/group combo box will be applied. * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), * this ensures that the GUI doesn't end up unresponsive. * * Both combos react on changes by scheduling a new change and unscheduling * or cancelling old pending changes. */ #define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */ static void schedule_directory_contents_update (NautilusPropertiesWindow *self); static void directory_contents_value_field_update (NautilusPropertiesWindow *self); static void file_changed_callback (NautilusFile *file, gpointer user_data); static void update_execution_row (GtkWidget *row, TargetPermissions *target_perm); static void update_permission_row (AdwComboRow *row, TargetPermissions *target_perm); static void value_field_update (GtkLabel *field, NautilusPropertiesWindow *self); static void properties_window_update (NautilusPropertiesWindow *self, GList *files); static void is_directory_ready_callback (NautilusFile *file, gpointer data); static void cancel_group_change_callback (GroupChange *change); static void cancel_owner_change_callback (OwnerChange *change); static void update_owner_row (AdwComboRow *row, TargetPermissions *target_perm); static void update_group_row (AdwComboRow *row, TargetPermissions *target_perm); static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWindow *self); static void set_icon (const char *icon_path, NautilusPropertiesWindow *self); static void remove_pending (StartupData *data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait); static void refresh_extension_model_pages (NautilusPropertiesWindow *self); static gboolean is_root_directory (NautilusFile *file); G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, ADW_TYPE_WINDOW); static gboolean is_multi_file_window (NautilusPropertiesWindow *self) { GList *l; int count; count = 0; for (l = self->original_files; l != NULL; l = l->next) { if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) { count++; if (count > 1) { return TRUE; } } } return FALSE; } static NautilusFile * get_original_file (NautilusPropertiesWindow *self) { g_return_val_if_fail (!is_multi_file_window (self), NULL); if (self->original_files == NULL) { return NULL; } return NAUTILUS_FILE (self->original_files->data); } static NautilusFile * get_target_file_for_original_file (NautilusFile *file) { NautilusFile *target_file; g_autoptr (GFile) location = NULL; g_autofree char *uri_to_display = NULL; uri_to_display = nautilus_file_get_uri (file); location = g_file_new_for_uri (uri_to_display); target_file = nautilus_file_get (location); return target_file; } static NautilusFile * get_target_file (NautilusPropertiesWindow *self) { return NAUTILUS_FILE (self->target_files->data); } static void navigate_main_page (NautilusPropertiesWindow *self, GParamSpec *params, GtkWidget *widget) { gtk_stack_set_visible_child_name (self->page_stack, "main"); } static void navigate_permissions_page (NautilusPropertiesWindow *self, GParamSpec *params, GtkWidget *widget) { gtk_stack_set_visible_child_name (self->page_stack, "permissions"); } static void navigate_extension_model_page (NautilusPropertiesWindow *self, GParamSpec *params, AdwPreferencesRow *row) { gtk_stack_set_visible_child (self->page_stack, g_object_get_data (G_OBJECT (row), "nautilus-extension-properties-page")); } static void get_image_for_properties_window (NautilusPropertiesWindow *self, char **icon_name, GdkPaintable **icon_paintable) { g_autoptr (NautilusIconInfo) icon = NULL; GList *l; gint icon_scale; icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (self)); for (l = self->original_files; l != NULL; l = l->next) { NautilusFile *file; g_autoptr (NautilusIconInfo) new_icon = NULL; file = NAUTILUS_FILE (l->data); if (!icon) { icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON); } else { new_icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale, NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON); if (!new_icon || new_icon != icon) { g_object_unref (icon); icon = NULL; break; } } } if (!icon) { g_autoptr (GIcon) gicon = g_themed_icon_new ("text-x-generic"); icon = nautilus_icon_info_lookup (gicon, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale); } if (icon_name != NULL) { *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon)); } if (icon_paintable != NULL) { *icon_paintable = nautilus_icon_info_get_paintable (icon); } } static void update_properties_window_icon (NautilusPropertiesWindow *self) { g_autoptr (GdkPaintable) paintable = NULL; g_autofree char *name = NULL; gint pixel_size; get_image_for_properties_window (self, &name, &paintable); if (name != NULL) { gtk_window_set_icon_name (GTK_WINDOW (self), name); } pixel_size = MAX (gdk_paintable_get_intrinsic_width (paintable), gdk_paintable_get_intrinsic_width (paintable)); gtk_image_set_from_paintable (GTK_IMAGE (self->icon_image), paintable); gtk_image_set_from_paintable (GTK_IMAGE (self->icon_button_image), paintable); gtk_image_set_pixel_size (GTK_IMAGE (self->icon_image), pixel_size); gtk_image_set_pixel_size (GTK_IMAGE (self->icon_button_image), pixel_size); } /* utility to test if a uri refers to a local image */ static gboolean uri_is_local_image (const char *uri) { g_autoptr (GdkPixbuf) pixbuf = NULL; g_autofree char *image_path = NULL; image_path = g_filename_from_uri (uri, NULL, NULL); if (image_path == NULL) { return FALSE; } pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); if (pixbuf == NULL) { return FALSE; } return TRUE; } static void reset_icon (NautilusPropertiesWindow *self) { GList *l; for (l = self->original_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, NULL); } } static void nautilus_properties_window_drag_drop_cb (GtkDropTarget *target, const GValue *value, gdouble x, gdouble y, gpointer user_data) { GSList *file_list; gboolean exactly_one; GtkImage *image; GtkWindow *window; image = GTK_IMAGE (user_data); window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (image))); if (!G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) { return; } file_list = g_value_get_boxed (value); exactly_one = file_list != NULL && g_slist_next (file_list) == NULL; if (!exactly_one) { show_dialog (_("You cannot assign more than one custom icon at a time!"), _("Please drop just one image to set a custom icon."), window, GTK_MESSAGE_ERROR); } else { g_autofree gchar *uri = g_file_get_uri (file_list->data); if (uri_is_local_image (uri)) { set_icon (uri, NAUTILUS_PROPERTIES_WINDOW (window)); } else { if (!g_file_is_native (file_list->data)) { show_dialog (_("The file that you dropped is not local."), _("You can only use local images as custom icons."), window, GTK_MESSAGE_ERROR); } else { show_dialog (_("The file that you dropped is not an image."), _("You can only use local images as custom icons."), window, GTK_MESSAGE_ERROR); } } } } static void star_clicked (NautilusPropertiesWindow *self) { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); NautilusFile *file = get_original_file (self); g_autofree gchar *uri = nautilus_file_get_uri (file); if (nautilus_tag_manager_file_is_starred (tag_manager, uri)) { nautilus_tag_manager_unstar_files (tag_manager, G_OBJECT (self), &(GList){ file, NULL }, NULL, NULL); } else { nautilus_tag_manager_star_files (tag_manager, G_OBJECT (self), &(GList){ file, NULL }, NULL, NULL); } } static void update_star (NautilusPropertiesWindow *self, NautilusTagManager *tag_manager) { gboolean is_starred; g_autofree gchar *file_uri = NULL; file_uri = nautilus_file_get_uri (get_target_file (self)); is_starred = nautilus_tag_manager_file_is_starred (tag_manager, file_uri); gtk_button_set_icon_name (GTK_BUTTON (self->star_button), is_starred ? "starred-symbolic" : "non-starred-symbolic"); /* Translators: This is a verb for tagging or untagging a file with a star. */ gtk_widget_set_tooltip_text (self->star_button, is_starred ? _("Unstar") : _("Star")); } static void on_starred_changed (NautilusTagManager *tag_manager, GList *changed_files, gpointer user_data) { NautilusPropertiesWindow *self = user_data; NautilusFile *file = get_target_file (self); if (g_list_find (changed_files, file)) { update_star (self, tag_manager); } } static void setup_star_button (NautilusPropertiesWindow *self) { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); NautilusFile *file = get_target_file (self); g_autoptr (GFile) parent_location = nautilus_file_get_parent_location (file); if (parent_location == NULL) { return; } if (nautilus_tag_manager_can_star_contents (tag_manager, parent_location)) { gtk_widget_show (self->star_button); update_star (self, tag_manager); g_signal_connect_object (tag_manager, "starred-changed", G_CALLBACK (on_starred_changed), self, 0); } } static void setup_image_widget (NautilusPropertiesWindow *self, gboolean is_customizable) { update_properties_window_icon (self); if (is_customizable) { GtkDropTarget *target; /* prepare the image to receive dropped objects to assign custom images */ target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY); gtk_widget_add_controller (self->icon_button, GTK_EVENT_CONTROLLER (target)); g_signal_connect (target, "drop", G_CALLBACK (nautilus_properties_window_drag_drop_cb), self->icon_button_image); g_signal_connect (self->icon_button, "clicked", G_CALLBACK (select_image_button_callback), self); gtk_stack_set_visible_child (self->icon_stack, self->icon_button); } else { gtk_stack_set_visible_child (self->icon_stack, self->icon_image); } } static void update_name_field (NautilusPropertiesWindow *self) { g_autoptr (GString) name_str = g_string_new (""); g_autofree gchar *os_name = NULL; gchar *name_value; guint file_counter = 0; for (GList *l = self->target_files; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); if (!nautilus_file_is_gone (file)) { g_autofree gchar *file_name = NULL; file_counter += 1; if (file_counter > 1) { g_string_append (name_str, ", "); } file_name = nautilus_file_get_display_name (file); g_string_append (name_str, file_name); } } if (!is_multi_file_window (self) && is_root_directory (get_original_file (self))) { os_name = g_get_os_info (G_OS_INFO_KEY_NAME); name_value = (os_name != NULL) ? os_name : _("Operating System"); } else { name_value = name_str->str; } gtk_label_set_text (self->name_value_label, name_value); } /** * Returns the attribute value if all files in file_list have identical * attributes, "unknown" if no files exist and NULL otherwise. */ static char * file_list_get_string_attribute (GList *file_list, const char *attribute_name) { g_autofree char *first_attr = NULL; for (GList *l = file_list; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); if (nautilus_file_is_gone (file)) { continue; } if (first_attr == NULL) { first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); } else { g_autofree char *attr = NULL; attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); if (!g_str_equal (attr, first_attr)) { /* Not all files have the same value for attribute_name. */ return NULL; } } } if (first_attr != NULL) { return g_steal_pointer (&first_attr); } else { return g_strdup (_("unknown")); } } static GtkWidget * create_extension_group_row (NautilusPropertiesItem *item, NautilusPropertiesWindow *self) { GtkWidget *row = adw_action_row_new (); GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3); GtkWidget *name_label = gtk_label_new (NULL); GtkWidget *value_label = gtk_label_new (NULL); gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE); gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); adw_action_row_add_prefix (ADW_ACTION_ROW (row), box); gtk_widget_set_margin_top (box, 7); gtk_widget_set_margin_bottom (box, 7); gtk_box_append (GTK_BOX (box), name_label); gtk_box_append (GTK_BOX (box), value_label); g_object_bind_property (item, "name", name_label, "label", G_BINDING_SYNC_CREATE); gtk_widget_add_css_class (name_label, "caption"); gtk_widget_add_css_class (name_label, "dim-label"); gtk_widget_set_halign (name_label, GTK_ALIGN_START); gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END); g_object_bind_property (item, "value", value_label, "label", G_BINDING_SYNC_CREATE); gtk_widget_set_halign (value_label, GTK_ALIGN_START); gtk_label_set_wrap (GTK_LABEL (value_label), TRUE); gtk_label_set_wrap_mode (GTK_LABEL (value_label), PANGO_WRAP_WORD_CHAR); gtk_label_set_selectable (GTK_LABEL (value_label), TRUE); return row; } static GtkWidget * add_extension_model_page (NautilusPropertiesModel *model, NautilusPropertiesWindow *self) { GListModel *list_model = nautilus_properties_model_get_model (model); GtkWidget *row; GtkWidget *title; GtkWidget *header_bar; GtkWidget *list_box; GtkWidget *clamp; GtkWidget *scrolled_window; GtkWidget *up_button; GtkWidget *box; row = adw_action_row_new (); g_object_bind_property (model, "title", row, "title", G_BINDING_SYNC_CREATE); gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE); gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE); adw_action_row_add_suffix (ADW_ACTION_ROW (row), gtk_image_new_from_icon_name ("go-next-symbolic")); g_signal_connect_swapped (row, "activated", G_CALLBACK (navigate_extension_model_page), self); title = adw_window_title_new (NULL, NULL); g_object_bind_property (model, "title", title, "title", G_BINDING_SYNC_CREATE); up_button = gtk_button_new_from_icon_name ("go-previous-symbolic"); g_signal_connect_swapped (up_button, "clicked", G_CALLBACK (navigate_main_page), self); header_bar = gtk_header_bar_new (); gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header_bar), title); gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), up_button); list_box = gtk_list_box_new (); gtk_widget_add_css_class (list_box, "boxed-list"); gtk_widget_set_valign (list_box, GTK_ALIGN_START); gtk_list_box_bind_model (GTK_LIST_BOX (list_box), list_model, (GtkListBoxCreateWidgetFunc) create_extension_group_row, self, NULL); clamp = adw_clamp_new (); adw_clamp_set_child (ADW_CLAMP (clamp), list_box); gtk_widget_set_margin_top (clamp, 18); gtk_widget_set_margin_bottom (clamp, 18); gtk_widget_set_margin_start (clamp, 18); gtk_widget_set_margin_end (clamp, 18); scrolled_window = gtk_scrolled_window_new (); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), clamp); gtk_widget_set_vexpand (scrolled_window, TRUE); box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_box_append (GTK_BOX (box), header_bar); gtk_box_append (GTK_BOX (box), scrolled_window); gtk_widget_add_css_class (scrolled_window, "background"); gtk_stack_add_named (self->page_stack, box, NULL); g_object_set_data (G_OBJECT (row), "nautilus-extension-properties-page", box); return row; } static void remove_from_dialog (NautilusPropertiesWindow *self, NautilusFile *file) { int index; GList *original_link; GList *target_link; g_autoptr (NautilusFile) original_file = NULL; g_autoptr (NautilusFile) target_file = NULL; index = g_list_index (self->target_files, file); if (index == -1) { index = g_list_index (self->original_files, file); g_return_if_fail (index != -1); } original_link = g_list_nth (self->original_files, index); target_link = g_list_nth (self->target_files, index); g_return_if_fail (original_link && target_link); original_file = NAUTILUS_FILE (original_link->data); target_file = NAUTILUS_FILE (target_link->data); self->original_files = g_list_delete_link (self->original_files, original_link); self->target_files = g_list_delete_link (self->target_files, target_link); g_hash_table_remove (self->initial_permissions, target_file); g_signal_handlers_disconnect_by_func (original_file, G_CALLBACK (file_changed_callback), self); g_signal_handlers_disconnect_by_func (target_file, G_CALLBACK (file_changed_callback), self); nautilus_file_monitor_remove (original_file, &self->original_files); nautilus_file_monitor_remove (target_file, &self->target_files); } static gboolean mime_list_equal (GList *a, GList *b) { while (a && b) { if (strcmp (a->data, b->data)) { return FALSE; } a = a->next; b = b->next; } return (a == b); } static GList * get_mime_list (NautilusPropertiesWindow *self) { return g_list_copy_deep (self->target_files, (GCopyFunc) nautilus_file_get_mime_type, NULL); } static gboolean start_spinner_callback (NautilusPropertiesWindow *self) { gtk_widget_show (self->contents_spinner); gtk_spinner_start (GTK_SPINNER (self->contents_spinner)); self->deep_count_spinner_timeout_id = 0; return FALSE; } static void schedule_start_spinner (NautilusPropertiesWindow *self) { if (self->deep_count_spinner_timeout_id == 0) { self->deep_count_spinner_timeout_id = g_timeout_add_seconds (1, (GSourceFunc) start_spinner_callback, self); } } static void stop_spinner (NautilusPropertiesWindow *self) { gtk_spinner_stop (GTK_SPINNER (self->contents_spinner)); gtk_widget_hide (self->contents_spinner); g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove); } static void stop_deep_count_for_file (NautilusPropertiesWindow *self, NautilusFile *file) { if (g_list_find (self->deep_count_files, file)) { g_signal_handlers_disconnect_by_func (file, G_CALLBACK (schedule_directory_contents_update), self); nautilus_file_unref (file); self->deep_count_files = g_list_remove (self->deep_count_files, file); } } static void start_deep_count_for_file (NautilusFile *file, NautilusPropertiesWindow *self) { if (!nautilus_file_is_directory (file)) { return; } if (!g_list_find (self->deep_count_files, file)) { nautilus_file_ref (file); self->deep_count_files = g_list_prepend (self->deep_count_files, file); nautilus_file_recompute_deep_counts (file); if (!self->deep_count_finished) { g_signal_connect_object (file, "updated-deep-count-in-progress", G_CALLBACK (schedule_directory_contents_update), self, G_CONNECT_SWAPPED); schedule_start_spinner (self); } } } static guint32 vfs_perms[3][3] = { {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, }; static guint32 permission_to_vfs (PermissionType type, PermissionValue perm) { guint32 vfs_perm; g_assert (type >= 0 && type < 3); vfs_perm = 0; if (perm & PERMISSION_READ) { vfs_perm |= vfs_perms[type][0]; } if (perm & PERMISSION_WRITE) { vfs_perm |= vfs_perms[type][1]; } if (perm & PERMISSION_EXEC) { vfs_perm |= vfs_perms[type][2]; } return vfs_perm; } static PermissionValue permission_from_vfs (PermissionType type, guint32 vfs_perm) { PermissionValue perm = PERMISSION_NONE; g_assert (type >= 0 && type < 3); if (vfs_perm & vfs_perms[type][0]) { perm |= PERMISSION_READ; } if (vfs_perm & vfs_perms[type][1]) { perm |= PERMISSION_WRITE; } if (vfs_perm & vfs_perms[type][2]) { perm |= PERMISSION_EXEC; } return perm; } static PermissionValue exec_permission_from_vfs (guint32 vfs_perm) { guint32 perm_user = vfs_perm & UNIX_PERM_USER_EXEC; guint32 perm_group = vfs_perm & UNIX_PERM_GROUP_EXEC; guint32 perm_other = vfs_perm & UNIX_PERM_OTHER_EXEC; if (perm_user && perm_group && perm_other) { return PERMISSION_EXEC; } else if (perm_user || perm_group || perm_other) { return PERMISSION_INCONSISTENT; } else { return PERMISSION_NONE; } } static TargetPermissions * get_target_permissions (NautilusPropertiesWindow *self) { TargetPermissions *p = g_new0 (TargetPermissions, 1); p->window = self; p->can_set_all_folder_permission = TRUE; p->can_set_all_file_permission = TRUE; for (GList *entry = self->target_files; entry != NULL; entry = entry->next) { guint32 vfs_permissions; gboolean can_set_permissions; NautilusFile *file = NAUTILUS_FILE (entry->data); if (nautilus_file_is_gone (file) || !nautilus_file_can_get_permissions (file)) { continue; } vfs_permissions = nautilus_file_get_permissions (file); can_set_permissions = nautilus_file_can_set_permissions (file); if (nautilus_file_is_directory (file)) { /* Gather permissions for each type (owner, group, other) */ for (PermissionType type = PERMISSION_USER; type < NUM_PERMISSION_TYPE; type += 1) { PermissionValue permissions = permission_from_vfs (type, vfs_permissions) & (PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC); if (!p->has_folders) { /* first found folder, initialize with its permissions */ p->folder_permissions[type] = permissions; } else if (permissions != p->folder_permissions[type]) { p->folder_permissions[type] = PERMISSION_INCONSISTENT; } } p->can_set_all_folder_permission &= can_set_permissions; p->has_folders = TRUE; } else { PermissionValue exec_permissions = exec_permission_from_vfs (vfs_permissions); if (!p->has_files) { p->file_exec_permissions = exec_permissions; } else if (exec_permissions != p->file_exec_permissions) { p->file_exec_permissions = PERMISSION_INCONSISTENT; } for (PermissionType type = PERMISSION_USER ; type < NUM_PERMISSION_TYPE; type += 1) { PermissionValue permissions = permission_from_vfs (type, vfs_permissions) & (PERMISSION_READ | PERMISSION_WRITE); if (!p->has_files) { /* first found file, initialize with its permissions */ p->file_permissions[type] = permissions; } else if (permissions != p->file_permissions[type]) { p->file_permissions[type] = PERMISSION_INCONSISTENT; } } p->can_set_all_file_permission &= can_set_permissions; p->can_set_any_file_permission |= can_set_permissions; p->has_files = TRUE; } } p->is_multi_file_window = is_multi_file_window (self); return p; } static void update_permissions_navigation_row (NautilusPropertiesWindow *self, TargetPermissions *target_perm) { if (!target_perm->is_multi_file_window) { uid_t user_id = geteuid (); gid_t group_id = getegid (); PermissionType permission_type = PERMISSION_OTHER; const gchar *text; if (user_id == nautilus_file_get_uid (get_original_file (self))) { permission_type = PERMISSION_USER; } else if (group_id == nautilus_file_get_gid (get_original_file (self))) { permission_type = PERMISSION_GROUP; } if (nautilus_file_is_directory (get_original_file (self))) { text = permission_value_to_string (target_perm->folder_permissions[permission_type], TRUE); } else { text = permission_value_to_string (target_perm->file_permissions[permission_type], FALSE); } gtk_label_set_text (GTK_LABEL (self->permissions_value_label), text); } } static void properties_window_update (NautilusPropertiesWindow *self, GList *files) { GList *mime_list; NautilusFile *changed_file; gboolean dirty_original = FALSE; gboolean dirty_target = FALSE; if (files == NULL) { dirty_original = TRUE; dirty_target = TRUE; } for (GList *tmp = files; tmp != NULL; tmp = tmp->next) { changed_file = NAUTILUS_FILE (tmp->data); if (changed_file && nautilus_file_is_gone (changed_file)) { /* Remove the file from the property dialog */ remove_from_dialog (self, changed_file); changed_file = NULL; if (self->original_files == NULL) { return; } } if (changed_file == NULL || g_list_find (self->original_files, changed_file)) { dirty_original = TRUE; } if (changed_file == NULL || g_list_find (self->target_files, changed_file)) { dirty_target = TRUE; } } if (dirty_original) { update_properties_window_icon (self); update_name_field (self); /* If any of the value fields start to depend on the original * value, value_field_updates should be added here */ } if (dirty_target) { g_autofree TargetPermissions *target_perm = get_target_permissions (self); update_permissions_navigation_row (self, target_perm); update_owner_row (self->owner_row, target_perm); update_group_row (self->group_row, target_perm); update_execution_row (GTK_WIDGET (self->execution_row), target_perm); g_list_foreach (self->permission_rows, (GFunc) update_permission_row, target_perm); g_list_foreach (self->value_fields, (GFunc) value_field_update, self); } mime_list = get_mime_list (self); if (self->mime_list == NULL) { self->mime_list = mime_list; } else { if (!mime_list_equal (self->mime_list, mime_list)) { refresh_extension_model_pages (self); } g_list_free_full (self->mime_list, g_free); self->mime_list = mime_list; } } static gboolean update_files_callback (gpointer data) { NautilusPropertiesWindow *self; self = NAUTILUS_PROPERTIES_WINDOW (data); self->update_files_timeout_id = 0; properties_window_update (self, self->changed_files); if (self->original_files == NULL) { /* Close the window if no files are left */ gtk_window_destroy (GTK_WINDOW (self)); } else { nautilus_file_list_free (self->changed_files); self->changed_files = NULL; } return FALSE; } static void schedule_files_update (NautilusPropertiesWindow *self) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); if (self->update_files_timeout_id == 0) { self->update_files_timeout_id = g_timeout_add (FILES_UPDATE_INTERVAL, update_files_callback, self); } } static gboolean location_show_original (NautilusPropertiesWindow *self) { NautilusFile *file; /* there is no way a recent item will be mixed with * other items so just pick the first file to check */ file = NAUTILUS_FILE (g_list_nth_data (self->original_files, 0)); return (file != NULL && !nautilus_file_is_in_recent (file)); } static void value_field_update (GtkLabel *label, NautilusPropertiesWindow *self) { GList *file_list; const char *attribute_name; g_autofree char *attribute_value = NULL; gboolean is_where; g_assert (GTK_IS_LABEL (label)); attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); is_where = (g_strcmp0 (attribute_name, "where") == 0); if (is_where && location_show_original (self)) { file_list = self->original_files; } else { file_list = self->target_files; } attribute_value = file_list_get_string_attribute (file_list, attribute_name); if (g_str_equal (attribute_name, "detailed_type")) { g_autofree char *mime_type = NULL; gchar *cap_label; mime_type = file_list_get_string_attribute (file_list, "mime_type"); gtk_widget_set_tooltip_text (GTK_WIDGET (label), mime_type); cap_label = eel_str_capitalize (attribute_value); if (cap_label != NULL) { g_free (attribute_value); attribute_value = cap_label; } } else if (g_str_equal (attribute_name, "size")) { g_autofree char *size_detail = NULL; size_detail = file_list_get_string_attribute (file_list, "size_detail"); gtk_widget_set_tooltip_text (GTK_WIDGET (label), size_detail); } gtk_label_set_text (label, attribute_value); } static guint hash_string_list (GList *list) { guint hash_value = 0; for (GList *node = list; node != NULL; node = node->next) { hash_value ^= g_str_hash ((gconstpointer) node->data); } return hash_value; } static gsize get_first_word_length (const gchar *str) { const gchar *space_pos = g_strstr_len (str, -1, " "); return space_pos ? space_pos - str : strlen (str); } static void update_combo_row_dropdown (AdwComboRow *row, GList *entries) { /* check if dropdown already exist and is up to date by comparing with stored hash. */ guint current_hash = hash_string_list (entries); guint stored_hash = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "dropdown-hash")); if (stored_hash != current_hash) { /* Recreate the drop down. */ g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL); for (GList *node = entries; node != NULL; node = node->next) { const char *entry = (const char *) node->data; gtk_string_list_append (new_model, entry); } adw_combo_row_set_model (row, G_LIST_MODEL (new_model)); g_object_set_data (G_OBJECT (row), "dropdown-hash", GUINT_TO_POINTER (current_hash)); } } typedef gboolean CompareOwnershipRowFunc (GListModel *list, guint position, const char *str); static void select_ownership_row_entry (AdwComboRow *row, const char *entry, CompareOwnershipRowFunc compare_func) { GListModel *list = adw_combo_row_get_model (row); guint index_to_select = GTK_INVALID_LIST_POSITION; guint n_entries; /* check if entry is already selected */ gint selected_pos = adw_combo_row_get_selected (row); if (selected_pos >= 0 && compare_func (list, selected_pos, entry)) { /* entry already selected */ return; } /* check if entry exists in model list */ n_entries = g_list_model_get_n_items (list); for (guint position = 0; position < n_entries; position += 1) { if (compare_func (list, position, entry)) { /* found entry in list, select it */ index_to_select = position; break; } } if (index_to_select == GTK_INVALID_LIST_POSITION) { /* entry not in list, add */ gtk_string_list_append (GTK_STRING_LIST (list), entry); index_to_select = n_entries; } adw_combo_row_set_selected (row, index_to_select); } static void ownership_row_set_single_entry (AdwComboRow *row, const char *entry, CompareOwnershipRowFunc compare_func) { GListModel *list = adw_combo_row_get_model (row); gint selected_pos = adw_combo_row_get_selected (row); /* check entry not already displayed */ if (selected_pos < 0 || g_list_model_get_n_items (list) > 1 || !compare_func (list, selected_pos, entry)) { g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL); /* set current entry as only entry */ gtk_string_list_append (new_model, entry); adw_combo_row_set_model (row, G_LIST_MODEL (new_model)); adw_combo_row_set_selected (row, 0); } } static void group_change_free (GroupChange *change) { nautilus_file_unref (change->file); g_free (change->group); g_object_unref (change->window); g_free (change); } static void group_change_callback (NautilusFile *file, GFile *res_loc, GError *error, GroupChange *change) { NautilusPropertiesWindow *self; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); if (!change->cancelled) { /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); nautilus_report_error_setting_group (change->file, error, change->window); } self = NAUTILUS_PROPERTIES_WINDOW (change->window); if (self->group_change == change) { self->group_change = NULL; } group_change_free (change); } static void cancel_group_change_callback (GroupChange *change) { g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); change->cancelled = TRUE; nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change); } static gboolean schedule_group_change_timeout (GroupChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); change->timeout = 0; eel_timed_wait_start ((EelCancelCallback) cancel_group_change_callback, change, _("Cancel Group Change?"), change->window); nautilus_file_set_group (change->file, change->group, (NautilusFileOperationCallback) group_change_callback, change); return FALSE; } static void schedule_group_change (NautilusPropertiesWindow *self, NautilusFile *file, const char *group) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); g_assert (self->group_change == NULL); g_assert (NAUTILUS_IS_FILE (file)); change = g_new0 (GroupChange, 1); change->file = nautilus_file_ref (file); change->group = g_strdup (group); change->window = GTK_WINDOW (g_object_ref (self)); change->timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_group_change_timeout, change); self->group_change = change; } static void unschedule_or_cancel_group_change (NautilusPropertiesWindow *self) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); change = self->group_change; if (change != NULL) { if (change->timeout == 0) { /* The operation was started, cancel it and let the operation callback free the change */ cancel_group_change_callback (change); eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); } else { g_source_remove (change->timeout); group_change_free (change); } self->group_change = NULL; } } /** Apply group owner change on user selection. */ static void changed_group_callback (AdwComboRow *row, GParamSpec *pspec, NautilusPropertiesWindow *self) { guint selected_pos = adw_combo_row_get_selected (row); g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); if (selected_pos >= 0) { NautilusFile *file = get_target_file (self); GListModel *list = adw_combo_row_get_model (row); const gchar *new_group_name = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos); g_autofree char *current_group_name = nautilus_file_get_group_name (file); g_assert (new_group_name); g_assert (current_group_name); if (strcmp (new_group_name, current_group_name) != 0) { /* Try to change file group. If this fails, complain to user. */ unschedule_or_cancel_group_change (self); schedule_group_change (self, file, new_group_name); } } } static void owner_change_free (OwnerChange *change) { nautilus_file_unref (change->file); g_free (change->owner); g_object_unref (change->window); g_free (change); } static void owner_change_callback (NautilusFile *file, GFile *result_location, GError *error, OwnerChange *change) { NautilusPropertiesWindow *self; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); if (!change->cancelled) { /* Report the error if it's an error. */ eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); nautilus_report_error_setting_owner (file, error, change->window); } self = NAUTILUS_PROPERTIES_WINDOW (change->window); if (self->owner_change == change) { self->owner_change = NULL; } owner_change_free (change); } static void cancel_owner_change_callback (OwnerChange *change) { g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); change->cancelled = TRUE; nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change); } static gboolean schedule_owner_change_timeout (OwnerChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); change->timeout = 0; eel_timed_wait_start ((EelCancelCallback) cancel_owner_change_callback, change, _("Cancel Owner Change?"), change->window); nautilus_file_set_owner (change->file, change->owner, (NautilusFileOperationCallback) owner_change_callback, change); return FALSE; } static void schedule_owner_change (NautilusPropertiesWindow *self, NautilusFile *file, const char *owner) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); g_assert (self->owner_change == NULL); g_assert (NAUTILUS_IS_FILE (file)); change = g_new0 (OwnerChange, 1); change->file = nautilus_file_ref (file); change->owner = g_strdup (owner); change->window = GTK_WINDOW (g_object_ref (self)); change->timeout = g_timeout_add (CHOWN_CHGRP_TIMEOUT, (GSourceFunc) schedule_owner_change_timeout, change); self->owner_change = change; } static void unschedule_or_cancel_owner_change (NautilusPropertiesWindow *self) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); change = self->owner_change; if (change != NULL) { g_assert (NAUTILUS_IS_FILE (change->file)); if (change->timeout == 0) { /* The operation was started, cancel it and let the operation callback free the change */ cancel_owner_change_callback (change); eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); } else { g_source_remove (change->timeout); owner_change_free (change); } self->owner_change = NULL; } } static void changed_owner_callback (AdwComboRow *row, GParamSpec *pspec, NautilusPropertiesWindow *self) { guint selected_pos = adw_combo_row_get_selected (row); g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); if (selected_pos >= 0) { NautilusFile *file = get_target_file (self); GListModel *list = adw_combo_row_get_model (row); const gchar *selected_owner_str = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos); gsize owner_name_length = get_first_word_length (selected_owner_str); g_autofree gchar *new_owner_name = g_strndup (selected_owner_str, owner_name_length); g_autofree char *current_owner_name = nautilus_file_get_owner_name (file); g_assert (NAUTILUS_IS_FILE (file)); if (strcmp (new_owner_name, current_owner_name) != 0) { /* Try to change file owner. If this fails, complain to user. */ unschedule_or_cancel_owner_change (self); schedule_owner_change (self, file, new_owner_name); } } } static gboolean string_list_item_starts_with_word (GListModel *list, guint position, const char *word) { const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position); return g_str_has_prefix (entry_str, word) && strlen (word) == get_first_word_length (entry_str); } static gboolean string_list_item_equals_string (GListModel *list, guint position, const char *string) { const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position); return strcmp (entry_str, string) == 0; } /* Select correct owner if file permissions have changed. */ static void update_owner_row (AdwComboRow *row, TargetPermissions *target_perm) { NautilusPropertiesWindow *self = target_perm->window; gboolean provide_dropdown = (!target_perm->is_multi_file_window && nautilus_file_can_set_owner (get_target_file (self))); gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row)); gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown); /* check if should provide dropdown */ if (provide_dropdown) { NautilusFile *file = get_target_file (self); g_autofree char *owner_name = nautilus_file_get_owner_name (file); GList *users = nautilus_get_user_names (); update_combo_row_dropdown (row, users); /* display current owner */ select_ownership_row_entry (row, owner_name, string_list_item_starts_with_word); if (!had_dropdown) { /* Update file when selection changes. */ g_signal_connect (row, "notify::selected", G_CALLBACK (changed_owner_callback), self); } } else { g_autofree char *owner_name = file_list_get_string_attribute (self->target_files, "owner"); if (owner_name == NULL) { owner_name = g_strdup (_("Multiple")); } g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_owner_callback), self); ownership_row_set_single_entry (row, owner_name, string_list_item_starts_with_word); } } /* Select correct group if file permissions have changed. */ static void update_group_row (AdwComboRow *row, TargetPermissions *target_perm) { NautilusPropertiesWindow *self = target_perm->window; gboolean provide_dropdown = (!target_perm->is_multi_file_window && nautilus_file_can_set_group (get_target_file (self))); gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row)); gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown); if (provide_dropdown) { NautilusFile *file = get_target_file (self); g_autofree char *group_name = nautilus_file_get_group_name (file); GList *groups = nautilus_file_get_settable_group_names (file); update_combo_row_dropdown (row, groups); /* display current group */ select_ownership_row_entry (row, group_name, string_list_item_equals_string); if (!had_dropdown) { /* Update file when selection changes. */ g_signal_connect (row, "notify::selected", G_CALLBACK (changed_group_callback), self); } } else { g_autofree char *group_name = file_list_get_string_attribute (self->target_files, "group"); if (group_name == NULL) { group_name = g_strdup (_("Multiple")); } g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_group_callback), self); ownership_row_set_single_entry (row, group_name, string_list_item_equals_string); } } static void setup_ownership_row (NautilusPropertiesWindow *self, AdwComboRow *row) { adw_combo_row_set_model (row, G_LIST_MODEL (gtk_string_list_new (NULL))); /* Intial setup of list model is handled via update function, called via properties_window_update. */ } static gboolean file_has_prefix (NautilusFile *file, GList *prefix_candidates) { GList *p; g_autoptr (GFile) location = NULL; location = nautilus_file_get_location (file); for (p = prefix_candidates; p != NULL; p = p->next) { g_autoptr (GFile) candidate_location = NULL; if (file == p->data) { continue; } candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data)); if (g_file_has_prefix (location, candidate_location)) { return TRUE; } } return FALSE; } static void directory_contents_value_field_update (NautilusPropertiesWindow *self) { NautilusRequestStatus file_status; g_autofree char *text = NULL; guint directory_count; guint file_count; guint total_count; guint unreadable_directory_count; goffset total_size; NautilusFile *file; GList *l; guint file_unreadable; goffset file_size; gboolean deep_count_active; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); total_count = 0; total_size = 0; unreadable_directory_count = FALSE; for (l = self->target_files; l; l = l->next) { file = NAUTILUS_FILE (l->data); if (file_has_prefix (file, self->target_files)) { /* don't count nested files twice */ continue; } if (nautilus_file_is_directory (file)) { file_status = nautilus_file_get_deep_counts (file, &directory_count, &file_count, &file_unreadable, &file_size, TRUE); total_count += (file_count + directory_count); total_size += file_size; if (file_unreadable) { unreadable_directory_count = TRUE; } if (file_status == NAUTILUS_REQUEST_DONE) { stop_deep_count_for_file (self, file); } } else { ++total_count; total_size += nautilus_file_get_size (file); } } deep_count_active = (self->deep_count_files != NULL); /* If we've already displayed the total once, don't do another visible * count-up if the deep_count happens to get invalidated. * But still display the new total, since it might have changed. */ if (self->deep_count_finished && deep_count_active) { return; } text = NULL; if (total_count == 0) { if (!deep_count_active) { if (unreadable_directory_count == 0) { text = g_strdup (_("Empty folder")); } else { text = g_strdup (_("Contents unreadable")); } } else { text = g_strdup ("…"); } } else { g_autofree char *size_str = NULL; size_str = g_format_size (total_size); text = g_strdup_printf (ngettext ("%'d item, with size %s", "%'d items, totalling %s", total_count), total_count, size_str); if (unreadable_directory_count != 0) { g_autofree char *temp = g_steal_pointer (&text); text = g_strconcat (temp, "\n", _("(some contents unreadable)"), NULL); } } gtk_label_set_text (GTK_LABEL (self->contents_value_label), text); if (!deep_count_active) { self->deep_count_finished = TRUE; stop_spinner (self); } } static gboolean update_directory_contents_callback (gpointer data) { NautilusPropertiesWindow *self; self = NAUTILUS_PROPERTIES_WINDOW (data); self->update_directory_contents_timeout_id = 0; directory_contents_value_field_update (self); return FALSE; } static void schedule_directory_contents_update (NautilusPropertiesWindow *self) { g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); if (self->update_directory_contents_timeout_id == 0) { self->update_directory_contents_timeout_id = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, update_directory_contents_callback, self); } } static void setup_contents_field (NautilusPropertiesWindow *self) { g_list_foreach (self->target_files, (GFunc) start_deep_count_for_file, self); /* Fill in the initial value. */ directory_contents_value_field_update (self); } static gboolean is_root_directory (NautilusFile *file) { g_autoptr (GFile) location = NULL; gboolean result; location = nautilus_file_get_location (file); result = nautilus_is_root_directory (location); return result; } static gboolean is_network_directory (NautilusFile *file) { g_autofree char *file_uri = NULL; file_uri = nautilus_file_get_uri (file); return strcmp (file_uri, "network:///") == 0; } static gboolean is_burn_directory (NautilusFile *file) { g_autofree char *file_uri = NULL; file_uri = nautilus_file_get_uri (file); return strcmp (file_uri, "burn:///") == 0; } static gboolean is_volume_properties (NautilusPropertiesWindow *self) { NautilusFile *file; gboolean success = FALSE; if (is_multi_file_window (self)) { return FALSE; } file = get_original_file (self); if (file == NULL) { return FALSE; } if (is_root_directory (file) && nautilus_application_is_sandboxed ()) { return FALSE; } if (nautilus_file_can_unmount (file)) { return TRUE; } success = is_root_directory (file); #ifdef TODO_GIO /* Look at is_mountpoint for activation uri */ #endif return success; } static gboolean should_show_custom_icon_buttons (NautilusPropertiesWindow *self) { if (is_multi_file_window (self) || is_volume_properties (self) || is_root_directory (get_original_file (self))) { return FALSE; } return TRUE; } static gboolean is_single_file_type (NautilusPropertiesWindow *self) { if (is_multi_file_window (self)) { g_autofree gchar *mime_type = NULL; GList *l = self->original_files; mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)); for (l = l->next; l != NULL; l = l->next) { g_autofree gchar *next_mime_type = NULL; if (nautilus_file_is_gone (NAUTILUS_FILE (l->data))) { continue; } next_mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)); if (g_strcmp0 (next_mime_type, mime_type) != 0) { return FALSE; } } } return TRUE; } static gboolean should_show_file_type (NautilusPropertiesWindow *self) { if (!is_single_file_type (self)) { return FALSE; } if (!is_multi_file_window (self) && (nautilus_file_is_in_trash (get_target_file (self)) || nautilus_file_is_directory (get_original_file (self)) || is_network_directory (get_target_file (self)) || is_burn_directory (get_target_file (self)) || is_volume_properties (self))) { return FALSE; } return TRUE; } static gboolean should_show_location_info (NautilusPropertiesWindow *self) { GList *l; for (l = self->original_files; l != NULL; l = l->next) { if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || is_root_directory (NAUTILUS_FILE (l->data)) || is_network_directory (NAUTILUS_FILE (l->data)) || is_burn_directory (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static gboolean should_show_trashed_info (NautilusPropertiesWindow *self) { GList *l; for (l = self->original_files; l != NULL; l = l->next) { if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static gboolean should_show_accessed_date (NautilusPropertiesWindow *self) { /* Accessed date for directory seems useless. If we some * day decide that it is useful, we should separately * consider whether it's useful for "trash:". */ if (nautilus_file_list_are_all_folders (self->target_files) || is_multi_file_window (self)) { return FALSE; } return TRUE; } static gboolean should_show_modified_date (NautilusPropertiesWindow *self) { return !is_multi_file_window (self); } static gboolean should_show_created_date (NautilusPropertiesWindow *self) { return !is_multi_file_window (self); } static gboolean should_show_link_target (NautilusPropertiesWindow *self) { if (!is_multi_file_window (self) && nautilus_file_is_symbolic_link (get_target_file (self))) { return TRUE; } return FALSE; } static gboolean should_show_free_space (NautilusPropertiesWindow *self) { if (!is_multi_file_window (self) && (nautilus_file_is_in_trash (get_target_file (self)) || is_network_directory (get_target_file (self)) || nautilus_file_is_in_recent (get_target_file (self)) || is_burn_directory (get_target_file (self)) || is_volume_properties (self))) { return FALSE; } if (nautilus_file_list_are_all_folders (self->target_files)) { return TRUE; } return FALSE; } static gboolean should_show_volume_usage (NautilusPropertiesWindow *self) { return is_volume_properties (self); } static void setup_volume_information (NautilusPropertiesWindow *self) { NautilusFile *file; g_autofree gchar *capacity = NULL; g_autofree gchar *used = NULL; g_autofree gchar *free = NULL; const char *fs_type; g_autofree gchar *uri = NULL; g_autoptr (GFile) location = NULL; g_autoptr (GFileInfo) info = NULL; capacity = g_format_size (self->volume_capacity); free = g_format_size (self->volume_free); used = g_format_size (self->volume_used); file = get_original_file (self); uri = nautilus_file_get_activation_uri (file); gtk_label_set_text (GTK_LABEL (self->disk_space_used_value), used); gtk_label_set_text (GTK_LABEL (self->disk_space_free_value), free); gtk_label_set_text (GTK_LABEL (self->disk_space_capacity_value), capacity); location = g_file_new_for_uri (uri); info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, NULL, NULL); if (info) { fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); /* We shouldn't be using filesystem::type, it's not meant for UI. * https://gitlab.gnome.org/GNOME/nautilus/-/issues/98 * * Until we fix that issue, workaround this common outrageous case. */ if (g_strcmp0 (fs_type, "msdos") == 0) { fs_type = "FAT"; } if (fs_type != NULL) { /* Translators: %s will be filled with a filesystem type, such as 'ext4' or 'msdos'. */ g_autofree gchar *fs_label = g_strdup_printf (_("%s Filesystem"), fs_type); gchar *cap_label = eel_str_capitalize (fs_label); if (cap_label != NULL) { g_free (fs_label); fs_label = cap_label; } gtk_label_set_text (self->type_file_system_label, fs_label); gtk_widget_show (GTK_WIDGET (self->type_file_system_label)); } } gtk_level_bar_set_value (self->disk_space_level_bar, (double) self->volume_used / (double) self->volume_capacity); /* display color changing based on filled level */ gtk_level_bar_add_offset_value (self->disk_space_level_bar, GTK_LEVEL_BAR_OFFSET_FULL, 0.0); } static void setup_volume_usage_widget (NautilusPropertiesWindow *self) { NautilusFile *file; g_autofree gchar *uri = NULL; g_autoptr (GFile) location = NULL; g_autoptr (GFileInfo) info = NULL; file = get_original_file (self); uri = nautilus_file_get_activation_uri (file); location = g_file_new_for_uri (uri); info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); if (info) { self->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); self->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED)) { self->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); } else { self->volume_used = self->volume_capacity - self->volume_free; } } else { self->volume_capacity = 0; self->volume_free = 0; self->volume_used = 0; } if (self->volume_capacity > 0) { setup_volume_information (self); } } static void open_parent_folder (NautilusPropertiesWindow *self) { g_autoptr (GFile) parent_location = NULL; parent_location = nautilus_file_get_parent_location (get_target_file (self)); g_return_if_fail (parent_location != NULL); nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), parent_location, NAUTILUS_OPEN_FLAG_NEW_WINDOW, &(GList){get_original_file (self), NULL}, NULL, NULL); } static void open_link_target (NautilusPropertiesWindow *self) { g_autofree gchar *link_target_uri = NULL; g_autoptr (GFile) link_target_location = NULL; g_autoptr (NautilusFile) link_target_file = NULL; g_autoptr (GFile) parent_location = NULL; link_target_uri = nautilus_file_get_symbolic_link_target_uri (get_target_file (self)); g_return_if_fail (link_target_uri != NULL); link_target_location = g_file_new_for_uri (link_target_uri); link_target_file = nautilus_file_get (link_target_location); parent_location = nautilus_file_get_parent_location (link_target_file); g_return_if_fail (parent_location != NULL); nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), parent_location, NAUTILUS_OPEN_FLAG_NEW_WINDOW, &(GList){link_target_file, NULL}, NULL, NULL); } static void open_in_disks (NautilusPropertiesWindow *self) { NautilusDBusLauncher *launcher = nautilus_dbus_launcher_get (); g_autoptr (GMount) mount = NULL; g_autoptr (GVolume) volume = NULL; g_autofree gchar *device_identifier = NULL; GVariant *parameters; mount = nautilus_file_get_mount (get_original_file (self)); volume = (mount != NULL) ? g_mount_get_volume (mount) : NULL; if (volume != NULL) { device_identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); } else { g_autoptr (GFile) location = NULL; g_autofree gchar *path = NULL; g_autoptr (GUnixMountEntry) mount_entry = NULL; location = nautilus_file_get_location (get_original_file (self)); path = g_file_get_path (location); mount_entry = (path != NULL) ? g_unix_mount_at (path, NULL) : NULL; if (mount_entry != NULL) { device_identifier = g_strdup (g_unix_mount_get_device_path (mount_entry)); } } if (device_identifier != NULL) { parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', " "@aay [], {'options': <{'block-device': <%s>}> })", device_identifier); } else { parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], @a{sv} {})"); } nautilus_dbus_launcher_call (launcher, NAUTILUS_DBUS_LAUNCHER_DISKS, "CommandLine", parameters, GTK_WINDOW (self)); } static void add_updatable_label (NautilusPropertiesWindow *self, GtkWidget *label, const char *file_attribute) { g_object_set_data_full (G_OBJECT (label), "file_attribute", g_strdup (file_attribute), g_free); self->value_fields = g_list_prepend (self->value_fields, label); } static void setup_basic_page (NautilusPropertiesWindow *self) { gboolean should_show_locations_list_box = FALSE; /* Icon pixmap */ setup_image_widget (self, should_show_custom_icon_buttons (self)); self->icon_chooser = NULL; if (!is_multi_file_window (self)) { setup_star_button (self); } update_name_field (self); if (should_show_volume_usage (self)) { gtk_widget_show (self->disk_list_box); setup_volume_usage_widget (self); } if (should_show_file_type (self)) { gtk_widget_show (self->type_value_label); add_updatable_label (self, self->type_value_label, "detailed_type"); } if (should_show_link_target (self)) { gtk_widget_show (self->link_target_row); add_updatable_label (self, self->link_target_value_label, "link_target"); should_show_locations_list_box = TRUE; } if (is_multi_file_window (self) || nautilus_file_is_directory (get_target_file (self))) { /* We have a more efficient way to measure used space in volumes. */ if (!is_volume_properties (self)) { gtk_widget_show (self->contents_box); setup_contents_field (self); } } else { gtk_widget_show (self->size_value_label); add_updatable_label (self, self->size_value_label, "size"); } if (should_show_location_info (self)) { gtk_widget_show (self->parent_folder_row); add_updatable_label (self, self->parent_folder_value_label, "where"); should_show_locations_list_box = TRUE; } if (should_show_trashed_info (self)) { gtk_widget_show (self->trashed_list_box); add_updatable_label (self, self->original_folder_value_label, "trash_orig_path"); add_updatable_label (self, self->trashed_on_value_label, "trashed_on_full"); } if (should_show_modified_date (self)) { gtk_widget_show (self->times_list_box); gtk_widget_show (self->modified_row); add_updatable_label (self, self->modified_value_label, "date_modified_full"); } if (should_show_created_date (self)) { gtk_widget_show (self->created_row); gtk_widget_show (self->times_list_box); add_updatable_label (self, self->created_value_label, "date_created_full"); } if (should_show_accessed_date (self)) { gtk_widget_show (self->times_list_box); gtk_widget_show (self->accessed_row); add_updatable_label (self, self->accessed_value_label, "date_accessed_full"); } if (should_show_free_space (self)) { /* We have a more efficient way to measure free space in volumes. */ if (!is_volume_properties (self)) { gtk_widget_show (self->free_space_value_label); add_updatable_label (self, self->free_space_value_label, "free_space"); } } if (should_show_locations_list_box) { gtk_widget_show (self->locations_list_box); } } static FilterType files_get_filter_type (NautilusPropertiesWindow *self) { FilterType filter_type = NO_FILES_OR_FOLDERS; for (GList *l = self->target_files; l != NULL && filter_type != FILES_AND_FOLDERS; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file)) { filter_type |= FOLDERS_ONLY; } else { filter_type |= FILES_ONLY; } } return filter_type; } static gboolean file_matches_filter_type (NautilusFile *file, FilterType filter_type) { gboolean is_directory = nautilus_file_is_directory (file); switch (filter_type) { case FILES_AND_FOLDERS: { return TRUE; } case FILES_ONLY: { return !is_directory; } case FOLDERS_ONLY: { return is_directory; } default: { return FALSE; } } } static gboolean files_has_changable_permissions_directory (NautilusPropertiesWindow *self) { GList *l; gboolean changable = FALSE; for (l = self->target_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file) && nautilus_file_can_get_permissions (file) && nautilus_file_can_set_permissions (file)) { changable = TRUE; } else { changable = FALSE; break; } } return changable; } static void start_long_operation (NautilusPropertiesWindow *self) { if (self->long_operation_underway == 0) { /* start long operation */ gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "wait"); } self->long_operation_underway++; } static void end_long_operation (NautilusPropertiesWindow *self) { if (gtk_native_get_surface (GTK_NATIVE (self)) != NULL && self->long_operation_underway == 1) { /* finished !! */ gtk_widget_set_cursor (GTK_WIDGET (self), NULL); } self->long_operation_underway--; } static void permission_change_callback (NautilusFile *file, GFile *res_loc, GError *error, gpointer callback_data) { g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data); g_assert (self != NULL); end_long_operation (self); /* Report the error if it's an error. */ nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (self)); } static void update_permissions (NautilusPropertiesWindow *self, guint32 vfs_new_perm, guint32 vfs_mask, FilterType filter_type, gboolean use_original) { for (GList *l = self->target_files; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); guint32 permissions; if (!nautilus_file_can_get_permissions (file)) { continue; } if (!nautilus_file_can_get_permissions (file) || !file_matches_filter_type (file, filter_type)) { continue; } permissions = nautilus_file_get_permissions (file); if (use_original) { gpointer ptr; if (g_hash_table_lookup_extended (self->initial_permissions, file, NULL, &ptr)) { permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); } } else { permissions = (permissions & ~vfs_mask) | vfs_new_perm; } start_long_operation (self); g_object_ref (self); nautilus_file_set_permissions (file, permissions, permission_change_callback, self); } } static void execution_bit_changed (NautilusPropertiesWindow *self, GParamSpec *params, GtkWidget *widget) { const guint32 permission_mask = UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC; const FilterType filter_type = FILES_ONLY; /* if activated from switch, switch state is already toggled, thus invert value via XOR. */ gboolean active = gtk_switch_get_state (self->execution_switch) ^ GTK_IS_SWITCH (widget); gboolean set_executable = !active; update_permissions (self, set_executable ? permission_mask : 0, permission_mask, filter_type, FALSE); } static gboolean should_show_exectution_switch (NautilusPropertiesWindow *self) { g_autofree gchar *mime_type = NULL; if (is_multi_file_window (self)) { return FALSE; } mime_type = nautilus_file_get_mime_type (get_target_file (self)); return g_content_type_can_be_executable (mime_type); } static void update_execution_row (GtkWidget *row, TargetPermissions *target_perm) { NautilusPropertiesWindow *self = target_perm->window; if (!should_show_exectution_switch (self)) { gtk_widget_hide (GTK_WIDGET (self->execution_row)); } else { g_signal_handlers_block_by_func (self->execution_switch, G_CALLBACK (execution_bit_changed), self); gtk_switch_set_state (self->execution_switch, target_perm->file_exec_permissions == PERMISSION_EXEC); g_signal_handlers_unblock_by_func (self->execution_switch, G_CALLBACK (execution_bit_changed), self); gtk_widget_set_sensitive (row, target_perm->can_set_any_file_permission); gtk_widget_show (GTK_WIDGET (self->execution_row)); } } static void on_permission_row_change (AdwComboRow *row, GParamSpec *pspec, NautilusPropertiesWindow *self) { GListModel *list = adw_combo_row_get_model (row); guint position = adw_combo_row_get_selected (row); g_autoptr (NautilusPermissionEntry) entry = NULL; FilterType filter_type; gboolean use_original; PermissionType type; PermissionValue mask; guint32 vfs_new_perm, vfs_mask; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); if (position == GTK_INVALID_LIST_POSITION) { return; } filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type")); type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type")); mask = PERMISSION_READ | PERMISSION_WRITE | ((filter_type == FOLDERS_ONLY) * PERMISSION_EXEC); vfs_mask = permission_to_vfs (type, mask); entry = g_list_model_get_item (list, position); vfs_new_perm = permission_to_vfs (type, entry->permission_value); use_original = entry->permission_value & PERMISSION_INCONSISTENT; update_permissions (self, vfs_new_perm, vfs_mask, filter_type, use_original); } static void list_store_append_nautilus_permission_entry (GListStore *list, PermissionValue permission_value, gboolean describes_folder) { g_autoptr (NautilusPermissionEntry) entry = g_object_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL); entry->name = g_strdup (permission_value_to_string (permission_value, describes_folder)); entry->permission_value = permission_value; g_list_store_append (list, entry); } static gint get_permission_value_list_position (GListModel *list, PermissionValue wanted_permissions) { const guint n_entries = g_list_model_get_n_items (list); for (guint position = 0; position < n_entries; position += 1) { g_autoptr (NautilusPermissionEntry) entry = g_list_model_get_item (list, position); if (entry->permission_value == wanted_permissions) { return position; } } return -1; } static void update_permission_row (AdwComboRow *row, TargetPermissions *target_perm) { NautilusPropertiesWindow *self = target_perm->window; PermissionType type; PermissionValue permissions_to_show; FilterType filter_type; gboolean is_folder; GListModel *model; gint position; model = adw_combo_row_get_model (row); filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type")); is_folder = (FOLDERS_ONLY == filter_type); type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type")); permissions_to_show = is_folder ? target_perm->folder_permissions[type] : target_perm->file_permissions[type] & ~PERMISSION_EXEC; g_signal_handlers_block_by_func (G_OBJECT (row), G_CALLBACK (on_permission_row_change), self); position = get_permission_value_list_position (model, permissions_to_show); if (position == GTK_INVALID_LIST_POSITION) { /* configured permissions not listed, create new entry */ position = g_list_model_get_n_items (model); list_store_append_nautilus_permission_entry (G_LIST_STORE (model), permissions_to_show, is_folder); } adw_combo_row_set_selected (row, position); /* Also enable if no files found (for recursive * file changes when only selecting folders) */ gtk_widget_set_sensitive (GTK_WIDGET (row), is_folder ? target_perm->can_set_all_folder_permission : target_perm->can_set_all_file_permission); g_signal_handlers_unblock_by_func (G_OBJECT (row), G_CALLBACK (on_permission_row_change), self); } static void setup_permissions_combo_box (GtkComboBox *combo, PermissionType type, FilterType filter_type) { g_autoptr (GtkListStore) store = NULL; GtkCellRenderer *cell; GtkTreeIter iter; store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING); gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store)); gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID); g_object_set_data (G_OBJECT (combo), "filter-type", GINT_TO_POINTER (filter_type)); g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); if (filter_type == FOLDERS_ONLY) { if (type != PERMISSION_USER) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, /* Translators: this is referred to the permissions * the user has in a directory. */ COLUMN_NAME, _("None"), COLUMN_VALUE, PERMISSION_NONE, COLUMN_ID, "none", -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("List files only"), COLUMN_VALUE, PERMISSION_READ, COLUMN_ID, "r", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Access files"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC, COLUMN_ID, "rx", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Create and delete files"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, COLUMN_ID, "rwx", -1); } else { if (type != PERMISSION_USER) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("None"), COLUMN_VALUE, PERMISSION_NONE, COLUMN_ID, "none", -1); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Read-only"), COLUMN_VALUE, PERMISSION_READ, COLUMN_ID, "r", -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COLUMN_NAME, _("Read and write"), COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE, COLUMN_ID, "rw", -1); } cell = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, "text", COLUMN_NAME, NULL); } static gboolean all_can_get_permissions (GList *file_list) { GList *l; for (l = file_list; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (!nautilus_file_can_get_permissions (file)) { return FALSE; } } return TRUE; } static gboolean all_can_set_permissions (GList *file_list) { GList *l; for (l = file_list; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (!nautilus_file_can_set_permissions (file)) { return FALSE; } } return TRUE; } static GHashTable * get_initial_permissions (GList *file_list) { GHashTable *ret; GList *l; ret = g_hash_table_new (g_direct_hash, g_direct_equal); for (l = file_list; l != NULL; l = l->next) { guint32 permissions; NautilusFile *file; file = NAUTILUS_FILE (l->data); permissions = nautilus_file_get_permissions (file); g_hash_table_insert (ret, file, GINT_TO_POINTER (permissions)); } return ret; } static GListModel * create_permission_list_model (PermissionType type, FilterType filter_type) { GListStore *store = g_list_store_new (NAUTILUS_TYPE_PERMISSION_ENTRY); if (type != PERMISSION_USER) { list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, /* unused */ FALSE); } if (filter_type == FOLDERS_ONLY) { list_store_append_nautilus_permission_entry (store, PERMISSION_READ, TRUE); list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC, TRUE); list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, TRUE); } else { list_store_append_nautilus_permission_entry (store, PERMISSION_READ, FALSE); list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_WRITE, FALSE); } return G_LIST_MODEL (store); } static void create_permissions_row (NautilusPropertiesWindow *self, AdwComboRow *row, PermissionType permission_type, FilterType filter_type) { g_autoptr (GtkExpression) expression = NULL; g_autoptr (GListModel) model = NULL; expression = gtk_property_expression_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL, "name"); adw_combo_row_set_expression (row, expression); gtk_widget_show (GTK_WIDGET (row)); g_object_set_data (G_OBJECT (row), "permission-type", GINT_TO_POINTER (permission_type)); g_object_set_data (G_OBJECT (row), "filter-type", GINT_TO_POINTER (filter_type)); model = create_permission_list_model (permission_type, filter_type); adw_combo_row_set_model (row, model); self->permission_rows = g_list_prepend (self->permission_rows, row); g_signal_connect (row, "notify::selected", G_CALLBACK (on_permission_row_change), self); } static void create_simple_permissions (NautilusPropertiesWindow *self) { FilterType filter_type = files_get_filter_type (self); g_assert (filter_type != NO_FILES_OR_FOLDERS); setup_ownership_row (self, self->owner_row); setup_ownership_row (self, self->group_row); if (filter_type == FILES_AND_FOLDERS) { /* owner */ create_permissions_row (self, self->owner_folder_access_row, PERMISSION_USER, FOLDERS_ONLY); create_permissions_row (self, self->owner_file_access_row, PERMISSION_USER, FILES_ONLY); /* group */ create_permissions_row (self, self->group_folder_access_row, PERMISSION_GROUP, FOLDERS_ONLY); create_permissions_row (self, self->group_file_access_row, PERMISSION_GROUP, FILES_ONLY); /* others */ create_permissions_row (self, self->others_folder_access_row, PERMISSION_OTHER, FOLDERS_ONLY); create_permissions_row (self, self->others_file_access_row, PERMISSION_OTHER, FILES_ONLY); } else { create_permissions_row (self, self->owner_access_row, PERMISSION_USER, filter_type); create_permissions_row (self, self->group_access_row, PERMISSION_GROUP, filter_type); create_permissions_row (self, self->others_access_row, PERMISSION_OTHER, filter_type); } /* Connect execution bit switch, independent of whether it will be visible or not. */ g_signal_connect_swapped (self->execution_row, "activated", G_CALLBACK (execution_bit_changed), self); g_signal_connect_swapped (self->execution_switch, "notify::active", G_CALLBACK (execution_bit_changed), self); } static void set_recursive_permissions_done (gboolean success, gpointer callback_data) { g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data); end_long_operation (self); } static void on_change_permissions_response (GtkDialog *dialog, int response, NautilusPropertiesWindow *self) { guint32 file_permission, file_permission_mask; guint32 dir_permission, dir_permission_mask; guint32 vfs_mask, vfs_new_perm; GtkWidget *combo; gboolean use_original; FilterType filter_type; GList *l; GtkTreeModel *model; GtkTreeIter iter; PermissionType type; int new_perm, mask; if (response != GTK_RESPONSE_OK) { g_clear_pointer (&self->change_permission_combos, g_list_free); gtk_window_destroy (GTK_WINDOW (dialog)); return; } file_permission = 0; file_permission_mask = 0; dir_permission = 0; dir_permission_mask = 0; /* Simple mode, minus exec checkbox */ for (l = self->change_permission_combos; l != NULL; l = l->next) { combo = l->data; if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { continue; } type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "filter-type")); model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); gtk_tree_model_get (model, &iter, COLUMN_VALUE, &new_perm, COLUMN_USE_ORIGINAL, &use_original, -1); if (use_original) { continue; } vfs_new_perm = permission_to_vfs (type, new_perm); mask = PERMISSION_READ | PERMISSION_WRITE; if (filter_type == FOLDERS_ONLY) { mask |= PERMISSION_EXEC; } vfs_mask = permission_to_vfs (type, mask); if (filter_type == FOLDERS_ONLY) { dir_permission_mask |= vfs_mask; dir_permission |= vfs_new_perm; } else { file_permission_mask |= vfs_mask; file_permission |= vfs_new_perm; } } for (l = self->target_files; l != NULL; l = l->next) { NautilusFile *file; file = NAUTILUS_FILE (l->data); if (nautilus_file_is_directory (file) && nautilus_file_can_set_permissions (file)) { g_autofree gchar *uri = NULL; uri = nautilus_file_get_uri (file); start_long_operation (self); g_object_ref (self); nautilus_file_set_permissions_recursive (uri, file_permission, file_permission_mask, dir_permission, dir_permission_mask, set_recursive_permissions_done, self); } } g_clear_pointer (&self->change_permission_combos, g_list_free); gtk_window_destroy (GTK_WINDOW (dialog)); } static void set_active_from_umask (GtkComboBox *combo, PermissionType type, FilterType filter_type) { mode_t initial; mode_t mask; mode_t p; const char *id; if (filter_type == FOLDERS_ONLY) { initial = (S_IRWXU | S_IRWXG | S_IRWXO); } else { initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } umask (mask = umask (0)); p = ~mask & initial; if (type == PERMISSION_USER) { p &= ~(S_IRWXG | S_IRWXO); if ((p & S_IRWXU) == S_IRWXU) { id = "rwx"; } else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR)) { id = "rw"; } else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR)) { id = "rx"; } else if ((p & S_IRUSR) == S_IRUSR) { id = "r"; } else { id = "none"; } } else if (type == PERMISSION_GROUP) { p &= ~(S_IRWXU | S_IRWXO); if ((p & S_IRWXG) == S_IRWXG) { id = "rwx"; } else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP)) { id = "rw"; } else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP)) { id = "rx"; } else if ((p & S_IRGRP) == S_IRGRP) { id = "r"; } else { id = "none"; } } else { p &= ~(S_IRWXU | S_IRWXG); if ((p & S_IRWXO) == S_IRWXO) { id = "rwx"; } else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH)) { id = "rw"; } else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH)) { id = "rx"; } else if ((p & S_IROTH) == S_IROTH) { id = "r"; } else { id = "none"; } } gtk_combo_box_set_active_id (combo, id); } static void on_change_permissions_clicked (GtkWidget *button, NautilusPropertiesWindow *self) { GtkWidget *dialog; GtkComboBox *combo; g_autoptr (GtkBuilder) change_permissions_builder = NULL; change_permissions_builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-file-properties-change-permissions.ui"); dialog = GTK_WIDGET (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog")); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self)); /* Owner Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo")); setup_permissions_combo_box (combo, PERMISSION_USER, FILES_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_USER, FILES_ONLY); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo")); setup_permissions_combo_box (combo, PERMISSION_USER, FOLDERS_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_USER, FOLDERS_ONLY); /* Group Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo")); setup_permissions_combo_box (combo, PERMISSION_GROUP, FILES_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_GROUP, FILES_ONLY); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo")); setup_permissions_combo_box (combo, PERMISSION_GROUP, FOLDERS_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_GROUP, FOLDERS_ONLY); /* Others Permissions */ combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo")); setup_permissions_combo_box (combo, PERMISSION_OTHER, FILES_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_OTHER, FILES_ONLY); combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo")); setup_permissions_combo_box (combo, PERMISSION_OTHER, FOLDERS_ONLY); self->change_permission_combos = g_list_prepend (self->change_permission_combos, combo); set_active_from_umask (combo, PERMISSION_OTHER, FOLDERS_ONLY); g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), self); gtk_widget_show (dialog); } static void setup_permissions_page (NautilusPropertiesWindow *self) { GList *file_list; file_list = self->original_files; self->initial_permissions = NULL; if (all_can_get_permissions (file_list) && all_can_get_permissions (self->target_files)) { self->initial_permissions = get_initial_permissions (self->target_files); self->has_recursive_apply = files_has_changable_permissions_directory (self); if (!all_can_set_permissions (file_list)) { gtk_widget_show (self->not_the_owner_label); gtk_widget_show (self->bottom_prompt_seperator); } gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permissions-box"); create_simple_permissions (self); #ifdef HAVE_SELINUX gtk_widget_show (self->security_context_list_box); /* Stash a copy of the file attribute name in this field for the callback's sake. */ g_object_set_data_full (G_OBJECT (self->security_context_value_label), "file_attribute", g_strdup ("selinux_context"), g_free); self->value_fields = g_list_prepend (self->value_fields, self->security_context_value_label); #endif if (self->has_recursive_apply) { gtk_widget_show (self->change_permissions_button_box); g_signal_connect (self->change_permissions_button, "clicked", G_CALLBACK (on_change_permissions_clicked), self); } } else { /* * This if block only gets executed if its a single file window, * in which case the label text needs to be different from the * default label text. The default label text for a multifile * window is set in nautilus-properties-window.ui so no else block. */ if (!is_multi_file_window (self)) { g_autofree gchar *file_name = NULL; g_autofree gchar *prompt_text = NULL; file_name = nautilus_file_get_display_name (get_target_file (self)); prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name); adw_status_page_set_description (ADW_STATUS_PAGE (self->unknown_permissions_page), prompt_text); } gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permission-indeterminable"); } } static void refresh_extension_model_pages (NautilusPropertiesWindow *self) { g_autoptr (GListStore) extensions_list = g_list_store_new (NAUTILUS_TYPE_PROPERTIES_MODEL); g_autolist (NautilusPropertiesModel) all_models = NULL; g_autolist (GObject) providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTIES_MODEL_PROVIDER); for (GList *l = providers; l != NULL; l = l->next) { GList *models = nautilus_properties_model_provider_get_models (l->data, self->original_files); all_models = g_list_concat (all_models, models); } for (GList *l = all_models; l != NULL; l = l->next) { g_list_store_append (extensions_list, NAUTILUS_PROPERTIES_MODEL (l->data)); } gtk_widget_set_visible (self->extension_models_list_box, g_list_model_get_n_items (G_LIST_MODEL (extensions_list)) > 0); gtk_list_box_bind_model (GTK_LIST_BOX (self->extension_models_list_box), G_LIST_MODEL (extensions_list), (GtkListBoxCreateWidgetFunc) add_extension_model_page, self, NULL); } static gboolean should_show_permissions (NautilusPropertiesWindow *self) { GList *l; /* Don't show permissions for Trash and Computer since they're not * really file system objects. */ for (l = self->original_files; l != NULL; l = l->next) { if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || nautilus_file_is_in_recent (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static char * get_pending_key (GList *file_list) { GList *uris = NULL; GList *l; GString *key; char *ret; uris = NULL; for (l = file_list; l != NULL; l = l->next) { uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); } uris = g_list_sort (uris, (GCompareFunc) strcmp); key = g_string_new (""); for (l = uris; l != NULL; l = l->next) { g_string_append (key, l->data); g_string_append (key, ";"); } g_list_free_full (uris, g_free); ret = key->str; g_string_free (key, FALSE); return ret; } static StartupData * startup_data_new (GList *original_files, GList *target_files, const char *pending_key, GtkWidget *parent_widget, GtkWindow *parent_window, const char *startup_id, NautilusPropertiesWindowCallback callback, gpointer callback_data, NautilusPropertiesWindow *window) { StartupData *data; GList *l; data = g_new0 (StartupData, 1); data->original_files = nautilus_file_list_copy (original_files); data->target_files = nautilus_file_list_copy (target_files); data->parent_widget = parent_widget; data->parent_window = parent_window; data->startup_id = g_strdup (startup_id); data->pending_key = g_strdup (pending_key); data->pending_files = g_hash_table_new (g_direct_hash, g_direct_equal); data->callback = callback; data->callback_data = callback_data; data->window = window; for (l = data->target_files; l != NULL; l = l->next) { g_hash_table_insert (data->pending_files, l->data, l->data); } return data; } static void startup_data_free (StartupData *data) { nautilus_file_list_free (data->original_files); nautilus_file_list_free (data->target_files); g_hash_table_destroy (data->pending_files); g_free (data->pending_key); g_free (data->startup_id); g_free (data); } static void file_changed_callback (NautilusFile *file, gpointer user_data) { NautilusPropertiesWindow *self = NAUTILUS_PROPERTIES_WINDOW (user_data); if (!g_list_find (self->changed_files, file)) { nautilus_file_ref (file); self->changed_files = g_list_prepend (self->changed_files, file); schedule_files_update (self); } } static NautilusPropertiesWindow * create_properties_window (StartupData *startup_data) { NautilusPropertiesWindow *window; GList *l; window = NAUTILUS_PROPERTIES_WINDOW (g_object_new (NAUTILUS_TYPE_PROPERTIES_WINDOW, NULL)); window->original_files = nautilus_file_list_copy (startup_data->original_files); window->target_files = nautilus_file_list_copy (startup_data->target_files); if (startup_data->parent_widget) { gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (startup_data->parent_widget)); } if (startup_data->parent_window) { gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window); } if (startup_data->startup_id) { gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id); } /* Start monitoring the file attributes we display. Note that some * of the attributes are for the original file, and some for the * target files. */ for (l = window->original_files; l != NULL; l = l->next) { NautilusFile *file; NautilusFileAttributes attributes; file = NAUTILUS_FILE (l->data); attributes = NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_INFO; nautilus_file_monitor_add (file, &window->original_files, attributes); } for (l = window->target_files; l != NULL; l = l->next) { NautilusFile *file; NautilusFileAttributes attributes; file = NAUTILUS_FILE (l->data); attributes = 0; if (nautilus_file_is_directory (file)) { attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS; } attributes |= NAUTILUS_FILE_ATTRIBUTE_INFO; nautilus_file_monitor_add (file, &window->target_files, attributes); } for (l = window->target_files; l != NULL; l = l->next) { g_signal_connect_object (NAUTILUS_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } for (l = window->original_files; l != NULL; l = l->next) { g_signal_connect_object (NAUTILUS_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (window), 0); } /* Create the pages. */ setup_basic_page (window); if (should_show_permissions (window)) { setup_permissions_page (window); gtk_widget_show (window->permissions_navigation_row); } if (should_show_exectution_switch (window)) { gtk_widget_show (GTK_WIDGET (window->execution_row)); } /* Add available extension models pages */ refresh_extension_model_pages (window); /* Update from initial state */ properties_window_update (window, NULL); return window; } static GList * get_target_file_list (GList *original_files) { return g_list_copy_deep (original_files, (GCopyFunc) get_target_file_for_original_file, NULL); } static void properties_window_finish (StartupData *data) { gboolean cancel_timed_wait; if (data->parent_widget != NULL) { g_signal_handlers_disconnect_by_data (data->parent_widget, data); } if (data->window != NULL) { g_signal_handlers_disconnect_by_data (data->window, data); } cancel_timed_wait = (data->window == NULL && !data->cancelled); remove_pending (data, TRUE, cancel_timed_wait); startup_data_free (data); } static void cancel_create_properties_window_callback (gpointer callback_data) { StartupData *data; data = callback_data; data->cancelled = TRUE; properties_window_finish (data); } static void parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) { g_assert (widget == ((StartupData *) callback_data)->parent_widget); properties_window_finish ((StartupData *) callback_data); } static void cancel_call_when_ready_callback (gpointer key, gpointer value, gpointer user_data) { nautilus_file_cancel_call_when_ready (NAUTILUS_FILE (key), is_directory_ready_callback, user_data); } static void remove_pending (StartupData *startup_data, gboolean cancel_call_when_ready, gboolean cancel_timed_wait) { if (cancel_call_when_ready) { g_hash_table_foreach (startup_data->pending_files, cancel_call_when_ready_callback, startup_data); } if (cancel_timed_wait) { eel_timed_wait_stop (cancel_create_properties_window_callback, startup_data); } if (startup_data->pending_key != NULL) { g_hash_table_remove (pending_lists, startup_data->pending_key); } } static gboolean widget_on_destroy (GtkWidget *widget, gpointer user_data) { StartupData *data = (StartupData *) user_data; if (data->callback != NULL) { data->callback (data->callback_data); } properties_window_finish (data); return GDK_EVENT_PROPAGATE; } static void is_directory_ready_callback (NautilusFile *file, gpointer data) { StartupData *startup_data; startup_data = data; g_hash_table_remove (startup_data->pending_files, file); if (g_hash_table_size (startup_data->pending_files) == 0) { NautilusPropertiesWindow *new_window; new_window = create_properties_window (startup_data); startup_data->window = new_window; remove_pending (startup_data, FALSE, TRUE); gtk_window_present (GTK_WINDOW (new_window)); g_signal_connect (GTK_WIDGET (new_window), "destroy", G_CALLBACK (widget_on_destroy), startup_data); /* We wish the label to be selectable, but not selected by default. */ gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1); } } void nautilus_properties_window_present (GList *original_files, GtkWidget *parent_widget, const gchar *startup_id, NautilusPropertiesWindowCallback callback, gpointer callback_data) { GList *l, *next; GtkWindow *parent_window; StartupData *startup_data; g_autolist (NautilusFile) target_files = NULL; g_autofree char *pending_key = NULL; g_return_if_fail (original_files != NULL); g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget)); if (pending_lists == NULL) { pending_lists = g_hash_table_new (g_str_hash, g_str_equal); } pending_key = get_pending_key (original_files); /* Look to see if we're already waiting for a window for this file. */ if (g_hash_table_lookup (pending_lists, pending_key) != NULL) { /* FIXME: No callback is done if this happen. In practice, it's a quite * corner case */ return; } target_files = get_target_file_list (original_files); if (parent_widget) { parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW)); } else { parent_window = NULL; } startup_data = startup_data_new (original_files, target_files, pending_key, parent_widget, parent_window, startup_id, callback, callback_data, NULL); /* Wait until we can tell whether it's a directory before showing, since * some one-time layout decisions depend on that info. */ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); if (parent_widget) { g_signal_connect (parent_widget, "destroy", G_CALLBACK (parent_widget_destroyed_callback), startup_data); } eel_timed_wait_start (cancel_create_properties_window_callback, startup_data, _("Creating Properties window."), parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); for (l = startup_data->target_files; l != NULL; l = next) { next = l->next; nautilus_file_call_when_ready (NAUTILUS_FILE (l->data), NAUTILUS_FILE_ATTRIBUTE_INFO, is_directory_ready_callback, startup_data); } } static void real_dispose (GObject *object) { NautilusPropertiesWindow *self; self = NAUTILUS_PROPERTIES_WINDOW (object); unschedule_or_cancel_group_change (self); unschedule_or_cancel_owner_change (self); g_list_foreach (self->original_files, (GFunc) nautilus_file_monitor_remove, &self->original_files); g_clear_list (&self->original_files, (GDestroyNotify) nautilus_file_unref); g_list_foreach (self->target_files, (GFunc) nautilus_file_monitor_remove, &self->target_files); g_clear_list (&self->target_files, (GDestroyNotify) nautilus_file_unref); g_clear_list (&self->changed_files, (GDestroyNotify) nautilus_file_unref); g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove); while (self->deep_count_files) { stop_deep_count_for_file (self, self->deep_count_files->data); } g_clear_list (&self->permission_rows, NULL); g_clear_list (&self->change_permission_combos, NULL); g_clear_pointer (&self->initial_permissions, g_hash_table_destroy); g_clear_list (&self->value_fields, NULL); g_clear_handle_id (&self->update_directory_contents_timeout_id, g_source_remove); g_clear_handle_id (&self->update_files_timeout_id, g_source_remove); G_OBJECT_CLASS (nautilus_properties_window_parent_class)->dispose (object); } static void real_finalize (GObject *object) { NautilusPropertiesWindow *self; self = NAUTILUS_PROPERTIES_WINDOW (object); g_list_free_full (self->mime_list, g_free); G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object); } /* icon selection callback to set the image of the file object to the selected file */ static void set_icon (const char *icon_uri, NautilusPropertiesWindow *self) { NautilusFile *file; g_autofree gchar *icon_path = NULL; g_assert (icon_uri != NULL); g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); icon_path = g_filename_from_uri (icon_uri, NULL, NULL); /* we don't allow remote URIs */ if (icon_path != NULL) { GList *l; for (l = self->original_files; l != NULL; l = l->next) { g_autofree gchar *file_uri = NULL; g_autoptr (GFile) file_location = NULL; g_autoptr (GFile) icon_location = NULL; g_autofree gchar *real_icon_uri = NULL; file = NAUTILUS_FILE (l->data); file_uri = nautilus_file_get_uri (file); file_location = nautilus_file_get_location (file); icon_location = g_file_new_for_uri (icon_uri); /* ’Tis a little bit of a misnomer. Actually a path. */ real_icon_uri = g_file_get_relative_path (icon_location, file_location); if (real_icon_uri == NULL) { real_icon_uri = g_strdup (icon_uri); } nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); } } } static void custom_icon_file_chooser_response_cb (GtkDialog *dialog, gint response, NautilusPropertiesWindow *self) { switch (response) { case GTK_RESPONSE_NO: { reset_icon (self); } break; case GTK_RESPONSE_OK: { g_autoptr (GFile) location = NULL; g_autofree gchar *uri = NULL; location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); if (location != NULL) { uri = g_file_get_uri (location); set_icon (uri, self); } else { reset_icon (self); } } break; default: { } break; } gtk_widget_hide (GTK_WIDGET (dialog)); } static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWindow *self) { GtkWidget *dialog; GtkFileFilter *filter; GList *l; NautilusFile *file; gboolean revert_is_sensitive; g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); dialog = self->icon_chooser; if (dialog == NULL) { g_autoptr (GFile) pictures_location = NULL; dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (self), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Revert"), GTK_RESPONSE_NO, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_OK, NULL); pictures_location = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES)); gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), pictures_location, NULL); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); filter = gtk_file_filter_new (); gtk_file_filter_add_pixbuf_formats (filter); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); self->icon_chooser = dialog; g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &self->icon_chooser); } /* it's likely that the user wants to pick an icon that is inside a local directory */ if (g_list_length (self->original_files) == 1) { file = NAUTILUS_FILE (self->original_files->data); if (nautilus_file_is_directory (file)) { g_autoptr (GFile) image_location = NULL; image_location = nautilus_file_get_location (file); if (image_location != NULL) { gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_location, NULL); } } } revert_is_sensitive = FALSE; for (l = self->original_files; l != NULL; l = l->next) { g_autofree gchar *image_path = NULL; file = NAUTILUS_FILE (l->data); image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); revert_is_sensitive = (image_path != NULL); if (revert_is_sensitive) { break; } } gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); g_signal_connect (dialog, "response", G_CALLBACK (custom_icon_file_chooser_response_cb), self); gtk_widget_show (dialog); } static void nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass) { GtkWidgetClass *widget_class; GObjectClass *oclass; widget_class = GTK_WIDGET_CLASS (klass); oclass = G_OBJECT_CLASS (klass); oclass->dispose = real_dispose; oclass->finalize = real_finalize; gtk_widget_class_add_binding (widget_class, GDK_KEY_Escape, 0, (GtkShortcutFunc) gtk_window_close, NULL); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-window.ui"); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, page_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_image); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button_image); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, star_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_file_system_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_spinner); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_level_bar); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_used_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_free_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_capacity_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, locations_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, times_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_navigation_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, extension_models_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, unknown_permissions_page); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_switch); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button); gtk_widget_class_bind_template_callback (widget_class, star_clicked); gtk_widget_class_bind_template_callback (widget_class, open_in_disks); gtk_widget_class_bind_template_callback (widget_class, open_parent_folder); gtk_widget_class_bind_template_callback (widget_class, open_link_target); gtk_widget_class_bind_template_callback (widget_class, navigate_main_page); gtk_widget_class_bind_template_callback (widget_class, navigate_permissions_page); } static void nautilus_properties_window_init (NautilusPropertiesWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); }