From 42175a96c2b7fc6c99ae9eba89fec56ebfeb6181 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Mon, 7 Oct 2024 01:05:01 -0400 Subject: [PATCH] Optionally restore last session on start Closes: #534 This patch adds a config option to restore closed sessions on start. So, if a user had three tabs opened, all three tabs would be restored when Files is launched again. Updates ------- * Searches and mounts aren't serialized anymore. I'm not sure if it makes sense to do so, but likely a user would raise an issue if they want it. --- i18n/en/cosmic_files.ftl | 4 ++ src/app.rs | 104 +++++++++++++++++++++++++++++---------- src/config.rs | 13 ++++- src/lib.rs | 21 ++++---- src/tab.rs | 5 +- 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 0a49147..0999074 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -197,6 +197,10 @@ match-desktop = Match desktop dark = Dark light = Light +### Session +session = Session +restore-session = Restore previous session on start + # Context menu add-to-sidebar = Add to sidebar compress = Compress diff --git a/src/app.rs b/src/app.rs index e18d263..ee73226 100644 --- a/src/app.rs +++ b/src/app.rs @@ -58,7 +58,7 @@ use wayland_client::{protocol::wl_output::WlOutput, Proxy}; use crate::{ clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, - config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig}, + config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, SessionConfig, TabConfig}, fl, home_dir, key_bind::key_binds, localize::LANGUAGE_SORTER, @@ -69,7 +69,7 @@ use crate::{ tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION}, }; -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Mode { App, Desktop, @@ -303,9 +303,11 @@ pub enum Message { Rename(Option), ReplaceResult(ReplaceResult), RestoreFromTrash(Option), + SaveSession, SearchActivate, SearchClear, SearchInput(String), + SessionConfig(SessionConfig), SystemThemeModeChange(cosmic_theme::ThemeMode), TabActivate(Entity), TabNext, @@ -1163,27 +1165,43 @@ impl App { fn settings(&self) -> Element { // TODO: Should dialog be updated here too? - widget::settings::view_column(vec![widget::settings::section() - .title(fl!("appearance")) - .add({ - let app_theme_selected = match self.config.app_theme { - AppTheme::Dark => 1, - AppTheme::Light => 2, - AppTheme::System => 0, - }; - widget::settings::item::builder(fl!("theme")).control(widget::dropdown( - &self.app_themes, - Some(app_theme_selected), - move |index| { - Message::AppTheme(match index { - 1 => AppTheme::Dark, - 2 => AppTheme::Light, - _ => AppTheme::System, - }) - }, - )) - }) - .into()]) + widget::settings::view_column(vec![ + widget::settings::section() + .title(fl!("appearance")) + .add({ + let app_theme_selected = match self.config.app_theme { + AppTheme::Dark => 1, + AppTheme::Light => 2, + AppTheme::System => 0, + }; + widget::settings::item::builder(fl!("theme")).control(widget::dropdown( + &self.app_themes, + Some(app_theme_selected), + move |index| { + Message::AppTheme(match index { + 1 => AppTheme::Dark, + 2 => AppTheme::Light, + _ => AppTheme::System, + }) + }, + )) + }) + .into(), + widget::settings::section() + .title(fl!("session")) + .add( + widget::settings::item::builder(fl!("restore-session")).toggler( + self.config.session.restore, + move |restore| { + Message::SessionConfig(SessionConfig { + restore, + ..Default::default() + }) + }, + ), + ) + .into(), + ]) .into() } } @@ -2339,6 +2357,35 @@ impl Application for App { self.operation(Operation::Restore { paths }); } } + Message::SaveSession => { + if self.config.session.restore && self.mode == Mode::App { + let session = SessionConfig { + tabs: Some( + self.tab_model + .iter() + .filter_map(|entity| { + match self + .tab_model + .data::(entity) + .map(|tab| &tab.location) + { + // Location's serialization implementation skips variants + // such as mounts + // However, we'd still clone all Locations here only for + // some to be skipped, so it's best to avoid the clone + loc @ Location::Path(_) + | Location::Recents + | Location::Trash => Some(loc.clone()), + _ => None, + } + }) + .collect(), + ), + ..self.config.session + }; + config_set!(session, session); + } + } Message::SearchActivate => { return if self.search_get().is_none() { self.search_set(Some(String::new())) @@ -2352,6 +2399,9 @@ impl Application for App { Message::SearchInput(input) => { return self.search_set(Some(input)); } + Message::SessionConfig(session) => { + config_set!(session, session); + } Message::SystemThemeModeChange(_theme_mode) => { return self.update_config(); } @@ -2419,9 +2469,12 @@ impl Application for App { // Remove item self.tab_model.remove(entity); - // If that was the last tab, close window + // If that was the last tab, close window and serialize empty session if necessary if self.tab_model.iter().next().is_none() { - return window::close(window::Id::MAIN); + return Command::batch([ + self.update(Message::SaveSession), + window::close(window::Id::MAIN), + ]); } return Command::batch([self.update_title(), self.update_watcher()]); @@ -2681,6 +2734,7 @@ impl Application for App { if let Some(window_id) = self.window_id_opt.take() { return Command::batch([ window::close(window_id), + self.update(Message::SaveSession), Command::perform(async move { message::app(Message::MaybeExit) }, |x| x), ]); } diff --git a/src/config.rs b/src/config.rs index a527a88..52201f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,10 @@ use cosmic::{ }; use serde::{Deserialize, Serialize}; -use crate::{app::App, tab::View}; +use crate::{ + app::App, + tab::{Location, View}, +}; pub const CONFIG_VERSION: u64 = 1; @@ -97,6 +100,7 @@ pub struct Config { pub favorites: Vec, pub show_details: bool, pub tab: TabConfig, + pub session: SessionConfig, } impl Config { @@ -144,6 +148,7 @@ impl Default for Config { ], show_details: false, tab: TabConfig::default(), + session: SessionConfig::default(), } } } @@ -229,3 +234,9 @@ impl IconSizes { percent!(self.grid, ICON_SIZE_GRID) as _ } } + +#[derive(Clone, Debug, Default, PartialEq, Eq, CosmicConfigEntry, Deserialize, Serialize)] +pub struct SessionConfig { + pub restore: bool, + pub tabs: Option>, +} diff --git a/src/lib.rs b/src/lib.rs index 96f1f91..b9435cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,22 +96,25 @@ pub fn main() -> Result<(), Box> { localize::localize(); - let (config_handler, config) = Config::load(); + let (config_handler, mut config) = Config::load(); let mut locations = Vec::new(); for arg in env::args().skip(1) { - let location = if &arg == "--trash" { - Location::Trash - } else { - match fs::canonicalize(&arg) { - Ok(absolute) => Location::Path(absolute), + match &*arg { + "--trash" => locations.push(Location::Trash), + // Override session regardless of config + "--no-session" => _ = config.session.tabs.take(), + path => match fs::canonicalize(path) { + Ok(absolute) => locations.push(Location::Path(absolute)), Err(err) => { log::warn!("failed to canonicalize {:?}: {}", arg, err); continue; } - } - }; - locations.push(location); + } + } + } + if let Some(session) = config.session.restore.then(|| config.session.tabs.take()).flatten() { + locations.extend(session); } let mut settings = Settings::default(); diff --git a/src/tab.rs b/src/tab.rs index 0675543..4ff41aa 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -860,12 +860,15 @@ pub fn scan_desktop( items } -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)] pub enum Location { + #[serde(skip)] Desktop(PathBuf, String, DesktopConfig), + #[serde(skip)] Network(String, String), Path(PathBuf), Recents, + #[serde(skip)] Search(PathBuf, String, bool, Instant), Trash, }