/* 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.h" #include #include #include #include #include #include #include #include #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-image.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-scheme.h" #include "nautilus-signaller.h" #include "nautilus-tag-manager.h" #include "nautilus-ui-utilities.h" #define NAUTILUS_TYPE_PROPERTIES_WIDGET (nautilus_properties_widget_get_type ()) G_DECLARE_FINAL_TYPE (NautilusPropertiesWidget, nautilus_properties_widget, NAUTILUS, PROPERTIES_WIDGET, AdwBin) typedef struct { NautilusFile *file; char *owner; NautilusPropertiesWidget *widget; unsigned int timeout; gboolean cancelled; } OwnerChange; typedef struct { NautilusFile *file; char *group; NautilusPropertiesWidget *widget; unsigned int timeout; gboolean cancelled; } GroupChange; struct _NautilusPropertiesWidget { AdwBin parent_instance; GList *files; NautilusFileListHandle *handle; GFileInfo *volume_fs_info; GCancellable *volume_fs_info_cancellable; AdwDialog *dialog; AdwToastOverlay *toast_overlay; AdwNavigationView *nav_view; /* Basic page */ GtkStack *icon_stack; GtkWidget *icon_image; GtkWidget *icon_overlay; GtkWidget *select_icon_button; GtkWidget *reset_icon_button; char *custom_icon_for_undo; GCancellable *icon_cancellable; /* Might be provided by glycin, see https://gitlab.gnome.org/GNOME/glycin/-/issues/278 */ GFile *icon_pending_location; GtkWidget *star_button; GtkWidget *popout_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_group; GtkWidget *volume_usage_row; GtkLevelBar *disk_space_level_bar; GtkWidget *disk_space_used_value; GtkWidget *disk_space_free_value; GtkWidget *disk_space_capacity_value; GtkWidget *open_in_disks_button_row; gchar *device_identifier; GtkWidget *locations_group; GtkWidget *link_target_row; GtkWidget *contents_spinner; guint update_directory_contents_timeout_id; guint update_files_timeout_id; GtkWidget *parent_folder_row; GtkWidget *trashed_group; GtkWidget *trashed_on_row; GtkWidget *original_folder_row; GtkWidget *times_group; GtkWidget *modified_row; GtkWidget *created_row; GtkWidget *accessed_row; GtkWidget *permissions_navigation_row; GtkWidget *permissions_value_label; GtkWidget *extension_models_group; GtkWidget *extension_models_list_box; /* Permissions page */ GtkWidget *permissions_stack; GtkWidget *unknown_permissions_page; AdwBanner *owner_permission_banner; 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; AdwSwitchRow *execution_row; GtkWidget *security_context_group; GtkWidget *security_context_row; GtkWidget *change_permissions_enclosed_files_group; GroupChange *group_change; OwnerChange *owner_change; AdwDialog *permissions_dialog; GList *permission_rows; GList *change_permission_combo_rows; GHashTable *initial_permissions; gboolean has_recursive_apply; /* Extensions */ GtkListBox *extension_list_box; GList *value_labels; GList *value_rows; char *mime_type; gboolean deep_count_finished; GList *deep_count_files; guint deep_count_spinner_timeout_id; guint long_operation_underway; GList *changed_files; GListStore *extensions_list; GFile *current_view_location; }; enum { HIDE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; 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 { NautilusPropertiesWidget *widget; 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; } PermissionsInfo; /* 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 }; static GParamSpec *perm_entry_properties[NUM_PROPERTIES]; struct _NautilusPermissionEntry { GObject parent; char *name; PermissionValue permission_value; }; G_DEFINE_FINAL_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; perm_entry_properties[PROP_NAME] = g_param_spec_string ("name", "", "", NULL, G_PARAM_READABLE); g_object_class_install_properties (gobject_class, G_N_ELEMENTS (perm_entry_properties), perm_entry_properties); } 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 and 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 */ #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 (NautilusPropertiesWidget *self); static void file_changed_callback (NautilusFile *file, gpointer user_data); static void update_execution_row (GtkWidget *row, PermissionsInfo *permissions_info); static void update_permission_row (AdwComboRow *row, PermissionsInfo *permissions_info); static void value_label_update (GtkLabel *field, NautilusPropertiesWidget *self); static void value_row_update (AdwActionRow *row, NautilusPropertiesWidget *self); static void cancel_group_change_callback (GroupChange *change); static void cancel_owner_change_callback (OwnerChange *change); static void update_owner_row (AdwComboRow *row, PermissionsInfo *permissions_info); static void update_group_row (AdwComboRow *row, PermissionsInfo *permissions_info); static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWidget *self); static void refresh_extension_model_pages (NautilusPropertiesWidget *self); static gboolean is_root_directory (NautilusFile *file); static gboolean is_volume_properties (NautilusPropertiesWidget *self); G_DEFINE_FINAL_TYPE (NautilusPropertiesWidget, nautilus_properties_widget, ADW_TYPE_BIN) static GtkWindow * get_parent_window (NautilusPropertiesWidget *self) { GtkWindow *parent_window = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW)); return parent_window; } static gboolean is_multi_file_window (NautilusPropertiesWidget *self) { GList *l; int count; count = 0; for (l = self->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_file (NautilusPropertiesWidget *self) { return NAUTILUS_FILE (self->files->data); } static void navigate_permissions_page (NautilusPropertiesWidget *self, GParamSpec *params, GtkWidget *widget) { adw_navigation_view_push_by_tag (self->nav_view, "permissions"); } static GdkPaintable * get_image_for_properties_widget (NautilusPropertiesWidget *self, gboolean *is_stacked) { /* Show a limited number of icons */ const uint max_icons = 5; gint scale = gtk_widget_get_scale_factor (GTK_WIDGET (self)); NautilusFileIconFlags flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON; guint size = NAUTILUS_GRID_ICON_SIZE_MEDIUM; g_autoqueue (GdkPaintable) shown_icons = g_queue_new (); for (NautilusFileList *l = self->files; l != NULL; l = l->next) { NautilusFile *file = l->data; g_autoptr (GdkPaintable) paintable = nautilus_file_get_icon_paintable (file, size, scale, flags); if (g_queue_find (shown_icons, paintable) == NULL) { g_queue_push_tail (shown_icons, g_steal_pointer (&paintable)); if (g_queue_get_length (shown_icons) >= max_icons) { break; } } } if (g_queue_get_length (shown_icons) == 1) { /* The paintable can be obtained automatically by NautilusImage. Just * obtain a fallback. */ return nautilus_file_get_icon_paintable (self->files->data, size, scale, NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON); } else { *is_stacked = TRUE; return nautilus_ui_draw_stacked_icons (shown_icons, size, GTK_WIDGET (self), g_list_length (self->files)); } } static void update_properties_widget_icon (NautilusPropertiesWidget *self) { gboolean is_stacked = FALSE; g_autoptr (GdkPaintable) paintable = get_image_for_properties_widget (self, &is_stacked); nautilus_image_set_fallback (NAUTILUS_IMAGE (self->icon_image), paintable); if (is_stacked || (self->files != NULL && !nautilus_file_should_show_thumbnail (self->files->data))) { nautilus_image_set_source (NAUTILUS_IMAGE (self->icon_image), NULL); } else { g_autoptr (GFile) location = nautilus_file_get_location (self->files->data); nautilus_image_set_source (NAUTILUS_IMAGE (self->icon_image), location); } } static void on_undo_icon_reset (NautilusPropertiesWidget *self) { NautilusFile *file = get_file (self); if (file != NULL && !nautilus_file_is_gone (file)) { nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, self->custom_icon_for_undo); } g_clear_pointer (&self->custom_icon_for_undo, g_free); } static void reset_icon (NautilusPropertiesWidget *self) { NautilusFile *file = get_file (self); if (file != NULL && !nautilus_file_is_gone (file)) { g_set_str (&self->custom_icon_for_undo, nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL)); nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, NULL); } gtk_widget_set_visible (self->reset_icon_button, FALSE); g_autoptr (AdwToast) toast = adw_toast_new (_("Custom icon removed")); adw_toast_set_button_label (toast, _("_Undo")); g_signal_connect_swapped (toast, "button-clicked", G_CALLBACK (on_undo_icon_reset), self); adw_toast_overlay_add_toast (self->toast_overlay, g_steal_pointer (&toast)); } static void icon_loaded (GlyLoader *loader, GAsyncResult *result, NautilusPropertiesWidget *self) { NautilusFile *file = get_file (self); g_autoptr (GError) error = NULL; g_autoptr (GFile) icon_location = g_steal_pointer (&self->icon_pending_location); g_autoptr (GlyImage) image = gly_loader_load_finish (loader, result, &error); gtk_stack_set_visible_child (self->icon_stack, self->icon_overlay); if (error != NULL || file == NULL || nautilus_file_is_gone (file)) { if (error != NULL && error->code != G_IO_ERROR_CANCELLED) { nautilus_show_ok_dialog (_("Cannot Use This File"), _("You can only use local images as custom icons."), GTK_WIDGET (self)); } return; } g_return_if_fail (icon_location != NULL); /* Store path in file metadata */ g_autoptr (GFile) file_location = nautilus_file_get_location (file); g_autofree char *path = g_file_get_relative_path (file_location, icon_location); if (path == NULL) { path = g_file_get_uri (icon_location); } nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, path); } static void set_icon (NautilusPropertiesWidget *self, GFile *location) { g_autoptr (GlyLoader) loader = gly_loader_new (location); /* Get tested image location */ g_autoptr (GFile) icon_location = NULL; g_object_get (loader, "file", &icon_location, NULL); g_return_if_fail (icon_location != NULL); if (self->icon_cancellable != NULL) { g_cancellable_cancel (self->icon_cancellable); g_clear_object (&self->icon_cancellable); } self->icon_cancellable = g_cancellable_new (); g_set_object (&self->icon_pending_location, location); gtk_stack_set_visible_child_name (self->icon_stack, "loading"); gly_loader_load_async (loader, self->icon_cancellable, (GAsyncReadyCallback) icon_loaded, self); } static void nautilus_properties_widget_drag_drop_cb (GtkDropTarget *target, const GValue *value, gdouble x, gdouble y, gpointer user_data) { NautilusPropertiesWidget *self = user_data; GtkWindow *window = GTK_WINDOW (gtk_widget_get_root (self->icon_image)); if (!G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) { return; } GSList *file_list = g_value_get_boxed (value); if (file_list == NULL) { return; } gboolean exactly_one = file_list != NULL && g_slist_next (file_list) == NULL; if (!exactly_one) { nautilus_show_ok_dialog (_("You cannot assign more than one custom icon at a time!"), _("Please drop just one image to set a custom icon."), GTK_WIDGET (window)); } else { GFile *dropped_file = file_list->data; if (g_file_has_uri_scheme (dropped_file, SCHEME_LOCAL)) { set_icon (self, dropped_file); } else { nautilus_show_ok_dialog (_("The file that you dropped is not local."), _("You can only use local images as custom icons."), GTK_WIDGET (window)); } } } static void star_clicked (NautilusPropertiesWidget *self) { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); NautilusFile *file = get_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){ .data = file }, (GAsyncReadyCallback) nautilus_tag_manager_announce_unstarred_cb, self, NULL); gtk_widget_remove_css_class (self->star_button, "starred"); } else { nautilus_tag_manager_star_files ( tag_manager, G_OBJECT (self), &(GList){ .data = file }, (GAsyncReadyCallback) nautilus_tag_manager_announce_starred_cb, self, NULL); gtk_widget_add_css_class (self->star_button, "starred"); } gtk_widget_add_css_class (self->star_button, "interacted"); } static void update_star (NautilusPropertiesWidget *self, NautilusTagManager *tag_manager) { gboolean is_starred; g_autofree gchar *file_uri = NULL; file_uri = nautilus_file_get_uri (get_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"); gtk_widget_set_tooltip_text (self->star_button, is_starred ? _("Unstar") : C_("Verb", "Star")); } static void on_starred_changed (NautilusTagManager *tag_manager, GList *changed_files, gpointer user_data) { NautilusPropertiesWidget *self = user_data; NautilusFile *file = get_file (self); if (g_list_find (changed_files, file)) { update_star (self, tag_manager); } } static void setup_star_button (NautilusPropertiesWidget *self) { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); NautilusFile *file = get_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_set_visible (self->star_button, TRUE); update_star (self, tag_manager); g_signal_connect_object (tag_manager, "starred-changed", G_CALLBACK (on_starred_changed), self, G_CONNECT_DEFAULT); } } static void update_image_widget (NautilusPropertiesWidget *self) { gboolean show_select = FALSE; gboolean show_reset = FALSE; update_properties_widget_icon (self); if (!is_multi_file_window (self) && !is_volume_properties (self)) { NautilusFile *file = get_file (self); if (file != NULL && !nautilus_file_is_gone (file) && !is_root_directory (file) && !nautilus_file_is_in_trash (file)) { const gchar *image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); show_select = nautilus_file_is_directory (file); show_reset = (image_path != NULL); } } if (!show_select && !show_reset) { gtk_stack_set_visible_child (self->icon_stack, self->icon_image); } else { gtk_stack_set_visible_child (self->icon_stack, self->icon_overlay); gtk_widget_set_visible (self->select_icon_button, show_select); gtk_widget_set_visible (self->reset_icon_button, show_reset); } } static void setup_image_widget (NautilusPropertiesWidget *self) { /* prepare the image to receive dropped objects to assign custom images */ GtkDropTarget *target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY); gtk_widget_add_controller (self->icon_overlay, GTK_EVENT_CONTROLLER (target)); g_signal_connect (target, "drop", G_CALLBACK (nautilus_properties_widget_drag_drop_cb), self); } static void update_name_field (NautilusPropertiesWidget *self) { /* Upper limit on how many names to show, above this use a generic fallback */ const guint max_listed_items = 5; guint number_of_files = g_list_length (self->files); if (number_of_files > max_listed_items) { g_autofree char *item_str = g_strdup_printf (ngettext ("%d Selected Item", "%d Selected Items", number_of_files), number_of_files); gtk_label_set_text (self->name_value_label, item_str); } else if (number_of_files > 1) { g_autoptr (GString) name_str = g_string_new (""); guint file_counter = 0; for (NautilusFileList *l = self->files; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); if (!nautilus_file_is_gone (file)) { file_counter += 1; if (file_counter > 1) { g_string_append (name_str, ", "); } g_string_append (name_str, nautilus_file_get_display_name (file)); } } gtk_label_set_text (self->name_value_label, name_str->str); } else if (is_root_directory (get_file (self))) { g_autofree gchar *os_name = g_get_os_info (G_OS_INFO_KEY_NAME); gtk_label_set_text (self->name_value_label, (os_name != NULL) ? os_name : _("Operating System")); } else { gtk_label_set_text (self->name_value_label, nautilus_file_get_display_name (get_file (self))); } /* We wish the label to be selectable, but not selected by default. */ gtk_label_select_region (self->name_value_label, -1, -1); } /** * 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, NautilusPropertiesWidget *self) { GtkWidget *row = adw_action_row_new (); adw_action_row_set_subtitle_selectable (ADW_ACTION_ROW (row), TRUE); gtk_widget_add_css_class (row, "property"); g_object_bind_property (item, "name", row, "title", G_BINDING_SYNC_CREATE); g_object_bind_property (item, "value", row, "subtitle", G_BINDING_SYNC_CREATE); return row; } static void navigate_extension_model_page (AdwPreferencesRow *row, gpointer user_data) { NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (user_data); GListModel *list_model = g_object_get_data (G_OBJECT (row), "nautilus-extension-properties-model"); AdwNavigationPage *page; gtk_list_box_bind_model (self->extension_list_box, list_model, (GtkListBoxCreateWidgetFunc) create_extension_group_row, self, NULL); page = adw_navigation_view_find_page (ADW_NAVIGATION_VIEW (self->nav_view), "extension"); adw_navigation_page_set_title (page, adw_preferences_row_get_title (row)); adw_navigation_view_push (self->nav_view, page); } static GtkWidget * add_extension_model_page (NautilusPropertiesModel *model, NautilusPropertiesWidget *self) { GListModel *list_model = nautilus_properties_model_get_model (model); GtkWidget *row; 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 (row, "activated", G_CALLBACK (navigate_extension_model_page), self); g_object_set_data (G_OBJECT (row), "nautilus-extension-properties-model", list_model); return row; } static void remove_from_dialog (NautilusPropertiesWidget *self, NautilusFile *file) { GList *link = g_list_find (self->files, file); g_return_if_fail (link != NULL); g_hash_table_remove (self->initial_permissions, file); g_signal_handlers_disconnect_by_func (file, G_CALLBACK (file_changed_callback), self); nautilus_file_monitor_remove (file, &self->files); /* Delete list link and release the reference it held. */ self->files = g_list_delete_link (self->files, link); g_object_unref (file); } static void start_spinner_callback (NautilusPropertiesWidget *self) { gtk_widget_set_visible (self->contents_spinner, TRUE); self->deep_count_spinner_timeout_id = 0; } static void schedule_start_spinner (NautilusPropertiesWidget *self) { if (self->deep_count_spinner_timeout_id == 0) { self->deep_count_spinner_timeout_id = g_timeout_add_once (1000, (GSourceOnceFunc) start_spinner_callback, self); } } static void stop_spinner (NautilusPropertiesWidget *self) { gtk_widget_set_visible (self->contents_spinner, FALSE); g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove); } static void stop_deep_count_for_file (NautilusPropertiesWidget *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, NautilusPropertiesWidget *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 PermissionsInfo * get_permissions_info (NautilusPropertiesWidget *self) { PermissionsInfo *p = g_new0 (PermissionsInfo, 1); p->widget = self; p->can_set_all_folder_permission = TRUE; p->can_set_all_file_permission = TRUE; for (GList *entry = self->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 (NautilusPropertiesWidget *self, PermissionsInfo *permissions_info) { if (!permissions_info->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_file (self))) { permission_type = PERMISSION_USER; } else if (group_id == nautilus_file_get_gid (get_file (self))) { permission_type = PERMISSION_GROUP; } if (nautilus_file_is_directory (get_file (self))) { text = permission_value_to_string (permissions_info->folder_permissions[permission_type], TRUE); } else { text = permission_value_to_string (permissions_info->file_permissions[permission_type], FALSE); } gtk_label_set_text (GTK_LABEL (self->permissions_value_label), text); } } static void update_extension_list (NautilusPropertiesWidget *self) { if (is_multi_file_window (self)) { g_free (self->mime_type); return; } const char *mime_type = nautilus_file_get_mime_type (get_file (self)); if (g_set_str (&self->mime_type, mime_type)) { refresh_extension_model_pages (self); } } static void properties_widget_update (NautilusPropertiesWidget *self, GList *files) { NautilusFile *changed_file; gboolean dirty = FALSE; if (files == NULL) { dirty = 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->files == NULL) { return; } } if (changed_file == NULL || g_list_find (self->files, changed_file)) { dirty = TRUE; } } if (dirty) { g_autofree PermissionsInfo *permissions_info = get_permissions_info (self); update_image_widget (self); update_name_field (self); update_permissions_navigation_row (self, permissions_info); adw_banner_set_revealed (self->owner_permission_banner, !permissions_info->can_set_all_file_permission || !permissions_info->can_set_all_folder_permission); update_owner_row (self->owner_row, permissions_info); update_group_row (self->group_row, permissions_info); update_execution_row (GTK_WIDGET (self->execution_row), permissions_info); g_list_foreach (self->permission_rows, (GFunc) update_permission_row, permissions_info); g_list_foreach (self->value_labels, (GFunc) value_label_update, self); g_list_foreach (self->value_rows, (GFunc) value_row_update, self); } update_extension_list (self); } static void update_files_callback (NautilusPropertiesWidget *self) { /* Take over list of change files and reset timer */ g_autolist (NautilusFile) changed_files = g_steal_pointer (&self->changed_files); self->update_files_timeout_id = 0; properties_widget_update (self, changed_files); if (self->files == NULL) { /* Hide properties if no files are left */ g_signal_emit (self, signals[HIDE], 0); } } static void value_label_update (GtkLabel *label, NautilusPropertiesWidget *self) { const char *attr_value_name; const char *attr_tooltip_name; g_autofree char *attribute_value = NULL; g_assert (GTK_IS_LABEL (label)); attr_value_name = g_object_get_data (G_OBJECT (label), "file_attribute_value"); attr_tooltip_name = g_object_get_data (G_OBJECT (label), "file_attribute_tooltip"); attribute_value = file_list_get_string_attribute (self->files, attr_value_name); if (attr_tooltip_name != NULL) { g_autofree char *attribute_tooltip = file_list_get_string_attribute (self->files, attr_tooltip_name); gtk_widget_set_tooltip_text (GTK_WIDGET (label), attribute_tooltip); } gtk_label_set_text (label, attribute_value); } static void value_row_update (AdwActionRow *row, NautilusPropertiesWidget *self) { const char *attribute_name; g_autofree char *attribute_value = NULL; g_assert (ADW_IS_ACTION_ROW (row)); attribute_name = g_object_get_data (G_OBJECT (row), "file_attribute"); attribute_value = file_list_get_string_attribute (self->files, attribute_name); adw_action_row_set_subtitle (row, 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 size_t length = strlen (str); const char *space_pos = memchr (str, ' ', length); if (space_pos != NULL) { /* Calculate length through pointer arithmetic. */ return (gsize) (space_pos - str); } return length; } 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); guint n_items = 0; for (GList *node = entries; node != NULL; node = node->next, n_items++) { 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)); adw_combo_row_set_enable_search (row, n_items >= 7); 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->widget); g_free (change); } static void group_change_callback (NautilusFile *file, GFile *res_loc, GError *error, GroupChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (change->widget)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (change->widget); if (!change->cancelled) { /* Report the error if it's an error. */ nautilus_ui_timed_wait_stop ((TimedWaitCancelCallback) cancel_group_change_callback, change); nautilus_report_error_setting_group (change->file, error, GTK_WIDGET (self)); } 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 void schedule_group_change_timeout (GroupChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (change->widget)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->group != NULL); change->timeout = 0; nautilus_ui_timed_wait_start ((TimedWaitCancelCallback) cancel_group_change_callback, change, _("Cancel Group Change?"), get_parent_window (change->widget)); nautilus_file_set_group (change->file, change->group, (NautilusFileOperationCallback) group_change_callback, change); } static void schedule_group_change (NautilusPropertiesWidget *self, NautilusFile *file, const char *group) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (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->widget = g_object_ref (self); change->timeout = g_timeout_add_once (CHOWN_CHGRP_TIMEOUT, (GSourceOnceFunc) schedule_group_change_timeout, change); self->group_change = change; } static void unschedule_or_cancel_group_change (NautilusPropertiesWidget *self) { GroupChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (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); nautilus_ui_timed_wait_stop ((TimedWaitCancelCallback) 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, NautilusPropertiesWidget *self) { guint selected_pos = adw_combo_row_get_selected (row); g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (self)); if (selected_pos != GTK_INVALID_LIST_POSITION) { NautilusFile *file = get_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); const 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->widget); g_free (change); } static void owner_change_callback (NautilusFile *file, GFile *result_location, GError *error, OwnerChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (change->widget)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); NautilusPropertiesWidget *self = change->widget; if (!change->cancelled) { /* Report the error if it's an error. */ nautilus_ui_timed_wait_stop ((TimedWaitCancelCallback) cancel_owner_change_callback, change); nautilus_report_error_setting_owner (file, error, GTK_WIDGET (self)); } 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 void schedule_owner_change_timeout (OwnerChange *change) { g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (change->widget)); g_assert (NAUTILUS_IS_FILE (change->file)); g_assert (change->owner != NULL); change->timeout = 0; nautilus_ui_timed_wait_start ((TimedWaitCancelCallback) cancel_owner_change_callback, change, _("Cancel Owner Change?"), get_parent_window (change->widget)); nautilus_file_set_owner (change->file, change->owner, (NautilusFileOperationCallback) owner_change_callback, change); } static void schedule_owner_change (NautilusPropertiesWidget *self, NautilusFile *file, const char *owner) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (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->widget = g_object_ref (self); change->timeout = g_timeout_add_once (CHOWN_CHGRP_TIMEOUT, (GSourceOnceFunc) schedule_owner_change_timeout, change); self->owner_change = change; } static void unschedule_or_cancel_owner_change (NautilusPropertiesWidget *self) { OwnerChange *change; g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (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); nautilus_ui_timed_wait_stop ((TimedWaitCancelCallback) 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, NautilusPropertiesWidget *self) { guint selected_pos = adw_combo_row_get_selected (row); g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (self)); if (selected_pos != GTK_INVALID_LIST_POSITION) { NautilusFile *file = get_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, PermissionsInfo *permissions_info) { NautilusPropertiesWidget *self = permissions_info->widget; gboolean provide_dropdown = (!permissions_info->is_multi_file_window && nautilus_file_can_set_owner (get_file (self))); gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row)); /* check if should provide dropdown */ if (provide_dropdown) { NautilusFile *file = get_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); g_list_free_full (users, g_free); /* 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->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, PermissionsInfo *permissions_info) { NautilusPropertiesWidget *self = permissions_info->widget; gboolean provide_dropdown = (!permissions_info->is_multi_file_window && nautilus_file_can_set_group (get_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_file (self); const char *group_name = nautilus_file_get_group_name (file); GList *groups = nautilus_file_get_settable_group_names (file); update_combo_row_dropdown (row, groups); g_list_free_full (groups, g_free); /* 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->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 (NautilusPropertiesWidget *self, AdwComboRow *row) { adw_combo_row_set_model (row, G_LIST_MODEL (gtk_string_list_new (NULL))); /* Initial setup of list model is handled via update function, called via properties_widget_update. */ } static gboolean file_has_prefix (NautilusFile *file, GHashTable *prefix_candidates) { for (g_autoptr (GFile) parent = nautilus_file_get_parent_location (file); parent != NULL;) { g_autofree gchar *parent_uri = g_file_get_uri (parent); if (g_hash_table_lookup (prefix_candidates, parent_uri)) { return TRUE; } else { g_autoptr (GFile) child = parent; parent = g_file_get_parent (parent); } } return FALSE; } static void directory_contents_value_field_update (NautilusPropertiesWidget *self) { NautilusRequestStatus file_status; g_autoptr (GHashTable) prefix_hashes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); g_autofree char *text = NULL; g_autofree char *bytes_str = NULL; g_autofree char *tooltip = 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_WIDGET (self)); total_count = 0; total_size = 0; unreadable_directory_count = FALSE; for (l = self->files; l; l = l->next) { g_autoptr (GFile) location = nautilus_file_get_location (NAUTILUS_FILE (l->data)); GFileType type = nautilus_file_get_file_type (NAUTILUS_FILE (l->data)); if (type & ~(G_FILE_TYPE_REGULAR | G_FILE_TYPE_SPECIAL | G_FILE_TYPE_SHORTCUT)) { g_hash_table_add (prefix_hashes, g_file_get_uri (location)); } } for (l = self->files; l; l = l->next) { file = NAUTILUS_FILE (l->data); if (file_has_prefix (file, prefix_hashes)) { /* 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); } } bytes_str = g_strdup_printf ("%'" G_GOFFSET_FORMAT, total_size); tooltip = g_strdup_printf (ngettext ("%s byte", "%s bytes", total_size), bytes_str); gtk_label_set_text (GTK_LABEL (self->contents_value_label), text); gtk_widget_set_tooltip_text (GTK_WIDGET (self->contents_value_label), tooltip); if (!deep_count_active) { self->deep_count_finished = TRUE; stop_spinner (self); } } static void update_directory_contents_callback (gpointer data) { NautilusPropertiesWidget *self; self = NAUTILUS_PROPERTIES_WIDGET (data); self->update_directory_contents_timeout_id = 0; directory_contents_value_field_update (self); } static void schedule_directory_contents_update (NautilusPropertiesWidget *self) { g_assert (NAUTILUS_IS_PROPERTIES_WIDGET (self)); if (self->update_directory_contents_timeout_id == 0) { self->update_directory_contents_timeout_id = g_timeout_add_once (DIRECTORY_CONTENTS_UPDATE_INTERVAL, (GSourceOnceFunc) update_directory_contents_callback, self); } } static void setup_contents_field (NautilusPropertiesWidget *self) { g_list_foreach (self->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_autoptr (GFile) location = nautilus_file_get_location (file); return nautilus_is_root_for_scheme (location, SCHEME_NETWORK); } static gboolean is_trash_directory (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); return nautilus_is_root_for_scheme (location, SCHEME_TRASH); } static gboolean is_burn_directory (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); return nautilus_is_root_for_scheme (location, SCHEME_BURN); } static gboolean is_volume_properties (NautilusPropertiesWidget *self) { NautilusFile *file; gboolean success = FALSE; if (is_multi_file_window (self)) { return FALSE; } file = get_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 is_single_file_type (NautilusPropertiesWidget *self) { if (is_multi_file_window (self)) { GList *l = self->files; const char *mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)); for (l = l->next; l != NULL; l = l->next) { NautilusFile *file = NAUTILUS_FILE (l->data); g_autofree gchar *next_mime_type = NULL; if (nautilus_file_is_gone (file)) { continue; } if (g_strcmp0 (nautilus_file_get_mime_type (file), mime_type) != 0) { return FALSE; } } } return TRUE; } static gboolean should_show_file_type (NautilusPropertiesWidget *self) { if (!is_single_file_type (self)) { return FALSE; } if (!is_multi_file_window (self) && (nautilus_file_is_in_trash (get_file (self)) || nautilus_file_is_directory (get_file (self)) || is_network_directory (get_file (self)) || is_burn_directory (get_file (self)) || is_volume_properties (self))) { return FALSE; } return TRUE; } static GFile * get_parent_location (NautilusFile *file) { return nautilus_file_get_parent_location (file); } static gboolean should_show_location_info (NautilusPropertiesWidget *self) { NautilusFile *file = get_file (self); g_autoptr (GFile) first_parent = get_parent_location (file); if (first_parent == NULL || nautilus_file_is_in_trash (file)) { return FALSE; } if (self->current_view_location != NULL && g_file_equal (first_parent, self->current_view_location)) { return FALSE; } g_autoptr (GFile) location = nautilus_file_get_location (file); g_autoptr (GMount) mount = g_file_find_enclosing_mount (location, NULL, NULL); if (mount != NULL) { g_autoptr (GFile) mount_root = g_mount_get_root (mount); if (mount_root != NULL && g_file_equal (first_parent, mount_root)) { return FALSE; } } if (is_multi_file_window (self)) { for (GList *l = self->files->next; l != NULL; l = l->next) { g_autoptr (GFile) parent = get_parent_location (NAUTILUS_FILE (l->data)); if (!g_file_equal (first_parent, parent)) { return FALSE; } } } return TRUE; } static gboolean should_show_trashed_info (NautilusPropertiesWidget *self) { return !is_multi_file_window (self) && nautilus_file_is_in_trash (get_file (self)) && !is_trash_directory (get_file (self)); } static gboolean should_show_accessed_date (NautilusPropertiesWidget *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->files) || is_multi_file_window (self)) { return FALSE; } return TRUE; } static gboolean should_show_modified_date (NautilusPropertiesWidget *self) { return !is_multi_file_window (self); } static gboolean should_show_created_date (NautilusPropertiesWidget *self) { return !is_multi_file_window (self); } static gboolean should_show_link_target (NautilusPropertiesWidget *self) { if (!is_multi_file_window (self) && nautilus_file_is_symbolic_link (get_file (self))) { return TRUE; } return FALSE; } static gboolean should_show_free_space (NautilusPropertiesWidget *self) { if (!is_multi_file_window (self) && (nautilus_file_is_in_trash (get_file (self)) || is_network_directory (get_file (self)) || is_burn_directory (get_file (self)) || is_volume_properties (self))) { return FALSE; } if (nautilus_file_list_are_all_folders (self->files)) { return TRUE; } return FALSE; } static gboolean should_show_volume_usage (NautilusPropertiesWidget *self) { return is_volume_properties (self); } static void setup_volume_information (NautilusPropertiesWidget *self) { if (self->volume_fs_info != NULL && g_file_info_has_attribute (self->volume_fs_info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)) { const char *fs_type = g_file_info_get_attribute_string (self->volume_fs_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 = nautilus_capitalize_str (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_set_visible (GTK_WIDGET (self->type_file_system_label), TRUE); } } } static void setup_volume_usage_widget (NautilusPropertiesWidget *self) { GFileInfo *info = self->volume_fs_info; guint64 volume_capacity = 0; guint64 volume_free = 0; guint64 volume_used = 0; if (info != NULL) { volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); 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)) { volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); } else { volume_used = volume_capacity - volume_free; } } gtk_widget_set_visible (self->volume_usage_row, (volume_capacity > 0)); if (volume_capacity > 0) { g_autofree gchar *capacity = g_format_size (volume_capacity); g_autofree gchar *used = g_format_size (volume_used); g_autofree gchar *free = g_format_size (volume_free); 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); gtk_level_bar_set_value (self->disk_space_level_bar, (double) volume_used / (double) 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 open_parent_folder (NautilusPropertiesWidget *self) { NautilusFile *file = get_file (self); g_autoptr (GFile) parent_location = get_parent_location (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, &(NautilusFileList){ .data = file }, NULL); } static void open_link_target (NautilusPropertiesWidget *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_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, &(NautilusFileList){ .data = link_target_file }, NULL); } static void setup_open_in_disks (NautilusPropertiesWidget *self) { g_autoptr (GMount) mount = NULL; g_autoptr (GVolume) volume = NULL; mount = nautilus_file_get_mount (get_file (self)); volume = (mount != NULL) ? g_mount_get_volume (mount) : NULL; if (volume != NULL) { self->device_identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); } else { g_autoptr (GFile) location = nautilus_file_get_location (get_file (self)); const gchar *path = g_file_peek_path (location); g_autoptr (GUnixMountEntry) mount_entry = (path != NULL) ? g_unix_mount_entry_at (path, NULL) : NULL; if (mount_entry != NULL) { self->device_identifier = g_strdup (g_unix_mount_entry_get_device_path (mount_entry)); } } gtk_widget_set_visible (self->open_in_disks_button_row, (self->device_identifier != NULL)); } static void open_in_disks (NautilusPropertiesWidget *self) { NautilusDBusLauncher *launcher = nautilus_dbus_launcher_get (); GVariant *parameters; g_return_if_fail (self->device_identifier != NULL); parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', " "@aay [], {'options': <{'block-device': <%s>}> })", self->device_identifier); nautilus_dbus_launcher_call (launcher, NAUTILUS_DBUS_LAUNCHER_DISKS, "CommandLine", parameters, get_parent_window (self)); } static void add_updatable_label (NautilusPropertiesWidget *self, GtkWidget *label, const char *file_attribute_value, const char *file_attribute_tooltip) { g_object_set_data_full (G_OBJECT (label), "file_attribute_value", g_strdup (file_attribute_value), g_free); if (file_attribute_tooltip != NULL) { g_object_set_data_full (G_OBJECT (label), "file_attribute_tooltip", g_strdup (file_attribute_tooltip), g_free); } self->value_labels = g_list_prepend (self->value_labels, label); } static void add_updatable_row (NautilusPropertiesWidget *self, GtkWidget *row, const char *file_attribute) { g_object_set_data_full (G_OBJECT (row), "file_attribute", g_strdup (file_attribute), g_free); self->value_rows = g_list_prepend (self->value_rows, row); } static void setup_basic_page (NautilusPropertiesWidget *self) { gboolean should_show_locations_group = FALSE; /* Icon pixmap */ setup_image_widget (self); if (!is_multi_file_window (self)) { setup_star_button (self); } update_name_field (self); if (should_show_volume_usage (self)) { setup_open_in_disks (self); setup_volume_information (self); setup_volume_usage_widget (self); gtk_widget_set_visible (self->disk_group, (gtk_widget_get_visible (self->volume_usage_row) || gtk_widget_get_visible (self->open_in_disks_button_row))); } if (should_show_file_type (self)) { gtk_widget_set_visible (self->type_value_label, TRUE); add_updatable_label (self, self->type_value_label, "detailed_type", "mime_type"); } if (should_show_link_target (self)) { gtk_widget_set_visible (self->link_target_row, TRUE); add_updatable_row (self, self->link_target_row, "link_target"); should_show_locations_group = TRUE; } if (is_multi_file_window (self) || nautilus_file_is_directory (get_file (self))) { /* We have a more efficient way to measure used space in volumes. */ if (!is_volume_properties (self)) { gtk_widget_set_visible (self->contents_box, TRUE); setup_contents_field (self); } } else { gtk_widget_set_visible (self->size_value_label, TRUE); add_updatable_label (self, self->size_value_label, "size", "size_detail"); } if (should_show_location_info (self)) { gtk_widget_set_visible (self->parent_folder_row, TRUE); add_updatable_row (self, self->parent_folder_row, "where"); should_show_locations_group = TRUE; } if (should_show_trashed_info (self)) { gtk_widget_set_visible (self->trashed_group, TRUE); add_updatable_row (self, self->original_folder_row, "trash_orig_path"); add_updatable_row (self, self->trashed_on_row, "trashed_on_full"); } if (should_show_modified_date (self)) { gtk_widget_set_visible (self->times_group, TRUE); gtk_widget_set_visible (self->modified_row, TRUE); add_updatable_row (self, self->modified_row, "date_modified_full"); } if (should_show_created_date (self)) { gtk_widget_set_visible (self->created_row, TRUE); gtk_widget_set_visible (self->times_group, TRUE); add_updatable_row (self, self->created_row, "date_created_full"); } if (should_show_accessed_date (self)) { gtk_widget_set_visible (self->times_group, TRUE); gtk_widget_set_visible (self->accessed_row, TRUE); add_updatable_row (self, self->accessed_row, "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_set_visible (self->free_space_value_label, TRUE); add_updatable_label (self, self->free_space_value_label, "free_space", NULL); } } if (should_show_locations_group) { gtk_widget_set_visible (self->locations_group, TRUE); } } static FilterType files_get_filter_type (NautilusPropertiesWidget *self) { FilterType filter_type = NO_FILES_OR_FOLDERS; for (GList *l = self->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 (NautilusPropertiesWidget *self) { GList *l; gboolean changeable = FALSE; for (l = self->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)) { changeable = TRUE; } else { changeable = FALSE; break; } } return changeable; } static void start_long_operation (NautilusPropertiesWidget *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 (NautilusPropertiesWidget *self) { GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); if (gtk_native_get_surface (native) != 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 (NautilusPropertiesWidget) self = NAUTILUS_PROPERTIES_WIDGET (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_WIDGET (self)); } static void update_permissions (NautilusPropertiesWidget *self, guint32 vfs_new_perm, guint32 vfs_mask, FilterType filter_type, gboolean use_original) { for (GList *l = self->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 (NautilusPropertiesWidget *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; gboolean set_executable = adw_switch_row_get_active (self->execution_row); update_permissions (self, set_executable ? permission_mask : 0, permission_mask, filter_type, FALSE); } static gboolean should_show_exectution_switch (NautilusPropertiesWidget *self) { if (is_multi_file_window (self)) { return FALSE; } const char *mime_type = nautilus_file_get_mime_type (get_file (self)); return g_content_type_can_be_executable (mime_type); } static void update_execution_row (GtkWidget *row, PermissionsInfo *permissions_info) { NautilusPropertiesWidget *self = permissions_info->widget; if (!should_show_exectution_switch (self)) { gtk_widget_set_visible (GTK_WIDGET (self->execution_row), FALSE); } else { g_signal_handlers_block_by_func (self->execution_row, G_CALLBACK (execution_bit_changed), self); adw_switch_row_set_active (self->execution_row, permissions_info->file_exec_permissions == PERMISSION_EXEC); g_signal_handlers_unblock_by_func (self->execution_row, G_CALLBACK (execution_bit_changed), self); gtk_widget_set_sensitive (row, permissions_info->can_set_any_file_permission); gtk_widget_set_visible (GTK_WIDGET (self->execution_row), TRUE); } } static void on_permission_row_change (AdwComboRow *row, GParamSpec *pspec, NautilusPropertiesWidget *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_WIDGET (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 guint 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 GTK_INVALID_LIST_POSITION; } static void update_permission_row (AdwComboRow *row, PermissionsInfo *permissions_info) { NautilusPropertiesWidget *self = permissions_info->widget; PermissionType type; PermissionValue permissions_to_show; FilterType filter_type; gboolean is_folder; GListModel *model; guint 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 ? permissions_info->folder_permissions[type] : permissions_info->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 ? permissions_info->can_set_all_folder_permission : permissions_info->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_row (AdwComboRow *combo_row, PermissionType type, FilterType filter_type) { g_autoptr (GListStore) store = NULL; g_autoptr (GtkExpression) expression = NULL; store = g_list_store_new (NAUTILUS_TYPE_PERMISSION_ENTRY); adw_combo_row_set_model (combo_row, G_LIST_MODEL (store)); expression = gtk_property_expression_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL, "name"); adw_combo_row_set_expression (combo_row, expression); g_object_set_data (G_OBJECT (combo_row), "filter-type", GINT_TO_POINTER (filter_type)); g_object_set_data (G_OBJECT (combo_row), "permission-type", GINT_TO_POINTER (type)); if (filter_type == FOLDERS_ONLY) { if (type != PERMISSION_USER) { list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, TRUE); } 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 { if (type != PERMISSION_USER) { list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, FALSE); } list_store_append_nautilus_permission_entry (store, PERMISSION_READ, FALSE); list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_WRITE, FALSE); } } 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 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 (NautilusPropertiesWidget *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_set_visible (GTK_WIDGET (row), TRUE); 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 (NautilusPropertiesWidget *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, "notify::active", G_CALLBACK (execution_bit_changed), self); } static void set_recursive_permissions_done (gboolean success, gpointer callback_data) { g_autoptr (NautilusPropertiesWidget) self = NAUTILUS_PROPERTIES_WIDGET (callback_data); end_long_operation (self); } static void on_change_permissions_response_cancel (NautilusPropertiesWidget *self) { g_clear_pointer (&self->change_permission_combo_rows, g_list_free); adw_dialog_close (self->permissions_dialog); } static void on_change_permissions_response_change (NautilusPropertiesWidget *self) { guint32 file_permission = 0, file_permission_mask = 0; guint32 dir_permission = 0, dir_permission_mask = 0; guint32 vfs_mask, vfs_new_perm; FilterType filter_type; GList *l; PermissionType type; int mask; /* Simple mode, minus exec checkbox */ for (l = self->change_permission_combo_rows; l != NULL; l = l->next) { AdwComboRow *combo_row = l->data; NautilusPermissionEntry *selected = adw_combo_row_get_selected_item (combo_row); if (selected == NULL) { continue; } type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo_row), "permission-type")); filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo_row), "filter-type")); vfs_new_perm = permission_to_vfs (type, selected->permission_value); 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->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_combo_rows, g_list_free); adw_dialog_close (self->permissions_dialog); } static void set_active_from_umask (AdwComboRow *combo_row, PermissionType type, FilterType filter_type) { GListModel *model = adw_combo_row_get_model (combo_row); mode_t initial; mode_t mask; mode_t p; PermissionValue perm; 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) { perm = PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE; } else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR)) { perm = PERMISSION_READ | PERMISSION_WRITE; } else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR)) { perm = PERMISSION_READ | PERMISSION_EXEC; } else if ((p & S_IRUSR) == S_IRUSR) { perm = PERMISSION_READ; } else { perm = PERMISSION_NONE; } } else if (type == PERMISSION_GROUP) { p &= ~(S_IRWXU | S_IRWXO); if ((p & S_IRWXG) == S_IRWXG) { perm = PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE; } else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP)) { perm = PERMISSION_READ | PERMISSION_WRITE; } else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP)) { perm = PERMISSION_READ | PERMISSION_EXEC; } else if ((p & S_IRGRP) == S_IRGRP) { perm = PERMISSION_READ; } else { perm = PERMISSION_NONE; } } else { p &= ~(S_IRWXU | S_IRWXG); if ((p & S_IRWXO) == S_IRWXO) { perm = PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE; } else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH)) { perm = PERMISSION_READ | PERMISSION_WRITE; } else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH)) { perm = PERMISSION_READ | PERMISSION_EXEC; } else if ((p & S_IROTH) == S_IROTH) { perm = PERMISSION_READ; } else { perm = PERMISSION_NONE; } } for (guint i = 0; i < g_list_model_get_n_items (model); i++) { g_autoptr (NautilusPermissionEntry) entry = g_list_model_get_item (model, i); if (entry->permission_value == perm) { adw_combo_row_set_selected (combo_row, i); break; } } } static gboolean on_change_permissions_close (GtkWidget *widget, GVariant *args, gpointer user_data) { on_change_permissions_response_cancel (NAUTILUS_PROPERTIES_WIDGET (user_data)); return TRUE; } static void on_change_permissions_clicked (NautilusPropertiesWidget *self) { AdwComboRow *combo_row; GtkButton *cancel_button, *change_button; GtkShortcut *esc_shortcut; GtkShortcutAction *cb_action; 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"); self->permissions_dialog = ADW_DIALOG (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog")); /* Owner Permissions */ combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "file_owner_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_USER, FILES_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_USER, FILES_ONLY); combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_USER, FOLDERS_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_USER, FOLDERS_ONLY); /* Group Permissions */ combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "file_group_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_GROUP, FILES_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_GROUP, FILES_ONLY); combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "folder_group_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_GROUP, FOLDERS_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_GROUP, FOLDERS_ONLY); /* Others Permissions */ combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "file_other_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_OTHER, FILES_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_OTHER, FILES_ONLY); combo_row = ADW_COMBO_ROW (gtk_builder_get_object (change_permissions_builder, "folder_other_combo_row")); setup_permissions_combo_row (combo_row, PERMISSION_OTHER, FOLDERS_ONLY); self->change_permission_combo_rows = g_list_prepend (self->change_permission_combo_rows, combo_row); set_active_from_umask (combo_row, PERMISSION_OTHER, FOLDERS_ONLY); cancel_button = GTK_BUTTON (gtk_builder_get_object (change_permissions_builder, "cancel_button")); change_button = GTK_BUTTON (gtk_builder_get_object (change_permissions_builder, "change_button")); g_signal_connect_swapped (cancel_button, "clicked", G_CALLBACK (on_change_permissions_response_cancel), self); g_signal_connect_swapped (change_button, "clicked", G_CALLBACK (on_change_permissions_response_change), self); esc_shortcut = GTK_SHORTCUT (gtk_builder_get_object (change_permissions_builder, "esc_shortcut")); cb_action = gtk_callback_action_new ((GtkShortcutFunc) on_change_permissions_close, self, NULL); gtk_shortcut_set_action (esc_shortcut, cb_action); adw_dialog_present (self->permissions_dialog, GTK_WIDGET (self)); } static void setup_permissions_page (NautilusPropertiesWidget *self) { self->initial_permissions = NULL; if (all_can_get_permissions (self->files)) { self->initial_permissions = get_initial_permissions (self->files); self->has_recursive_apply = files_has_changable_permissions_directory (self); gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permissions-box"); create_simple_permissions (self); #ifdef HAVE_SELINUX gtk_widget_set_visible (self->security_context_group, TRUE); add_updatable_row (self, self->security_context_row, "selinux_context"); #endif if (self->has_recursive_apply) { gtk_widget_set_visible (self->change_permissions_enclosed_files_group, TRUE); } } 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-widget.blp so no else block. */ if (!is_multi_file_window (self)) { g_autofree gchar *prompt_text = NULL; prompt_text = g_strdup_printf (_("The permissions of ā€œ%sā€ could not be determined."), nautilus_file_get_display_name (get_file (self))); 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 (NautilusPropertiesWidget *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->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_group, 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); g_set_object (&self->extensions_list, extensions_list); } static gboolean should_show_permissions (NautilusPropertiesWidget *self) { GList *l; /* Don't show permissions for Trash since they're not really file system * objects. */ for (l = self->files; l != NULL; l = l->next) { if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } static void file_changed_callback (NautilusFile *file, gpointer user_data) { NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (user_data); if (!g_list_find (self->changed_files, file)) { nautilus_file_ref (file); self->changed_files = g_list_prepend (self->changed_files, file); if (self->update_files_timeout_id == 0) { /* Schedule new update */ self->update_files_timeout_id = g_timeout_add_once (FILES_UPDATE_INTERVAL, (GSourceOnceFunc) update_files_callback, self); } } } static void setup_properties_widget (NautilusPropertiesWidget *self) { for (NautilusFileList *l = self->files; l != NULL; l = l->next) { NautilusFile *file = l->data; NautilusAttributes attributes = NAUTILUS_ATTRIBUTE_THUMBNAIL_BUFFER; if (nautilus_file_is_directory (file)) { attributes |= NAUTILUS_ATTRIBUTE_DEEP_COUNT; } nautilus_file_monitor_add (file, &self->files, attributes); g_signal_connect_object (NAUTILUS_FILE (l->data), "changed", G_CALLBACK (file_changed_callback), G_OBJECT (self), G_CONNECT_DEFAULT); } /* Create the pages. */ setup_basic_page (self); if (should_show_permissions (self)) { setup_permissions_page (self); gtk_widget_set_visible (self->permissions_navigation_row, TRUE); } if (should_show_exectution_switch (self)) { gtk_widget_set_visible (GTK_WIDGET (self->execution_row), TRUE); } /* Update from initial state */ properties_widget_update (self, NULL); } #define DEFAULT_PROPERTIES_WIDTH 480 static void properties_files_are_ready (NautilusPropertiesWidget *self) { const char * const main_tag[] = {"main"}; setup_properties_widget (self); /* Show main page */ adw_navigation_view_replace_with_tags (self->nav_view, main_tag, 1); int current_width = gtk_widget_get_width (GTK_WIDGET (self)); if (current_width != 0) { /* Properties were already drawn while loading, adjust height */ int natural_height; gtk_widget_measure (GTK_WIDGET (self), GTK_ORIENTATION_VERTICAL, current_width, NULL, &natural_height, NULL, NULL); if (self->dialog != NULL) { adw_dialog_set_content_height (self->dialog, natural_height); } else { gtk_window_set_default_size (get_parent_window (self), DEFAULT_PROPERTIES_WIDTH, natural_height); } } } static void volume_fs_info_callback (GObject *object, GAsyncResult *res, gpointer data) { g_autoptr (GError) error = NULL; g_autoptr (GFileInfo) info = g_file_query_filesystem_info_finish (G_FILE (object), res, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Properties was disposed, bailout. */ return; } NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (data); self->volume_fs_info = g_steal_pointer (&info); g_clear_object (&self->volume_fs_info_cancellable); properties_files_are_ready (self); } static void file_list_ready_callback (GList *file_list, gpointer data) { NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (data); self->handle = NULL; if (should_show_volume_usage (self)) { /* Need to additionally query Filesystem info for a volume */ NautilusFile *file = get_file (self); g_autofree gchar *uri = nautilus_file_get_activation_uri (file); g_autoptr (GFile) location = g_file_new_for_uri (uri); self->volume_fs_info_cancellable = g_cancellable_new (); g_file_query_filesystem_info_async (location, "filesystem::*", G_PRIORITY_DEFAULT, self->volume_fs_info_cancellable, volume_fs_info_callback, self); } else { properties_files_are_ready (self); } } static NautilusPropertiesWidget * properties_widget_new (NautilusFileList *files, GFile *current_view_location) { NautilusPropertiesWidget *self = NAUTILUS_PROPERTIES_WIDGET (g_object_new (NAUTILUS_TYPE_PROPERTIES_WIDGET, NULL)); if (current_view_location != NULL) { self->current_view_location = g_object_ref (current_view_location); } for (GList *l = files; l != NULL; l = l->next) { NautilusFile *file = l->data; if (nautilus_file_is_in_recent (file)) { g_autofree char *uri = nautilus_file_get_activation_uri (file); self->files = g_list_prepend (self->files, nautilus_file_get_by_uri (uri)); } else { self->files = g_list_prepend (self->files, nautilus_file_ref (file)); } } self->files = g_list_reverse (self->files); nautilus_file_list_call_when_ready (self->files, NAUTILUS_ATTRIBUTE_INFO | NAUTILUS_ATTRIBUTE_THUMBNAIL_BUFFER, &self->handle, file_list_ready_callback, self); return self; } static GtkWindow * create_properties_window (GtkWidget *content) { g_autoptr (GtkBuilder) builder = gtk_builder_new_from_resource ( "/org/gnome/nautilus/ui/nautilus-properties-window.ui"); AdwWindow *window = ADW_WINDOW (gtk_builder_get_object (builder, "properties_window")); adw_window_set_content (window, GTK_WIDGET (content)); return GTK_WINDOW (window); } GtkWindow * nautilus_properties_present_window (NautilusFileList *files, const char *startup_id) { g_return_val_if_fail (files != NULL, NULL); NautilusPropertiesWidget *self = properties_widget_new (files, NULL); GtkWindow *window = create_properties_window (GTK_WIDGET (self)); g_signal_connect_swapped (self, "hide-properties", G_CALLBACK (gtk_window_close), window); if (startup_id != NULL) { gtk_window_set_startup_id (window, startup_id); } gtk_window_present (window); return window; } void nautilus_properties_present_dialog (NautilusFileList *files, GtkWidget *parent_widget, GFile *current_view_location) { g_return_if_fail (files != NULL); g_return_if_fail (GTK_IS_WIDGET (parent_widget)); NautilusPropertiesWidget *self = properties_widget_new (files, current_view_location); AdwDialog *dialog = adw_dialog_new (); adw_dialog_set_content_width (dialog, DEFAULT_PROPERTIES_WIDTH); adw_dialog_set_child (dialog, GTK_WIDGET (self)); g_signal_connect_object (self, "hide-properties", G_CALLBACK (adw_dialog_force_close), dialog, G_CONNECT_SWAPPED); self->dialog = dialog; gtk_widget_set_visible (self->popout_button, TRUE); adw_dialog_present (dialog, parent_widget); } static gboolean close_request_popout_window (gpointer user_data) { GtkWindow *window = GTK_WINDOW (user_data); gtk_window_close (window); return FALSE; } static void popout_window_clicked (NautilusPropertiesWidget *self) { GtkWidget *previous_container; /* Drop from previous container */ if (self->dialog != NULL) { adw_dialog_set_child (self->dialog, NULL); previous_container = GTK_WIDGET (self->dialog); } else { AdwWindow *old_window = ADW_WINDOW (get_parent_window (self)); adw_window_set_content (old_window, NULL); previous_container = GTK_WIDGET (old_window); } GtkWindow *new_window = create_properties_window (GTK_WIDGET (self)); gtk_window_set_display (new_window, gtk_widget_get_display (previous_container)); self->dialog = NULL; gtk_widget_set_visible (self->popout_button, FALSE); g_clear_object (&self->current_view_location); if (!gtk_widget_get_visible (self->parent_folder_row) && should_show_location_info (self)) { add_updatable_row (self, self->parent_folder_row, "where"); value_row_update (ADW_ACTION_ROW (self->parent_folder_row), self); gtk_widget_set_visible (self->parent_folder_row, TRUE); gtk_widget_set_visible (self->locations_group, TRUE); } /* Cleanup previous container, then connect new one */ g_signal_emit (self, signals[HIDE], 0); g_signal_connect_swapped (self, "hide-properties", G_CALLBACK (gtk_window_close), new_window); g_signal_connect (new_window, "close-request", G_CALLBACK (close_request_popout_window), NULL); g_signal_connect_object (g_application_get_default (), "last-window-closed", G_CALLBACK (gtk_window_close), new_window, G_CONNECT_SWAPPED); gtk_window_present (new_window); } static void real_dispose (GObject *object) { G_OBJECT_CLASS (nautilus_properties_widget_parent_class)->dispose (object); NautilusPropertiesWidget *self; self = NAUTILUS_PROPERTIES_WIDGET (object); unschedule_or_cancel_group_change (self); unschedule_or_cancel_owner_change (self); g_clear_object (&self->icon_cancellable); g_clear_object (&self->icon_pending_location); g_clear_object (&self->current_view_location); g_clear_pointer (&self->custom_icon_for_undo, g_free); g_clear_pointer (&self->handle, nautilus_file_list_cancel_call_when_ready); if (self->volume_fs_info_cancellable) { g_cancellable_cancel (self->volume_fs_info_cancellable); g_clear_object (&self->volume_fs_info_cancellable); } g_list_foreach (self->files, (GFunc) nautilus_file_monitor_remove, &self->files); g_clear_list (&self->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); g_clear_object (&self->volume_fs_info); 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_combo_rows, NULL); g_clear_pointer (&self->initial_permissions, g_hash_table_destroy); g_clear_list (&self->value_labels, NULL); g_clear_list (&self->value_rows, 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); gtk_widget_dispose_template (GTK_WIDGET (self), NAUTILUS_TYPE_PROPERTIES_WIDGET); } static void real_finalize (GObject *object) { NautilusPropertiesWidget *self; self = NAUTILUS_PROPERTIES_WIDGET (object); g_free (self->mime_type); g_free (self->device_identifier); g_clear_object (&self->extensions_list); G_OBJECT_CLASS (nautilus_properties_widget_parent_class)->finalize (object); } static void custom_icon_file_chooser_response_cb (GtkFileDialog *dialog, GAsyncResult *result, NautilusPropertiesWidget *self) { g_autoptr (GError) error = NULL; g_autoptr (GFile) location = gtk_file_dialog_open_finish (dialog, result, &error); if (location != NULL) { set_icon (self, location); } else if (error != NULL && !g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED)) { g_warning ("Error while choosing an icon: %s", error->message); } } static void select_image_button_callback (GtkWidget *widget, NautilusPropertiesWidget *self) { g_autoptr (GtkFileDialog) dialog = gtk_file_dialog_new (); g_autoptr (GtkFileFilter) filter = gtk_file_filter_new (); g_auto (GStrv) mime_types = gly_loader_get_mime_types (); NautilusFile *file = get_file (self); gtk_file_dialog_set_title (dialog, _("Select Custom Icon")); gtk_file_dialog_set_accept_label (dialog, _("_Select")); gtk_file_filter_add_mime_types (filter, (const char **) mime_types); gtk_file_dialog_set_default_filter (dialog, filter); /* it's likely that the user wants to pick an icon that is inside a local directory */ 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_dialog_set_initial_folder (dialog, image_location); } } gtk_file_dialog_open (dialog, get_parent_window (self), NULL, (GAsyncReadyCallback) custom_icon_file_chooser_response_cb, self); } static void nautilus_properties_widget_class_init (NautilusPropertiesWidgetClass *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; g_type_ensure (NAUTILUS_TYPE_IMAGE); signals[HIDE] = g_signal_new ("hide-properties", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-widget.ui"); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, toast_overlay); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, nav_view); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, icon_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, icon_image); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, icon_overlay); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, select_icon_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, reset_icon_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, star_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, popout_button); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, name_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, type_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, type_file_system_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, size_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, contents_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, contents_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, contents_spinner); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, disk_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, volume_usage_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, disk_space_level_bar); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, disk_space_used_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, disk_space_free_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, disk_space_capacity_value); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, open_in_disks_button_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, locations_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, link_target_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, parent_folder_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, trashed_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, trashed_on_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, original_folder_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, times_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, modified_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, created_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, accessed_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, permissions_navigation_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, permissions_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, extension_models_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, extension_models_list_box); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, free_space_value_label); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, permissions_stack); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, owner_permission_banner); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, unknown_permissions_page); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, owner_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, owner_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, owner_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, owner_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, group_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, group_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, group_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, group_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, others_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, others_folder_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, others_file_access_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, execution_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, security_context_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, security_context_row); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, change_permissions_enclosed_files_group); gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWidget, extension_list_box); gtk_widget_class_bind_template_callback (widget_class, star_clicked); gtk_widget_class_bind_template_callback (widget_class, popout_window_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_permissions_page); gtk_widget_class_bind_template_callback (widget_class, reset_icon); gtk_widget_class_bind_template_callback (widget_class, select_image_button_callback); gtk_widget_class_bind_template_callback (widget_class, on_change_permissions_clicked); } static void nautilus_properties_widget_init (NautilusPropertiesWidget *self) { gtk_widget_init_template (GTK_WIDGET (self)); }