diff --git a/gresources/nemo-file-management-properties.glade b/gresources/nemo-file-management-properties.glade index 02b8e4cb4..7bb4fffbe 100644 --- a/gresources/nemo-file-management-properties.glade +++ b/gresources/nemo-file-management-properties.glade @@ -1256,6 +1256,23 @@ along with . If not, see . 3 + + + Restore last window tabs on startup + True + True + False + True + 0 + True + + + False + False + 3 + 4 + + Automatically expand rows during drag-and-drop @@ -1269,7 +1286,7 @@ along with . If not, see . False False - 4 + 5 diff --git a/libnemo-private/nemo-global-preferences.h b/libnemo-private/nemo-global-preferences.h index 576a4616d..91f404854 100644 --- a/libnemo-private/nemo-global-preferences.h +++ b/libnemo-private/nemo-global-preferences.h @@ -122,6 +122,13 @@ typedef enum #define NEMO_WINDOW_STATE_DEVICES_EXPANDED "devices-expanded" #define NEMO_WINDOW_STATE_NETWORK_EXPANDED "network-expanded" +/* Saved session (last closed window) */ +#define NEMO_WINDOW_STATE_SAVED_SPLIT_VIEW "saved-split-view" +#define NEMO_WINDOW_STATE_SAVED_TABS_LEFT "saved-tabs-left" +#define NEMO_WINDOW_STATE_SAVED_TABS_RIGHT "saved-tabs-right" +#define NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_LEFT "saved-active-tab-left" +#define NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_RIGHT "saved-active-tab-right" + /* Sorting order */ #define NEMO_PREFERENCES_SORT_DIRECTORIES_FIRST "sort-directories-first" #define NEMO_PREFERENCES_SORT_FAVORITES_FIRST "sort-favorites-first" @@ -137,6 +144,7 @@ typedef enum #define NEMO_PREFERENCES_CLOSE_DEVICE_VIEW_ON_EJECT "close-device-view-on-device-eject" #define NEMO_PREFERENCES_START_WITH_DUAL_PANE "start-with-dual-pane" +#define NEMO_PREFERENCES_RESTORE_TABS_ON_STARTUP "restore-tabs-on-startup" #define NEMO_PREFERENCES_IGNORE_VIEW_METADATA "ignore-view-metadata" #define NEMO_PREFERENCES_SHOW_BOOKMARKS_IN_TO_MENUS "show-bookmarks-in-to-menus" #define NEMO_PREFERENCES_SHOW_PLACES_IN_TO_MENUS "show-places-in-to-menus" diff --git a/libnemo-private/org.nemo.gschema.xml b/libnemo-private/org.nemo.gschema.xml index a43065d35..abc2814d4 100644 --- a/libnemo-private/org.nemo.gschema.xml +++ b/libnemo-private/org.nemo.gschema.xml @@ -346,6 +346,11 @@ Whether to default to showing dual-pane view when a new window is opened If set to true, new Nemo windows will default to showing two panes + + false + Restore the previous window tabs on startup + If set to true, Nemo will restore the last saved window tab state when launched without explicit locations. + false Whether to ignore folder metadata for view zoom levels and layouts @@ -702,6 +707,33 @@ Side pane view The side pane view to show in newly opened windows. + + + + false + Whether split view was enabled when the last window was closed + Internal setting used to restore the last closed window's split view and tabs. + + + [] + Saved tab URIs for the left pane + Internal setting used to restore the last closed window's tabs for the left pane. + + + [] + Saved tab URIs for the right pane + Internal setting used to restore the last closed window's tabs for the right pane. + + + 0 + Index of the active tab in the left pane + Internal setting used to restore which tab was active in the left pane. + + + 0 + Index of the active tab in the right pane + Internal setting used to restore which tab was active in the right pane. + diff --git a/src/nemo-application.c b/src/nemo-application.c index 5366afb2e..38a8db767 100644 --- a/src/nemo-application.c +++ b/src/nemo-application.c @@ -534,6 +534,34 @@ nemo_application_quit (NemoApplication *self) GList *windows; windows = gtk_application_get_windows (GTK_APPLICATION (app)); + + /* Save session state once, before we destroy all windows. + * Save the last non-desktop window we find. */ + { + NemoWindow *last_window = NULL; + + for (GList *l = windows; l != NULL; l = l->next) { + GtkWindow *w = GTK_WINDOW (l->data); + + if (!NEMO_IS_WINDOW (w)) { + continue; + } + + NemoWindow *nw = NEMO_WINDOW (w); + + /* Avoid saving the desktop window */ + if (nw->details != NULL && nw->details->disable_chrome) { + continue; + } + + last_window = nw; + } + + if (last_window != NULL) { + nemo_window_save_session_state (last_window); + } + } + g_list_foreach (windows, (GFunc) gtk_widget_destroy, NULL); /* we have been asked to force quit */ diff --git a/src/nemo-file-management-properties.c b/src/nemo-file-management-properties.c index d0a1958e1..1566a467f 100644 --- a/src/nemo-file-management-properties.c +++ b/src/nemo-file-management-properties.c @@ -99,6 +99,7 @@ #define NEMO_FILE_MANAGEMENT_PROPERTIES_DETECT_CONTENT_MEDIA_WIDGET "media_detect_content_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_ADVANCED_PERMISSIONS_WIDGET "show_advanced_permissions_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_START_WITH_DUAL_PANE_WIDGET "start_with_dual_pane_checkbutton" +#define NEMO_FILE_MANAGEMENT_PROPERTIES_RESTORE_TABS_ON_STARTUP_WIDGET "restore_tabs_on_startup_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_IGNORE_VIEW_METADATA_WIDGET "ignore_view_metadata_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_BOOKMARKS_IN_TO_MENUS_WIDGET "bookmarks_in_to_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_PLACES_IN_TO_MENUS_WIDGET "places_in_to_checkbutton" @@ -1067,6 +1068,10 @@ nemo_file_management_properties_dialog_setup (GtkBuilder *builder, NEMO_FILE_MANAGEMENT_PROPERTIES_START_WITH_DUAL_PANE_WIDGET, NEMO_PREFERENCES_START_WITH_DUAL_PANE); + bind_builder_bool (builder, nemo_preferences, + NEMO_FILE_MANAGEMENT_PROPERTIES_RESTORE_TABS_ON_STARTUP_WIDGET, + NEMO_PREFERENCES_RESTORE_TABS_ON_STARTUP); + bind_builder_bool (builder, nemo_preferences, NEMO_FILE_MANAGEMENT_PROPERTIES_IGNORE_VIEW_METADATA_WIDGET, NEMO_PREFERENCES_IGNORE_VIEW_METADATA); diff --git a/src/nemo-main-application.c b/src/nemo-main-application.c index b0f42581a..4efe081bd 100644 --- a/src/nemo-main-application.c +++ b/src/nemo-main-application.c @@ -474,8 +474,34 @@ open_windows (NemoMainApplication *application, gint i; if (files == NULL || files[0] == NULL) { - /* Open a window pointing at the default location. */ - open_window (application, NULL, screen, geometry); + /* No explicit locations requested: try restoring the last session. */ + NemoWindow *window; + gboolean have_geometry; + gboolean do_restore; + + window = nemo_main_application_create_window (NEMO_APPLICATION (application), screen); + + have_geometry = geometry != NULL && strcmp (geometry, "") != 0; + if (have_geometry && !gtk_widget_get_visible (GTK_WIDGET (window))) { + /* never maximize windows opened from shell if a + * custom geometry has been requested. + */ + gtk_window_unmaximize (GTK_WINDOW (window)); + eel_gtk_window_set_initial_geometry_from_string (GTK_WINDOW (window), + geometry, + APPLICATION_WINDOW_MIN_WIDTH, + APPLICATION_WINDOW_MIN_HEIGHT, + FALSE); + } + + do_restore = g_settings_get_boolean (nemo_preferences, NEMO_PREFERENCES_RESTORE_TABS_ON_STARTUP); + + if (!do_restore || !nemo_window_restore_saved_tabs (window)) { + /* Fall back to a safe default location */ + GFile *home = g_file_new_for_path (g_get_home_dir ()); + nemo_window_go_to (window, home); + g_object_unref (home); + } } else { if (open_in_existing_window) { /* Open one tab at each requested location in an existing window */ @@ -533,6 +559,7 @@ nemo_main_application_open (GApplication *app, gboolean open_in_tabs = FALSE; gchar *geometry = NULL; gboolean open_in_existing_window = strcmp (options, "EXISTING_WINDOW") == 0; + gboolean default_no_args = FALSE; const char splitter = '='; g_debug ("Open called on the GApplication instance; %d files", n_files); @@ -541,7 +568,12 @@ nemo_main_application_open (GApplication *app, /* Check if local command line passed --geometry or --tabs */ if (strlen (options) > 0) { gchar** split_options = g_strsplit (options, &splitter, 2); - if (strcmp (split_options[0], "NULL") != 0) { + if (g_str_has_prefix (split_options[0], "DEFAULT")) { + default_no_args = TRUE; + if (g_str_has_prefix (split_options[0], "DEFAULT+")) { + geometry = g_strdup (split_options[0] + strlen ("DEFAULT+")); + } + } else if (strcmp (split_options[0], "NULL") != 0) { geometry = g_strdup (split_options[0]); } sscanf (split_options[1], "%d", &open_in_tabs); @@ -556,7 +588,13 @@ nemo_main_application_open (GApplication *app, geometry ? geometry : "none", open_in_existing_window ? "yes" : "no"); - open_windows (self, files, n_files, gdk_screen_get_default (), geometry, open_in_tabs, open_in_existing_window); + if (default_no_args) { + /* Treat this as a no-arg launch; open_windows() will attempt session restore + * and fall back to Home if restore isn't possible. */ + open_windows (self, NULL, 0, gdk_screen_get_default (), geometry, open_in_tabs, open_in_existing_window); + } else { + open_windows (self, files, n_files, gdk_screen_get_default (), geometry, open_in_tabs, open_in_existing_window); + } g_clear_pointer (&geometry, g_free); } @@ -798,9 +836,11 @@ nemo_main_application_local_command_line (GApplication *application, GFile **files; gint idx, len; + gboolean used_default_location; len = 0; files = NULL; + used_default_location = FALSE; /* Convert args to GFiles */ if (remaining != NULL) { @@ -822,32 +862,50 @@ nemo_main_application_local_command_line (GApplication *application, } if (files == NULL && !no_default_window) { + /* Original behavior: default to Home when no URIs are provided. */ files = g_malloc0 (2 * sizeof (GFile *)); len = 1; files[0] = g_file_new_for_path (g_get_home_dir ()); files[1] = NULL; + + /* Mark that this was a no-arg launch, not an explicit URI. */ + used_default_location = TRUE; } - /* Invoke "Open" to open in existing window or create new windows */ + + /* Invoke "Open" to open in existing window or create new windows. + */ if (len > 0) { gchar* concatOptions = g_malloc0(64); if (open_in_existing_window) { g_stpcpy (concatOptions, "EXISTING_WINDOW"); } else { if (self->priv->geometry == NULL) { - g_snprintf (concatOptions, 64, "NULL=%d", open_in_tabs); + /* If Home was synthesized because no URIs were passed, signal that + * to the primary instance so it can attempt session restore. */ + if (used_default_location) { + g_snprintf (concatOptions, 64, "DEFAULT=%d", open_in_tabs); + } else { + g_snprintf (concatOptions, 64, "NULL=%d", open_in_tabs); + } } else { - g_snprintf (concatOptions, 64, "%s=%d", self->priv->geometry, open_in_tabs); + if (used_default_location) { + g_snprintf (concatOptions, 64, "DEFAULT+%s=%d", self->priv->geometry, open_in_tabs); + } else { + g_snprintf (concatOptions, 64, "%s=%d", self->priv->geometry, open_in_tabs); + } } } g_application_open (application, files, len, concatOptions); g_free (concatOptions); } - for (idx = 0; idx < len; idx++) { - g_object_unref (files[idx]); + if (files != NULL) { + for (idx = 0; idx < len; idx++) { + g_object_unref (files[idx]); + } + g_free (files); } - g_free (files); out: g_option_context_free (context); diff --git a/src/nemo-window-private.h b/src/nemo-window-private.h index b8f8e0444..1f43e5f40 100644 --- a/src/nemo-window-private.h +++ b/src/nemo-window-private.h @@ -154,6 +154,9 @@ void nemo_window_set_active_pane (NemoWindow NemoWindowPane *new_pane); NemoWindowPane * nemo_window_get_active_pane (NemoWindow *window); +gboolean nemo_window_restore_saved_tabs (NemoWindow *window); +void nemo_window_save_session_state (NemoWindow *window); + /* sync window GUI with current slot. Used when changing slots, * and when updating the slot state. diff --git a/src/nemo-window.c b/src/nemo-window.c index a9e502d72..c7956eb10 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -2025,11 +2025,309 @@ real_get_icon (NemoWindow *window, NEMO_FILE_ICON_FLAGS_USE_MOUNT_ICON); } +static gboolean +uri_is_native_session_uri (const char *uri) +{ + GFile *file; + gboolean is_native; + + if (uri == NULL || uri[0] == '\0') { + return FALSE; + } + + file = g_file_new_for_uri (uri); + + /* skip searches and non-native locations for this simple session restore */ + if (g_file_has_uri_scheme (file, "x-nemo-search")) { + g_object_unref (file); + return FALSE; + } + + is_native = g_file_is_native (file); + g_object_unref (file); + + return is_native; +} + +static char ** +collect_pane_saved_tab_uris (NemoWindowPane *pane, gint *active_index_out) +{ + GtkNotebook *notebook; + int n_pages, i; + int current_page; + int saved_index = 0; + int saved_active_index = 0; + GPtrArray *arr; + + if (active_index_out != NULL) { + *active_index_out = 0; + } + + if (pane == NULL || pane->notebook == NULL) { + return g_new0 (char *, 1); + } + + notebook = GTK_NOTEBOOK (pane->notebook); + n_pages = gtk_notebook_get_n_pages (notebook); + current_page = gtk_notebook_get_current_page (notebook); + + arr = g_ptr_array_new_with_free_func (g_free); + + for (i = 0; i < n_pages; i++) { + GtkWidget *page; + NemoWindowSlot *slot; + char *uri; + + page = gtk_notebook_get_nth_page (notebook, i); + if (page == NULL) { + continue; + } + + slot = NEMO_WINDOW_SLOT (page); + uri = nemo_window_slot_get_location_uri (slot); + + if (uri_is_native_session_uri (uri)) { + if (i == current_page) { + saved_active_index = saved_index; + } + g_ptr_array_add (arr, uri); + saved_index++; + } else { + g_free (uri); + } + } + + g_ptr_array_add (arr, NULL); + + if (active_index_out != NULL) { + *active_index_out = saved_active_index; + } + + return (char **) g_ptr_array_free (arr, FALSE); +} + +void +nemo_window_save_session_state (NemoWindow *window) +{ + NemoWindowPane *left_pane; + NemoWindowPane *right_pane; + char **left_uris; + char **right_uris; + gint left_active = 0; + gint right_active = 0; + gboolean split_view; + GtkPaned *paned; + GtkWidget *child1; + GtkWidget *child2; + + g_return_if_fail (NEMO_IS_WINDOW (window)); + + /* Do not store session state for the desktop window */ + if (nemo_window_is_desktop (window)) { + return; + } + + paned = GTK_PANED (window->details->split_view_hpane); + child1 = gtk_paned_get_child1 (paned); + child2 = gtk_paned_get_child2 (paned); + + left_pane = child1 != NULL ? NEMO_WINDOW_PANE (child1) : NULL; + right_pane = child2 != NULL ? NEMO_WINDOW_PANE (child2) : NULL; + + left_uris = collect_pane_saved_tab_uris (left_pane, &left_active); + right_uris = collect_pane_saved_tab_uris (right_pane, &right_active); + split_view = (right_pane != NULL); + + g_settings_set_boolean (nemo_window_state, NEMO_WINDOW_STATE_SAVED_SPLIT_VIEW, split_view); + g_settings_set_strv (nemo_window_state, NEMO_WINDOW_STATE_SAVED_TABS_LEFT, (const gchar * const *) left_uris); + g_settings_set_strv (nemo_window_state, NEMO_WINDOW_STATE_SAVED_TABS_RIGHT, (const gchar * const *) right_uris); + g_settings_set_int (nemo_window_state, NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_LEFT, left_active); + g_settings_set_int (nemo_window_state, NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_RIGHT, right_active); + + g_strfreev (left_uris); + g_strfreev (right_uris); +} + +static void +clear_pane_to_single_slot (NemoWindowPane *pane) +{ + GtkNotebook *notebook; + int n_pages; + + if (pane == NULL || pane->notebook == NULL) { + return; + } + + notebook = GTK_NOTEBOOK (pane->notebook); + n_pages = gtk_notebook_get_n_pages (notebook); + + /* Ensure there is a predictable active tab */ + if (n_pages > 0) { + gtk_notebook_set_current_page (notebook, 0); + } + + /* Close all tabs except the first one */ + while (gtk_notebook_get_n_pages (notebook) > 1) { + GtkWidget *page; + NemoWindowSlot *slot; + int last = gtk_notebook_get_n_pages (notebook) - 1; + + page = gtk_notebook_get_nth_page (notebook, last); + if (page == NULL) { + break; + } + + slot = NEMO_WINDOW_SLOT (page); + nemo_window_pane_close_slot (pane, slot); + } +} + +static void +open_uri_list_in_pane (NemoWindowPane *pane, char **uris) +{ + int i; + + if (pane == NULL) { + return; + } + + /* If no URIs were saved for this pane, leave its first tab alone */ + if (uris == NULL || uris[0] == NULL) { + return; + } + + for (i = 0; uris[i] != NULL; i++) { + NemoWindowSlot *slot; + GFile *location; + + if (!uri_is_native_session_uri (uris[i])) { + continue; + } + + if (i == 0) { + /* Reuse the existing first tab */ + slot = pane->active_slot; + if (slot == NULL && pane->notebook != NULL) { + GtkWidget *page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (pane->notebook), 0); + if (page != NULL) { + slot = NEMO_WINDOW_SLOT (page); + } + } + } else { + slot = nemo_window_pane_open_slot (pane, NEMO_WINDOW_OPEN_SLOT_APPEND); + } + + if (slot == NULL) { + continue; + } + + location = g_file_new_for_uri (uris[i]); + nemo_window_slot_open_location (slot, location, 0); + g_object_unref (location); + } +} + +gboolean +nemo_window_restore_saved_tabs (NemoWindow *window) +{ + NemoWindowPane *left_pane; + NemoWindowPane *right_pane; + char **left_uris; + char **right_uris; + gint left_active; + gint right_active; + gboolean want_split; + gboolean saved_split; + + g_return_val_if_fail (NEMO_IS_WINDOW (window), FALSE); + + /* Never restore tabs for the desktop window */ + if (nemo_window_is_desktop (window)) { + return FALSE; + } + + left_uris = g_settings_get_strv (nemo_window_state, NEMO_WINDOW_STATE_SAVED_TABS_LEFT); + right_uris = g_settings_get_strv (nemo_window_state, NEMO_WINDOW_STATE_SAVED_TABS_RIGHT); + left_active = g_settings_get_int (nemo_window_state, NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_LEFT); + right_active = g_settings_get_int (nemo_window_state, NEMO_WINDOW_STATE_SAVED_ACTIVE_TAB_RIGHT); + saved_split = g_settings_get_boolean (nemo_window_state, NEMO_WINDOW_STATE_SAVED_SPLIT_VIEW); + + if ((left_uris == NULL || left_uris[0] == NULL) && + (right_uris == NULL || right_uris[0] == NULL)) { + g_strfreev (left_uris); + g_strfreev (right_uris); + return FALSE; + } + + /* Only create the extra pane if we actually have tabs to restore there */ + want_split = saved_split && (right_uris != NULL && right_uris[0] != NULL); + + if (want_split && !nemo_window_split_view_showing (window)) { + nemo_window_split_view_on (window); + } else if (!want_split && nemo_window_split_view_showing (window)) { + nemo_window_split_view_off (window); + } + + { + GtkPaned *paned = GTK_PANED (window->details->split_view_hpane); + GtkWidget *child1 = gtk_paned_get_child1 (paned); + GtkWidget *child2 = gtk_paned_get_child2 (paned); + + left_pane = child1 != NULL ? NEMO_WINDOW_PANE (child1) : NULL; + right_pane = (want_split && child2 != NULL) ? NEMO_WINDOW_PANE (child2) : NULL; + } + + /* Reset panes to one tab each, then rebuild tabs in saved order */ + clear_pane_to_single_slot (left_pane); + clear_pane_to_single_slot (right_pane); + + /* If nothing saved for the left pane, open Home as a minimal fallback */ + if (left_uris == NULL || left_uris[0] == NULL) { + GFile *home = g_file_new_for_path (g_get_home_dir ()); + if (left_pane != NULL && left_pane->active_slot != NULL) { + nemo_window_slot_open_location (left_pane->active_slot, home, 0); + } + g_object_unref (home); + } else { + open_uri_list_in_pane (left_pane, left_uris); + } + + open_uri_list_in_pane (right_pane, right_uris); + + /* Restore active tabs (clamp indices) */ + if (left_pane != NULL && left_pane->notebook != NULL) { + GtkNotebook *nb = GTK_NOTEBOOK (left_pane->notebook); + int n = gtk_notebook_get_n_pages (nb); + if (n > 0) { + gtk_notebook_set_current_page (nb, CLAMP (left_active, 0, n - 1)); + } + } + + if (right_pane != NULL && right_pane->notebook != NULL) { + GtkNotebook *nb = GTK_NOTEBOOK (right_pane->notebook); + int n = gtk_notebook_get_n_pages (nb); + if (n > 0) { + gtk_notebook_set_current_page (nb, CLAMP (right_active, 0, n - 1)); + } + } + + /* Make the left pane active for a predictable starting point */ + if (left_pane != NULL) { + nemo_window_set_active_pane (window, left_pane); + } + + g_strfreev (left_uris); + g_strfreev (right_uris); + + return TRUE; +} + static void real_window_close (NemoWindow *window) { g_return_if_fail (NEMO_IS_WINDOW (window)); + nemo_window_save_session_state (window); nemo_window_save_geometry (window); gtk_widget_destroy (GTK_WIDGET (window));