/* 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));
}