/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2005-2007 Benedikt Meurer * Copyright (c) 2005 Jeff Franks * Copyright (c) 2009-2011 Jannis Pohlmann * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MEMORY_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_TIME_H #include #endif #include #ifdef HAVE_GUDEV #include #endif #include "thunar/thunar-application.h" #include "thunar/thunar-browser.h" #include "thunar/thunar-dbus-service.h" #include "thunar/thunar-dialogs.h" #include "thunar/thunar-gdk-extensions.h" #include "thunar/thunar-gobject-extensions.h" #include "thunar/thunar-gtk-extensions.h" #include "thunar/thunar-io-jobs.h" #include "thunar/thunar-preferences.h" #include "thunar/thunar-private.h" #include "thunar/thunar-progress-dialog.h" #include "thunar/thunar-renamer-dialog.h" #include "thunar/thunar-session-client.h" #include "thunar/thunar-thumbnail-cache.h" #include "thunar/thunar-thumbnailer.h" #include "thunar/thunar-transfer-job.h" #include "thunar/thunar-util.h" #include "thunar/thunar-view.h" #include #define ACCEL_MAP_PATH "Thunar/accels.scm" /* option values */ static gboolean opt_version = FALSE; /* the sm-client-id option is only honored while starting the primary instance, * and will be silently ignored on every other invocation */ static gchar *opt_sm_client_id = NULL; /* clang-format off */ /* option entries */ static const GOptionEntry option_entries[] = { { "bulk-rename", 'B', 0, G_OPTION_ARG_NONE, NULL, N_ ("Open the bulk rename dialog"), NULL, }, { "window", 'w', 0, G_OPTION_ARG_NONE, NULL, N_ ("Force open in a new window"), NULL, }, { "daemon", 0, 0, G_OPTION_ARG_NONE, NULL, N_ ("Run in daemon mode"), NULL, }, { "sm-client-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_sm_client_id, NULL, NULL, }, { "quit", 'q', 0, G_OPTION_ARG_NONE, NULL, N_ ("Quit a running Thunar instance"), NULL, }, { "version", 'V', 0, G_OPTION_ARG_NONE, &opt_version, N_ ("Print version information and exit"), NULL, }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, NULL, NULL, }, { NULL, }, }; /* clang-format on */ /* Prototype for the Thunar job launchers */ typedef ThunarJob *(*Launcher) (GList *source_path_list, GList *target_path_list); /* Property identifiers */ enum { PROP_0, PROP_DAEMON, }; static void thunar_application_finalize (GObject *object); static void thunar_application_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void thunar_application_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void thunar_application_dbus_acquired_cb (GDBusConnection *conn, const gchar *name, gpointer user_data); static void thunar_application_name_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static void thunar_application_dbus_name_lost_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static void thunar_application_dbus_init (ThunarApplication *application); static void thunar_application_startup (GApplication *application); static void thunar_application_shutdown (GApplication *application); static void thunar_application_activate (GApplication *application); static int thunar_application_handle_local_options (GApplication *application, GVariantDict *options); static int thunar_application_command_line (GApplication *application, GApplicationCommandLine *command_line); static gboolean thunar_application_dbus_register (GApplication *application, GDBusConnection *connection, const gchar *object_path, GError **error); static void thunar_application_load_css (void); static void thunar_application_accel_map_changed (ThunarApplication *application); static gboolean thunar_application_accel_map_save (gpointer user_data); static void thunar_application_collect_and_launch (ThunarApplication *application, gpointer parent, const gchar *icon_name, const gchar *title, Launcher launcher, GList *source_file_list, GFile *target_file, gboolean update_source_folders, gboolean update_target_folders, ThunarOperationLogMode log_mode, GClosure *new_files_closure); static void thunar_application_launch_finished (ThunarJob *job, GList *containing_folders); static void thunar_application_launch (ThunarApplication *application, gpointer parent, const gchar *icon_name, const gchar *title, Launcher launcher, GList *source_path_list, GList *target_path_list, gboolean update_source_folders, gboolean update_target_folders, ThunarOperationLogMode log_mode, GClosure *new_files_closure); #ifdef HAVE_GUDEV static void thunar_application_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device, ThunarApplication *application); static gboolean thunar_application_volman_idle (gpointer user_data); static void thunar_application_volman_idle_destroy (gpointer user_data); static void thunar_application_volman_watch (GPid pid, gint status, gpointer user_data); static void thunar_application_volman_watch_destroy (gpointer user_data); #endif static gboolean thunar_application_show_progress_dialog_timeout (gpointer user_data); static void thunar_application_show_progress_dialog_timeout_destroy (gpointer user_data); static GtkWidget * thunar_application_get_progress_dialog (ThunarApplication *application); static void thunar_application_process_files (ThunarApplication *application); struct _ThunarApplicationClass { GtkApplicationClass __parent__; }; struct _ThunarApplication { GtkApplication __parent__; ThunarSessionClient *session_client; ThunarPreferences *preferences; GtkWidget *progress_dialog; ThunarThumbnailCache *thumbnail_cache; ThunarThumbnailer *thumbnailer; ThunarDBusService *dbus_service; gboolean daemon; gboolean force_new_window; guint accel_map_save_id; GtkAccelMap *accel_map; guint show_progress_dialog_n_jobs_before; guint show_progress_dialog_timer_id; #ifdef HAVE_GUDEV GUdevClient *udev_client; GSList *volman_udis; guint volman_idle_id; guint volman_watch_id; #endif GList *files_to_launch; ThunarApplicationProcessAction process_file_action; guint dbus_owner_id_xfce; guint dbus_owner_id_fdo; }; static GQuark thunar_application_screen_quark; static GQuark thunar_application_startup_id_quark; static GQuark thunar_application_file_quark; G_DEFINE_TYPE_EXTENDED (ThunarApplication, thunar_application, GTK_TYPE_APPLICATION, 0, G_IMPLEMENT_INTERFACE (THUNAR_TYPE_BROWSER, NULL)) static void thunar_application_class_init (ThunarApplicationClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GApplicationClass *gapplication_class = G_APPLICATION_CLASS (klass); /* pre-allocate the required quarks */ thunar_application_screen_quark = g_quark_from_static_string ("thunar-application-screen"); thunar_application_startup_id_quark = g_quark_from_static_string ("thunar-application-startup-id"); thunar_application_file_quark = g_quark_from_static_string ("thunar-application-file"); gobject_class->finalize = thunar_application_finalize; gobject_class->get_property = thunar_application_get_property; gobject_class->set_property = thunar_application_set_property; gapplication_class->startup = thunar_application_startup; gapplication_class->activate = thunar_application_activate; gapplication_class->shutdown = thunar_application_shutdown; gapplication_class->handle_local_options = thunar_application_handle_local_options; gapplication_class->command_line = thunar_application_command_line; gapplication_class->dbus_register = thunar_application_dbus_register; /** * ThunarApplication:daemon: * * %TRUE if the application should be run in daemon mode, * in which case it will never terminate. %FALSE if the * application should terminate once the last window is * closed. **/ g_object_class_install_property (gobject_class, PROP_DAEMON, g_param_spec_boolean ("daemon", "daemon", "daemon", FALSE, EXO_PARAM_READWRITE)); } static void thunar_application_init (ThunarApplication *application) { /* we do most initialization in GApplication::startup since it is only needed * in the primary instance anyways */ application->force_new_window = FALSE; application->files_to_launch = NULL; application->process_file_action = THUNAR_APPLICATION_SELECT_FILES; application->progress_dialog = NULL; application->preferences = NULL; g_application_set_flags (G_APPLICATION (application), G_APPLICATION_HANDLES_COMMAND_LINE); g_application_add_main_option_entries (G_APPLICATION (application), option_entries); } static void thunar_application_dbus_acquired_cb (GDBusConnection *conn, const gchar *name, gpointer user_data) { g_debug (_("Acquired the session message bus '%s'\n"), name); } static void thunar_application_name_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_debug (_("Acquired the name '%s' on the session message bus\n"), name); } static void thunar_application_dbus_name_lost_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_warning (_("Name '%s' lost on the message dbus."), name); } /* TODO: [GTK3 Port] Check if there's a cleaner way to register */ /* these extra dbus names (besides org.xfce.Thunar) */ static void thunar_application_dbus_init (ThunarApplication *application) { application->dbus_owner_id_xfce = g_bus_own_name (G_BUS_TYPE_SESSION, "org.xfce.FileManager", G_BUS_NAME_OWNER_FLAGS_NONE, thunar_application_dbus_acquired_cb, thunar_application_name_acquired_cb, thunar_application_dbus_name_lost_cb, application, NULL); application->dbus_owner_id_fdo = g_bus_own_name (G_BUS_TYPE_SESSION, "org.freedesktop.FileManager1", G_BUS_NAME_OWNER_FLAGS_NONE, thunar_application_dbus_acquired_cb, thunar_application_name_acquired_cb, thunar_application_dbus_name_lost_cb, application, NULL); } static void thunar_application_startup (GApplication *gapp) { ThunarApplication *application = THUNAR_APPLICATION (gapp); #ifdef HAVE_GUDEV static const gchar *subsystems[] = { "block", "input", "usb", NULL }; #endif /* initialize the application */ application->preferences = thunar_preferences_get (); #ifdef HAVE_GUDEV /* establish connection with udev */ application->udev_client = g_udev_client_new (subsystems); /* connect to the client in order to be notified when devices are plugged in * or disconnected from the computer */ g_signal_connect (application->udev_client, "uevent", G_CALLBACK (thunar_application_uevent), application); #endif thunar_application_dbus_init (application); G_APPLICATION_CLASS (thunar_application_parent_class)->startup (gapp); /* connect to the session manager */ application->session_client = thunar_session_client_new (opt_sm_client_id); /* will be loaded when the first window is opened */ application->accel_map = NULL; thunar_application_load_css (); } static void thunar_application_shutdown (GApplication *gapp) { ThunarApplication *application = THUNAR_APPLICATION (gapp); /* unqueue all files waiting to be processed */ thunar_g_list_free_full (application->files_to_launch); /* save the current accel map */ if (G_UNLIKELY (application->accel_map_save_id != 0)) { g_source_remove (application->accel_map_save_id); thunar_application_accel_map_save (application); } if (application->accel_map != NULL) g_object_unref (G_OBJECT (application->accel_map)); #ifdef HAVE_GUDEV /* cancel any pending volman watch source */ if (G_UNLIKELY (application->volman_watch_id != 0)) g_source_remove (application->volman_watch_id); /* cancel any pending volman idle source */ if (G_UNLIKELY (application->volman_idle_id != 0)) g_source_remove (application->volman_idle_id); /* drop all pending volume manager UDIs */ g_slist_free_full (application->volman_udis, g_free); /* disconnect from the udev client */ g_object_unref (application->udev_client); #endif /* drop any running "show dialogs" timer */ if (G_UNLIKELY (application->show_progress_dialog_timer_id != 0)) g_source_remove (application->show_progress_dialog_timer_id); /* drop ref on the thumbnailer */ if (application->thumbnailer != NULL) g_object_unref (application->thumbnailer); /* release the thumbnail cache */ if (application->thumbnail_cache != NULL) g_object_unref (G_OBJECT (application->thumbnail_cache)); /* disconnect from the preferences */ g_object_unref (G_OBJECT (application->preferences)); /* disconnect from the session manager */ g_object_unref (G_OBJECT (application->session_client)); /* remove the dbus service */ g_clear_pointer (&application->dbus_service, g_object_unref); G_APPLICATION_CLASS (thunar_application_parent_class)->shutdown (gapp); } static void thunar_application_finalize (GObject *object) { /* rule of thumb: what gets initialized in GApplication::startup is cleaned up * in GApplication::shutdown. Therefore, this method doesn't do very much */ (*G_OBJECT_CLASS (thunar_application_parent_class)->finalize) (object); } static int thunar_application_handle_local_options (GApplication *gapp, GVariantDict *options) { /* check if we should print version information */ if (G_UNLIKELY (opt_version)) { g_print ("%s %s (Xfce %s)\n\n", PACKAGE_NAME, PACKAGE_VERSION, xfce_version_string ()); g_print ("%s\n", "Copyright (c) 2004-2024"); g_print ("\t%s\n\n", _("The Thunar development team. All rights reserved.")); g_print ("%s\n\n", _("Written by Benedikt Meurer .")); g_print (_("Please report bugs to <%s>."), PACKAGE_BUGREPORT); g_print ("\n"); return EXIT_SUCCESS; } /* continue processing on the primary instance */ return -1; } static int thunar_application_command_line (GApplication *gapp, GApplicationCommandLine *command_line) { ThunarApplication *application = THUNAR_APPLICATION (gapp); gboolean bulk_rename = FALSE; gboolean daemon = FALSE; gboolean quit = FALSE; GStrv filenames = NULL; const char *cwd = g_application_command_line_get_cwd (command_line); GVariantDict *options_dict = g_application_command_line_get_options_dict (command_line); GError *error = NULL; gchar *current_directory[] = { (gchar *) ".", NULL }; gboolean restore_tabs; gboolean open_first_window = TRUE; GList *window_list; /* retrieve arguments */ g_variant_dict_lookup (options_dict, "bulk-rename", "b", &bulk_rename); g_variant_dict_lookup (options_dict, "window", "b", &application->force_new_window); g_variant_dict_lookup (options_dict, "quit", "b", &quit); g_variant_dict_lookup (options_dict, "daemon", "b", &daemon); g_variant_dict_lookup (options_dict, G_OPTION_REMAINING, "^aay", &filenames); /* FIXME: --quit should be named --suicide */ if (G_UNLIKELY (quit)) { g_debug ("quitting"); g_application_quit (gapp); goto out; } /* check whether we should daemonize */ if (G_UNLIKELY (daemon)) { thunar_application_set_daemon (application, TRUE); goto out; } window_list = thunar_application_get_windows (application); if (window_list != NULL) { open_first_window = FALSE; g_list_free (window_list); } /* check if we should open the bulk rename dialog */ if (G_UNLIKELY (bulk_rename)) { /* try to open the bulk rename dialog */ if (!thunar_application_bulk_rename (application, cwd, filenames, TRUE, NULL, NULL, &error)) { /* FIXME? */ g_application_command_line_printerr (command_line, "Thunar: Failed to open bulk rename: %s\n", error->message); } goto out; } /* if no filenames are provided, open current directory as default */ if (!thunar_application_process_filenames (application, cwd, filenames == NULL ? current_directory : filenames, NULL, NULL, &error, THUNAR_APPLICATION_SELECT_FILES)) { /* we failed to process the filenames or the bulk rename failed */ g_application_command_line_printerr (command_line, "Thunar: %s\n", error->message); } /* reopen tabs if desired and this is the first thunar window we open */ g_object_get (G_OBJECT (application->preferences), "last-restore-tabs", &restore_tabs, NULL); window_list = thunar_application_get_windows (application); if (restore_tabs && open_first_window && window_list != NULL) { ThunarWindow *window; gchar **tabs_left; gchar **tabs_right; gboolean has_left_tabs; /* used to check whether the split-view should be enabled */ gint last_focused_tab; /* this will be the topmost Window */ window = THUNAR_WINDOW (g_list_last (window_list)->data); /* restore left tabs */ has_left_tabs = FALSE; g_object_get (G_OBJECT (application->preferences), "last-tabs-left", &tabs_left, "last-focused-tab-left", &last_focused_tab, NULL); if (tabs_left != NULL && g_strv_length (tabs_left) > 0) { for (guint i = 0; i < g_strv_length (tabs_left); i++) { ThunarFile *directory = thunar_file_get_for_uri (tabs_left[i], NULL); if (G_LIKELY (directory != NULL) && thunar_file_is_directory (directory)) { /* replace the first tab if opened by default */ if (filenames == 0 && i == 0) thunar_window_set_current_directory (window, directory); else thunar_window_notebook_add_new_tab (window, directory, THUNAR_NEW_TAB_BEHAVIOR_SWITCH); } if (G_LIKELY (directory != NULL)) g_object_unref (directory); } thunar_window_notebook_set_current_tab (window, last_focused_tab); has_left_tabs = TRUE; } /* restore right tabs */ /* (will be restored on the left if no left tabs are found) */ g_object_get (G_OBJECT (application->preferences), "last-tabs-right", &tabs_right, "last-focused-tab-right", &last_focused_tab, NULL); if (tabs_right != NULL && g_strv_length (tabs_right) > 0) { if (has_left_tabs) { thunar_window_notebook_toggle_split_view (window); thunar_window_paned_notebooks_switch (window); } for (guint i = 0; i < g_strv_length (tabs_right); i++) { ThunarFile *directory = thunar_file_get_for_uri (tabs_right[i], NULL); if (G_LIKELY (directory != NULL) && thunar_file_is_directory (directory)) { /* replace the first tab (opened by default via "thunar_window_notebook_toggle_split_view") */ if (i == 0) thunar_window_set_current_directory (window, directory); else thunar_window_notebook_add_new_tab (window, directory, THUNAR_NEW_TAB_BEHAVIOR_SWITCH); } if (G_LIKELY (directory != NULL)) g_object_unref (directory); } thunar_window_notebook_set_current_tab (window, last_focused_tab); } /* free memory */ g_strfreev (tabs_left); g_strfreev (tabs_right); } g_list_free (window_list); out: /* cleanup */ g_strfreev (filenames); if (error) { g_error_free (error); return EXIT_FAILURE; } else return EXIT_SUCCESS; } static gboolean thunar_application_dbus_register (GApplication *gapp, GDBusConnection *connection, const gchar *object_path, GError **error) { ThunarApplication *application = THUNAR_APPLICATION (gapp); if (application->dbus_service) /* WTF? */ return TRUE; application->dbus_service = g_object_new (THUNAR_TYPE_DBUS_SERVICE, NULL); return thunar_dbus_service_export_on_connection (application->dbus_service, connection, error); } static void thunar_application_load_css (void) { GtkCssProvider *css_provider; GdkScreen *screen; css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (css_provider, /* for the location-buttons any margin looks ugly */ ".location-button { margin-right: 0; }" /* add missing top border to side pane */ ".sidebar { border-top-style: solid; }" /* remove extra borders */ ".preview-pane { border-left-width: 0px; border-right-width: 0px; border-bottom-width: 0px; }" ".standard-view { border-left-width: 0px; border-right-width: 0px; }" /* make border thicker during DnD */ ".standard-view:drop(active) { border-width: 2px; }" /* change background color of inactive split view pane */ ".split-view-inactive-pane .view { background-color: @theme_unfocused_bg_color; }" /* for the toorbar in headerbar looks ugly */ "headerbar toolbar { background: none; }" /* for the example box in properties dialog > highlight tab */ "#example { border-radius: 10px; }", -1, NULL); screen = gdk_screen_get_default (); gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (css_provider); } static void thunar_application_activate (GApplication *gapp) { /* TODO */ G_APPLICATION_CLASS (thunar_application_parent_class)->activate (gapp); } static void thunar_application_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ThunarApplication *application = THUNAR_APPLICATION (object); switch (prop_id) { case PROP_DAEMON: g_value_set_boolean (value, thunar_application_get_daemon (application)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void thunar_application_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ThunarApplication *application = THUNAR_APPLICATION (object); switch (prop_id) { case PROP_DAEMON: thunar_application_set_daemon (application, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean thunar_application_accel_map_save (gpointer user_data) { ThunarApplication *application = THUNAR_APPLICATION (user_data); gchar *path; _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), FALSE); application->accel_map_save_id = 0; /* save the current accel map */ path = xfce_resource_save_location (XFCE_RESOURCE_CONFIG, ACCEL_MAP_PATH, TRUE); if (G_LIKELY (path != NULL)) { /* save the accel map */ gtk_accel_map_save (path); g_free (path); } return FALSE; } gboolean thunar_application_accel_map_init (ThunarApplication *application) { gchar *path; /* no need to initialize the map twice */ if (application->accel_map != NULL) return FALSE; /* check if we have a saved accel map and load it, if found */ path = xfce_resource_lookup (XFCE_RESOURCE_CONFIG, ACCEL_MAP_PATH); if (G_LIKELY (path != NULL)) { /* load the accel map */ gtk_accel_map_load (path); g_free (path); } /* watch for thunar-internal accelerator changes, in order to write changes to file if required */ application->accel_map = gtk_accel_map_get (); g_signal_connect_swapped (G_OBJECT (application->accel_map), "changed", G_CALLBACK (thunar_application_accel_map_changed), application); return TRUE; } static void thunar_application_accel_map_changed (ThunarApplication *application) { GList *windows; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* stop pending save */ if (application->accel_map_save_id != 0) { g_source_remove (application->accel_map_save_id); application->accel_map_save_id = 0; } /* schedule new save */ application->accel_map_save_id = g_timeout_add_seconds (10, thunar_application_accel_map_save, application); /* update the accelerators for each window */ windows = thunar_application_get_windows (application); for (GList *lp = windows; lp != NULL; lp = lp->next) { ThunarWindow *window = lp->data; thunar_window_reconnect_accelerators (window); } g_list_free (windows); } static void thunar_application_collect_and_launch (ThunarApplication *application, gpointer parent, const gchar *icon_name, const gchar *title, Launcher launcher, GList *source_file_list, GFile *target_file, gboolean update_source_folders, gboolean update_target_folders, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { GFile *file; GError *err = NULL; GList *target_file_list = NULL; GList *lp; gchar *base_name; /* check if we have anything to operate on */ if (G_UNLIKELY (source_file_list == NULL)) return; /* generate the target path list */ for (lp = g_list_last (source_file_list); err == NULL && lp != NULL; lp = lp->prev) { /* verify that we're not trying to collect a root node */ if (G_UNLIKELY (thunar_g_file_is_root (lp->data))) { /* tell the user that we cannot perform the requested operation */ g_set_error (&err, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s", g_strerror (EINVAL)); } else { base_name = g_file_get_basename (lp->data); file = g_file_resolve_relative_path (target_file, base_name); g_free (base_name); /* add to the target file list */ target_file_list = thunar_g_list_prepend_deep (target_file_list, file); g_object_unref (file); } } /* check if we failed */ if (G_UNLIKELY (err != NULL)) { /* display an error message to the user */ thunar_dialogs_show_error (parent, err, _("Failed to launch operation")); /* release the error */ g_error_free (err); } else { /* launch the operation */ thunar_application_launch (application, parent, icon_name, title, launcher, source_file_list, target_file_list, update_source_folders, update_target_folders, log_mode, new_files_closure); } /* release the target path list */ thunar_g_list_free_full (target_file_list); } static void thunar_application_launch_finished (ThunarJob *job, GList *containing_folders) { GList *lp; ThunarFile *file; ThunarFolder *folder; _thunar_return_if_fail (THUNAR_IS_JOB (job)); for (lp = containing_folders; lp != NULL; lp = lp->next) { if (lp->data == NULL) continue; file = thunar_file_get (lp->data, NULL); if (file != NULL) { if (thunar_file_is_directory (file)) { folder = thunar_folder_get_for_file (file); if (folder != NULL) { /* If the folder is connected to a folder monitor, we dont need to trigger the reload manually */ if (!thunar_folder_has_folder_monitor (folder)) { thunar_folder_reload (folder, FALSE); } g_object_unref (folder); } } g_object_unref (file); } /* Unref all containing_folders (refs obtained by g_file_get_parent in thunar_g_file_list_get_parents ) */ g_object_unref (lp->data); } g_list_free (containing_folders); } static void thunar_application_launch (ThunarApplication *application, gpointer parent, const gchar *icon_name, const gchar *title, Launcher launcher, GList *source_file_list, GList *target_file_list, gboolean update_source_folders, gboolean update_target_folders, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { GtkWidget *dialog; GdkScreen *screen; ThunarJob *job; GList *parent_folder_list = NULL; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, NULL); /* try to allocate a new job for the operation */ job = (*launcher) (source_file_list, target_file_list); if (update_source_folders) parent_folder_list = g_list_concat (parent_folder_list, thunar_g_file_list_get_parents (source_file_list)); if (update_target_folders) parent_folder_list = g_list_concat (parent_folder_list, thunar_g_file_list_get_parents (target_file_list)); thunar_job_set_log_mode (job, log_mode); /* connect a callback to instantly refresh the parent folders after the operation finished */ g_signal_connect (G_OBJECT (job), "finished", G_CALLBACK (thunar_application_launch_finished), parent_folder_list); /* connect the "new-files" closure (if any) */ if (G_LIKELY (new_files_closure != NULL)) g_signal_connect_closure (job, "new-files", new_files_closure, FALSE); /* get the shared progress dialog */ dialog = thunar_application_get_progress_dialog (application); /* place the dialog on the given screen */ if (screen != NULL) gtk_window_set_screen (GTK_WINDOW (dialog), screen); application->show_progress_dialog_n_jobs_before = thunar_progress_dialog_n_jobs (THUNAR_PROGRESS_DIALOG (dialog)); /* add the job to the dialog */ thunar_progress_dialog_add_job (THUNAR_PROGRESS_DIALOG (dialog), job, icon_name, title); /* Set up a timer to show the dialog, to make sure we don't * just popup and destroy a dialog for a very short job. */ if (G_LIKELY (application->show_progress_dialog_timer_id == 0)) { application->show_progress_dialog_timer_id = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, 500, thunar_application_show_progress_dialog_timeout, application, thunar_application_show_progress_dialog_timeout_destroy); } /* drop our reference on the job */ g_object_unref (job); } #ifdef HAVE_GUDEV static void thunar_application_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device, ThunarApplication *application) { const gchar *sysfs_path; gboolean is_cdrom = FALSE; gboolean has_media = FALSE; GSList *lp; _thunar_return_if_fail (G_UDEV_IS_CLIENT (client)); _thunar_return_if_fail (action != NULL && *action != '\0'); _thunar_return_if_fail (G_UDEV_IS_DEVICE (device)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (client == application->udev_client); /* determine the sysfs path of the device */ sysfs_path = g_udev_device_get_sysfs_path (device); /* check if the device is a CD drive */ is_cdrom = g_udev_device_get_property_as_boolean (device, "ID_CDROM"); has_media = g_udev_device_get_property_as_boolean (device, "ID_CDROM_MEDIA"); /* distinguish between "add", "change" and "remove" actions, ignore "move" */ if (g_strcmp0 (action, "add") == 0 || (is_cdrom && has_media && g_strcmp0 (action, "change") == 0)) { /* only insert the path if we don't have it already */ if (g_slist_find_custom (application->volman_udis, sysfs_path, (GCompareFunc) (void (*) (void)) g_utf8_collate) == NULL) { application->volman_udis = g_slist_prepend (application->volman_udis, g_strdup (sysfs_path)); /* check if there's currently no active or scheduled handler */ if (G_LIKELY (application->volman_idle_id == 0 && application->volman_watch_id == 0)) { /* schedule a new handler using the idle source, which invokes the handler */ application->volman_idle_id = g_idle_add_full (G_PRIORITY_LOW, thunar_application_volman_idle, application, thunar_application_volman_idle_destroy); } } } else if (g_strcmp0 (action, "remove") == 0) { /* look for the sysfs path in the list of pending paths */ lp = g_slist_find_custom (application->volman_udis, sysfs_path, (GCompareFunc) (void (*) (void)) g_utf8_collate); if (G_LIKELY (lp != NULL)) { /* free the sysfs path string */ g_free (lp->data); /* drop the sysfs path from the list of pending device paths */ application->volman_udis = g_slist_delete_link (application->volman_udis, lp); } } } static gboolean thunar_application_volman_idle (gpointer user_data) { ThunarApplication *application = THUNAR_APPLICATION (user_data); GdkScreen *screen; gboolean misc_volume_management; GError *err = NULL; gchar **argv; GPid pid; char *display = NULL; /* check if volume management is enabled (otherwise, we don't spawn anything, but clear the list here) */ g_object_get (G_OBJECT (application->preferences), "misc-volume-management", &misc_volume_management, NULL); if (G_LIKELY (misc_volume_management)) { /* check if we don't already have a handler, and we have a pending UDI */ if (application->volman_watch_id == 0 && application->volman_udis != NULL) { /* generate the argument list for the volman */ argv = g_new (gchar *, 4); argv[0] = g_strdup ("thunar-volman"); argv[1] = g_strdup ("--device-added"); argv[2] = application->volman_udis->data; argv[3] = NULL; /* remove the first list item from the pending list */ application->volman_udis = g_slist_delete_link (application->volman_udis, application->volman_udis); /* locate the currently active screen (the one with the pointer) */ screen = xfce_gdk_screen_get_active (NULL); if (screen != NULL) display = g_strdup (gdk_display_get_name (gdk_screen_get_display (screen))); /* try to spawn the volman on the active screen */ if (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, thunar_setup_display_cb, display, &pid, &err)) { /* add a child watch for the volman handler */ application->volman_watch_id = g_child_watch_add_full (G_PRIORITY_LOW, pid, thunar_application_volman_watch, application, thunar_application_volman_watch_destroy); } else { /* failed to spawn, tell the user, giving a hint to install the thunar-volman package */ g_warning ("Failed to launch the volume manager (%s), make sure you have the \"thunar-volman\" package installed.", err->message); g_error_free (err); } /* cleanup */ g_free (display); g_strfreev (argv); } } else { /* drop all pending HAL device UDIs */ g_slist_free_full (application->volman_udis, g_free); application->volman_udis = NULL; } /* keep the idle source alive as long as no handler is * active and we have pending UDIs that must be handled */ return (application->volman_watch_id == 0 && application->volman_udis != NULL); } static void thunar_application_volman_idle_destroy (gpointer user_data) { THUNAR_APPLICATION (user_data)->volman_idle_id = 0; } static void thunar_application_volman_watch (GPid pid, gint status, gpointer user_data) { ThunarApplication *application = THUNAR_APPLICATION (user_data); /* check if the idle source isn't active, but we have pending UDIs */ if (application->volman_idle_id == 0 && application->volman_udis != NULL) { /* schedule a new handler using the idle source, which invokes the handler */ application->volman_idle_id = gdk_threads_add_idle_full (G_PRIORITY_LOW, thunar_application_volman_idle, application, thunar_application_volman_idle_destroy); } /* be sure to close the pid handle */ g_spawn_close_pid (pid); } static void thunar_application_volman_watch_destroy (gpointer user_data) { THUNAR_APPLICATION (user_data)->volman_watch_id = 0; } #endif /* HAVE_GUDEV */ static gboolean thunar_application_show_progress_dialog_timeout (gpointer user_data) { guint show_progress_dialog_n_jobs_now; ThunarApplication *application = THUNAR_APPLICATION (user_data); if (application->progress_dialog == NULL) return FALSE; show_progress_dialog_n_jobs_now = thunar_progress_dialog_n_jobs (THUNAR_PROGRESS_DIALOG (application->progress_dialog)); /* Only raise the dialog if the new job is still present after the timeout */ if (show_progress_dialog_n_jobs_now != application->show_progress_dialog_n_jobs_before) gtk_window_present (GTK_WINDOW (application->progress_dialog)); return FALSE; } static void thunar_application_show_progress_dialog_timeout_destroy (gpointer user_data) { THUNAR_APPLICATION (user_data)->show_progress_dialog_timer_id = 0; } /** * thunar_application_get: * * Returns the global shared #ThunarApplication instance. * This method takes a reference on the global instance * for the caller, so you must call g_object_unref() * on it when done. * * Return value: the shared #ThunarApplication instance. **/ ThunarApplication * thunar_application_get (void) { GApplication *default_app = g_application_get_default (); if (default_app) return THUNAR_APPLICATION (g_object_ref (default_app)); else return g_object_ref_sink (g_object_new (THUNAR_TYPE_APPLICATION, "application-id", "org.xfce.Thunar", NULL)); } /** * thunar_application_quit: * @application : a #ThunarApplication. * * Attempts to exit daemon mode(required if application is on hold) and leaves the gtk main loop **/ void thunar_application_quit (ThunarApplication *application) { _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); thunar_application_set_daemon (application, FALSE); /* For some unknown reason we need to close all open thunar windows before quit */ /* Otherwise thunar will hangup on quit and thunar_application_shutdown will not be called */ thunar_application_close_all_windows (application); g_application_quit (G_APPLICATION (application)); } /** * thunar_application_get_daemon: * @application : a #ThunarApplication. * * Returns %TRUE if @application is in daemon mode. * * Return value: %TRUE if @application is in daemon mode. **/ gboolean thunar_application_get_daemon (ThunarApplication *application) { _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), FALSE); return application->daemon; } /** * thunar_application_set_daemon: * @application : a #ThunarApplication. * @daemonize : %TRUE to set @application into daemon mode. * * If @daemon is %TRUE, @application will be set into daemon mode. **/ void thunar_application_set_daemon (ThunarApplication *application, gboolean daemonize) { _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); if (application->daemon != daemonize) { application->daemon = daemonize; g_object_notify (G_OBJECT (application), "daemon"); if (daemonize) g_application_hold (G_APPLICATION (application)); else g_application_release (G_APPLICATION (application)); } } /** * thunar_application_close_all_windows: * @application : a #ThunarApplication. * * Closes all #ThunarWindows currently handled by @application. **/ void thunar_application_close_all_windows (ThunarApplication *application) { GList *gtk_windows, *lp; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); gtk_windows = gtk_application_get_windows (GTK_APPLICATION (application)); for (lp = gtk_windows; lp != NULL; lp = lp->next) if (G_LIKELY (THUNAR_IS_WINDOW (lp->data))) gtk_window_close (lp->data); } /** * thunar_application_get_windows: * @application : a #ThunarApplication. * * Returns the list of regular #ThunarWindows currently handled by * @application. The returned list is owned by the caller and * must be freed using g_list_free(). * * Return value: the list of regular #ThunarWindows in @application. **/ GList * thunar_application_get_windows (ThunarApplication *application) { GList *gtk_windows, *thunar_windows = NULL, *lp; _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), NULL); gtk_windows = gtk_application_get_windows (GTK_APPLICATION (application)); for (lp = gtk_windows; lp != NULL; lp = lp->next) if (G_LIKELY (THUNAR_IS_WINDOW (lp->data))) thunar_windows = g_list_prepend (thunar_windows, lp->data); return thunar_windows; } /** * thunar_application_has_windows: * @application : a #ThunarApplication. * * Returns %TRUE if @application controls atleast one window. * * Return value: %TRUE if @application controls atleast one window. **/ gboolean thunar_application_has_windows (ThunarApplication *application) { _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), FALSE); /* FIXME: this is possible inconsisitent with thunar_application_get_windows() */ return gtk_application_get_windows (GTK_APPLICATION (application)) != NULL; } /** * thunar_application_take_window: * @application : a #ThunarApplication. * @window : a #GtkWindow. * * Lets @application take over control of the specified @window. * @application will not exit until the last controlled #GtkWindow * is closed by the user. * * If the @window has no transient window, it will also create a * new #GtkWindowGroup for this window. This will make different * windows work independant (think gtk_dialog_run). **/ void thunar_application_take_window (ThunarApplication *application, GtkWindow *window) { GtkWindowGroup *group; _thunar_return_if_fail (GTK_IS_WINDOW (window)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* only windows without a parent get a new window group */ if (gtk_window_get_transient_for (window) == NULL && !gtk_window_has_group (window)) { group = gtk_window_group_new (); gtk_window_group_add_window (group, window); g_object_weak_ref (G_OBJECT (window), (GWeakNotify) (void (*) (void)) g_object_unref, group); } /* add the ourselves to the window */ gtk_window_set_application (window, GTK_APPLICATION (application)); } /** * thunar_application_open_window: * @application : a #ThunarApplication. * @directory : the directory to open. * @screen : the #GdkScreen on which to open the window or %NULL * to open on the default screen. * @startup_id : startup id from startup notification passed along * with dbus to make focus stealing work properly. * @force_new_window : set this flag to force a new window, even if misc-open-new-window-as-tab is set * * Opens a new #ThunarWindow (or tab if preferred) for @application, displaying the * given @directory. * * Return value: the newly allocated #ThunarWindow. **/ GtkWidget * thunar_application_open_window (ThunarApplication *application, ThunarFile *directory, GdkScreen *screen, const gchar *startup_id, gboolean force_new_window) { GList *window_list; GtkWidget *window; gchar *role; gboolean open_new_window_as_tab; gboolean misc_always_enable_split_view; gboolean misc_open_new_windows_in_split_view; gboolean restore_tabs; _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), NULL); _thunar_return_val_if_fail (directory == NULL || THUNAR_IS_FILE (directory), NULL); _thunar_return_val_if_fail (screen == NULL || GDK_IS_SCREEN (screen), NULL); window_list = thunar_application_get_windows (application); if (G_UNLIKELY (screen == NULL)) screen = gdk_screen_get_default (); /* open as tab instead, if preferred */ g_object_get (G_OBJECT (application->preferences), "misc-open-new-window-as-tab", &open_new_window_as_tab, NULL); if (G_UNLIKELY (!force_new_window && open_new_window_as_tab)) { if (window_list != NULL) { GtkWidget *data; /* this will be the topmost Window */ data = g_list_last (window_list)->data; if (directory != NULL) thunar_window_notebook_add_new_tab (THUNAR_WINDOW (data), directory, THUNAR_NEW_TAB_BEHAVIOR_SWITCH); /* bring the window to front */ gtk_window_present (GTK_WINDOW (data)); g_list_free (window_list); return data; } } /* generate a unique role for the new window (for session management) */ role = g_strdup_printf ("Thunar-%u-%u", (guint) time (NULL), (guint) g_random_int ()); /* allocate the window */ window = g_object_new (THUNAR_TYPE_WINDOW, "role", role, "screen", screen, NULL); /* The accel map will be loaded after the first window is created */ /* For some reason it is important to do so only AFTER creation of the window! */ /* When loaded, the accelerators of the first window need to be reconnected */ if (thunar_application_accel_map_init (application)) thunar_window_reconnect_accelerators (THUNAR_WINDOW (window)); /* cleanup */ g_free (role); /* set the startup id */ if (startup_id != NULL) gtk_window_set_startup_id (GTK_WINDOW (window), startup_id); /* hook up the window */ thunar_application_take_window (application, GTK_WINDOW (window)); /* show the new window */ gtk_widget_show (window); /* change the directory */ if (directory != NULL) thunar_window_set_current_directory (THUNAR_WINDOW (window), directory); /* Migrate old "misc-open-new-windows-in-split-view" preference. Drop for or after 4.22 */ if (thunar_preferences_has_property (application->preferences, "/misc-open-new-windows-in-split-view") && !thunar_preferences_has_property (application->preferences, "/misc-always-enable-split-view")) { g_object_get (G_OBJECT (application->preferences), "misc-open-new-windows-in-split-view", &misc_open_new_windows_in_split_view, NULL); g_object_set (G_OBJECT (application->preferences), "misc-always-enable-split-view", misc_open_new_windows_in_split_view, NULL); } /* enable split view, if preferred */ g_object_get (G_OBJECT (application->preferences), "misc-always-enable-split-view", &misc_always_enable_split_view, "last-restore-tabs", &restore_tabs, NULL); /* Dont force split view for the first window if tabs restore is active */ if (misc_always_enable_split_view && (window_list != NULL || !restore_tabs)) thunar_window_notebook_toggle_split_view (THUNAR_WINDOW (window)); g_list_free (window_list); return window; } /** * thunar_application_bulk_rename: * @application : a #ThunarApplication. * @working_directory : the default working directory for the bulk rename dialog. * @filenames : the list of file names that should be renamed or the empty * list to start with an empty rename dialog. The file names * can either be absolute paths, supported URIs or relative file * names to @working_directory. * @standalone : %TRUE to display the bulk rename dialog like a standalone * application. * @screen : the #GdkScreen on which to rename the @filenames or %NULL * to use the default #GdkScreen. * @startup_id : startup notification id to properly finish startup notification * and focus the window when focus stealing is enabled or %NULL. * @error : return location for errors or %NULL. * * Tries to popup the bulk rename dialog. * * Return value: %TRUE if the dialog was opened successfully, otherwise %FALSE. **/ gboolean thunar_application_bulk_rename (ThunarApplication *application, const gchar *working_directory, gchar **filenames, gboolean standalone, GdkScreen *screen, const gchar *startup_id, GError **error) { ThunarFile *current_directory = NULL; ThunarFile *file; gboolean result = FALSE; GList *file_list = NULL; gchar *filename; gchar *empty_strv[] = { NULL }; gint n; _thunar_return_val_if_fail (screen == NULL || GDK_IS_SCREEN (screen), FALSE); _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), FALSE); _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); _thunar_return_val_if_fail (working_directory != NULL, FALSE); if (!filenames) filenames = empty_strv; /* determine the file for the working directory */ current_directory = thunar_file_get_for_uri (working_directory, error); if (G_UNLIKELY (current_directory == NULL)) return FALSE; /* check if we should use the default screen */ if (G_LIKELY (screen == NULL)) screen = gdk_screen_get_default (); /* try to process all filenames and convert them to the appropriate file objects */ for (n = 0; filenames[n] != NULL; ++n) { /* check if the filename is an absolute path or looks like an URI */ if (g_path_is_absolute (filenames[n]) || g_uri_is_valid (filenames[n], G_URI_FLAGS_NONE, NULL)) { /* determine the file for the filename directly */ file = thunar_file_get_for_uri (filenames[n], error); } else { /* translate the filename into an absolute path first */ filename = g_build_filename (working_directory, filenames[n], NULL); file = thunar_file_get_for_uri (filename, error); g_free (filename); } /* verify that we have a valid file */ if (G_LIKELY (file != NULL)) file_list = g_list_append (file_list, file); else break; } /* check if the filenames where resolved successfully */ if (G_LIKELY (filenames[n] == NULL)) { /* popup the bulk rename dialog */ thunar_show_renamer_dialog (screen, current_directory, file_list, standalone, startup_id); /* we succeed */ result = TRUE; } /* cleanup */ g_object_unref (G_OBJECT (current_directory)); thunar_g_list_free_full (file_list); return result; } static GtkWidget * thunar_application_get_progress_dialog (ThunarApplication *application) { _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), NULL); if (application->progress_dialog == NULL) { application->progress_dialog = thunar_progress_dialog_new (); g_object_add_weak_pointer (G_OBJECT (application->progress_dialog), (gpointer) &application->progress_dialog); thunar_application_take_window (application, GTK_WINDOW (application->progress_dialog)); } return application->progress_dialog; } static void thunar_application_process_files_finish (ThunarBrowser *browser, ThunarFile *file, ThunarFile *target_file, GError *error, gpointer unused) { ThunarApplication *application = THUNAR_APPLICATION (browser); GdkScreen *screen; const gchar *startup_id; _thunar_return_if_fail (THUNAR_IS_BROWSER (browser)); _thunar_return_if_fail (THUNAR_IS_FILE (file)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* determine and reset the screen of the file */ screen = g_object_get_qdata (G_OBJECT (file), thunar_application_screen_quark); g_object_set_qdata (G_OBJECT (file), thunar_application_screen_quark, NULL); /* determine and the startup id of the file */ startup_id = g_object_get_qdata (G_OBJECT (file), thunar_application_startup_id_quark); /* check if resolving/mounting failed */ if (error != NULL) { /* don't display cancel errors */ if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED) { /* tell the user that we were unable to launch the file specified */ if (g_strcmp0 (thunar_file_get_display_name (file), thunar_file_get_basename (file)) != 0) thunar_dialogs_show_error (screen, error, _("Failed to open \"%s\" (%s)"), thunar_file_get_display_name (file), thunar_file_get_basename (file)); else thunar_dialogs_show_error (screen, error, _("Failed to open \"%s\""), thunar_file_get_display_name (file)); } /* stop processing files */ thunar_g_list_free_full (application->files_to_launch); application->files_to_launch = NULL; } else { if (application->process_file_action == THUNAR_APPLICATION_LAUNCH_FILES) { /* try to launch the file / open the directory */ thunar_file_launch (target_file, screen, startup_id, &error); } else if (thunar_file_is_directory (file)) { thunar_application_open_window (application, file, screen, startup_id, application->force_new_window); } else { /* Note that for security reasons we do not execute files passed via command line */ /* Lets rather open the containing directory and select the file */ ThunarFile *parent = thunar_file_get_parent (file, NULL); if (G_LIKELY (parent != NULL)) { GList *files = NULL; GtkWidget *window; window = thunar_application_open_window (application, parent, screen, startup_id, application->force_new_window); g_object_unref (parent); files = g_list_append (files, thunar_file_get_file (file)); thunar_window_show_and_select_files (THUNAR_WINDOW (window), files); g_list_free (files); } } /* remove the file from the list */ application->files_to_launch = g_list_delete_link (application->files_to_launch, application->files_to_launch); /* check if we have more files to process */ if (application->files_to_launch != NULL) { /* continue processing the next file */ thunar_application_process_files (application); } application->force_new_window = FALSE; } /* unset the startup id */ if (startup_id != NULL) g_object_set_qdata (G_OBJECT (file), thunar_application_startup_id_quark, NULL); /* release the file */ g_object_unref (file); /* release the application */ g_application_release (G_APPLICATION (application)); } static void thunar_application_process_files (ThunarApplication *application) { ThunarFile *file; GdkScreen *screen; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* don't do anything if no files are to be processed */ if (application->files_to_launch == NULL) return; /* take the next file from the queue */ file = THUNAR_FILE (application->files_to_launch->data); /* retrieve the screen we need to launch the file on */ screen = g_object_get_qdata (G_OBJECT (file), thunar_application_screen_quark); /* make sure to hold a reference to the application while file processing is going on */ g_application_hold (G_APPLICATION (application)); /* resolve the file and/or mount its enclosing volume * before handling it in the callback */ thunar_browser_poke_file (THUNAR_BROWSER (application), file, screen, thunar_application_process_files_finish, NULL); } /** * thunar_application_process_filenames: * @application : a #ThunarApplication. * @working_directory : the working directory relative to which the @filenames should * be interpreted. * @filenames : a list of supported URIs or filenames. If a filename is specified * it can be either an absolute path or a path relative to the * @working_directory. * @screen : the #GdkScreen on which to process the @filenames, or %NULL to * use the default screen. * @startup_id : startup id to finish startup notification and properly focus the * window when focus stealing is enabled or %NULL. * @error : return location for errors or %NULL. * @action : action to invoke on the files * * Tells @application to process the given @filenames and launch them appropriately. * * Return value: %TRUE on success, %FALSE if @error is set. **/ gboolean thunar_application_process_filenames (ThunarApplication *application, const gchar *working_directory, gchar **filenames, GdkScreen *screen, const gchar *startup_id, GError **error, ThunarApplicationProcessAction action) { ThunarFile *file; GError *derror = NULL; gchar *filename; GList *file_list = NULL; GList *lp; gint n; _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), FALSE); _thunar_return_val_if_fail (working_directory != NULL, FALSE); _thunar_return_val_if_fail (filenames != NULL, FALSE); _thunar_return_val_if_fail (*filenames != NULL, FALSE); _thunar_return_val_if_fail (screen == NULL || GDK_IS_SCREEN (screen), FALSE); _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* try to process all filenames and convert them to the appropriate file objects */ for (n = 0; filenames[n] != NULL; ++n) { /* check if the filename is an absolute path or looks like an URI */ if (g_path_is_absolute (filenames[n]) || g_uri_is_valid (filenames[n], G_URI_FLAGS_NONE, NULL)) { /* determine the file for the filename directly */ file = thunar_file_get_for_uri (filenames[n], &derror); } else { /* translate the filename into an absolute path first */ filename = g_build_filename (working_directory, filenames[n], NULL); file = thunar_file_get_for_uri (filename, &derror); g_free (filename); } /* verify that we have a valid file */ if (G_LIKELY (file != NULL)) { file_list = g_list_append (file_list, file); } else { /* tell the user that we were unable to launch the file specified */ thunar_dialogs_show_error (screen, derror, _("Failed to open \"%s\""), filenames[n]); g_set_error (error, derror->domain, derror->code, _("Failed to open \"%s\": %s"), filenames[n], derror->message); g_error_free (derror); thunar_g_list_free_full (file_list); return FALSE; } } /* loop over all files */ for (lp = file_list; lp != NULL; lp = lp->next) { /* remember the screen to launch the file on */ g_object_set_qdata (G_OBJECT (lp->data), thunar_application_screen_quark, screen); /* remember the startup id to set on the window */ if (G_LIKELY (startup_id != NULL && *startup_id != '\0')) g_object_set_qdata_full (G_OBJECT (lp->data), thunar_application_startup_id_quark, g_strdup (startup_id), (GDestroyNotify) g_free); /* append the file to the list of files we need to launch */ application->files_to_launch = g_list_append (application->files_to_launch, lp->data); } /* start processing files if we have any to launch */ if (application->files_to_launch != NULL) { application->process_file_action = action; thunar_application_process_files (application); } /* free the file list */ g_list_free (file_list); return TRUE; } static void thunar_application_rename_file_error (ExoJob *job, GError *error, ThunarApplication *application) { ThunarFile *file; GdkScreen *screen; _thunar_return_if_fail (EXO_IS_JOB (job)); _thunar_return_if_fail (error != NULL); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); screen = g_object_get_qdata (G_OBJECT (job), thunar_application_screen_quark); file = g_object_get_qdata (G_OBJECT (job), thunar_application_file_quark); g_assert (screen != NULL); g_assert (file != NULL); if (g_strcmp0 (thunar_file_get_display_name (file), thunar_file_get_basename (file)) != 0) thunar_dialogs_show_error (screen, error, _("Failed to rename \"%s\" (%s)"), thunar_file_get_display_name (file), thunar_file_get_basename (file)); else thunar_dialogs_show_error (screen, error, _("Failed to rename \"%s\""), thunar_file_get_display_name (file)); } static void thunar_application_rename_file_finished (ExoJob *job, gpointer user_data) { _thunar_return_if_fail (EXO_IS_JOB (job)); /* destroy the job object */ g_object_unref (job); } /** * thunar_application_rename_file: * @application : a #ThunarApplication. * @file : a #ThunarFile to be renamed. * @screen : the #GdkScreen on which to open the window * to open on the default screen. * @startup_id : startup id from startup notification passed along * with dbus to make focus stealing work properly. Ignored if NULL. * @log_mode : a #ThunarOperationLogMode to control logging of the operation * * Prompts the user to rename the @file. **/ void thunar_application_rename_file (ThunarApplication *application, ThunarFile *file, GdkScreen *screen, const gchar *startup_id, ThunarOperationLogMode log_mode) { ThunarJob *job; gint response; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (THUNAR_IS_FILE (file)); _thunar_return_if_fail (GDK_IS_SCREEN (screen)); /* TODO pass the startup ID to the rename dialog */ if (thunar_file_is_desktop_file (file)) { response = xfce_message_dialog (NULL, _("Rename Launcher Options"), "dialog-question", NULL, _("Do you want to rename the launcher, or the file itself?"), XFCE_BUTTON_TYPE_MIXED, NULL, _("Rename _Launcher"), THUNAR_RESPONSE_LAUNCHERNAME, XFCE_BUTTON_TYPE_MIXED, NULL, _("Rename _File"), THUNAR_RESPONSE_FILENAME, NULL); if (response == THUNAR_RESPONSE_LAUNCHERNAME) { thunar_dialog_show_launcher_props (file, screen); return; } else if (response != THUNAR_RESPONSE_FILENAME) return; } /* run the rename dialog */ job = thunar_dialogs_show_rename_file (screen, file, log_mode); if (G_LIKELY (job != NULL)) { /* remember the screen and file */ g_object_set_qdata (G_OBJECT (job), thunar_application_screen_quark, screen); g_object_set_qdata_full (G_OBJECT (job), thunar_application_file_quark, g_object_ref (file), g_object_unref); /* handle rename errors */ g_signal_connect (job, "error", G_CALLBACK (thunar_application_rename_file_error), application); /* destroy the job when it has finished */ g_signal_connect (job, "finished", G_CALLBACK (thunar_application_rename_file_finished), NULL); } } /** * thunar_application_create_file: * @application : a #ThunarApplication. * @parent_directory : the #ThunarFile of the parent directory. * @content_type : the content type of the new file. * @screen : the #GdkScreen on which to open the window or %NULL * to open on the default screen. * @startup_id : startup id from startup notification passed along * with dbus to make focus stealing work properly. * @log_mode : a #ThunarOperationLogMode to control logging * * Prompts the user to create a new file or directory in @parent_directory. * The @content_type defines the icon and other elements in the filename * prompt dialog. **/ void thunar_application_create_file (ThunarApplication *application, ThunarFile *parent_directory, const gchar *content_type, GdkScreen *screen, const gchar *startup_id, ThunarOperationLogMode log_mode) { const gchar *dialog_title; const gchar *title; gboolean is_directory; GList path_list; gchar *name; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (THUNAR_IS_FILE (parent_directory)); _thunar_return_if_fail (content_type != NULL && *content_type != '\0'); _thunar_return_if_fail (GDK_IS_SCREEN (screen)); _thunar_return_if_fail (startup_id != NULL); is_directory = (g_strcmp0 (content_type, "inode/directory") == 0); if (is_directory) { dialog_title = _("New Folder"); title = _("Create New Folder"); } else { dialog_title = _("New File"); title = _("Create New File"); } /* TODO pass the startup ID to the rename dialog */ /* ask the user to enter a name for the new folder */ name = thunar_dialogs_show_create (screen, content_type, dialog_title, title); if (G_LIKELY (name != NULL)) { path_list.data = g_file_get_child (thunar_file_get_file (parent_directory), name); path_list.next = path_list.prev = NULL; /* launch the operation */ if (is_directory) thunar_application_mkdir (application, screen, &path_list, NULL, log_mode); else thunar_application_creat (application, screen, &path_list, NULL, NULL, log_mode); g_object_unref (path_list.data); g_free (name); } } /** * thunar_application_create_file_from_template: * @application : a #ThunarApplication. * @parent_directory : the #ThunarFile of the parent directory. * @template_file : the #ThunarFile of the template. * @screen : the #GdkScreen on which to open the window or %NULL * to open on the default screen. * @startup_id : startup id from startup notification passed along * with dbus to make focus stealing work properly. * @log_mode : a #ThunarOperationLogMode to control logging * * Prompts the user to create a new file or directory in @parent_directory * from an existing @template_file which predefines the name and extension * in the create dialog. **/ void thunar_application_create_file_from_template (ThunarApplication *application, ThunarFile *parent_directory, ThunarFile *template_file, GdkScreen *screen, const gchar *startup_id, ThunarOperationLogMode log_mode) { GList target_path_list; gchar *name; gchar *title; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (THUNAR_IS_FILE (parent_directory)); _thunar_return_if_fail (THUNAR_IS_FILE (template_file)); _thunar_return_if_fail (GDK_IS_SCREEN (screen)); _thunar_return_if_fail (startup_id != NULL); /* generate a title for the create dialog */ title = g_strdup_printf (_("Create Document from template \"%s\""), thunar_file_get_display_name (template_file)); /* TODO pass the startup ID to the rename dialog */ /* ask the user to enter a name for the new document */ name = thunar_dialogs_show_create (screen, thunar_file_get_content_type (template_file), thunar_file_get_display_name (template_file), title); if (G_LIKELY (name != NULL)) { /* fake the target path list */ target_path_list.data = g_file_get_child (thunar_file_get_file (parent_directory), name); target_path_list.next = target_path_list.prev = NULL; /* launch the operation */ thunar_application_creat (application, screen, &target_path_list, thunar_file_get_file (template_file), NULL, log_mode); /* release the target path */ g_object_unref (target_path_list.data); /* release the file name */ g_free (name); } /* clean up */ g_free (title); } /** * thunar_application_copy_to: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @source_file_list : the lst of #GFiles that should be copied. * @target_file_list : the list of #GFiles where files should be copied to. * @log_mode : a #ThunarOperationLogMode controlling the logging of the operation. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Copies all files from @source_file_list to their locations specified in * @target_file_list. * * @source_file_list and @target_file_list must be of the same length. **/ void thunar_application_copy_to (ThunarApplication *application, gpointer parent, GList *source_file_list, GList *target_file_list, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { _thunar_return_if_fail (g_list_length (source_file_list) == g_list_length (target_file_list)); _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* launch the operation */ thunar_application_launch (application, parent, "edit-copy", _("Copying files..."), thunar_io_jobs_copy_files, source_file_list, target_file_list, FALSE, TRUE, log_mode, new_files_closure); } /** * thunar_application_copy_into: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @source_file_list : the list of #GFiles that should be copied. * @target_file : the #GFile to the target directory. * @log_mode : a #ThunarOperationLogMode controlling the logging of the operation. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Copies all files referenced by the @source_file_list to the directory * referenced by @target_file. This method takes care of all user interaction. **/ void thunar_application_copy_into (ThunarApplication *application, gpointer parent, GList *source_file_list, GFile *target_file, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { ThunarFile *target_folder; GVolume *volume = NULL; gchar *display_name = NULL; gchar *title = NULL; gchar *volume_name = NULL; gchar *volume_uuid = NULL; gboolean use_display_name; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (G_IS_FILE (target_file)); target_folder = thunar_file_get (target_file, NULL); if (target_folder != NULL) { volume = thunar_file_get_volume (target_folder); g_object_unref (target_folder); } if (volume != NULL) { volume_name = g_volume_get_name (volume); volume_uuid = g_volume_get_uuid (volume); } /* generate a title for the progress dialog */ display_name = thunar_file_cached_display_name (target_file); /* if the display_name is equal to the volume_uuid display the volume_name */ use_display_name = g_strcmp0 (display_name, volume_uuid) != 0; title = g_strdup_printf (_("Copying files to \"%s\"..."), use_display_name ? display_name : volume_name); g_free (display_name); /* collect the target files and launch the job */ thunar_application_collect_and_launch (application, parent, "edit-copy", title, thunar_io_jobs_copy_files, source_file_list, target_file, FALSE, TRUE, log_mode, new_files_closure); /* free */ g_free (title); if (volume != NULL) { g_free (volume_name); g_free (volume_uuid); g_object_unref (G_OBJECT (volume)); } } /** * thunar_application_link_into: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @source_file_list : the list of #GFiles that should be symlinked. * @target_file : the target directory. * @log_mode : A #ThunarOperationLogMode enum to control logging. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Symlinks all files referenced by the @source_file_list to the directory * referenced by @target_file. This method takes care of all user * interaction. **/ void thunar_application_link_into (ThunarApplication *application, gpointer parent, GList *source_file_list, GFile *target_file, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { gchar *display_name; gchar *title; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (G_IS_FILE (target_file)); /* generate a title for the progress dialog */ display_name = thunar_file_cached_display_name (target_file); title = g_strdup_printf (_("Creating symbolic links in \"%s\"..."), display_name); g_free (display_name); /* collect the target files and launch the job */ thunar_application_collect_and_launch (application, parent, "insert-link", title, thunar_io_jobs_link_files, source_file_list, target_file, FALSE, TRUE, log_mode, new_files_closure); /* free the title */ g_free (title); } /** * thunar_application_move_into: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @source_file_list : the list of #GFiles that should be moved. * @target_file : the target directory. * @log_mode : A #ThunarOperationLogMode enum to control logging. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Moves all files referenced by the @source_file_list to the directory * referenced by @target_file. This method takes care of all user * interaction. **/ void thunar_application_move_into (ThunarApplication *application, gpointer parent, GList *source_file_list, GFile *target_file, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { gchar *display_name; gchar *title; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (target_file != NULL); /* Source folder cannot be moved into its subdirectory */ for (GList *lp = source_file_list; lp != NULL; lp = lp->next) { if (thunar_g_file_is_descendant (target_file, G_FILE (lp->data))) { gchar *file_name = g_file_get_basename (G_FILE (lp->data)); thunar_dialogs_show_error (NULL, NULL, "The folder (%s) cannot be moved into its own subdirectory", file_name); g_free (file_name); return; } } /* launch the appropriate operation depending on the target file */ if (thunar_g_file_is_trash (target_file)) { thunar_application_trash (application, parent, source_file_list, THUNAR_OPERATION_LOG_OPERATIONS); } else { /* generate a title for the progress dialog */ display_name = thunar_file_cached_display_name (target_file); title = g_strdup_printf (_("Moving files into \"%s\"..."), display_name); g_free (display_name); /* collect the target files and launch the job */ thunar_application_collect_and_launch (application, parent, "stock_folder-move", title, thunar_io_jobs_move_files, source_file_list, target_file, TRUE, TRUE, log_mode, new_files_closure); /* free the title */ g_free (title); } } /** * thunar_application_move_files: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @source_file_list : the list of #GFiles that should be moved. * @target_file_list : the list of #GFiles that the files should be moved to * @log_mode : a #ThunarOperationLogMode controlling the logging of the operation. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Moves all the files in the source file list to the files given in the target file list. **/ void thunar_application_move_files (ThunarApplication *application, gpointer parent, GList *source_file_list, GList *target_file_list, ThunarOperationLogMode log_mode, GClosure *new_files_closure) { _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); thunar_application_launch (application, parent, "stock_folder-move", _("Moving files ..."), thunar_io_jobs_move_files, source_file_list, target_file_list, TRUE, TRUE, log_mode, new_files_closure); } static ThunarJob * unlink_stub (GList *source_path_list, GList *target_path_list) { return thunar_io_jobs_unlink_files (source_path_list); } /** * thunar_application_unlink_files: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @file_list : the list of #ThunarFiles that should be deleted. * @permanently : whether to unlink the files permanently. * @warn : whether to warn the user if moving files to trash. * @log_mode : log mode * * Deletes all files in the @file_list and takes care of all user interaction. * * If the user pressed the shift key while triggering the delete action, * the files will be deleted permanently (after confirming the action), * otherwise the files will be moved to the trash. * * Return value: TRUE if the trash/delete operation was canceled, FALSE otehrwise **/ gboolean thunar_application_unlink_files (ThunarApplication *application, gpointer parent, GList *file_list, gboolean permanently, gboolean warn, const ThunarOperationLogMode log_mode) { GtkWidget *dialog; GtkWidget *message_area; GtkWindow *window = NULL; GdkScreen *screen; GList *path_list = NULL; GList *lp; GList *children; gchar *message; guint n_path_list = 0; gint response; gboolean operation_canceled = FALSE; _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), TRUE); _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), TRUE); /* determine the paths for the files */ for (lp = g_list_last (file_list); lp != NULL; lp = lp->prev, ++n_path_list) { /* prepend the path to the path list */ path_list = thunar_g_list_prepend_deep (path_list, thunar_file_get_file (lp->data)); /* permanently delete if at least one of the file is not a local * file (e.g. resides in the trash) or cannot be trashed */ if (!thunar_file_is_local (lp->data) || !thunar_file_can_be_trashed (lp->data)) permanently = TRUE; } /* nothing to do if we don't have any paths */ if (G_UNLIKELY (n_path_list == 0)) return FALSE; /* ask the user to confirm if deleting permanently */ if (G_UNLIKELY (permanently)) { /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, &window); /* generate the question to confirm the delete operation */ if (G_LIKELY (n_path_list == 1)) { if (g_strcmp0 (thunar_file_get_display_name (file_list->data), thunar_file_get_basename (file_list->data)) != 0) message = g_strdup_printf (_("Are you sure that you want to\npermanently delete \"%s\" (%s)?"), thunar_file_get_display_name (THUNAR_FILE (file_list->data)), thunar_file_get_basename (THUNAR_FILE (file_list->data))); else message = g_strdup_printf (_("Are you sure that you want to\npermanently delete \"%s\"?"), thunar_file_get_display_name (THUNAR_FILE (file_list->data))); } else { message = g_strdup_printf (ngettext ("Are you sure that you want to permanently\ndelete the selected file?", "Are you sure that you want to permanently\ndelete the %u selected files?", n_path_list), n_path_list); } /* ask the user to confirm the delete operation */ dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); gtk_window_set_title (GTK_WINDOW (dialog), _("Attention")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Delete"), GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("If you delete a file, it is permanently lost.")); /* additionally wrap long single-word filenames at character boundaries */ message_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog)); children = gtk_container_get_children (GTK_CONTAINER (message_area)); if (children != NULL && GTK_IS_LABEL (children->data)) { gtk_label_set_line_wrap_mode (GTK_LABEL (children->data), PANGO_WRAP_WORD_CHAR); thunar_gtk_label_disable_hyphens (GTK_LABEL (children->data)); } g_list_free (children); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_free (message); /* perform the delete operation */ if (G_LIKELY (response == GTK_RESPONSE_YES)) { /* launch the "Delete" operation */ thunar_application_launch (application, parent, "edit-delete", _("Deleting files..."), unlink_stub, path_list, path_list, TRUE, FALSE, log_mode, NULL); } else operation_canceled = TRUE; } else if (G_LIKELY (!permanently) && warn) { /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, &window); /* generate the question to confirm the move to trash operation */ if (G_LIKELY (n_path_list == 1)) { if (g_strcmp0 (thunar_file_get_display_name (file_list->data), thunar_file_get_basename (file_list->data)) != 0) message = g_strdup_printf (_("Are you sure that you want to\nmove \"%s\" (%s) to trash ?"), thunar_file_get_display_name (THUNAR_FILE (file_list->data)), thunar_file_get_basename (THUNAR_FILE (file_list->data))); else message = g_strdup_printf (_("Are you sure that you want to\nmove \"%s\" to trash ?"), thunar_file_get_display_name (THUNAR_FILE (file_list->data))); } else { message = g_strdup_printf (ngettext ("Are you sure that you want to\nmove the selected file to trash?", "Are you sure that you want to\nmove the %u selected files to trash?", n_path_list), n_path_list); } /* ask the user to confirm the move to trash operation */ dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); gtk_window_set_title (GTK_WINDOW (dialog), _("Attention")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("Move to Trash"), GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_free (message); if (G_LIKELY (response == GTK_RESPONSE_YES)) thunar_application_trash (application, parent, path_list, log_mode); else operation_canceled = TRUE; } else { /* launch the "Move to Trash" operation */ thunar_application_trash (application, parent, path_list, log_mode); } /* release the path list */ thunar_g_list_free_full (path_list); return operation_canceled; } static ThunarJob * trash_stub (GList *source_file_list, GList *target_file_list) { return thunar_io_jobs_trash_files (source_file_list); } /* thunar_application_trash: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @file_list : the list of #GFiles to trash. * @log_mode : a #ThunarOperationLogMode to control logging * * Trashes the files specified by @file_list **/ void thunar_application_trash (ThunarApplication *application, gpointer parent, GList *file_list, ThunarOperationLogMode log_mode) { _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (file_list != NULL); thunar_application_launch (application, parent, "user-trash-full", _("Moving files into the trash..."), trash_stub, file_list, NULL, TRUE, FALSE, log_mode, NULL); } static ThunarJob * creat_stub (GList *template_file, GList *target_path_list) { _thunar_return_val_if_fail (template_file->data == NULL || G_IS_FILE (template_file->data), NULL); return thunar_io_jobs_create_files (target_path_list, template_file->data); } /** * thunar_application_creat: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @file_list : the list of files to create. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * @log_mode : a #ThunarOperationLogMode to control logging * * Creates empty files for all #GFiles listed in @file_list. This * method takes care of all user interaction. **/ void thunar_application_creat (ThunarApplication *application, gpointer parent, GList *file_list, GFile *template_file, GClosure *new_files_closure, ThunarOperationLogMode log_mode) { GList template_list; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); template_list.next = template_list.prev = NULL; template_list.data = template_file; /* launch the operation */ thunar_application_launch (application, parent, "document-new", _("Creating files..."), creat_stub, &template_list, file_list, FALSE, TRUE, log_mode, new_files_closure); } static ThunarJob * mkdir_stub (GList *source_path_list, GList *target_path_list) { return thunar_io_jobs_make_directories (source_path_list); } /** * thunar_application_mkdir: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @file_list : the list of directories to create. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * @log_mode : a #ThunarOperationLogMode to control logging * * Creates all directories referenced by the @file_list. This method takes care of all user * interaction. **/ void thunar_application_mkdir (ThunarApplication *application, gpointer parent, GList *file_list, GClosure *new_files_closure, ThunarOperationLogMode log_mode) { _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); /* launch the operation */ thunar_application_launch (application, parent, "folder-new", _("Creating directories..."), mkdir_stub, file_list, file_list, TRUE, FALSE, log_mode, new_files_closure); } /** * thunar_application_empty_trash: * @application : a #ThunarApplication. * @parent : the parent, which can either be the associated * #GtkWidget, the screen on which display the * progress and the confirmation, or %NULL. * * Deletes all files and folders in the Trash after asking * the user to confirm the operation. **/ void thunar_application_empty_trash (ThunarApplication *application, gpointer parent, const gchar *startup_id) { GtkWidget *dialog; GtkWindow *window = NULL; GdkScreen *screen; GList file_list; gint response; _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, &window); /* ask the user to confirm the operation */ dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Remove all files and folders from the Trash?")); if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); gtk_window_set_startup_id (GTK_WINDOW (dialog), startup_id); gtk_window_set_title (GTK_WINDOW (dialog), _("Empty Trash")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Empty Trash"), GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("If you choose to empty the Trash, all items in it will be permanently lost. " "Please note that you can also delete them separately.")); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); /* check if the user confirmed */ if (G_LIKELY (response == GTK_RESPONSE_YES)) { /* fake a path list with only the trash root (the root * folder itself will never be unlinked, so this is safe) */ file_list.data = thunar_g_file_new_for_trash (); file_list.next = NULL; file_list.prev = NULL; /* launch the operation */ thunar_application_launch (application, parent, "user-trash", _("Emptying the Trash..."), unlink_stub, &file_list, NULL, TRUE, FALSE, THUNAR_OPERATION_LOG_OPERATIONS, NULL); /* cleanup */ g_object_unref (file_list.data); } } /** * thunar_application_restore_files: * @application : a #ThunarApplication. * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @trash_file_list : a #GList of #ThunarFiles in the trash. * @new_files_closure : a #GClosure to connect to the job's "new-files" signal, * which will be emitted when the job finishes with the * list of #GFiles created by the job, or * %NULL if you're not interested in the signal. * * Restores all #ThunarFiles in the @trash_file_list to their original * location. **/ void thunar_application_restore_files (ThunarApplication *application, gpointer parent, GList *trash_file_list, GClosure *new_files_closure) { const gchar *original_uri; GError *err = NULL; GFile *target_path; GList *source_path_list = NULL; GList *target_path_list = NULL; GList *lp; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); for (lp = trash_file_list; lp != NULL; lp = lp->next) { original_uri = thunar_file_get_original_path (lp->data); if (G_UNLIKELY (original_uri == NULL)) { /* no OriginalPath, impossible to continue */ if (g_strcmp0 (thunar_file_get_display_name (lp->data), thunar_file_get_basename (lp->data)) != 0) g_set_error (&err, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Failed to determine the original path for \"%s\" (%s)"), thunar_file_get_display_name (lp->data), thunar_file_get_basename (lp->data)); else g_set_error (&err, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Failed to determine the original path for \"%s\""), thunar_file_get_display_name (lp->data)); break; } /* TODO we might have to distinguish between URIs and paths here */ target_path = g_file_new_for_commandline_arg (original_uri); source_path_list = thunar_g_list_append_deep (source_path_list, thunar_file_get_file (lp->data)); target_path_list = thunar_g_list_append_deep (target_path_list, target_path); g_object_unref (target_path); } if (G_UNLIKELY (err != NULL)) { /* display an error dialog */ if (g_strcmp0 (thunar_file_get_display_name (lp->data), thunar_file_get_basename (lp->data)) != 0) thunar_dialogs_show_error (parent, err, _("Could not restore \"%s\" (%s)"), thunar_file_get_display_name (lp->data), thunar_file_get_basename (lp->data)); else thunar_dialogs_show_error (parent, err, _("Could not restore \"%s\""), thunar_file_get_display_name (lp->data)); g_error_free (err); } else { /* launch the operation */ thunar_application_launch (application, parent, "stock_folder-move", _("Restoring files..."), thunar_io_jobs_restore_files, source_path_list, target_path_list, TRUE, TRUE, THUNAR_OPERATION_LOG_OPERATIONS, new_files_closure); } /* free path lists */ thunar_g_list_free_full (source_path_list); thunar_g_list_free_full (target_path_list); } ThunarThumbnailCache * thunar_application_get_thumbnail_cache (ThunarApplication *application) { _thunar_return_val_if_fail (THUNAR_IS_APPLICATION (application), NULL); if (application->thumbnail_cache == NULL) application->thumbnail_cache = thunar_thumbnail_cache_new (); return g_object_ref (application->thumbnail_cache); } static gboolean thunar_application_malloc_trim_idle (gpointer application_ptr) { #ifdef __GLIBC__ /* Workaround to make the kernel reclaim memory which is not used by Thunar anymore. */ /* When using glibc, after releasing bigger memory chunks it seems to be required to call that 'garbage collector'. */ /* More information here: https://gitlab.xfce.org/xfce/thunar/-/issues/1552#note_101990 */ malloc_trim (0); #endif return FALSE; } void thunar_application_malloc_trim_on_idle (ThunarApplication *application) { _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); g_idle_add (thunar_application_malloc_trim_idle, application); }