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",
|
||||
"mime_guess",
|
||||
"notify-debouncer-full",
|
||||
"notify-rust",
|
||||
"once_cell",
|
||||
"open",
|
||||
"paste",
|
||||
|
|
@ -3541,6 +3542,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
|
|
@ -3826,6 +3840,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
|
@ -4460,6 +4487,15 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.34.0"
|
||||
|
|
@ -5329,6 +5365,17 @@ version = "0.12.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "temp-dir"
|
||||
version = "0.1.13"
|
||||
|
|
@ -6221,7 +6268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quick-xml 0.34.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
|
|
@ -6609,6 +6656,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ gio = { version = "0.19", optional = true }
|
|||
glob = "0.3"
|
||||
ignore = "0.4"
|
||||
image = "0.24"
|
||||
notify-rust = "4"
|
||||
once_cell = "1.19"
|
||||
open = "5.0.2"
|
||||
icu_collator = "1.5"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ empty-folder = Empty folder
|
|||
empty-folder-hidden = Empty folder (has hidden items)
|
||||
filesystem = Filesystem
|
||||
home = Home
|
||||
notification-in-progress = File operations are in progress.
|
||||
trash = Trash
|
||||
undo = Undo
|
||||
|
||||
|
|
|
|||
105
src/app.rs
105
src/app.rs
|
|
@ -11,7 +11,8 @@ use cosmic::{
|
|||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
||||
subscription::{self, Subscription},
|
||||
widget::scrollable,
|
||||
window, Alignment, Event, Length,
|
||||
window::{self, Event as WindowEvent},
|
||||
Alignment, Event, Length,
|
||||
},
|
||||
iced_runtime::clipboard,
|
||||
style, theme,
|
||||
|
|
@ -33,10 +34,11 @@ use std::{
|
|||
any::TypeId,
|
||||
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
||||
env, fmt, fs,
|
||||
future::pending,
|
||||
num::NonZeroU16,
|
||||
path::PathBuf,
|
||||
process,
|
||||
sync::Arc,
|
||||
sync::{Arc, Mutex},
|
||||
time::{self, Instant},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
|
@ -214,6 +216,7 @@ pub enum Message {
|
|||
EditLocation(Option<Entity>),
|
||||
Key(Modifiers, Key),
|
||||
LaunchUrl(String),
|
||||
MaybeExit,
|
||||
Modifiers(Modifiers),
|
||||
MoveToTrash(Option<Entity>),
|
||||
MounterItems(MounterKey, MounterItems),
|
||||
|
|
@ -221,6 +224,7 @@ pub enum Message {
|
|||
NavBarContext(Entity),
|
||||
NavMenuAction(NavMenuAction),
|
||||
NewItem(Option<Entity>, bool),
|
||||
Notification(Arc<Mutex<notify_rust::NotificationHandle>>),
|
||||
NotifyEvents(Vec<DebouncedEvent>),
|
||||
NotifyWatcher(WatcherWrapper),
|
||||
OpenTerminal(Option<Entity>),
|
||||
|
|
@ -355,6 +359,7 @@ pub struct App {
|
|||
modifiers: Modifiers,
|
||||
mounters: Mounters,
|
||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||
notification_opt: Option<Arc<Mutex<notify_rust::NotificationHandle>>>,
|
||||
pending_operation_id: u64,
|
||||
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
||||
complete_operations: BTreeMap<u64, Operation>,
|
||||
|
|
@ -364,6 +369,7 @@ pub struct App {
|
|||
search_input: String,
|
||||
toasts: widget::toaster::Toasts<Message>,
|
||||
watcher_opt: Option<(Debouncer<RecommendedWatcher, FileIdMap>, HashSet<PathBuf>)>,
|
||||
window_id_opt: Option<window::Id>,
|
||||
nav_dnd_hover: Option<(Location, Instant)>,
|
||||
tab_dnd_hover: Option<(Entity, Instant)>,
|
||||
nav_drag_id: DragId,
|
||||
|
|
@ -539,6 +545,30 @@ impl App {
|
|||
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> {
|
||||
let window_title = match self.tab_model.text(self.tab_model.active()) {
|
||||
Some(tab_title) => format!("{tab_title} — {}", fl!("cosmic-files")),
|
||||
|
|
@ -981,6 +1011,7 @@ impl Application for App {
|
|||
modifiers: Modifiers::empty(),
|
||||
mounters: mounters(),
|
||||
mounter_items: HashMap::new(),
|
||||
notification_opt: None,
|
||||
pending_operation_id: 0,
|
||||
pending_operations: BTreeMap::new(),
|
||||
complete_operations: BTreeMap::new(),
|
||||
|
|
@ -990,6 +1021,7 @@ impl Application for App {
|
|||
search_input: String::new(),
|
||||
toasts: widget::toaster::Toasts::new(Message::CloseToast),
|
||||
watcher_opt: None,
|
||||
window_id_opt: Some(window::Id::MAIN),
|
||||
nav_dnd_hover: None,
|
||||
tab_dnd_hover: None,
|
||||
nav_drag_id: DragId::new(),
|
||||
|
|
@ -1024,6 +1056,10 @@ impl Application for App {
|
|||
(app, Command::batch(commands))
|
||||
}
|
||||
|
||||
fn main_window_id(&self) -> window::Id {
|
||||
self.window_id_opt.unwrap_or(window::Id::MAIN)
|
||||
}
|
||||
|
||||
fn nav_context_menu(
|
||||
&self,
|
||||
id: widget::nav_bar::Id,
|
||||
|
|
@ -1090,6 +1126,10 @@ impl Application for App {
|
|||
Command::none()
|
||||
}
|
||||
|
||||
fn on_app_exit(&mut self) -> Option<Message> {
|
||||
Some(Message::WindowClose)
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> Command<Self::Message> {
|
||||
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) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
|
|
@ -1347,6 +1393,9 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
Message::Notification(notification) => {
|
||||
self.notification_opt = Some(notification);
|
||||
}
|
||||
Message::NotifyEvents(events) => {
|
||||
log::debug!("{:?}", events);
|
||||
|
||||
|
|
@ -1542,7 +1591,7 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
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(description) = op.toast() {
|
||||
|
|
@ -1563,6 +1612,8 @@ impl Application for App {
|
|||
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
|
||||
commands.push(self.rescan_trash());
|
||||
return Command::batch(commands);
|
||||
|
|
@ -1579,6 +1630,7 @@ impl Application for App {
|
|||
if let Some((_, progress)) = self.pending_operations.get_mut(&id) {
|
||||
*progress = new_progress;
|
||||
}
|
||||
return self.update_notification();
|
||||
}
|
||||
Message::RescanTrash => {
|
||||
// Update trash icon if empty/full
|
||||
|
|
@ -1946,7 +1998,12 @@ impl Application for App {
|
|||
self.operation(Operation::Restore { paths });
|
||||
}
|
||||
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() {
|
||||
Ok(exe) => match process::Command::new(&exe).spawn() {
|
||||
|
|
@ -2483,6 +2540,7 @@ impl Application for App {
|
|||
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||
Some(Message::Modifiers(modifiers))
|
||||
}
|
||||
Event::Window(_id, WindowEvent::CloseRequested) => Some(Message::WindowClose),
|
||||
_ => None,
|
||||
}),
|
||||
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() {
|
||||
//TODO: use recipe?
|
||||
let id = *id;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut settings = Settings::default();
|
||||
settings = settings.theme(config.app_theme.theme());
|
||||
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
||||
settings = settings.exit_on_close(false);
|
||||
|
||||
let flags = Flags {
|
||||
config_handler,
|
||||
|
|
|
|||
|
|
@ -2651,7 +2651,11 @@ impl Tab {
|
|||
)));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue