diff --git a/Cargo.lock b/Cargo.lock index b01240f..5d2823c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1494,7 +1494,8 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.19.0" +version = "0.18.2" +source = "git+https://github.com/pop-os/cosmic-text.git#4d74f795cc771fdcc7ea0f9cacba63fcf036fad6" dependencies = [ "bitflags 2.11.1", "fontdb", @@ -1512,7 +1513,6 @@ dependencies = [ "unicode-linebreak", "unicode-script", "unicode-segmentation", - "unicode-width", ] [[package]] @@ -9448,3 +9448,7 @@ dependencies = [ "syn", "winnow 0.7.15", ] + +[[patch.unused]] +name = "cosmic-text" +version = "0.19.0" diff --git a/Cargo.toml b/Cargo.toml index 6ba4d74..18c9010 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,7 @@ window_clipboard = { path = "/home/lionel/Devels/window_clipboard" } dnd = { path = "/home/lionel/Devels/window_clipboard/dnd" } mime = { path = "/home/lionel/Devels/window_clipboard/mime" } -[patch.'https://github.com/pop-os/cosmic-text.git'] +[patch.'https://github.com/pop-os/cosmic-text'] cosmic-text = { path = "/home/lionel/Devels/cosmic-text" } [workspace] diff --git a/docs/local-performance-and-portal-notes.md b/docs/local-performance-and-portal-notes.md deleted file mode 100644 index a3dabbe..0000000 --- a/docs/local-performance-and-portal-notes.md +++ /dev/null @@ -1,78 +0,0 @@ -# Local performance and portal notes - -Date: 2026-05-05 - -This repository carries a local patch aimed at improving initial directory -display latency in large folders, plus a system-level workaround for a COSMIC -portal FileChooser crash observed with Firefox and Chromium. - -## Directory listing latency - -The slow path was the initial construction of `Item` values in `src/tab.rs`. -On a test folder with about 2000 entries, raw filesystem enumeration and stat -calls completed in a few milliseconds, while COSMIC Files took multiple seconds -before showing the directory. - -The local patch keeps initial item construction cheap: - -- directory child counts are no longer computed synchronously in - `item_from_entry` and `item_from_gvfs_info`; -- initial MIME detection uses extension-based `mime_guess`; -- regular files use a generic file icon during the initial scan instead of - resolving the full MIME icon immediately. - -This keeps folders and `.desktop` files special-cased, while avoiding expensive -per-file work for ordinary files during first paint. - -Measured locally during investigation: - -- before the MIME/icon changes: about 3.1 seconds for `~/Téléchargements`; -- after avoiding full MIME icon resolution during scan: below the temporary - 100 ms perf-log threshold for the same folder. - -## File chooser portal workaround - -Firefox and Chromium were failing to open `Save As` on the first attempt because -`xdg-desktop-portal-cosmic` crashed while handling `FileChooser`. - -Logs showed: - -```text -Backend call failed: Remote peer disconnected -xdg-desktop-portal-cosmic ... status=11/SEGV -``` - -The working local system workaround is to remove -`org.freedesktop.impl.portal.FileChooser` from: - -```text -/usr/share/xdg-desktop-portal/portals/cosmic.portal -``` - -so the file chooser falls back to GTK. The resulting local `cosmic.portal` -keeps COSMIC for the other interfaces: - -```ini -[portal] -DBusName=org.freedesktop.impl.portal.desktop.cosmic -Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast -UseIn=COSMIC -``` - -Backups created locally: - -```text -/usr/share/xdg-desktop-portal/portals/cosmic.portal.bak-codex-20260505-filechooser -/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505 -/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505-2 -``` - -After editing portal files, restart: - -```sh -systemctl --user restart xdg-desktop-portal.service xdg-desktop-portal-gtk.service -``` - -Package updates may overwrite `/usr/share/xdg-desktop-portal/portals/cosmic.portal`. -If `Save As` starts needing two attempts again, re-check that `FileChooser` has -not been reintroduced in `cosmic.portal`. diff --git a/src/clipboard.rs b/src/clipboard.rs index 89da7d7..5892071 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -132,10 +132,9 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { match mime.as_str() { "text/uri-list" => { let text = str::from_utf8(&data)?; - for line in text.lines().filter(|line| { - let line = line.trim(); - !line.is_empty() && !line.starts_with('#') - }) { + let lines = text.lines(); + + for line in text.lines() { let url = Url::parse(line)?; match url.to_file_path() { Ok(path) => paths.push(path), diff --git a/src/tab.rs b/src/tab.rs index 641239f..94eacdd 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -302,26 +302,6 @@ pub fn folder_icon_symbolic(path: &PathBuf, icon_size: u16) -> widget::icon::Han .handle() } -fn generic_file_icons( - sizes: IconSizes, -) -> ( - widget::icon::Handle, - widget::icon::Handle, - widget::icon::Handle, -) { - ( - widget::icon::from_name("text-x-generic") - .size(sizes.grid()) - .handle(), - widget::icon::from_name("text-x-generic") - .size(sizes.list()) - .handle(), - widget::icon::from_name("text-x-generic") - .size(sizes.list_condensed()) - .handle(), - ) -} - //TODO: replace with Path::has_trailing_sep when stable fn has_trailing_sep(path: &Path) -> bool { path.as_os_str() @@ -685,9 +665,9 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS folder_icon(&path, sizes.list_condensed()), ) } else { - // Keep the initial directory scan cheap. Opening files still - // recalculates MIME from the real path before launching apps. - let mime = mime_guess::from_path(&path).first_or_octet_stream(); + // ALWAYS assume we're remote for mime guessing here, since gvfs reading can be expensive + // @todo - expose this as a config option? + let mime = mime_for_path(&path, None, true); //TODO: clean this up, implement for trash let icon_name_opt = if mime == "application/x-desktop" { @@ -704,21 +684,28 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS desktop_icon_handle(&icon_name, sizes.list_condensed()), ) } else { - let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = - generic_file_icons(sizes); ( - mime, - icon_handle_grid, - icon_handle_list, - icon_handle_list_condensed, + mime.clone(), + mime_icon(mime.clone(), sizes.grid()), + mime_icon(mime.clone(), sizes.list()), + mime_icon(mime, sizes.list_condensed()), ) } }; - let children_opt = None; + let mut children_opt = None; let mut dir_size = DirSize::NotDirectory; if is_dir && !remote { dir_size = DirSize::Calculating(Controller::default()); + //TODO: calculate children in the background (and make it cancellable?) + match fs::read_dir(&path) { + Ok(entries) => { + children_opt = Some(entries.count()); + } + Err(err) => { + log::warn!("failed to read directory {}: {}", path.display(), err); + } + } } let display_name = display_name_for_file(&path, &file_info.display_name(), false, is_desktop); @@ -820,9 +807,7 @@ pub fn item_from_entry( folder_icon(&path, sizes.list_condensed()), ) } else { - // Keep the initial directory scan cheap. Opening files still - // recalculates MIME from the real path before launching apps. - let mime = mime_guess::from_path(&path).first_or_octet_stream(); + let mime = mime_for_path(&path, Some(&metadata), remote); //TODO: clean this up, implement for trash let icon_name_opt = if mime == "application/x-desktop" { is_desktop = true; @@ -838,21 +823,28 @@ pub fn item_from_entry( desktop_icon_handle(&icon_name, sizes.list_condensed()), ) } else { - let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = - generic_file_icons(sizes); ( - mime, - icon_handle_grid, - icon_handle_list, - icon_handle_list_condensed, + mime.clone(), + mime_icon(mime.clone(), sizes.grid()), + mime_icon(mime.clone(), sizes.list()), + mime_icon(mime, sizes.list_condensed()), ) } }; - let children_opt = None; + let mut children_opt = None; let mut dir_size = DirSize::NotDirectory; if metadata.is_dir() && !remote { dir_size = DirSize::Calculating(Controller::default()); + //TODO: calculate children in the background (and make it cancellable?) + match fs::read_dir(&path) { + Ok(entries) => { + children_opt = Some(entries.count()); + } + Err(err) => { + log::warn!("failed to read directory {}: {}", path.display(), err); + } + } } let display_name = display_name_for_file(&path, &name, is_gvfs, is_desktop); diff --git a/src/trash.rs b/src/trash.rs index cc39d02..ce37c83 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -27,7 +27,7 @@ pub trait TrashExt { Vec::new() } - fn scan_search bool + Sync>(callback: F, regex: &Regex); + fn scan_search bool + Sync>(callback: F, regex: &Regex) {} fn icon(icon_size: u16) -> widget::icon::Handle { widget::icon::from_name(if Self::is_empty() { @@ -142,12 +142,4 @@ impl TrashExt for Trash { not(target_os = "android") ) )))] -impl TrashExt for Trash { - fn scan_search bool + Sync>(callback: F, regex: &Regex) { - log::warn!( - "searching trash not supported on this platform for pattern {:?}", - regex.as_str() - ); - drop(callback); - } -} +impl TrashExt for Trash {}