Continue operations in the background if the window is closed
This commit is contained in:
parent
190029aa27
commit
da329004aa
6 changed files with 166 additions and 6 deletions
58
Cargo.lock
generated
58
Cargo.lock
generated
|
|
@ -1161,6 +1161,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"notify-debouncer-full",
|
"notify-debouncer-full",
|
||||||
|
"notify-rust",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
"paste",
|
"paste",
|
||||||
|
|
@ -3541,6 +3542,19 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mac-notification-sys"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"dirs-next",
|
||||||
|
"objc-foundation",
|
||||||
|
"objc_id",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
|
|
@ -3826,6 +3840,19 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-rust"
|
||||||
|
version = "4.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26a1d03b6305ecefdd9c6c60150179bb8d9f0cd4e64bbcad1e41419e7bf5e414"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"mac-notification-sys",
|
||||||
|
"serde",
|
||||||
|
"tauri-winrt-notification",
|
||||||
|
"zbus 4.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
|
@ -4460,6 +4487,15 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.34.0"
|
version = "0.34.0"
|
||||||
|
|
@ -5329,6 +5365,17 @@ version = "0.12.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
|
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-winrt-notification"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f89f5fb70d6f62381f5d9b2ba9008196150b40b75f3068eb24faeddf1c686871"
|
||||||
|
dependencies = [
|
||||||
|
"quick-xml 0.31.0",
|
||||||
|
"windows 0.56.0",
|
||||||
|
"windows-version",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "temp-dir"
|
name = "temp-dir"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
|
@ -6221,7 +6268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
|
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quick-xml",
|
"quick-xml 0.34.0",
|
||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -6609,6 +6656,15 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-version"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ gio = { version = "0.19", optional = true }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
image = "0.24"
|
image = "0.24"
|
||||||
|
notify-rust = "4"
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
open = "5.0.2"
|
open = "5.0.2"
|
||||||
icu_collator = "1.5"
|
icu_collator = "1.5"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ empty-folder = Empty folder
|
||||||
empty-folder-hidden = Empty folder (has hidden items)
|
empty-folder-hidden = Empty folder (has hidden items)
|
||||||
filesystem = Filesystem
|
filesystem = Filesystem
|
||||||
home = Home
|
home = Home
|
||||||
|
notification-in-progress = File operations are in progress.
|
||||||
trash = Trash
|
trash = Trash
|
||||||
undo = Undo
|
undo = Undo
|
||||||
|
|
||||||
|
|
|
||||||
105
src/app.rs
105
src/app.rs
|
|
@ -11,7 +11,8 @@ use cosmic::{
|
||||||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
keyboard::{Event as KeyEvent, Key, Modifiers},
|
||||||
subscription::{self, Subscription},
|
subscription::{self, Subscription},
|
||||||
widget::scrollable,
|
widget::scrollable,
|
||||||
window, Alignment, Event, Length,
|
window::{self, Event as WindowEvent},
|
||||||
|
Alignment, Event, Length,
|
||||||
},
|
},
|
||||||
iced_runtime::clipboard,
|
iced_runtime::clipboard,
|
||||||
style, theme,
|
style, theme,
|
||||||
|
|
@ -33,10 +34,11 @@ use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
||||||
env, fmt, fs,
|
env, fmt, fs,
|
||||||
|
future::pending,
|
||||||
num::NonZeroU16,
|
num::NonZeroU16,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process,
|
process,
|
||||||
sync::Arc,
|
sync::{Arc, Mutex},
|
||||||
time::{self, Instant},
|
time::{self, Instant},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
@ -214,6 +216,7 @@ pub enum Message {
|
||||||
EditLocation(Option<Entity>),
|
EditLocation(Option<Entity>),
|
||||||
Key(Modifiers, Key),
|
Key(Modifiers, Key),
|
||||||
LaunchUrl(String),
|
LaunchUrl(String),
|
||||||
|
MaybeExit,
|
||||||
Modifiers(Modifiers),
|
Modifiers(Modifiers),
|
||||||
MoveToTrash(Option<Entity>),
|
MoveToTrash(Option<Entity>),
|
||||||
MounterItems(MounterKey, MounterItems),
|
MounterItems(MounterKey, MounterItems),
|
||||||
|
|
@ -221,6 +224,7 @@ pub enum Message {
|
||||||
NavBarContext(Entity),
|
NavBarContext(Entity),
|
||||||
NavMenuAction(NavMenuAction),
|
NavMenuAction(NavMenuAction),
|
||||||
NewItem(Option<Entity>, bool),
|
NewItem(Option<Entity>, bool),
|
||||||
|
Notification(Arc<Mutex<notify_rust::NotificationHandle>>),
|
||||||
NotifyEvents(Vec<DebouncedEvent>),
|
NotifyEvents(Vec<DebouncedEvent>),
|
||||||
NotifyWatcher(WatcherWrapper),
|
NotifyWatcher(WatcherWrapper),
|
||||||
OpenTerminal(Option<Entity>),
|
OpenTerminal(Option<Entity>),
|
||||||
|
|
@ -355,6 +359,7 @@ pub struct App {
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
mounters: Mounters,
|
mounters: Mounters,
|
||||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||||
|
notification_opt: Option<Arc<Mutex<notify_rust::NotificationHandle>>>,
|
||||||
pending_operation_id: u64,
|
pending_operation_id: u64,
|
||||||
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
||||||
complete_operations: BTreeMap<u64, Operation>,
|
complete_operations: BTreeMap<u64, Operation>,
|
||||||
|
|
@ -364,6 +369,7 @@ pub struct App {
|
||||||
search_input: String,
|
search_input: String,
|
||||||
toasts: widget::toaster::Toasts<Message>,
|
toasts: widget::toaster::Toasts<Message>,
|
||||||
watcher_opt: Option<(Debouncer<RecommendedWatcher, FileIdMap>, HashSet<PathBuf>)>,
|
watcher_opt: Option<(Debouncer<RecommendedWatcher, FileIdMap>, HashSet<PathBuf>)>,
|
||||||
|
window_id_opt: Option<window::Id>,
|
||||||
nav_dnd_hover: Option<(Location, Instant)>,
|
nav_dnd_hover: Option<(Location, Instant)>,
|
||||||
tab_dnd_hover: Option<(Entity, Instant)>,
|
tab_dnd_hover: Option<(Entity, Instant)>,
|
||||||
nav_drag_id: DragId,
|
nav_drag_id: DragId,
|
||||||
|
|
@ -539,6 +545,30 @@ impl App {
|
||||||
self.nav_model = nav_model.build();
|
self.nav_model = nav_model.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_notification(&mut self) -> Command<Message> {
|
||||||
|
// Handle closing notification if there are no operations
|
||||||
|
if self.pending_operations.is_empty() {
|
||||||
|
if let Some(notification_arc) = self.notification_opt.take() {
|
||||||
|
return Command::perform(
|
||||||
|
async move {
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
//TODO: this is nasty
|
||||||
|
let notification_mutex = Arc::try_unwrap(notification_arc).unwrap();
|
||||||
|
let notification = notification_mutex.into_inner().unwrap();
|
||||||
|
notification.close();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
message::app(Message::MaybeExit)
|
||||||
|
},
|
||||||
|
|x| x,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
fn update_title(&mut self) -> Command<Message> {
|
fn update_title(&mut self) -> Command<Message> {
|
||||||
let window_title = match self.tab_model.text(self.tab_model.active()) {
|
let window_title = match self.tab_model.text(self.tab_model.active()) {
|
||||||
Some(tab_title) => format!("{tab_title} — {}", fl!("cosmic-files")),
|
Some(tab_title) => format!("{tab_title} — {}", fl!("cosmic-files")),
|
||||||
|
|
@ -981,6 +1011,7 @@ impl Application for App {
|
||||||
modifiers: Modifiers::empty(),
|
modifiers: Modifiers::empty(),
|
||||||
mounters: mounters(),
|
mounters: mounters(),
|
||||||
mounter_items: HashMap::new(),
|
mounter_items: HashMap::new(),
|
||||||
|
notification_opt: None,
|
||||||
pending_operation_id: 0,
|
pending_operation_id: 0,
|
||||||
pending_operations: BTreeMap::new(),
|
pending_operations: BTreeMap::new(),
|
||||||
complete_operations: BTreeMap::new(),
|
complete_operations: BTreeMap::new(),
|
||||||
|
|
@ -990,6 +1021,7 @@ impl Application for App {
|
||||||
search_input: String::new(),
|
search_input: String::new(),
|
||||||
toasts: widget::toaster::Toasts::new(Message::CloseToast),
|
toasts: widget::toaster::Toasts::new(Message::CloseToast),
|
||||||
watcher_opt: None,
|
watcher_opt: None,
|
||||||
|
window_id_opt: Some(window::Id::MAIN),
|
||||||
nav_dnd_hover: None,
|
nav_dnd_hover: None,
|
||||||
tab_dnd_hover: None,
|
tab_dnd_hover: None,
|
||||||
nav_drag_id: DragId::new(),
|
nav_drag_id: DragId::new(),
|
||||||
|
|
@ -1024,6 +1056,10 @@ impl Application for App {
|
||||||
(app, Command::batch(commands))
|
(app, Command::batch(commands))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main_window_id(&self) -> window::Id {
|
||||||
|
self.window_id_opt.unwrap_or(window::Id::MAIN)
|
||||||
|
}
|
||||||
|
|
||||||
fn nav_context_menu(
|
fn nav_context_menu(
|
||||||
&self,
|
&self,
|
||||||
id: widget::nav_bar::Id,
|
id: widget::nav_bar::Id,
|
||||||
|
|
@ -1090,6 +1126,10 @@ impl Application for App {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_app_exit(&mut self) -> Option<Message> {
|
||||||
|
Some(Message::WindowClose)
|
||||||
|
}
|
||||||
|
|
||||||
fn on_escape(&mut self) -> Command<Self::Message> {
|
fn on_escape(&mut self) -> Command<Self::Message> {
|
||||||
let entity = self.tab_model.active();
|
let entity = self.tab_model.active();
|
||||||
|
|
||||||
|
|
@ -1256,6 +1296,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::MaybeExit => {
|
||||||
|
if self.window_id_opt.is_none() && self.pending_operations.is_empty() {
|
||||||
|
// Exit if window is closed and there are no pending operations
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::LaunchUrl(url) => match open::that_detached(&url) {
|
Message::LaunchUrl(url) => match open::that_detached(&url) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -1347,6 +1393,9 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Notification(notification) => {
|
||||||
|
self.notification_opt = Some(notification);
|
||||||
|
}
|
||||||
Message::NotifyEvents(events) => {
|
Message::NotifyEvents(events) => {
|
||||||
log::debug!("{:?}", events);
|
log::debug!("{:?}", events);
|
||||||
|
|
||||||
|
|
@ -1542,7 +1591,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::PendingComplete(id) => {
|
Message::PendingComplete(id) => {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::with_capacity(3);
|
||||||
|
|
||||||
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
||||||
if let Some(description) = op.toast() {
|
if let Some(description) = op.toast() {
|
||||||
|
|
@ -1563,6 +1612,8 @@ impl Application for App {
|
||||||
self.complete_operations.insert(id, op);
|
self.complete_operations.insert(id, op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Potentially show a notification
|
||||||
|
commands.push(self.update_notification());
|
||||||
// Manually rescan any trash tabs after any operation is completed
|
// Manually rescan any trash tabs after any operation is completed
|
||||||
commands.push(self.rescan_trash());
|
commands.push(self.rescan_trash());
|
||||||
return Command::batch(commands);
|
return Command::batch(commands);
|
||||||
|
|
@ -1579,6 +1630,7 @@ impl Application for App {
|
||||||
if let Some((_, progress)) = self.pending_operations.get_mut(&id) {
|
if let Some((_, progress)) = self.pending_operations.get_mut(&id) {
|
||||||
*progress = new_progress;
|
*progress = new_progress;
|
||||||
}
|
}
|
||||||
|
return self.update_notification();
|
||||||
}
|
}
|
||||||
Message::RescanTrash => {
|
Message::RescanTrash => {
|
||||||
// Update trash icon if empty/full
|
// Update trash icon if empty/full
|
||||||
|
|
@ -1946,7 +1998,12 @@ impl Application for App {
|
||||||
self.operation(Operation::Restore { paths });
|
self.operation(Operation::Restore { paths });
|
||||||
}
|
}
|
||||||
Message::WindowClose => {
|
Message::WindowClose => {
|
||||||
return window::close(window::Id::MAIN);
|
if let Some(window_id) = self.window_id_opt.take() {
|
||||||
|
return Command::batch([
|
||||||
|
window::close(window_id),
|
||||||
|
Command::perform(async move { message::app(Message::MaybeExit) }, |x| x),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::WindowNew => match env::current_exe() {
|
Message::WindowNew => match env::current_exe() {
|
||||||
Ok(exe) => match process::Command::new(&exe).spawn() {
|
Ok(exe) => match process::Command::new(&exe).spawn() {
|
||||||
|
|
@ -2483,6 +2540,7 @@ impl Application for App {
|
||||||
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||||
Some(Message::Modifiers(modifiers))
|
Some(Message::Modifiers(modifiers))
|
||||||
}
|
}
|
||||||
|
Event::Window(_id, WindowEvent::CloseRequested) => Some(Message::WindowClose),
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
cosmic_config::config_subscription(
|
cosmic_config::config_subscription(
|
||||||
|
|
@ -2669,6 +2727,45 @@ impl Application for App {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.pending_operations.is_empty() {
|
||||||
|
//TODO: inhibit suspend/shutdown?
|
||||||
|
|
||||||
|
if self.window_id_opt.is_none() {
|
||||||
|
struct NotificationSubscription;
|
||||||
|
subscriptions.push(subscription::channel(
|
||||||
|
TypeId::of::<NotificationSubscription>(),
|
||||||
|
1,
|
||||||
|
move |msg_tx| async move {
|
||||||
|
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
|
||||||
|
tokio::task::spawn_blocking(move || match notify_rust::Notification::new()
|
||||||
|
.summary(&fl!("notification-in-progress"))
|
||||||
|
.timeout(notify_rust::Timeout::Never)
|
||||||
|
.show()
|
||||||
|
{
|
||||||
|
Ok(notification) => {
|
||||||
|
let _ = futures::executor::block_on(async {
|
||||||
|
msg_tx
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.send(Message::Notification(Arc::new(Mutex::new(
|
||||||
|
notification,
|
||||||
|
))))
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to create notification: {}", err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
pending().await
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (id, (pending_operation, _)) in self.pending_operations.iter() {
|
for (id, (pending_operation, _)) in self.pending_operations.iter() {
|
||||||
//TODO: use recipe?
|
//TODO: use recipe?
|
||||||
let id = *id;
|
let id = *id;
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut settings = Settings::default();
|
let mut settings = Settings::default();
|
||||||
settings = settings.theme(config.app_theme.theme());
|
settings = settings.theme(config.app_theme.theme());
|
||||||
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
||||||
|
settings = settings.exit_on_close(false);
|
||||||
|
|
||||||
let flags = Flags {
|
let flags = Flags {
|
||||||
config_handler,
|
config_handler,
|
||||||
|
|
|
||||||
|
|
@ -2651,7 +2651,11 @@ impl Tab {
|
||||||
)));
|
)));
|
||||||
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
children.push(container(horizontal_rule(1)).padding([0, space_xxxs]).into());
|
children.push(
|
||||||
|
container(horizontal_rule(1))
|
||||||
|
.padding([0, space_xxxs])
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue