Compare commits

...

21 commits

Author SHA1 Message Date
57ab1ecbf4 fix: clean files warnings for terminal build 2026-05-24 10:27:32 +02:00
6f3adcd993 chore: clean feature-gated warnings 2026-05-23 20:49:24 +02:00
Votre Nom
35e115fdb5 yoda: switch cosmic-text patch to public Forgejo fork
Replace the path-local [patch] redirect with a git redirect to
https://forge.aditua.com/leyoda/cosmic-text.git (branch local/pr-503,
which carries upstream PR #503 plus the EAW monospace width fix). No
more absolute path dependency on /home/lionel/Devels/cosmic-text.

Leyoda 2026 – GPLv3
2026-05-05 15:32:47 +02:00
Votre Nom
69c35ab80f yoda: switch window_clipboard patch to public Forgejo fork
Replace the path-local [patch] redirect with a git redirect to
https://forge.aditua.com/leyoda/window_clipboard.git (branch
yoda-x11-optional). Removes the absolute path dependency on
/home/lionel/Devels/window_clipboard so any clone can build.

The patch is still needed to consolidate the upstream pop-os/libcosmic
chain (pulled by cosmic-settings-daemon) onto the same fork iced uses,
otherwise cargo would compile two versions of window_clipboard.

Leyoda 2026 – GPLv3
2026-05-05 13:47:17 +02:00
Votre Nom
d080bc85af Resolve cosmic-files warnings without masking
Parse text/uri-list according to the real clipboard format, make unsupported trash search explicit, and update the lockfile so the local cosmic-text patch is actually used instead of reported as unused.
2026-05-05 08:09:17 +02:00
Votre Nom
338354c4d0 Improve initial directory listing latency
Avoid synchronous child counts during item construction, use extension-based MIME detection for initial scans, and defer expensive MIME icon resolution by using generic file icons for ordinary files. Document the local xdg-desktop-portal FileChooser workaround for COSMIC portal crashes.
2026-05-05 08:00:08 +02:00
f0538190d9 yoda: toolbar icon-only + clean visual (Control style, 32px squares)
User feedback: segmented visual was noisy; only want icons, no labels.

Changes:
- rebuild_toolbar_model no longer sets .text() on entities, so the
  segmented_button widget draws icon-only squares.
- toolbar renders with style(SegmentedButton::Control), button_height
  + min/max_button_width pinned at 32 px (square icon buttons),
  button_spacing = space_xs for clear separation (was space_xxs which
  looked conjoined), Alignment::Center.
- container width = Length::Shrink so the toolbar only takes the space
  it needs instead of stretching across the window.
2026-04-24 11:12:26 +02:00
94c3e6c551 yoda: toolbar as segmented_button for working drag reorder
The generic dnd_source+dnd_destination pairing didn't reliably fire
on intra-window reorders in this setup, while segmented_button's
built-in drag (same primitive powering tab_bar, which does work) is
proven. Switched the toolbar rendering to segmented_button::horizontal
with drag enabled — each segment carries its ToolbarAction as data.

App state:
- new toolbar_model: segmented_button::Model<SingleSelect>
- rebuild_toolbar_model() mirrors config.toolbar into the model on
  every update_config (including the initial app.update_config at
  startup)
- sync_toolbar_config_from_model() is the reverse: walk the model's
  entity order after a reorder, write the new Vec<ToolbarAction>
  directly via config.set_toolbar (without calling update_config so
  we don't rebuild the model and wipe the reorder the user just did)

Messages:
- ToolbarTabActivate(Entity): look up action via model.data(), clear
  the model's active selection (segmented_button single-select would
  keep the last click highlighted; we don't want that for action
  buttons), dispatch the action's message.
- ToolbarTabReorder(ReorderEvent): model.reorder then sync.

View:
- replaces the row-of-dnd-wrapped-icon-buttons with
  segmented_button::horizontal(&self.toolbar_model)
  .enable_tab_drag("x-cosmic-files/toolbar-dnd") .on_reorder(...) .on_activate(...)
- fixed 36-px square buttons so it still looks toolbar-y rather than
  stretched pill-segmented-control

Kept: Settings panel ↑↓/add/remove UI (no regression).
Removed: dnd_source/dnd_destination wrappers from the toolbar (but
the ToolbarActionPayload + MIME constant remain in case Settings DnD
gets unstuck later).
2026-04-24 11:03:05 +02:00
af843d204d yoda: direct drag-drop reorder on the toolbar itself
User feedback: going through Settings to reorder is too indirect. Now
each toolbar button is wrapped in dnd_source + dnd_destination so the
user can grab an icon in the live toolbar and drop it onto another
icon to reorder in-place. The underlying icon button keeps firing its
on_press for quick clicks (dnd_source only starts a drag past the
default 8 px motion threshold), so regular clicks continue to run the
associated Action — no mode-switch needed.

Settings retains the ↑↓/add/remove UI as a fallback (discoverability
+ keyboard-friendly) and for the same drag-drop if the user prefers
working from the panel. The config model (Vec<ToolbarAction> +
ToolbarReorder message) is already shared, so both paths mutate the
same state.
2026-04-24 08:37:19 +02:00
11d435770e yoda: add ↑↓ buttons next to drag handle in toolbar editor
DnD source+destination wiring from 1cf17dc builds but the drag-drop
doesn't fire reliably in practice (suspected: either dnd_source's
shell.capture_event() on CursorMoved swallows events the destination
needs to see, or intra-window DnD has a Wayland-specific hiccup on the
current sctk/cosmic-comp pairing).

Adds two plain click-handled buttons per enabled row (↑ go-up-symbolic,
↓ go-down-symbolic, both disabled at list edges) so reorder is
functional regardless of DnD state. Backed by new messages
ToolbarMoveUp / ToolbarMoveDown that swap adjacent positions in the
Vec<ToolbarAction>.

The drag handle + DnD source/destination wrapping stays in place — if
DnD gets fixed upstream or on a future libcosmic it'll Just Work, and
the arrows remain as a keyboard-friendly fallback.
2026-04-24 08:29:07 +02:00
1cf17dcde8 yoda: phase 3 — drag-drop toolbar editor in Settings
Migrates the config model from the phase-2 bag-of-bools (ToolbarItems)
to an ordered Vec<ToolbarAction> so the user can pick BOTH the set of
buttons AND their order in the toolbar.

Config (config.rs):
- new ToolbarAction enum with 11 variants (LocationUp, Reload,
  NewFolder, NewFile, Rename, Delete, Cut, Copy, Paste,
  ToggleShowHidden, OpenTerminal) + to_u8/from_u8 for DnD payload
- Config.toolbar: Vec<ToolbarAction>, default = default_toolbar()
  (NewFolder, Rename, Delete, Cut, Copy, Paste — same 6 as phase 2)

Rendering (view()):
- iterate self.config.toolbar in order and emit a tooltip'd icon button
  per entry via the new toolbar_action_ui(action) helper shared with
  the Settings page. Paste stays disabled when clipboard empty.
- No hardcoded groups or auto-dividers anymore — order is 100% user.

Settings page (toolbar_settings_section):
- two stacked lists:
  * 'Toolbar': currently-enabled actions in their Vec order. Each row
    is wrapped in dnd_source (drags a ToolbarActionPayload carrying
    the enum discriminant) + dnd_destination (accepts drops from other
    rows, fires Message::ToolbarReorder { src, target } to move src
    before target in the Vec). A list-drag-handle icon + a minus button
    (ToolbarRemove) per row.
  * 'Available': actions not yet enabled, each with a plus button
    (ToolbarAdd) that pushes to the end of the Vec.
- 'Reset to defaults' button at the bottom (ToolbarReset).

DnD infra (app.rs top):
- TOOLBAR_MIME constant: 'application/x-cosmic-files-toolbar-action'
- ToolbarActionPayload(u8) with AsMimeTypes + AllowedMimeTypes +
  TryFrom<(Vec<u8>, String)> impls — single-byte wire format matching
  the enum discriminant.

Messages:
- ToolbarAdd(ToolbarAction) — append to toolbar vec if absent
- ToolbarRemove(ToolbarAction)
- ToolbarReorder { src, target } — remove src, reinsert before target
- ToolbarReset — restore default_toolbar()

i18n (en + fr):
- new keys: toolbar-available, toolbar-empty-hint, toolbar-reset

Migration: existing installs with a phase-2 ToolbarItems struct in
their config will error at load time (different shape); cosmic_config
falls back to Self::default() which gives the phase-2 minimal-6 set —
a safe reset rather than a broken partial read.
2026-04-24 08:13:30 +02:00
33a5c8ff99 yoda: phase 2 — customizable toolbar (settings toggles per button)
Phase 1 shipped a fixed 6-button toolbar. Phase 2 moves visibility to
the config so users pick which buttons appear.

Config (config.rs):
- new ToolbarItems struct (CosmicConfigEntry) with one bool per button
- Config.toolbar: ToolbarItems, default = 'minimal 6' set from phase 1
  (new_folder, rename, delete, cut, copy, paste) + 5 extras off
  (new_file, reload, toggle_show_hidden, open_terminal, location_up)

Rendering (view()):
- iterate through self.config.toolbar fields in fixed logical order
  (location → create/edit → clipboard → view toggles)
- dividers inserted only between non-empty groups
- whole toolbar hidden if every button is off (no empty container)

Settings page (settings()):
- new 'Toolbar' section with one toggler per button, wired through
  Message::SetToolbar(ToolbarItems) which persists via config_set!

i18n (en + fr):
- added 'toolbar' + 'parent-directory' strings
- reused existing new-folder / new-file / rename / delete / cut / copy /
  paste / reload-folder / show-hidden-files / open-in-terminal

All actions dispatch through Action::message so keybindings and toolbar
share one code path.
2026-04-24 07:53:49 +02:00
8b51af1632 yoda: use pencil-symbolic for the Rename toolbar button
document-edit-symbolic isn't in the Cosmic theme (checked Cosmic, Pop,
Adwaita, WhiteSur-dark ship lists) so it rendered empty. The Cosmic
theme ships pencil-symbolic which matches the edit/rename semantics
and is guaranteed to resolve through the current icon theme chain.
2026-04-24 07:48:53 +02:00
4b6d345139 yoda: fix missing rename icon in toolbar
edit-rename-symbolic isn't part of the COSMIC/Pop/Adwaita/WhiteSur-dark
icon sets, so the Rename button rendered empty. Swap to
document-edit-symbolic which is present in Adwaita (the standard fallback
in the freedesktop-icons resolution chain) and semantically fits edit/rename.
2026-04-24 07:43:13 +02:00
0595296609 yoda: Dolphin-style quick actions toolbar under the headerbar
Adds a full-width row of 6 icon buttons between the tab bar and the
tab view: New folder · Rename · Delete | Cut · Copy · Paste. Paste is
disabled when the clipboard is empty (existing self.clipboard_has_content
check). A vertical divider separates file-ops (3 first) from clipboard
ops (3 last).

Implementation reuses Action::message(entity_opt = None) so keybinding
and toolbar dispatch share exactly the same code path — no duplication.
Icons are freedesktop *-symbolic names so they inherit the COSMIC
theme's symbolic color. Tooltips use the existing fl!() strings
(new-folder / rename / delete / cut / copy / paste, EN + FR).

Customization (pick which buttons show up) is deferred to a follow-up
commit — this first pass is fixed at the minimal-6 set per the user's
spec.
2026-04-24 07:38:17 +02:00
8fb2b15c68 yoda wayland-v5: redirect window_clipboard + cosmic-text to local forks
Propagates the [patch] blocks added in cosmic-yoterm v5 to keep the
whole yoda app family on a single Wayland-only stack. Without these,
iced_winit fails to select a window_clipboard version because our
fork exposes a `wayland` feature that upstream doesn't.

- window_clipboard → /home/lionel/Devels/window_clipboard (x11 gated
  behind opt-in feature)
- cosmic-text → /home/lionel/Devels/cosmic-text (EAW terminal_cells +
  upstream PR#503 applied)
2026-04-24 07:09:48 +02:00
e8d62ae43d yoda: add "Always use this app" toggle to OpenWith dialog
The 'Open with...' dialog let you pick an app but never remembered your
choice — you'd see the same dialog again next time. The infrastructure
was already there (MimeAppCache::set_default writes to mimeapps.list),
just never wired to the UI.

Adds a toggler below the app list labelled 'Always use this app for this
file type' (EN) / 'Toujours utiliser cette application pour ce type de
fichier' (FR). When enabled, after spawning the selected app, the
default handler for the file's mime type is persisted via
self.mime_app_cache.set_default(mime, app.id).

Implementation:
- DialogPage::OpenWith gains a set_default: bool field (defaulted false)
- Message::OpenWithToggleDefault(bool) + handler mutates the dialog state
- DialogComplete handler for OpenWith calls set_default after a clean
  spawn when the flag is set
- Dialog rendering adds a .control(widget::row) with label + toggler,
  between the scrollable list and the action buttons
- i18n strings added: en/fr open-with-set-default
2026-04-23 20:18:21 +02:00
a025fd6380 yoda: prefer cosmic-yoterm over upstream cosmic-term in terminal fallback
mime_app::MimeAppCache::terminal() hardcoded "com.system76.CosmicTerm"
as the only non-xdg-default fallback. On a yoda stack the relevant
terminal is our fork cosmic-yoterm (desktop id com.aditua.CosmicYoterm),
so we add it first in preference_order. xdg-mime default still wins
when set — this just covers the case where it isn't.

Fixes "Open in terminal" launching Konsole (or first random terminal in
apps list) instead of cosmic-yoterm when xdg-mime default is unset or
points to something else.
2026-04-23 19:17:26 +02:00
02adcc3cf6 lockfile: libcosmic-yoda 0.1.0-yoda -> 0.1.0-yoda.2
Picks up the yoda-v2 libcosmic changes (color_picker Theme ref +
context_menu/menu winit ungate). Binary rebuilt and installed.
2026-04-23 18:46:16 +02:00
04abd13d93 yoda: depend on libcosmic-yoda (path) instead of upstream libcosmic
Rewire cosmic-files (lib + file manager) onto the yoda fork of libcosmic.
- [dependencies.libcosmic] removed, replaced by [dependencies.libcosmic-yoda]
  pointing at ../libcosmic (local path; the leyoda/libcosmic-yoda clone)
- Features: winit dropped, wayland added explicitly in the default set
- Feature refs "libcosmic/xxx" rewritten to "libcosmic-yoda/xxx"
- [patch] block removed — transitive libcosmic refs no longer exist

cosmic-files lib and the file manager binary build clean against
libcosmic-yoda 0.1.0-yoda (3 warnings, all pre-existing unused-var
in search code).
2026-04-23 15:38:04 +02:00
Votre Nom
9bcfe7a1f5 Cargo.toml: patch libcosmic via local path for dev builds
Activates the [patch.'https://github.com/pop-os/libcosmic'] override
pointing at ../libcosmic, enabling local development against a
patched libcosmic checkout (e.g. to pick up
WindowControlsPosition / macOS-style window controls).

This branch is intentionally dev-local: do NOT merge upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:13:55 +02:00
17 changed files with 851 additions and 270 deletions

283
Cargo.lock generated
View file

@ -255,7 +255,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -266,7 +266,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -328,12 +328,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "as-raw-xcb-connection"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
[[package]]
name = "as-slice"
version = "0.2.1"
@ -979,7 +973,7 @@ dependencies = [
[[package]]
name = "clipboard_macos"
version = "0.1.0"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"objc",
"objc-foundation",
@ -989,7 +983,7 @@ dependencies = [
[[package]]
name = "clipboard_wayland"
version = "0.2.2"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"dnd",
"mime 0.1.0",
@ -999,7 +993,7 @@ dependencies = [
[[package]]
name = "clipboard_x11"
version = "0.4.2"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"thiserror 1.0.69",
"x11rb",
@ -1314,14 +1308,13 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
"cosmic-config-derive 1.0.0",
"cosmic-settings-daemon",
"dirs 6.0.0",
"futures-util",
"iced_futures",
"iced_futures 0.14.0",
"known-folders",
"notify",
"ron 0.12.1",
@ -1332,10 +1325,35 @@ dependencies = [
"zbus 5.14.0",
]
[[package]]
name = "cosmic-config"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic#dad5f1e2731dbdccb3044f136a81f18dfead9de4"
dependencies = [
"atomicwrites",
"cosmic-config-derive 1.0.0 (git+https://github.com/pop-os/libcosmic)",
"dirs 6.0.0",
"iced_futures 0.14.0 (git+https://github.com/pop-os/libcosmic)",
"known-folders",
"notify",
"ron 0.12.1",
"serde",
"tracing",
"xdg",
]
[[package]]
name = "cosmic-config-derive"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "cosmic-config-derive"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic#dad5f1e2731dbdccb3044f136a81f18dfead9de4"
dependencies = [
"quote",
"syn",
@ -1368,7 +1386,7 @@ dependencies = [
"jiff-icu",
"jxl-oxide",
"libc",
"libcosmic",
"libcosmic-yoda",
"log",
"lzma-rust2",
"md-5",
@ -1461,7 +1479,7 @@ name = "cosmic-settings-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-settings-daemon#716da6d6af0b252e2f78aba2ad72ee19ae0241e0"
dependencies = [
"cosmic-config",
"cosmic-config 1.0.0 (git+https://github.com/pop-os/libcosmic)",
"ron 0.11.0",
"serde",
"serde_with",
@ -1479,8 +1497,8 @@ dependencies = [
[[package]]
name = "cosmic-text"
version = "0.18.2"
source = "git+https://github.com/pop-os/cosmic-text.git#4d74f795cc771fdcc7ea0f9cacba63fcf036fad6"
version = "0.19.0"
source = "git+https://forge.aditua.com/leyoda/cosmic-text.git?branch=local%2Fpr-503#63072bbe29a1657d82cd3deb5db45070404ec7a1"
dependencies = [
"bitflags 2.11.1",
"fontdb",
@ -1498,16 +1516,16 @@ dependencies = [
"unicode-linebreak",
"unicode-script",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "cosmic-theme"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"almost",
"configparser",
"cosmic-config",
"cosmic-config 1.0.0",
"csscolorparser",
"dirs 6.0.0",
"palette",
@ -1615,12 +1633,6 @@ dependencies = [
"uncased",
]
[[package]]
name = "ctor-lite"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300"
[[package]]
name = "cursor-icon"
version = "1.2.0"
@ -1801,7 +1813,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@ -1850,7 +1862,7 @@ dependencies = [
[[package]]
name = "dnd"
version = "0.1.0"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"bitflags 2.11.1",
"mime 0.1.0",
@ -1879,45 +1891,6 @@ name = "dpi"
version = "0.1.2"
source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366"
[[package]]
name = "drm"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde"
dependencies = [
"bitflags 2.11.1",
"bytemuck",
"drm-ffi",
"drm-fourcc",
"rustix 0.38.44",
]
[[package]]
name = "drm-ffi"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6"
dependencies = [
"drm-sys",
"rustix 0.38.44",
]
[[package]]
name = "drm-fourcc"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
[[package]]
name = "drm-sys"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176"
dependencies = [
"libc",
"linux-raw-sys 0.6.5",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
@ -2011,7 +1984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -2489,7 +2462,7 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows-link 0.2.1",
"windows-link 0.1.3",
"windows-result 0.4.1",
]
@ -2618,7 +2591,7 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -2988,13 +2961,12 @@ dependencies = [
[[package]]
name = "iced"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"dnd",
"iced_accessibility",
"iced_core",
"iced_core 0.14.0",
"iced_debug",
"iced_futures",
"iced_futures 0.14.0",
"iced_program",
"iced_renderer",
"iced_runtime",
@ -3009,7 +2981,6 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"accesskit",
"accesskit_winit",
@ -3018,7 +2989,6 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"bitflags 2.11.1",
"bytes",
@ -3039,23 +3009,43 @@ dependencies = [
"window_clipboard",
]
[[package]]
name = "iced_core"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic#dad5f1e2731dbdccb3044f136a81f18dfead9de4"
dependencies = [
"bitflags 2.11.1",
"bytes",
"dnd",
"glam",
"lilt",
"log",
"mime 0.1.0",
"num-traits",
"palette",
"raw-window-handle",
"rustc-hash 2.1.2",
"smol_str",
"thiserror 2.0.18",
"web-time",
"window_clipboard",
]
[[package]]
name = "iced_debug"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"iced_core",
"iced_futures",
"iced_core 0.14.0",
"iced_futures 0.14.0",
"log",
]
[[package]]
name = "iced_futures"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"futures",
"iced_core",
"iced_core 0.14.0",
"log",
"rustc-hash 2.1.2",
"tokio",
@ -3063,17 +3053,29 @@ dependencies = [
"wasmtimer",
]
[[package]]
name = "iced_futures"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic#dad5f1e2731dbdccb3044f136a81f18dfead9de4"
dependencies = [
"futures",
"iced_core 0.14.0 (git+https://github.com/pop-os/libcosmic)",
"log",
"rustc-hash 2.1.2",
"wasm-bindgen-futures",
"wasmtimer",
]
[[package]]
name = "iced_graphics"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"bitflags 2.11.1",
"bytemuck",
"cosmic-text",
"half",
"iced_core",
"iced_futures",
"iced_core 0.14.0",
"iced_futures 0.14.0",
"image",
"kamadak-exif",
"log",
@ -3087,7 +3089,6 @@ dependencies = [
[[package]]
name = "iced_program"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"iced_graphics",
"iced_runtime",
@ -3096,7 +3097,6 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -3108,13 +3108,12 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"bytes",
"cosmic-client-toolkit",
"dnd",
"iced_core",
"iced_futures",
"iced_core 0.14.0",
"iced_futures 0.14.0",
"raw-window-handle",
"thiserror 2.0.18",
"window_clipboard",
@ -3123,7 +3122,6 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"bytemuck",
"cosmic-text",
@ -3140,9 +3138,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"as-raw-xcb-connection",
"bitflags 2.11.1",
"bytemuck",
"cosmic-client-toolkit",
@ -3159,19 +3155,16 @@ dependencies = [
"rustc-hash 2.1.2",
"rustix 0.38.44",
"thiserror 2.0.18",
"tiny-xlib",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-sys",
"wgpu",
"x11rb",
]
[[package]]
name = "iced_widget"
version = "0.14.2"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"cosmic-client-toolkit",
"dnd",
@ -3189,13 +3182,12 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.14.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
dependencies = [
"cosmic-client-toolkit",
"cursor-icon",
"dnd",
"iced_debug",
"iced_futures",
"iced_futures 0.14.0",
"iced_graphics",
"iced_program",
"iced_runtime",
@ -3892,7 +3884,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -4234,7 +4226,7 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@ -4309,15 +4301,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
[[package]]
name = "libcosmic"
version = "1.0.0"
source = "git+https://github.com/pop-os/libcosmic.git#c423ad1bfc25057922406c687f2ddc75ead5ab67"
name = "libcosmic-yoda"
version = "0.1.0-yoda.2"
dependencies = [
"apply",
"ashpd 0.12.3",
"auto_enums",
"cosmic-client-toolkit",
"cosmic-config",
"cosmic-config 1.0.0",
"cosmic-freedesktop-icons",
"cosmic-settings-config",
"cosmic-settings-daemon",
@ -4330,8 +4321,8 @@ dependencies = [
"i18n-embed",
"i18n-embed-fl",
"iced",
"iced_core",
"iced_futures",
"iced_core 0.14.0",
"iced_futures 0.14.0",
"iced_renderer",
"iced_runtime",
"iced_tiny_skia",
@ -4420,12 +4411,6 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@ -4663,7 +4648,7 @@ dependencies = [
[[package]]
name = "mime"
version = "0.1.0"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"smithay-clipboard",
]
@ -4897,7 +4882,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@ -5274,7 +5259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@ -6261,7 +6246,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -6635,7 +6620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -6643,12 +6628,10 @@ name = "softbuffer"
version = "0.4.1"
source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#a3f77e251e7422803f693df6e3fc313c010c4dcb"
dependencies = [
"as-raw-xcb-connection",
"bytemuck",
"cfg_aliases",
"cocoa",
"core-graphics",
"drm",
"fastrand",
"foreign-types",
"js-sys",
@ -6658,14 +6641,12 @@ dependencies = [
"raw-window-handle",
"redox_syscall 0.5.18",
"rustix 0.38.44",
"tiny-xlib",
"wasm-bindgen",
"wayland-backend",
"wayland-client",
"wayland-sys",
"web-sys",
"windows-sys 0.52.0",
"x11rb",
]
[[package]]
@ -6857,7 +6838,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -7038,19 +7019,6 @@ dependencies = [
"strict-num",
]
[[package]]
name = "tiny-xlib"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e"
dependencies = [
"as-raw-xcb-connection",
"ctor-lite",
"libloading",
"pkg-config",
"tracing",
]
[[package]]
name = "tinystr"
version = "0.8.3"
@ -7299,7 +7267,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -8008,7 +7976,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@ -8020,7 +7988,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "window_clipboard"
version = "0.4.1"
source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#319db02e5219c557c8f03b0e33a8eb4075cabb85"
dependencies = [
"clipboard-win",
"clipboard_macos",
@ -8560,7 +8528,6 @@ dependencies = [
"winit-wayland",
"winit-web",
"winit-win32",
"winit-x11",
]
[[package]]
@ -8611,7 +8578,6 @@ dependencies = [
"smol_str",
"tracing",
"winit-core",
"x11-dl",
"xkbcommon-dl",
]
@ -8729,29 +8695,6 @@ dependencies = [
"winit-core",
]
[[package]]
name = "winit-x11"
version = "0.31.0-beta.2"
source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366"
dependencies = [
"bitflags 2.11.1",
"bytemuck",
"calloop",
"cursor-icon",
"dpi",
"libc",
"percent-encoding",
"raw-window-handle",
"rustix 1.1.4",
"smol_str",
"tracing",
"winit-common",
"winit-core",
"x11-dl",
"x11rb",
"xkbcommon-dl",
]
[[package]]
name = "winnow"
version = "0.7.15"
@ -8873,31 +8816,15 @@ dependencies = [
"either",
]
[[package]]
name = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
dependencies = [
"libc",
"once_cell",
"pkg-config",
]
[[package]]
name = "x11rb"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
dependencies = [
"as-raw-xcb-connection",
"gethostname",
"libc",
"libloading",
"once_cell",
"rustix 1.1.4",
"x11rb-protocol",
"xcursor",
]
[[package]]

View file

@ -72,8 +72,9 @@ version = "0.18"
default-features = false
features = ["fs", "io", "macros", "polling", "runtime"]
[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch).
[dependencies.libcosmic-yoda]
path = "../libcosmic"
default-features = false
#TODO: a11y feature crashes
features = [
@ -82,7 +83,7 @@ features = [
"autosize",
"multi-window",
"tokio",
"winit",
"wayland",
"surface-message",
]
@ -110,15 +111,15 @@ default = [
"wayland",
"wgpu",
]
dbus-config = ["libcosmic/dbus-config"]
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
dbus-config = ["libcosmic-yoda/dbus-config"]
desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
desktop-applet = []
gvfs = ["dep:gio", "dep:glib"]
io-uring = ["compio/io-uring"]
jemalloc = ["dep:tikv-jemallocator"]
notify = ["dep:notify-rust"]
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic/wgpu"]
wayland = ["libcosmic-yoda/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic-yoda/wgpu"]
[profile.dev]
opt-level = 1
@ -144,20 +145,22 @@ fastrand = "2"
test-log = "0.2"
tokio = { version = "1", features = ["rt", "macros"] }
# [patch.'https://github.com/pop-os/cosmic-text']
# cosmic-text = { path = "../cosmic-text" }
# Yoda fork — libcosmic dep is now a direct path dep (libcosmic-yoda above),
# no [patch] block needed anymore. Keeping the block below would be a no-op
# since nothing in the dep graph still asks for pop-os/libcosmic.git.
# [patch.'https://github.com/pop-os/libcosmic']
# libcosmic = { path = "../libcosmic" }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# Yoda wayland cut: redirect window_clipboard (x11 gated behind opt-in
# feature) and cosmic-text (PR#503: EAW monospace width fix) to our public
# Forgejo forks. The window_clipboard patch is needed to consolidate the
# upstream pop-os/libcosmic chain (pulled by cosmic-settings-daemon) onto
# the same fork iced uses, otherwise cargo compiles two versions.
[patch.'https://github.com/pop-os/window_clipboard.git']
window_clipboard = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
dnd = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
# [patch.'https://github.com/pop-os/smithay-clipboard']
# smithay-clipboard = { path = "../smithay-clipboard" }
[patch.'https://github.com/pop-os/cosmic-text.git']
cosmic-text = { git = "https://forge.aditua.com/leyoda/cosmic-text.git", branch = "local/pr-503" }
[workspace]
members = ["cosmic-files-applet"]

View file

@ -0,0 +1,78 @@
# 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`.

View file

@ -96,6 +96,7 @@ save-file = Save file
## Open With Dialog
open-with-title = How do you want to open "{$name}"?
open-with-set-default = Always use this app for this file type
browse-store = Browse {$store}
other-apps = Other applications
related-apps = Related applications
@ -138,6 +139,11 @@ open-with = Open with
owner = Owner
group = Group
other = Other
toolbar = Toolbar
toolbar-available = Available
toolbar-empty-hint = No buttons. Drag or add from below.
toolbar-reset = Reset to defaults
parent-directory = Parent directory
mixed = Mixed
### Mode 0
none = None

View file

@ -92,6 +92,7 @@ save-file = Enregistrer fichier
## Open With Dialog
open-with-title = Comment souhaitez-vous ouvrir "{ $name }"?
open-with-set-default = Toujours utiliser cette application pour ce type de fichier
browse-store = Parcourir { $store }
## Permanently delete Dialog
@ -130,6 +131,11 @@ open-with = Ouvrir avec
owner = Propriétaire
group = Groupe
other = Autre
toolbar = Barre d'outils
toolbar-available = Disponibles
toolbar-empty-hint = Aucun bouton. Glisser-déposer ou ajouter depuis la liste ci-dessous.
toolbar-reset = Rétablir par défaut
parent-directory = Dossier parent
### Mode 0

View file

@ -51,6 +51,8 @@ use notify_debouncer_full::{
};
use rustc_hash::{FxHashMap, FxHashSet};
use slotmap::Key as SlotMapKey;
#[cfg(feature = "notify")]
use std::sync::Mutex;
use std::{
any::TypeId,
collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
@ -61,7 +63,7 @@ use std::{
path::{Path, PathBuf},
pin::Pin,
process,
sync::{Arc, LazyLock, Mutex},
sync::{Arc, LazyLock},
time::{self, Duration, Instant},
};
use tokio::sync::mpsc;
@ -77,7 +79,7 @@ use crate::{
},
config::{
AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig,
TimeConfig, TypeToSearch,
TimeConfig, ToolbarAction, TypeToSearch, default_toolbar,
},
context_action,
dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings},
@ -85,7 +87,7 @@ use crate::{
key_bind::key_binds,
localize::LANGUAGE_SORTER,
menu,
mime_app::{self, MimeApp, MimeAppCache},
mime_app::{MimeApp, MimeAppCache},
mime_icon,
mounter::{MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage},
operation::{
@ -145,6 +147,105 @@ pub struct Flags {
pub uris: Vec<url::Url>,
}
/// Yoda phase 3: MIME for the DnD payload carried when a user drags a
/// toolbar row in the Settings editor. A single byte = ToolbarAction
/// discriminant (see `ToolbarAction::to_u8`).
const TOOLBAR_MIME: &str = "application/x-cosmic-files-toolbar-action";
/// Yoda phase 3: DnD payload wrapping a ToolbarAction discriminant.
#[derive(Clone, Debug)]
pub struct ToolbarActionPayload(pub u8);
impl cosmic::iced::clipboard::mime::AsMimeTypes for ToolbarActionPayload {
fn available(&self) -> std::borrow::Cow<'static, [String]> {
std::borrow::Cow::Owned(vec![TOOLBAR_MIME.to_owned()])
}
fn as_bytes(&self, mime_type: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
if mime_type == TOOLBAR_MIME {
Some(std::borrow::Cow::Owned(vec![self.0]))
} else {
None
}
}
}
impl cosmic::iced::clipboard::mime::AllowedMimeTypes for ToolbarActionPayload {
fn allowed() -> std::borrow::Cow<'static, [String]> {
std::borrow::Cow::Owned(vec![TOOLBAR_MIME.to_owned()])
}
}
impl TryFrom<(Vec<u8>, String)> for ToolbarActionPayload {
type Error = ();
fn try_from((data, _mime): (Vec<u8>, String)) -> Result<Self, Self::Error> {
if data.len() == 1 {
Ok(Self(data[0]))
} else {
Err(())
}
}
}
/// Yoda phase 3 helper: map a ToolbarAction to its button UI (icon name,
/// localized label, app Message). Shared by the toolbar renderer in
/// `view()` and by the Settings page row renderer so the two stay in
/// sync.
fn toolbar_action_ui(a: ToolbarAction) -> (&'static str, String, Message) {
match a {
ToolbarAction::LocationUp => (
"go-up-symbolic",
fl!("parent-directory"),
Action::LocationUp.message(None),
),
ToolbarAction::Reload => (
"view-refresh-symbolic",
fl!("reload-folder"),
Action::Reload.message(None),
),
ToolbarAction::NewFolder => (
"folder-new-symbolic",
fl!("new-folder"),
Action::NewFolder.message(None),
),
ToolbarAction::NewFile => (
"document-new-symbolic",
fl!("new-file"),
Action::NewFile.message(None),
),
ToolbarAction::Rename => (
"pencil-symbolic",
fl!("rename"),
Action::Rename.message(None),
),
ToolbarAction::Delete => (
"edit-delete-symbolic",
fl!("delete"),
Action::Delete.message(None),
),
ToolbarAction::Cut => ("edit-cut-symbolic", fl!("cut"), Action::Cut.message(None)),
ToolbarAction::Copy => (
"edit-copy-symbolic",
fl!("copy"),
Action::Copy.message(None),
),
ToolbarAction::Paste => (
"edit-paste-symbolic",
fl!("paste"),
Action::Paste.message(None),
),
ToolbarAction::ToggleShowHidden => (
"view-reveal-symbolic",
fl!("show-hidden-files"),
Action::ToggleShowHidden.message(None),
),
ToolbarAction::OpenTerminal => (
"utilities-terminal-symbolic",
fl!("open-in-terminal"),
Action::OpenTerminal.message(None),
),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
About,
@ -407,6 +508,7 @@ pub enum Message {
OpenWithBrowse,
OpenWithDialog(Option<Entity>),
OpenWithSelection(usize),
OpenWithToggleDefault(bool),
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
Overlap(window::Id, OverlapNotifyEvent),
Paste(Option<Entity>),
@ -447,6 +549,22 @@ pub enum Message {
SearchInput(String),
SetShowDetails(bool),
SetShowRecents(bool),
/// Yoda phase 3 — toolbar editing messages.
ToolbarAdd(ToolbarAction),
ToolbarRemove(ToolbarAction),
ToolbarReorder {
src: ToolbarAction,
target: ToolbarAction,
},
/// Move one step up (toward index 0) inside the enabled toolbar list.
ToolbarMoveUp(ToolbarAction),
/// Move one step down (toward the end) inside the enabled toolbar list.
ToolbarMoveDown(ToolbarAction),
ToolbarReset,
/// Click on a toolbar button (via segmented_button activation).
ToolbarTabActivate(segmented_button::Entity),
/// Drag-reorder inside the toolbar (via segmented_button drag).
ToolbarTabReorder(segmented_button::ReorderEvent),
SetTypeToSearch(TypeToSearch),
SystemThemeModeChange,
Size(window::Id, Size),
@ -577,6 +695,9 @@ pub enum DialogPage {
mime: mime_guess::Mime,
selected: usize,
store_opt: Option<MimeApp>,
/// When true, the chosen app is written to mimeapps.list as the
/// default handler for `mime` after it spawns.
set_default: bool,
},
PermanentlyDelete {
paths: Box<[PathBuf]>,
@ -734,6 +855,11 @@ pub struct App {
nav_bar_context_id: segmented_button::Entity,
nav_model: segmented_button::SingleSelectModel,
tab_model: segmented_button::Model<segmented_button::SingleSelect>,
/// Yoda phase 3: segmented_button model mirroring config.toolbar so the
/// toolbar row gets free drag-reorder + click activation (same widget
/// that powers the tab bar, its reorder is proven to work in this
/// setup unlike the generic dnd_source/dnd_destination wrappers).
toolbar_model: segmented_button::Model<segmented_button::SingleSelect>,
config_handler: Option<cosmic_config::Config>,
state_handler: Option<cosmic_config::Config>,
config: Config,
@ -906,7 +1032,7 @@ impl App {
for path in paths.iter().map(AsRef::as_ref) {
match DesktopEntry::from_path::<&str>(path, None) {
Ok(entry) => match entry.exec() {
Some(exec) => match mime_app::exec_to_command(exec, &[] as &[&str; 0]) {
Some(exec) => match crate::mime_app::exec_to_command(exec, &[] as &[&str; 0]) {
Some(commands) => {
let cwd_opt = entry.desktop_entry("Path");
@ -1081,7 +1207,7 @@ impl App {
.sort_by(|a, b| (b.1.width * b.1.height).total_cmp(&(a.1.width * b.1.height)));
for (w_id, overlap) in sorted_overlaps {
let Some((bl, br, tl, tr, mut size)) = self.layer_sizes.get(w_id).map(|s| {
let Some((bl, br, tl, tr, size)) = self.layer_sizes.get(w_id).map(|s| {
(
Rectangle::new(
Point::new(0., s.height / 2.),
@ -1131,30 +1257,18 @@ impl App {
if tl && !(tr || bl) {
*top += min_dim.1;
*left += min_dim.0;
size.height -= min_dim.1;
size.width -= min_dim.0;
}
if tr && !(tl || br) {
*top += min_dim.1;
*right += min_dim.0;
size.height -= min_dim.1;
size.width -= min_dim.0;
}
if bl && !(br || tl) {
*bottom += min_dim.1;
*left += min_dim.0;
size.height -= min_dim.1;
size.width -= min_dim.0;
}
if br && !(bl || tr) {
*bottom += min_dim.1;
*right += min_dim.0;
size.height -= min_dim.1;
size.width -= min_dim.0;
}
}
self.margin = overlaps;
@ -1513,12 +1627,18 @@ impl App {
) -> Task<Message> {
log::info!("rescan_tab {entity:?} {location:?} {selection_paths:?}");
let icon_sizes = self.config.tab.icon_sizes;
#[cfg(feature = "gvfs")]
let mounter_items = self.mounter_items.clone();
Task::future(async move {
let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok((parent_item_opt, mut items)) => {
Ok((parent_item_opt, items)) => {
#[cfg(feature = "gvfs")]
let mut items = items;
#[cfg(not(feature = "gvfs"))]
let items = items;
#[cfg(feature = "gvfs")]
{
let mounter_paths: Box<[_]> = mounter_items
@ -1688,6 +1808,7 @@ impl App {
fn update_config(&mut self) -> Task<Message> {
self.update_nav_model();
self.rebuild_toolbar_model();
// Tabs are collected first to placate the borrowck
let tabs: Box<[_]> = self.tab_model.iter().collect();
// Update main conf and each tab with the new config
@ -1701,6 +1822,52 @@ impl App {
Task::batch(commands)
}
/// Yoda phase 3: rebuild `toolbar_model` so it matches `config.toolbar`.
/// Called on init and on every config update. Each entity carries the
/// associated `ToolbarAction` as data so click/reorder handlers can
/// round-trip Entity → ToolbarAction without maintaining a side map.
///
/// We insert ONLY the icon (no `.text()`) so the toolbar renders as a
/// clean icon row — user-visible labels stay in Settings/tooltips on
/// other surfaces.
fn rebuild_toolbar_model(&mut self) {
self.toolbar_model.clear();
for action in self.config.toolbar.iter().copied() {
let (icon_name, _label, _msg) = toolbar_action_ui(action);
self.toolbar_model
.insert()
.icon(widget::icon::from_name(icon_name).size(16).icon())
.data::<ToolbarAction>(action);
}
}
/// Yoda phase 3: after a drag-reorder, sync `config.toolbar` with the
/// new entity order in `toolbar_model`. Inlines what `config_set!`
/// would do (the macro lives inside update()).
fn sync_toolbar_config_from_model(&mut self) -> Task<Message> {
let new_toolbar: Vec<ToolbarAction> = self
.toolbar_model
.iter()
.filter_map(|e| self.toolbar_model.data::<ToolbarAction>(e).copied())
.collect();
if new_toolbar == self.config.toolbar {
return Task::none();
}
match self.config_handler.as_ref() {
Some(h) => {
if let Err(err) = self.config.set_toolbar(h, new_toolbar) {
log::warn!("failed to save toolbar order: {err}");
}
}
None => self.config.toolbar = new_toolbar,
}
// Don't call update_config() — that would rebuild the
// toolbar_model from config and undo the reorder the user just
// made. The model already has the new order; config is just
// catching up for persistence.
Task::none()
}
fn update_desktop(&mut self) -> Task<Message> {
let needs_reload: Box<[_]> = (self.tab_model.iter())
.filter_map(|entity| {
@ -2280,10 +2447,134 @@ impl App {
.toggler(self.config.show_recents, Message::SetShowRecents)
})
.into(),
// Yoda phase 3: toolbar editor. Two stacked lists:
// - top: enabled buttons in their current order (drag to reorder)
// - bottom: available (not-yet-enabled) buttons
// Each row's toggle adds/removes; enabled rows are also
// drag sources + drop targets.
self.toolbar_settings_section(),
])
.into()
}
/// Yoda phase 3: build the Toolbar settings section.
fn toolbar_settings_section(&self) -> Element<'_, Message> {
use iced::clipboard::dnd::DndAction;
let enabled = &self.config.toolbar;
let disabled: Vec<ToolbarAction> = ToolbarAction::ALL
.iter()
.copied()
.filter(|a| !enabled.contains(a))
.collect();
let space_xxs = theme::active().cosmic().spacing.space_xxs;
let drag_icon = |size: u16| -> Element<'static, Message> {
widget::icon::from_name("list-drag-handle-symbolic")
.size(size)
.into()
};
let row_enabled =
|action: ToolbarAction, pos: usize, last: usize| -> Element<'_, Message> {
let (icon, label, _msg) = toolbar_action_ui(action);
let up_btn =
widget::button::icon(widget::icon::from_name("go-up-symbolic").size(14));
let up_btn = if pos > 0 {
up_btn.on_press(Message::ToolbarMoveUp(action))
} else {
up_btn
};
let down_btn =
widget::button::icon(widget::icon::from_name("go-down-symbolic").size(14));
let down_btn = if pos < last {
down_btn.on_press(Message::ToolbarMoveDown(action))
} else {
down_btn
};
let row_content: Element<_> = widget::row::with_children(vec![
drag_icon(14),
widget::icon::from_name(icon).size(16).into(),
widget::text::body(label).width(Length::Fill).into(),
up_btn.into(),
down_btn.into(),
widget::button::icon(widget::icon::from_name("list-remove-symbolic").size(14))
.on_press(Message::ToolbarRemove(action))
.into(),
])
.spacing(space_xxs)
.align_y(Alignment::Center)
.into();
let row_container = widget::container(row_content)
.width(Length::Fill)
.padding(space_xxs);
// Wrap as DnD source (drags itself) + DnD destination (accepts
// drops from other enabled rows; on drop, move the src before
// this row).
let source = widget::dnd_source::<Message, ToolbarActionPayload>(row_container)
.drag_content(move || ToolbarActionPayload(action.to_u8()));
widget::dnd_destination(source, vec![std::borrow::Cow::Borrowed(TOOLBAR_MIME)])
.data_received_for::<ToolbarActionPayload>(
move |payload: Option<ToolbarActionPayload>| {
match payload.and_then(|p| ToolbarAction::from_u8(p.0)) {
Some(src) if src != action => Message::ToolbarReorder {
src,
target: action,
},
// No-op if payload missing / malformed / same row.
_ => Message::ToolbarReorder {
src: action,
target: action,
},
}
},
)
.action(DndAction::Move)
.into()
};
let row_disabled = |action: ToolbarAction| -> Element<'_, Message> {
let (icon, label, _msg) = toolbar_action_ui(action);
widget::row::with_children(vec![
widget::icon::from_name(icon).size(16).into(),
widget::text::body(label).width(Length::Fill).into(),
widget::button::icon(widget::icon::from_name("list-add-symbolic").size(14))
.on_press(Message::ToolbarAdd(action))
.into(),
])
.spacing(space_xxs)
.align_y(Alignment::Center)
.padding(space_xxs)
.into()
};
let mut section = widget::settings::section().title(fl!("toolbar"));
if enabled.is_empty() {
section = section.add(widget::text::body(fl!("toolbar-empty-hint")));
} else {
let last = enabled.len() - 1;
for (pos, a) in enabled.iter().copied().enumerate() {
section = section.add(row_enabled(a, pos, last));
}
}
let mut col = widget::column::with_capacity(3).spacing(space_xxs);
col = col.push(section);
if !disabled.is_empty() {
let mut avail = widget::settings::section().title(fl!("toolbar-available"));
for a in disabled {
avail = avail.add(row_disabled(a));
}
col = col.push(avail);
}
col = col
.push(widget::button::standard(fl!("toolbar-reset")).on_press(Message::ToolbarReset));
col.into()
}
fn get_apps_for_mime(&self, mime_type: &Mime) -> Vec<(&MimeApp, MimeAppMatch)> {
let mut results = Vec::new();
@ -2455,6 +2746,7 @@ impl Application for App {
nav_bar_context_id: segmented_button::Entity::null(),
nav_model: segmented_button::ModelBuilder::default().build(),
tab_model: segmented_button::ModelBuilder::default().build(),
toolbar_model: segmented_button::ModelBuilder::default().build(),
config_handler: flags.config_handler,
state_handler: flags.state_handler,
config: flags.config,
@ -3232,6 +3524,7 @@ impl Application for App {
path,
mime,
selected,
set_default,
..
} => {
let available_apps = self.get_apps_for_mime(&mime);
@ -3250,6 +3543,11 @@ impl Application for App {
None,
);
}
// Yoda: persist as default if the user asked for it.
if set_default {
self.mime_app_cache
.set_default(mime.clone(), app.id.clone());
}
}
Err(err) => {
log::warn!(
@ -3872,6 +4170,7 @@ impl Application for App {
.and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned()
}),
set_default: false,
},
Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()),
);
@ -3883,6 +4182,13 @@ impl Application for App {
*selected = index;
}
}
Message::OpenWithToggleDefault(enabled) => {
if let Some(DialogPage::OpenWith { set_default, .. }) =
self.dialog_pages.front_mut()
{
*set_default = enabled;
}
}
Message::Paste(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
@ -4359,6 +4665,86 @@ impl Application for App {
config_set!(show_recents, show_recents);
return self.update_config();
}
Message::ToolbarAdd(action) => {
let mut tb = self.config.toolbar.clone();
if !tb.contains(&action) {
tb.push(action);
}
config_set!(toolbar, tb);
return self.update_config();
}
Message::ToolbarRemove(action) => {
let mut tb = self.config.toolbar.clone();
tb.retain(|a| a != &action);
config_set!(toolbar, tb);
return self.update_config();
}
Message::ToolbarReorder { src, target } => {
let mut tb = self.config.toolbar.clone();
if let (Some(src_idx), Some(tgt_idx)) = (
tb.iter().position(|a| a == &src),
tb.iter().position(|a| a == &target),
) && src_idx != tgt_idx
{
// Pull src out, then insert before the target's new position.
let item = tb.remove(src_idx);
let new_tgt = if src_idx < tgt_idx {
tgt_idx - 1
} else {
tgt_idx
};
tb.insert(new_tgt, item);
config_set!(toolbar, tb);
return self.update_config();
}
return Task::none();
}
Message::ToolbarMoveUp(action) => {
let mut tb = self.config.toolbar.clone();
if let Some(i) = tb.iter().position(|a| a == &action)
&& i > 0
{
tb.swap(i, i - 1);
config_set!(toolbar, tb);
return self.update_config();
}
return Task::none();
}
Message::ToolbarMoveDown(action) => {
let mut tb = self.config.toolbar.clone();
if let Some(i) = tb.iter().position(|a| a == &action)
&& i + 1 < tb.len()
{
tb.swap(i, i + 1);
config_set!(toolbar, tb);
return self.update_config();
}
return Task::none();
}
Message::ToolbarReset => {
config_set!(toolbar, default_toolbar());
return self.update_config();
}
Message::ToolbarTabActivate(entity) => {
// Dispatch the stored ToolbarAction's message, then clear
// the "active" selection so the button doesn't stay
// highlighted after a click (we use segmented_button for
// layout/drag but toolbar buttons are action-firing, not
// a mutual-exclusive choice).
let action = self.toolbar_model.data::<ToolbarAction>(entity).copied();
self.toolbar_model.deactivate();
if let Some(action) = action {
let (_, _, msg) = toolbar_action_ui(action);
return self.update(msg);
}
return Task::none();
}
Message::ToolbarTabReorder(event) => {
let _ = self
.toolbar_model
.reorder(event.dragged, event.target, event.position);
return self.sync_toolbar_config_from_model();
}
Message::SetTypeToSearch(type_to_search) => {
config_set!(type_to_search, type_to_search);
return self.update_config();
@ -5110,6 +5496,7 @@ impl Application for App {
.and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned()
}),
set_default: false,
},
None,
);
@ -5953,7 +6340,7 @@ impl Application for App {
mime,
selected,
store_opt,
..
set_default,
} => {
let name = match path.file_name() {
Some(file_name) => file_name.to_str(),
@ -6038,7 +6425,21 @@ impl Application for App {
} else {
Length::Shrink
}
}));
}))
// Yoda: let the user make this choice stick. A plain row
// instead of settings::item::builder because the latter
// returns a section Item, not an Element usable in .control().
.control(
widget::row::with_children([
widget::text::body(fl!("open-with-set-default")).into(),
widget::space::horizontal().into(),
widget::toggler(*set_default)
.on_toggle(Message::OpenWithToggleDefault)
.into(),
])
.spacing(space_s)
.align_y(Alignment::Center),
);
if let Some(app) = store_opt {
dialog = dialog.tertiary_action(
@ -6444,7 +6845,10 @@ impl Application for App {
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
let cosmic_theme::Spacing {
space_xxs, space_s, ..
space_xxs,
space_xs,
space_s,
..
} = theme::active().cosmic().spacing;
let mut tab_column = widget::column::with_capacity(4);
@ -6488,6 +6892,36 @@ impl Application for App {
);
}
// Yoda phase 3: Dolphin-style quick actions toolbar via
// segmented_button::horizontal — the same widget that powers the
// tab bar, so its built-in drag reorder works reliably (unlike the
// generic dnd_source+dnd_destination pairing we tried earlier).
// Short click = action (ToolbarTabActivate → dispatch the stored
// ToolbarAction's message). Drag past threshold = reorder
// (ToolbarTabReorder → model.reorder + sync to config).
if !self.config.toolbar.is_empty() {
// Use Control style (no TabBar underline, no bottom border)
// and let each button shrink to its icon. Spacing = space_xs
// keeps the buttons visually separated so it looks like an
// icon toolbar rather than a conjoined segmented control.
let toolbar = widget::segmented_button::horizontal(&self.toolbar_model)
.style(theme::SegmentedButton::Control)
.button_height(32)
.button_spacing(space_xs)
.button_alignment(Alignment::Center)
.minimum_button_width(32)
.maximum_button_width(32)
.enable_tab_drag(String::from("x-cosmic-files/toolbar-dnd"))
.on_reorder(Message::ToolbarTabReorder)
.tab_drag_threshold(8.)
.on_activate(Message::ToolbarTabActivate);
tab_column = tab_column.push(
widget::container(toolbar)
.width(Length::Shrink)
.padding([space_xxs, space_s]),
);
}
let entity = self.tab_model.active();
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
let tab_view = tab

View file

@ -132,9 +132,10 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
match mime.as_str() {
"text/uri-list" => {
let text = str::from_utf8(&data)?;
let lines = text.lines();
for line in text.lines() {
for line in text.lines().filter(|line| {
let line = line.trim();
!line.is_empty() && !line.starts_with('#')
}) {
let url = Url::parse(line)?;
match url.to_file_path() {
Ok(path) => paths.push(path),

View file

@ -172,6 +172,11 @@ pub struct Config {
pub show_details: bool,
pub show_recents: bool,
pub tab: TabConfig,
/// Yoda phase 3: Dolphin-style quick actions toolbar. An ordered list
/// of enabled buttons — position in the vec drives the toolbar order.
/// Reorder in Settings via drag-drop; items not in the vec are
/// hidden. Default = the minimal-6 set from phase 1.
pub toolbar: Vec<ToolbarAction>,
pub type_to_search: TypeToSearch,
}
@ -236,11 +241,97 @@ impl Default for Config {
show_details: false,
show_recents: true,
tab: TabConfig::default(),
toolbar: default_toolbar(),
type_to_search: TypeToSearch::Recursive,
}
}
}
/// Yoda phase 3: ordered enum of quick-action toolbar buttons.
/// The Config stores `Vec<ToolbarAction>` so the user can pick BOTH
/// visibility (just include/exclude the variant) AND order (position in
/// the vec). Drag-drop reorder in the Settings page moves items around.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ToolbarAction {
LocationUp,
Reload,
NewFolder,
NewFile,
Rename,
Delete,
Cut,
Copy,
Paste,
ToggleShowHidden,
OpenTerminal,
}
impl ToolbarAction {
/// Stable list of every supported action. Ordered roughly by logical
/// grouping (location → create/edit → clipboard → view/misc) so that
/// the default enabled set follows a sensible shape and the Settings
/// row for a not-yet-enabled action lands in a predictable spot.
pub const ALL: &'static [Self] = &[
Self::LocationUp,
Self::Reload,
Self::NewFolder,
Self::NewFile,
Self::Rename,
Self::Delete,
Self::Cut,
Self::Copy,
Self::Paste,
Self::ToggleShowHidden,
Self::OpenTerminal,
];
/// u8 discriminant used to carry the action over a DnD mime payload.
pub const fn to_u8(self) -> u8 {
match self {
Self::LocationUp => 0,
Self::Reload => 1,
Self::NewFolder => 2,
Self::NewFile => 3,
Self::Rename => 4,
Self::Delete => 5,
Self::Cut => 6,
Self::Copy => 7,
Self::Paste => 8,
Self::ToggleShowHidden => 9,
Self::OpenTerminal => 10,
}
}
pub const fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::LocationUp),
1 => Some(Self::Reload),
2 => Some(Self::NewFolder),
3 => Some(Self::NewFile),
4 => Some(Self::Rename),
5 => Some(Self::Delete),
6 => Some(Self::Cut),
7 => Some(Self::Copy),
8 => Some(Self::Paste),
9 => Some(Self::ToggleShowHidden),
10 => Some(Self::OpenTerminal),
_ => None,
}
}
}
/// Default set shown on a fresh install — same "minimal 6" as phase 1/2.
pub fn default_toolbar() -> Vec<ToolbarAction> {
vec![
ToolbarAction::NewFolder,
ToolbarAction::Rename,
ToolbarAction::Delete,
ToolbarAction::Cut,
ToolbarAction::Copy,
ToolbarAction::Paste,
]
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
#[serde(default)]
pub struct DesktopConfig {

View file

@ -744,11 +744,17 @@ impl App {
fn rescan_tab(&self, selection_paths: Option<Vec<PathBuf>>) -> Task<Message> {
let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes;
#[cfg(feature = "gvfs")]
let mounter_items = self.mounter_items.clone();
Task::future(async move {
let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok((parent_item_opt, mut items)) => {
Ok((parent_item_opt, items)) => {
#[cfg(feature = "gvfs")]
let mut items = items;
#[cfg(not(feature = "gvfs"))]
let items = items;
#[cfg(feature = "gvfs")]
{
let mounter_paths: Box<[_]> = mounter_items

View file

@ -37,6 +37,7 @@ mod zoom;
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
#[cfg(feature = "gvfs")]
pub(crate) fn err_str<T: ToString>(err: T) -> String {
err.to_string()
}

View file

@ -14,6 +14,7 @@ use cosmic::{
responsive_menu_bar, space, text,
},
};
#[cfg(feature = "desktop")]
use i18n_embed::LanguageLoader;
use mime_guess::Mime;
use std::{collections::HashMap, sync::LazyLock};
@ -196,11 +197,11 @@ pub fn context_menu<'a>(
if !Trash::is_empty() {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
}
} else if let Some(entry) = selected_desktop_entry {
} else if let Some(_entry) = selected_desktop_entry {
children.push(menu_item(fl!("open"), Action::Open).into());
#[cfg(feature = "desktop")]
{
children.extend(entry.desktop_actions.into_iter().enumerate().map(
children.extend(_entry.desktop_actions.into_iter().enumerate().map(
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
));
}

View file

@ -6,13 +6,12 @@ use cosmic::desktop;
use cosmic::widget;
pub use mime_guess::Mime;
use rustc_hash::FxHashMap;
#[cfg(feature = "desktop")]
use std::{cmp::Ordering, fs, io, time::Instant};
use std::{
cmp::Ordering,
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
process,
time::Instant,
};
// Supported exec key field codes
@ -399,9 +398,12 @@ impl MimeAppCache {
// The current approach works but might not adhere to the spec (yet)
// Look for and return preferred terminals
//TODO: fallback order beyond cosmic-term?
let mut preference_order = vec!["com.system76.CosmicTerm".to_string()];
// Yoda: cosmic-yoterm (our fork) wins over upstream cosmic-term if both
// are installed — useful when xdg-mime default is not set.
let mut preference_order = vec![
"com.aditua.CosmicYoterm".to_string(),
"com.system76.CosmicTerm".to_string(),
];
if let Some(id) = self.get_default_terminal() {
preference_order.insert(0, id);

View file

@ -75,10 +75,10 @@ impl MounterItem {
}
}
pub fn icon(&self, symbolic: bool) -> Option<widget::icon::Handle> {
pub fn icon(&self, _symbolic: bool) -> Option<widget::icon::Handle> {
match self {
#[cfg(feature = "gvfs")]
Self::Gvfs(item) => item.icon(symbolic),
Self::Gvfs(item) => item.icon(_symbolic),
Self::None => unreachable!(),
}
}
@ -103,6 +103,7 @@ impl MounterItem {
pub type MounterItems = Vec<MounterItem>;
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum MounterMessage {
Items(MounterItems),
MountResult(MounterItem, Result<bool, String>),

View file

@ -9,6 +9,7 @@ use compio::buf::{IntoInner, IoBuf};
use compio::driver::{ToSharedFd, op::AsyncifyFd};
use compio::io::{AsyncReadAt, AsyncWriteAt};
use cosmic::iced::futures;
#[cfg(feature = "gvfs")]
use futures::{FutureExt, StreamExt};
use std::future::Future;
use std::pin::Pin;

View file

@ -28,6 +28,7 @@ use cosmic::{
space,
},
};
#[cfg(feature = "desktop")]
use i18n_embed::LanguageLoader;
use icu::{
datetime::{
@ -302,6 +303,26 @@ 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()
@ -558,7 +579,7 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
}
#[cfg(not(feature = "desktop"))]
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
fn get_desktop_file_display_name(_path: &Path) -> Option<String> {
None
}
@ -577,7 +598,7 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
}
#[cfg(not(feature = "desktop"))]
fn get_desktop_file_icon(path: &Path) -> Option<String> {
fn get_desktop_file_icon(_path: &Path) -> Option<String> {
None
}
@ -665,9 +686,9 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
folder_icon(&path, sizes.list_condensed()),
)
} else {
// 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);
// 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();
//TODO: clean this up, implement for trash
let icon_name_opt = if mime == "application/x-desktop" {
@ -684,28 +705,21 @@ 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.clone(),
mime_icon(mime.clone(), sizes.grid()),
mime_icon(mime.clone(), sizes.list()),
mime_icon(mime, sizes.list_condensed()),
mime,
icon_handle_grid,
icon_handle_list,
icon_handle_list_condensed,
)
}
};
let mut children_opt = None;
let 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);
@ -759,7 +773,10 @@ pub fn item_from_entry(
sizes: IconSizes,
) -> Item {
let mut is_desktop = false;
#[cfg(feature = "gvfs")]
let mut is_gvfs = false;
#[cfg(not(feature = "gvfs"))]
let is_gvfs = false;
let hidden = name.starts_with('.') || hidden_attribute(&metadata);
@ -807,7 +824,9 @@ pub fn item_from_entry(
folder_icon(&path, sizes.list_condensed()),
)
} else {
let mime = mime_for_path(&path, Some(&metadata), remote);
// 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();
//TODO: clean this up, implement for trash
let icon_name_opt = if mime == "application/x-desktop" {
is_desktop = true;
@ -823,28 +842,21 @@ 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.clone(),
mime_icon(mime.clone(), sizes.grid()),
mime_icon(mime.clone(), sizes.list()),
mime_icon(mime, sizes.list_condensed()),
mime,
icon_handle_grid,
icon_handle_list,
icon_handle_list_condensed,
)
}
};
let mut children_opt = None;
let 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);
@ -958,7 +970,10 @@ pub fn item_from_path<P: Into<PathBuf>>(path: P, sizes: IconSizes) -> Result<Ite
pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
let mut items = Vec::new();
let mut hidden_files = Box::from([]);
#[cfg(feature = "gvfs")]
let mut remote_scannable = false;
#[cfg(not(feature = "gvfs"))]
let remote_scannable = false;
#[cfg(feature = "gvfs")]
{

View file

@ -5,12 +5,12 @@
use cosmic::desktop::fde::GenericEntry;
use mime_guess::Mime;
use rustc_hash::FxHashMap;
#[cfg(feature = "desktop")]
use std::{fs, time::Instant};
use std::{
fs,
path::Path,
process,
sync::{LazyLock, Mutex},
time::Instant,
};
#[derive(Clone, Debug)]

View file

@ -27,7 +27,7 @@ pub trait TrashExt {
Vec::new()
}
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex);
fn icon(icon_size: u16) -> widget::icon::Handle {
widget::icon::from_name(if Self::is_empty() {
@ -142,4 +142,12 @@ impl TrashExt for Trash {
not(target_os = "android")
)
)))]
impl TrashExt for Trash {}
impl TrashExt for Trash {
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
log::warn!(
"searching trash not supported on this platform for pattern {:?}",
regex.as_str()
);
drop(callback);
}
}