Merge remote-tracking branch 'upstream/master' into local/libcosmic-path-patch
# Conflicts: # Cargo.lock # Cargo.toml # src/app.rs # src/dialog.rs # src/menu.rs # src/tab.rs
This commit is contained in:
commit
1488e2b01b
43 changed files with 2154 additions and 1007 deletions
111
src/app.rs
111
src/app.rs
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::app::{self, Core, Task, context_drawer};
|
||||
use cosmic::core::Auto;
|
||||
use cosmic::cosmic_config::{self, ConfigSet};
|
||||
use cosmic::iced::clipboard::dnd::DndAction;
|
||||
use cosmic::iced::core::SmolStr;
|
||||
|
|
@ -27,7 +28,7 @@ use cosmic::iced::{
|
|||
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
||||
},
|
||||
platform_specific::shell::wayland::commands::layer_surface::{
|
||||
Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface,
|
||||
Anchor, KeyboardInteractivity, Layer, destroy_layer_surface,
|
||||
},
|
||||
};
|
||||
use cosmic::widget::about::About;
|
||||
|
|
@ -2718,6 +2719,9 @@ impl Application for App {
|
|||
),
|
||||
]);
|
||||
|
||||
if matches!(flags.mode, Mode::Desktop) {
|
||||
core.set_auto_blur(Auto::Window | Auto::Popup);
|
||||
}
|
||||
let mut app = Self {
|
||||
core,
|
||||
about,
|
||||
|
|
@ -2836,8 +2840,16 @@ impl Application for App {
|
|||
cosmic::Action::App(Message::NavMenuAction(NavMenuAction::OpenInNewTab(entity)))
|
||||
})
|
||||
.context_menu(self.nav_context_menu())
|
||||
.close_icon(icon::from_name("media-eject-symbolic").size(16).icon())
|
||||
.into_container();
|
||||
.close_icon(icon::from_name("media-eject-symbolic").size(16).icon());
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
nav = nav
|
||||
.window_id_maybe(self.core().main_window_id())
|
||||
.on_surface_action(|m| cosmic::Action::Cosmic(cosmic::app::Action::Surface(m)))
|
||||
}
|
||||
|
||||
let mut nav = nav.into_container();
|
||||
|
||||
if !self.core.is_condensed() {
|
||||
nav = nav.max_width(280);
|
||||
|
|
@ -2848,14 +2860,12 @@ impl Application for App {
|
|||
))
|
||||
}
|
||||
|
||||
fn nav_context_menu(
|
||||
&self,
|
||||
) -> Option<Vec<widget::menu::Tree<cosmic::Action<Self::Message>>>> {
|
||||
fn nav_context_menu(&self) -> Option<Vec<widget::menu::Tree<cosmic::Action<Self::Message>>>> {
|
||||
let items = self.nav_model.iter().map(|entity| {
|
||||
let favorite_index_opt = self.nav_model.data::<FavoriteIndex>(entity);
|
||||
let location_opt = self.nav_model.data::<Location>(entity);
|
||||
|
||||
let mut items = Vec::with_capacity(7);
|
||||
let mut items: Vec<widget::menu::Item<NavMenuAction, String>> = Vec::with_capacity(7);
|
||||
|
||||
if location_opt
|
||||
.and_then(Location::path_opt)
|
||||
|
|
@ -2982,10 +2992,14 @@ impl Application for App {
|
|||
})
|
||||
&& let Some(mounter) = MOUNTERS.get(&key)
|
||||
{
|
||||
return mounter.network_drive(uri.clone()).map(move |()| {
|
||||
cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount {
|
||||
entity,
|
||||
})
|
||||
return mounter.network_drive(uri.clone()).map(move |mounted| {
|
||||
if mounted {
|
||||
cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount {
|
||||
entity,
|
||||
})
|
||||
} else {
|
||||
cosmic::action::none()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3896,7 +3910,7 @@ impl Application for App {
|
|||
Some((*mounter_key, self.network_drive_input.clone()));
|
||||
return mounter
|
||||
.network_drive(self.network_drive_input.clone())
|
||||
.map(|()| cosmic::action::none());
|
||||
.map(|_| cosmic::action::none());
|
||||
}
|
||||
log::warn!(
|
||||
"no mounter found for connecting to {:?}",
|
||||
|
|
@ -4909,9 +4923,10 @@ impl Application for App {
|
|||
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
|
||||
Anchor, Gravity,
|
||||
};
|
||||
use cosmic::iced::runtime::platform_specific::wayland::popup::{
|
||||
use cosmic::{iced::runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
}, widget::menu::StyleSheet as _};
|
||||
|
||||
let window_id = WindowId::unique();
|
||||
self.windows.insert(
|
||||
window_id,
|
||||
|
|
@ -4921,10 +4936,24 @@ impl Application for App {
|
|||
)),
|
||||
);
|
||||
commands.push(self.update(Message::CheckClipboard));
|
||||
let t = self.core.system_theme();
|
||||
let styling = t.appearance(
|
||||
&cosmic::theme::menu_bar::MenuBarStyle::Default,
|
||||
false,
|
||||
);
|
||||
let rad = styling.menu_border_radius;
|
||||
|
||||
commands.push(self.update(Message::Surface(
|
||||
cosmic::surface::action::app_popup(
|
||||
|_: &Self| cosmic::surface::action::LiveSettings::default(),
|
||||
move |app: &mut Self| -> SctkPopupSettings {
|
||||
move |_| cosmic::surface::action::LiveSettings {
|
||||
corners: Some(iced::runtime::platform_specific::wayland::CornerRadius {
|
||||
top_left: rad[0] as u32,
|
||||
top_right: rad[1] as u32,
|
||||
bottom_left: rad[2] as u32,
|
||||
bottom_right: rad[3] as u32,
|
||||
}),
|
||||
..Default::default()
|
||||
}, move |app: &mut Self| -> SctkPopupSettings {
|
||||
let anchor_rect = Rectangle {
|
||||
x: point.x as i32,
|
||||
y: point.y as i32,
|
||||
|
|
@ -5694,24 +5723,37 @@ impl Application for App {
|
|||
.insert(surface_id, Window::new(WindowKind::Desktop(entity)));
|
||||
return Task::batch([
|
||||
command,
|
||||
get_layer_surface(SctkLayerSurfaceSettings {
|
||||
id: surface_id,
|
||||
layer: Layer::Bottom,
|
||||
keyboard_interactivity: KeyboardInteractivity::OnDemand,
|
||||
input_zone: None,
|
||||
anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
|
||||
output: IcedOutput::Output(output),
|
||||
namespace: "cosmic-files-applet".into(),
|
||||
size: Some((None, None)),
|
||||
margin: IcedMargin {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
exclusive_zone: 0,
|
||||
size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
|
||||
}),
|
||||
cosmic::task::message(cosmic::action::cosmic(
|
||||
cosmic::app::Action::Surface(
|
||||
cosmic::surface::action::app_layer_shell(
|
||||
|_| Default::default(),
|
||||
move |_: &mut App| SctkLayerSurfaceSettings {
|
||||
id: surface_id,
|
||||
layer: Layer::Bottom,
|
||||
keyboard_interactivity: KeyboardInteractivity::OnDemand,
|
||||
input_zone: None,
|
||||
anchor: Anchor::TOP
|
||||
| Anchor::BOTTOM
|
||||
| Anchor::LEFT
|
||||
| Anchor::RIGHT,
|
||||
output: IcedOutput::Output(output.clone()),
|
||||
namespace: "cosmic-files-applet".into(),
|
||||
size: Some((None, None)),
|
||||
margin: IcedMargin {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
exclusive_zone: 0,
|
||||
size_limits: Limits::NONE
|
||||
.min_width(1.0)
|
||||
.min_height(1.0),
|
||||
},
|
||||
None,
|
||||
),
|
||||
),
|
||||
)),
|
||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||
overlap_notify(surface_id, true),
|
||||
]);
|
||||
|
|
@ -6898,7 +6940,6 @@ impl Application for App {
|
|||
})
|
||||
.drag_id(self.tab_drag_id),
|
||||
)
|
||||
.class(style::Container::Background)
|
||||
.width(Length::Fill)
|
||||
.padding([0, space_s]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1796,12 +1796,28 @@ impl Application for App {
|
|||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
use cosmic::iced::Rectangle;
|
||||
use cosmic::widget::menu::StyleSheet as _;
|
||||
|
||||
let window_id = window::Id::unique();
|
||||
self.context_menu_window = Some(window_id);
|
||||
let autosize_id = widget::Id::unique();
|
||||
let t = self.core.system_theme();
|
||||
let styling = t.appearance(
|
||||
&cosmic::theme::menu_bar::MenuBarStyle::Default,
|
||||
false,
|
||||
);
|
||||
let rad = styling.menu_border_radius;
|
||||
commands.push(self.update(Message::Surface(
|
||||
cosmic::surface::action::app_popup(
|
||||
|_: &Self| cosmic::surface::action::LiveSettings::default(),
|
||||
move |_| cosmic::surface::action::LiveSettings {
|
||||
corners: Some(iced::runtime::platform_specific::wayland::CornerRadius {
|
||||
top_left: rad[0] as u32,
|
||||
top_right: rad[1] as u32,
|
||||
bottom_left: rad[2] as u32,
|
||||
bottom_right: rad[3] as u32,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
move |app: &mut Self| -> SctkPopupSettings {
|
||||
let anchor_rect = Rectangle {
|
||||
x: point.x as i32,
|
||||
|
|
|
|||
10
src/menu.rs
10
src/menu.rs
|
|
@ -70,7 +70,7 @@ pub fn context_menu<'a>(
|
|||
String::new()
|
||||
};
|
||||
fn key_style(theme: &cosmic::Theme) -> TextStyle {
|
||||
let mut color = theme.cosmic().background(false).component.on;
|
||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||
color.alpha *= 0.75;
|
||||
TextStyle {
|
||||
color: Some(color.into()),
|
||||
|
|
@ -78,7 +78,7 @@ pub fn context_menu<'a>(
|
|||
}
|
||||
}
|
||||
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
|
||||
let mut color = theme.cosmic().background(false).component.on;
|
||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||
color.alpha *= 0.5;
|
||||
TextStyle {
|
||||
color: Some(color.into()),
|
||||
|
|
@ -445,12 +445,12 @@ pub fn context_menu<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
container(column::with_children(children))
|
||||
container(cosmic::widget::menu::menu_column::MenuColumn::with_children(children))
|
||||
.padding(1)
|
||||
//TODO: move style to libcosmic
|
||||
.style(|theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
let component = &cosmic.background(false).component;
|
||||
let component = &cosmic.background(theme.transparent);
|
||||
container::Style {
|
||||
icon_color: Some(component.on.into()),
|
||||
text_color: Some(component.on.into()),
|
||||
|
|
@ -844,7 +844,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess
|
|||
.padding(1)
|
||||
.style(|theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
let component = &cosmic.background(false).component;
|
||||
let component = &cosmic.background(theme.transparent).component;
|
||||
container::Style {
|
||||
icon_color: Some(component.on.into()),
|
||||
text_color: Some(component.on.into()),
|
||||
|
|
|
|||
|
|
@ -659,7 +659,7 @@ impl Mounter for Gvfs {
|
|||
)
|
||||
}
|
||||
|
||||
fn network_drive(&self, uri: String) -> Task<()> {
|
||||
fn network_drive(&self, uri: String) -> Task<bool> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
|
|
@ -668,9 +668,15 @@ impl Mounter for Gvfs {
|
|||
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
|
||||
res_rx.await
|
||||
},
|
||||
|x| {
|
||||
if let Err(err) = x {
|
||||
|result| match result {
|
||||
Ok(Ok(())) => true,
|
||||
Ok(Err(err)) => {
|
||||
log::error!("{err:?}");
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{err:?}");
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ pub trait Mounter: Send + Sync {
|
|||
fn items(&self, sizes: IconSizes) -> Option<MounterItems>;
|
||||
//TODO: send result
|
||||
fn mount(&self, item: MounterItem) -> Task<()>;
|
||||
fn network_drive(&self, uri: String) -> Task<()>;
|
||||
fn network_drive(&self, uri: String) -> Task<bool>;
|
||||
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
|
||||
fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>;
|
||||
fn unmount(&self, item: MounterItem) -> Task<()>;
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ impl Op {
|
|||
}
|
||||
}
|
||||
|
||||
let (from_file, metadata, to_file) = cosmic::iced::futures::join!(
|
||||
let (from_file_open_result, metadata, to_file_open_result) = cosmic::iced::futures::join!(
|
||||
async {
|
||||
compio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
|
|
@ -470,12 +470,28 @@ impl Op {
|
|||
.write(true)
|
||||
.open(&self.to)
|
||||
.await
|
||||
.with_context(|| format!("failed to open {} for writing", self.to.display()))
|
||||
}
|
||||
);
|
||||
|
||||
let from_file = from_file?;
|
||||
let mut to_file = to_file?;
|
||||
let from_file = from_file_open_result?;
|
||||
|
||||
let mut to_file = match to_file_open_result {
|
||||
Ok(file) => file,
|
||||
#[cfg(not(feature = "gvfs"))]
|
||||
Err(why) => {
|
||||
_ = from_file.close().await;
|
||||
return Err(why).with_context(|| format!("failed to open {} for writing", self.to.display())).map_err(Into::into);
|
||||
}
|
||||
#[cfg(feature = "gvfs")]
|
||||
Err(_why) => {
|
||||
_ = from_file.close().await;
|
||||
return self
|
||||
.gio_file_copy(ctx, progress)
|
||||
.await
|
||||
.map(|()| true)
|
||||
.map_err(Into::into);
|
||||
}
|
||||
};
|
||||
progress.total_bytes = metadata.as_ref().map(|m| m.len());
|
||||
(ctx.on_progress)(self, &progress);
|
||||
|
||||
|
|
|
|||
81
src/tab.rs
81
src/tab.rs
|
|
@ -181,7 +181,9 @@ fn button_appearance(
|
|||
appearance.icon_color = Some(Color::from(cosmic.on_bg_component_color()));
|
||||
appearance.text_color = Some(Color::from(cosmic.on_bg_component_color()));
|
||||
if cut {
|
||||
appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled));
|
||||
appearance.text_color = Some(Color::from(
|
||||
cosmic.background(theme.transparent).component.on_disabled,
|
||||
));
|
||||
} else {
|
||||
appearance.text_color = Some(Color::from(cosmic.on_bg_component_color()));
|
||||
}
|
||||
|
|
@ -192,12 +194,16 @@ fn button_appearance(
|
|||
appearance.background = Some(Color::from(cosmic.bg_color()).into());
|
||||
appearance.icon_color = Some(Color::from(cosmic.on_bg_color()));
|
||||
if cut {
|
||||
appearance.text_color = Some(Color::from(cosmic.background(false).component.disabled));
|
||||
appearance.text_color = Some(Color::from(
|
||||
cosmic.background(theme.transparent).component.disabled,
|
||||
));
|
||||
} else {
|
||||
appearance.text_color = Some(Color::from(cosmic.on_bg_color()));
|
||||
}
|
||||
} else if cut {
|
||||
appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled));
|
||||
appearance.text_color = Some(Color::from(
|
||||
cosmic.background(theme.transparent).component.on_disabled,
|
||||
));
|
||||
}
|
||||
if focused && accent {
|
||||
appearance.outline_width = 1.0;
|
||||
|
|
@ -945,11 +951,24 @@ fn item_from_trash_child(
|
|||
metadata: fs::Metadata,
|
||||
sizes: IconSizes,
|
||||
) -> Option<Item> {
|
||||
let original_path = crate::trash::original_path_for_trash_child(&path)?;
|
||||
let Some(original_path) = crate::trash::original_path_for_trash_child(&path) else {
|
||||
log::warn!(
|
||||
"failed to resolve original path for trash item {}, skipping entry",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
};
|
||||
let Some(original_parent) = original_path.parent() else {
|
||||
log::warn!(
|
||||
"trash item {} has no original parent, skipping entry",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
};
|
||||
let entry = trash::TrashItem {
|
||||
id: path.as_os_str().to_os_string(),
|
||||
name: std::ffi::OsString::from(&name),
|
||||
original_parent: original_path.parent()?.to_path_buf(),
|
||||
original_parent: original_parent.to_path_buf(),
|
||||
time_deleted: 0,
|
||||
};
|
||||
let size = if metadata.is_dir() {
|
||||
|
|
@ -1042,6 +1061,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
|||
if !remote_scannable {
|
||||
match fs::read_dir(tab_path) {
|
||||
Ok(entries) => {
|
||||
let trash = crate::trash::is_trash_path(tab_path);
|
||||
items = entries
|
||||
.filter_map(|entry_res| {
|
||||
let entry = entry_res
|
||||
|
|
@ -1082,7 +1102,6 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
|||
})
|
||||
.ok()?;
|
||||
|
||||
let trash = crate::trash::is_trash_path(tab_path);
|
||||
if trash {
|
||||
item_from_trash_child(path, name, metadata, sizes)
|
||||
} else {
|
||||
|
|
@ -2382,10 +2401,8 @@ impl Item {
|
|||
widget::image(handle.clone()).into()
|
||||
}
|
||||
ItemThumbnail::Svg(handle) => widget::svg(handle.clone()).into(),
|
||||
ItemThumbnail::Text(content) => widget::text_editor(content)
|
||||
.class(cosmic::theme::iced::TextEditor::Custom(Box::new(
|
||||
text_editor_class,
|
||||
)))
|
||||
ItemThumbnail::Text(content) => widget::text_editor::text_editor(content)
|
||||
.style(text_editor_class)
|
||||
.width(THUMBNAIL_SIZE as f32)
|
||||
.height(Length::Fixed(THUMBNAIL_SIZE as f32))
|
||||
.padding(spacing.space_xxs)
|
||||
|
|
@ -2436,7 +2453,7 @@ impl Item {
|
|||
);
|
||||
|
||||
let mut details = widget::column::with_capacity(8).spacing(space_xxxs);
|
||||
details = details.push(widget::text::heading(self.name.clone()));
|
||||
details = details.push(widget::selectable_text::heading(self.name.clone()));
|
||||
details = details.push(widget::text::body(fl!(
|
||||
"type",
|
||||
mime = self.mime.to_string()
|
||||
|
|
@ -2493,21 +2510,21 @@ impl Item {
|
|||
let time_formatter = time_formatter(military_time);
|
||||
|
||||
if let Ok(time) = metadata.created() {
|
||||
details = details.push(widget::text::body(fl!(
|
||||
details = details.push(widget::selectable_text::body(fl!(
|
||||
"item-created",
|
||||
created = format_time(time, &date_time_formatter, &time_formatter).to_string()
|
||||
)));
|
||||
}
|
||||
|
||||
if let Ok(time) = metadata.modified() {
|
||||
details = details.push(widget::text::body(fl!(
|
||||
details = details.push(widget::selectable_text::body(fl!(
|
||||
"item-modified",
|
||||
modified = format_time(time, &date_time_formatter, &time_formatter).to_string()
|
||||
)));
|
||||
}
|
||||
|
||||
if let Ok(time) = metadata.accessed() {
|
||||
details = details.push(widget::text::body(fl!(
|
||||
details = details.push(widget::selectable_text::body(fl!(
|
||||
"item-accessed",
|
||||
accessed = format_time(time, &date_time_formatter, &time_formatter).to_string()
|
||||
)));
|
||||
|
|
@ -3859,6 +3876,28 @@ impl Tab {
|
|||
}
|
||||
Message::EditLocationSubmit => {
|
||||
if let Some(mut edit_location) = self.edit_location.take() {
|
||||
let typed_opt = match &edit_location.location {
|
||||
Location::Path(path) => path.to_str().map(str::to_string),
|
||||
Location::Network(uri, ..) => Some(uri.clone()),
|
||||
_ => None,
|
||||
};
|
||||
let mut typed_uri = false;
|
||||
if let Some(typed) = typed_opt {
|
||||
match typed.trim().parse::<url::Url>() {
|
||||
Ok(url) if url.scheme() != "file" && url.has_host() => {
|
||||
let uri = url.as_str().to_string();
|
||||
edit_location =
|
||||
Location::Network(uri.clone(), uri, None).normalize().into();
|
||||
typed_uri = true;
|
||||
}
|
||||
Err(_) if matches!(edit_location.location, Location::Network(..)) => {
|
||||
edit_location =
|
||||
Location::Path(PathBuf::from(typed)).normalize().into();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Select first completion if current location does not exist
|
||||
if edit_location.selected.is_none()
|
||||
&& edit_location
|
||||
|
|
@ -3874,6 +3913,9 @@ impl Tab {
|
|||
}
|
||||
|
||||
cd = edit_location.resolve();
|
||||
if cd.is_none() && typed_uri {
|
||||
cd = Some(edit_location.location);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::EditLocationTab => {
|
||||
|
|
@ -5174,9 +5216,11 @@ impl Tab {
|
|||
}
|
||||
ItemThumbnail::Text(text) => {
|
||||
element_opt = Some(
|
||||
widget::container(widget::text_editor(text).padding(space_xxs).class(
|
||||
cosmic::theme::iced::TextEditor::Custom(Box::new(text_editor_class)),
|
||||
))
|
||||
widget::container(
|
||||
widget::text_editor::text_editor(text)
|
||||
.padding(space_xxs)
|
||||
.style(text_editor_class),
|
||||
)
|
||||
.center(Length::Fill)
|
||||
.into(),
|
||||
);
|
||||
|
|
@ -7012,7 +7056,8 @@ impl Tab {
|
|||
let path = path.clone();
|
||||
|
||||
// Acquire semaphore permit
|
||||
_ = THUMB_SEMAPHORE.acquire().await;
|
||||
let _permit =
|
||||
THUMB_SEMAPHORE.acquire().await.unwrap();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let start = Instant::now();
|
||||
|
|
|
|||
30
src/trash.rs
30
src/trash.rs
|
|
@ -2,6 +2,7 @@ use cosmic::widget;
|
|||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::config::IconSizes;
|
||||
use crate::tab::{Item, SearchItem};
|
||||
|
|
@ -89,11 +90,12 @@ pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
|
|||
/// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path
|
||||
/// - Compute: `<original_path>/sub/file.txt`
|
||||
pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
|
||||
let files = p.ancestors().find(|a| a.ends_with("files"))?;
|
||||
let files = trash_files_dir(p)?;
|
||||
let root = files.parent()?;
|
||||
let top = p.strip_prefix(files).ok()?.components().next()?;
|
||||
let info =
|
||||
std::fs::read_to_string(root.join("info").join(top).with_extension("trashinfo")).ok()?;
|
||||
let mut trashinfo_name = top.as_os_str().to_os_string();
|
||||
trashinfo_name.push(".trashinfo");
|
||||
let info = std::fs::read_to_string(root.join("info").join(trashinfo_name)).ok()?;
|
||||
let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?;
|
||||
let rel = p.strip_prefix(files.join(top)).ok()?;
|
||||
let mut result = PathBuf::from(&orig);
|
||||
|
|
@ -103,9 +105,29 @@ pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
|
|||
Some(result)
|
||||
}
|
||||
|
||||
static TRASH_FOLDERS: LazyLock<HashSet<PathBuf>> = LazyLock::new(|| {
|
||||
Trash::folders().unwrap_or_else(|e| {
|
||||
log::warn!("failed to list trash folders: {}", e);
|
||||
HashSet::new()
|
||||
})
|
||||
});
|
||||
|
||||
fn is_trash_root(root: &Path) -> bool {
|
||||
if TRASH_FOLDERS.is_empty() {
|
||||
root.join("info").is_dir()
|
||||
} else {
|
||||
TRASH_FOLDERS.contains(root)
|
||||
}
|
||||
}
|
||||
|
||||
fn trash_files_dir(path: &Path) -> Option<&Path> {
|
||||
path.ancestors()
|
||||
.find(|a| a.ends_with("files") && a.parent().is_some_and(is_trash_root))
|
||||
}
|
||||
|
||||
/// Check whether a path is inside any trash `files/` directory.
|
||||
pub fn is_trash_path(path: &Path) -> bool {
|
||||
path.ancestors().any(|a| a.ends_with("files"))
|
||||
trash_files_dir(path).is_some()
|
||||
}
|
||||
|
||||
pub struct Trash;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue