Compare commits

..

1 commit

Author SHA1 Message Date
a71ab4ce11 fix(dialog): keep sortable column headers visible in narrow Save/Open windows
In Mode::Dialog (file picker), the location_view used to drop the sortable
heading row whenever the window dropped below the 600px condensed threshold —
which is most of the time for Save As dialogs (the min window width is 360px).
Users lost the ability to click Name/Modified/Size to toggle ascending /
descending sort.

Add a compact heading row reused in dialog mode when the window is condensed.
It keeps the same Message::ToggleSort(HeadingOptions) wiring; only the
column widths differ (Length::Shrink instead of Fixed) so it fits in a
narrow dialog. The regular wide layout is unchanged.

Leyoda 2026 – GPLv3
2026-05-22 08:05:13 +02:00
86 changed files with 4623 additions and 10086 deletions

View file

@ -1,15 +0,0 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

3231
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,30 @@
[package]
name = "cosmic-files"
version = "1.9.0"
version = "1.0.8"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024"
license = "GPL-3.0-only"
rust-version = "1.93"
rust-version = "1.90"
[dependencies]
anyhow = "1"
jiff = "0.2"
jiff-icu = "0.2"
icu = { version = "2.2.0", features = ["compiled_data"] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c0cff4d", optional = true }
chrono = { version = "0.4", features = ["unstable-locales"] }
icu = { version = "2.1.1", features = ["compiled_data"] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
dirs = "6.0.0"
env_logger = "0.11"
gio = { version = "0.21", optional = true }
glib = { version = "0.21", optional = true }
glob = "0.3"
ignore = "0.4"
image.workspace = true
image = "0.25"
libc = "0.2"
log.workspace = true
log = "0.4"
mime_guess = "2"
notify-debouncer-full = "0.7"
notify-rust = { version = "4", optional = true }
open = "5.3.4"
open = "5.3.3"
paste = "1.0"
regex = "1"
rustc-hash = "2.1"
@ -33,18 +33,18 @@ shlex = { version = "1.3" }
tempfile = "3"
tikv-jemallocator = { version = "0.6", optional = true }
tokio = { version = "1", features = ["process", "sync"] }
trash = "5.2.6"
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
url = "2.5"
walkdir = "2.5.0"
wayland-client = { version = "0.31.14", optional = true }
wayland-client = { version = "0.31.12", optional = true }
xdg = { version = "3.0", optional = true }
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
# Compression
bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate
flate2 = "1.1"
tar = "0.4.45"
lzma-rust2 = { version = "0.16", optional = true }
ordermap = { version = "1.2.0", features = ["serde"] }
tar = "0.4.44"
lzma-rust2 = { version = "0.15.7", optional = true }
ordermap = { version = "1.1.0", features = ["serde"] }
# Internationalization
i18n-embed = { version = "0.16", features = [
"fluent-system",
@ -54,19 +54,13 @@ i18n-embed-fl = "0.10"
rust-embed = "8"
slotmap = "1.1.1"
recently-used-xbel = "1.2.0"
zip = "8"
zip = "7"
uzers = "0.12.2"
md-5 = "0.10.6"
sha2 = "0.10"
png = "0.18"
jxl-oxide = { version = "0.12.5", features = ["image"] }
num_cpus = "1.17.0"
filetime = "0.2"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
thiserror = "2.0.18"
atomic_float = "1.1.0"
num_enum = "0.7.6"
bstr = "1.12.1"
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
[dependencies.compio]
@ -74,18 +68,17 @@ version = "0.18"
default-features = false
features = ["fs", "io", "macros", "polling", "runtime"]
# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch).
[dependencies.libcosmic-yoda]
path = "../libcosmic"
[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
default-features = false
#TODO: a11y feature crashes
features = [
"about",
"advanced-shaping",
"autosize",
"desktop",
"multi-window",
"tokio",
"wayland",
"winit",
"surface-message",
]
@ -113,15 +106,15 @@ default = [
"wayland",
"wgpu",
]
dbus-config = ["libcosmic-yoda/dbus-config"]
desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
dbus-config = ["libcosmic/dbus-config"]
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-yoda/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic-yoda/wgpu"]
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic/wgpu"]
[profile.dev]
opt-level = 1
@ -131,8 +124,7 @@ inherits = "release"
debug = true
[target.'cfg(unix)'.dependencies]
fork = "0.7"
uzers = "0.12.2"
fork = "0.6"
[target.'cfg(target_os = "linux")'.dependencies]
procfs = "0.18"
@ -147,27 +139,20 @@ fastrand = "2"
test-log = "0.2"
tokio = { version = "1", features = ["rt", "macros"] }
# 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/cosmic-text']
# cosmic-text = { path = "../cosmic-text" }
# Yoda wayland cut: redirect window_clipboard (x11 gated behind opt-in
# feature) to our public Forgejo fork and cosmic-text to the local checkout
# carrying PR#503 plus the EAW monospace width fix. 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/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" }
[patch.'https://github.com/pop-os/cosmic-text.git']
cosmic-text = { path = "../cosmic-text" }
# [patch.'https://github.com/pop-os/smithay-clipboard']
# smithay-clipboard = { path = "../smithay-clipboard" }
[workspace]
members = ["cosmic-files-applet", "cosmic-files-thumbnailer"]
[workspace.dependencies]
image = "0.25"
log = "0.4"
members = ["cosmic-files-applet"]

View file

@ -1,5 +1,4 @@
use std::path::PathBuf;
use std::{env, fs};
use std::{env, fs, path::PathBuf};
use xdgen::{App, Context, FluentString};
fn main() {

View file

@ -1,10 +1,10 @@
[package]
name = "cosmic-files-applet"
version = "1.9.0"
version = "1.0.8"
edition = "2024"
[dependencies]
log.workspace = true
log = "0.4"
zbus = "4" # Blocking connection in zbus 5 hangs
[dependencies.cosmic-files]

View file

@ -1,8 +0,0 @@
[package]
name = "cosmic-files-thumbnailer"
version = "1.9.0"
edition = "2024"
[dependencies]
image.workspace = true
pelite = "0.10"

View file

@ -1,254 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use image::{DynamicImage, ImageFormat, RgbaImage};
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::io;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
thumbnail_from_args(std::env::args_os().skip(1))
}
#[allow(dead_code)]
pub fn thumbnail_from_args(mut args: impl Iterator<Item = OsString>) -> Result<(), Box<dyn Error>> {
let usage = || {
io::Error::new(
io::ErrorKind::InvalidInput,
"usage: cosmic-files-thumbnailer OUTPUT --size SIZE INPUT",
)
};
let output = args.next().ok_or_else(usage)?;
if args.next().as_deref() != Some(OsStr::new("--size")) {
return Err(usage().into());
}
let size = args
.next()
.and_then(|arg| arg.into_string().ok())
.and_then(|arg| arg.parse::<u32>().ok())
.filter(|size| *size > 0)
.ok_or_else(usage)?;
let input = args.next().ok_or_else(usage)?;
if args.next().is_some() {
return Err(usage().into());
}
thumbnail(Path::new(&input), Path::new(&output), size)
}
#[allow(dead_code)]
pub fn thumbnail(input: &Path, output: &Path, size: u32) -> Result<(), Box<dyn Error>> {
let icon = thumbnail_image(input, size)?;
DynamicImage::ImageRgba8(icon).save_with_format(output, ImageFormat::Png)?;
Ok(())
}
pub fn thumbnail_image(input: &Path, size: u32) -> Result<RgbaImage, Box<dyn Error>> {
if size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"thumbnail size must be positive",
)
.into());
}
// Map the PE so only the headers and resource pages touched by pelite are read.
let data = pelite::FileMap::open(input)?;
let bytes = data.as_ref();
if bytes.len() < 64 || !bytes.starts_with(b"MZ") {
return Err(io::Error::new(io::ErrorKind::InvalidData, "input is not a PE file").into());
}
let file = pelite::PeFile::from_bytes(bytes).map_err(invalid_data)?;
let resources = file.resources().map_err(invalid_data)?;
let (_name, group) = resources
.icons()
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PE file has no icon group"))?
.map_err(invalid_data)?;
let mut ico = Vec::new();
group.write(&mut ico).map_err(invalid_data)?;
Ok(
DynamicImage::ImageRgba8(largest_icon(&ico).map_err(invalid_data)?)
.thumbnail(size, size)
.into_rgba8(),
)
}
fn invalid_data(error: impl std::fmt::Display) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error.to_string())
}
fn largest_icon(ico: &[u8]) -> Result<RgbaImage, String> {
if ico.len() < 6 {
return Err("ICO header is truncated".to_string());
}
let count = usize::from(u16::from_le_bytes([ico[4], ico[5]]));
let mut best: Option<(u64, RgbaImage)> = None;
let mut errors = Vec::new();
for i in 0..count.min(32) {
let entry = 6 + i * 16;
if ico.len() < entry + 16 {
break;
}
let size = u32::from_le_bytes([
ico[entry + 8],
ico[entry + 9],
ico[entry + 10],
ico[entry + 11],
]) as usize;
let offset = u32::from_le_bytes([
ico[entry + 12],
ico[entry + 13],
ico[entry + 14],
ico[entry + 15],
]) as usize;
let Some(end) = offset.checked_add(size) else {
continue;
};
let Some(data) = ico.get(offset..end) else {
continue;
};
match decode_icon(&ico[entry..entry + 16], data) {
Ok(image) => {
let pixels = u64::from(image.width()) * u64::from(image.height());
if best
.as_ref()
.is_none_or(|(best_pixels, _)| pixels > *best_pixels)
{
best = Some((pixels, image));
}
}
Err(err) => errors.push(format!("frame {i}: {err}")),
}
}
best.map(|(_, image)| image).ok_or_else(|| {
if errors.is_empty() {
"PE icon contains no complete frames".to_string()
} else {
format!("PE icon could not be decoded ({})", errors.join("; "))
}
})
}
fn decode_icon(entry: &[u8], data: &[u8]) -> Result<RgbaImage, String> {
if data.starts_with(b"\x89PNG\r\n\x1a\n") {
return image::ImageReader::with_format(io::Cursor::new(data), ImageFormat::Png)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string());
}
let capacity = 22usize
.checked_add(data.len())
.ok_or_else(|| "frame is too large".to_string())?;
let data_len = u32::try_from(data.len()).map_err(|_| "frame is too large".to_string())?;
let mut ico = Vec::with_capacity(capacity);
ico.extend_from_slice(&[0, 0, 1, 0, 1, 0]);
ico.extend_from_slice(entry);
ico[14..18].copy_from_slice(&data_len.to_le_bytes());
ico[18..22].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(data);
image::ImageReader::with_format(io::Cursor::new(ico), ImageFormat::Ico)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string())
}
#[cfg(test)]
mod tests {
use super::{largest_icon, thumbnail_from_args};
use image::{DynamicImage, ImageFormat, Rgb, RgbImage, Rgba, RgbaImage};
use std::ffi::OsString;
use std::io::Cursor;
#[test]
fn malformed_thumbnail_request_returns_an_error() {
let args = [OsString::from("output.png")];
assert!(thumbnail_from_args(args.into_iter()).is_err());
}
#[test]
fn largest_icon_skips_invalid_entries() {
let image = RgbaImage::from_pixel(2, 2, Rgba([1, 2, 3, 255]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgba8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 2, 0];
let mut invalid_entry = [0; 16];
invalid_entry[8..12].copy_from_slice(&4u32.to_le_bytes());
invalid_entry[12..16].copy_from_slice(&u32::MAX.to_le_bytes());
ico.extend_from_slice(&invalid_entry);
let mut valid_entry = [0; 16];
valid_entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 32, 0]);
valid_entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
valid_entry[12..16].copy_from_slice(&38u32.to_le_bytes());
ico.extend_from_slice(&valid_entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid icon after malformed entry");
assert_eq!(decoded.dimensions(), (2, 2));
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
#[test]
fn dib_and_mask_is_applied() {
let mut dib = vec![0; 40];
dib[0..4].copy_from_slice(&40u32.to_le_bytes());
dib[4..8].copy_from_slice(&2i32.to_le_bytes());
dib[8..12].copy_from_slice(&4i32.to_le_bytes());
dib[12..14].copy_from_slice(&1u16.to_le_bytes());
dib[14..16].copy_from_slice(&24u16.to_le_bytes());
// Two bottom-up BGR rows, padded to four-byte boundaries.
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
// The first mask row is the bottom image row; its first pixel is transparent.
dib.extend_from_slice(&[0b1000_0000, 0, 0, 0]);
dib.extend_from_slice(&[0, 0, 0, 0]);
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(dib.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&dib);
let decoded = largest_icon(&ico).expect("valid DIB icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([255, 0, 0, 255]));
assert_eq!(decoded.get_pixel(0, 1), &Rgba([255, 0, 0, 0]));
}
#[test]
fn rgb_png_icon_is_decoded() {
let image = RgbImage::from_pixel(2, 2, Rgb([1, 2, 3]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgb8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid RGB PNG icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
}

96
debian/changelog vendored
View file

@ -1,99 +1,3 @@
cosmic-files (1.9.0) noble; urgency=medium
* Epoch 1.9.0 version update
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 20:35:19 +0200
cosmic-files (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:14:53 -0600
cosmic-files (1.7.0) noble; urgency=medium
* Epoch 1.7.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:20:22 -0600
cosmic-files (1.6.0) noble; urgency=medium
* Epoch 1.6.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 18 Aug 2026 07:13:48 -0600
cosmic-files (1.5.0) noble; urgency=medium
* Epoch 1.5.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 28 Jul 2026 09:14:09 -0600
cosmic-files (1.4.0) noble; urgency=medium
* Epoch 1.4.0 version update
-- Jeremy Soller <jeremy@system76.com> Mon, 20 Jul 2026 14:36:06 -0600
cosmic-files (1.3.0) noble; urgency=medium
* Epoch 1.3.0 version update
-- Jeremy Soller <jeremy@system76.com> Sun, 12 Jul 2026 13:21:18 -0600
cosmic-files (1.2.0) noble; urgency=medium
* Epoch 1.2.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 30 Jun 2026 12:21:07 -0600
cosmic-files (1.1.0) noble; urgency=medium
* Epoch 1.1.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 23 Jun 2026 08:47:18 -0600
cosmic-files (1.0.16) noble; urgency=medium
* Epoch 1.0.16 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 09 Jun 2026 10:26:05 -0600
cosmic-files (1.0.15) noble; urgency=medium
* Epoch 1.0.15 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 02 Jun 2026 11:02:29 -0600
cosmic-files (1.0.14) noble; urgency=medium
* Epoch 1.0.14 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 26 May 2026 08:30:59 -0600
cosmic-files (1.0.13) noble; urgency=medium
* Epoch 1.0.13 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 12 May 2026 09:39:14 -0600
cosmic-files (1.0.12) noble; urgency=medium
* Epoch 1.0.12 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 05 May 2026 10:23:57 -0600
cosmic-files (1.0.11) noble; urgency=medium
* Epoch 1.0.11 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 14 Apr 2026 11:09:44 -0600
cosmic-files (1.0.9) noble; urgency=medium
* Epoch 1.0.9 version update
-- Jeremy Soller <jeremy@system76.com> Mon, 06 Apr 2026 15:10:13 -0600
cosmic-files (1.0.8) noble; urgency=medium
* Epoch 1.0.8 version update

View file

@ -1,6 +1,4 @@
tar-ignore=.github
tar-ignore=target/.rustc_info.json
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=.vscode
tar-ignore=vendor
tar-ignore=target

View file

@ -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`.

View file

@ -1,8 +1,6 @@
use cosmic_files::operation::recursive::{Context, Method};
use cosmic_files::operation::{Controller, ReplaceResult};
use std::error::Error;
use std::io;
use std::path::PathBuf;
use cosmic_files::operation::recursive::Method;
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
use std::{error::Error, io, path::PathBuf};
#[compio::main]
async fn main() -> Result<(), Box<dyn Error>> {

View file

@ -1,30 +1,17 @@
use cosmic::app::{self, Core, Settings, Task};
use cosmic::iced::{Subscription, window};
use cosmic::{Application, Element, executor, widget};
use cosmic::{
Application, Element,
app::{self, Core, Settings, Task},
executor,
iced::{Subscription, window},
widget,
};
use cosmic_files::dialog::{
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
DialogMessage, DialogResult, DialogSettings,
};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let log_format = tracing_subscriber::fmt::format()
.pretty()
.without_time()
.with_line_number(true)
.with_file(true)
.with_target(false)
.with_thread_names(true);
let log_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(std::io::stderr)
.event_format(log_format);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
.with(log_layer)
.init();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let settings = Settings::default();
app::run::<App>(settings, ())?;
@ -161,7 +148,7 @@ impl Application for App {
}
fn view(&self) -> Element<'_, Message> {
let mut column = widget::column::with_capacity(8).spacing(8).padding(8);
let mut column = widget::column().spacing(8).padding(8);
{
let mut button = widget::button::standard("Open File");
if self.dialog_opt.is_none() {

View file

@ -22,10 +22,10 @@ empty-trash-warning = سيتم حذف العناصر الموجودة في مج
## New File/Folder Dialog
create-new-file = أنشئ ملف جديد
create-new-folder = أنشئ مجلَّد جديد
create-new-file = إنشاء ملف جديد
create-new-folder = إنشاء مجلد جديد
file-name = اسم الملف
folder-name = اسم المجلَّد
folder-name = اسم المجلد
file-already-exists = يوجد ملف بهذا الاسم بالفعل
folder-already-exists = يوجد مجلد بهذا الاسم بالفعل
name-hidden = الاسماء التي تبدأ بنقطة «.» ستكون مخفية
@ -34,10 +34,10 @@ name-no-slashes = لا يمكن أن يحتوي الاسم على شرطات م
## Open/Save Dialog
cancel = ألغِ
open = افتح
open-file = افتح ملف
open-folder = افتح مجلَّد
cancel = إلغاء
open = فتح
open-file = فتح ملف
open-folder = افتح مجلد
open-in-new-tab = افتح في لسان جديد
open-in-new-window = افتح في نافذة جديدة
open-multiple-files = افتح عدة ملفات
@ -84,7 +84,7 @@ settings = الإعدادات
### Appearance
appearance = المظهر
theme = السمة
theme = النسق
match-desktop = طابق سطح المكتب
dark = داكن
light = فاتح
@ -110,7 +110,7 @@ new-tab = لسان جديد
new-window = نافذة جديدة
rename = غيِّر الاسم...
close-tab = أغلق اللسان
quit = إنهِ
quit = أنهِ
## Edit
@ -147,15 +147,15 @@ icon-size-and-spacing = حجم الأيقونة والتباعد
icon-size = حجم الأيقونة
grid-spacing = تباعد شبكي
trashed-on = مهمل
details = تفاصيل
details = التفاصيل
pause = ألبث
resume = استئناف
create-archive = أنشئ أرشيف
create-archive = إنشاء أرشيف
extract-password-required = كلمة السر مطلوبة
extract-to = استخرِج إلى...
extract-to-title = استخرِج إلى مجلّد
mount-error = تعذر الوصول إلى القرص
create = أنشئ
create = إنشاء
open-item-location = افتح مكان العنصر
set-executable-and-launch-description = أتريد تعيين «{ $name }» كقابل للتنفيذ وتشغيله؟
favorite-path-error-description =
@ -259,7 +259,7 @@ related-apps = تطبيقات ذات صلة
selected-items = العناصر { $items } المحدّدة
permanently-delete-question = احذف نهائيًا؟
delete = احذف
permanently-delete-warning = سيُحذف { $target } نهائيًا. لا يمكن التراجع عن هذا الإجراء.
permanently-delete-warning = هل أنت متأكد من أنك تريد حذف { $target } نهائيًا؟ لا يمكن التراجع عن هذا الإجراء.
replace-warning-operation = أتريد استبداله؟ استبداله سيكتب فوق محتواه.
original-file = الملف الأصلي
replace-with = استبدل بـ
@ -371,19 +371,3 @@ move-to = انقل إلى...
show-recents = مجلد الحديثة في الشريط الجانبي
clear-recents-history = امحُ التأريخ الحديث
copy-path = انسخ المسار
mixed = مختلط
context-action = إجراء السياق
context-action-confirm-title = شغِّل "{ $name }"؟
context-action-confirm-warning =
سيُشغِّل هذا على { $items } { $items ->
[one] عنصر
[two] عنصرين
[few] عناصر
[many] عنصرًا
*[other] عنصر
}.
run = شغِّل
checksum = تدقيق المجموع { $kind }
calculate = احسب
error = خطأ
rename-confirm = أعد التسمية

View file

@ -1,11 +1,11 @@
cosmic-files = Fitxers del COSMIC
empty-folder = Carpeta buida
empty-folder-hidden = Carpeta buida (conté elements ocults)
empty-folder-hidden = Carpeta buida (té elements ocults)
no-results = No s'ha trobat cap resultat
filesystem = Sistema de fitxers
home = Inici
networks = Xarxes
notification-in-progress = Operacions de fitxers en curs
notification-in-progress = Hi ha operacions amb fitxers en curs.
trash = Paperera
recents = Recents
undo = Desfés
@ -29,14 +29,14 @@ details = Detalls
dismiss = Descarta el missatge
operations-running =
{ $running } { $running ->
[one] operació
[one] operacion
*[other] operacions
} en curs ({ $percent }%)...
operations-running-finished =
{ $running }{ $running ->
[one] operació
[one] operacion
*[other] operacions
} en curs ({ $percent }%), { $finished } acabada/es...
} en curs ({ $percent }%), { $finished } acabat...
pause = Pausa
resume = Reprèn
@ -54,7 +54,7 @@ extract-password-required = Cal una contrasenya
## Empty Trash Dialog
empty-trash = Buida la paperera
empty-trash-warning = Els elements dins la Paperera s'eliminaran permanentment
empty-trash-warning = Voleu suprimir permanentment tots els fitxers de la paperera?
## Mount Error Dialog
@ -64,20 +64,20 @@ mount-error = No es pot accedir a la unitat
create-new-file = Crea un nou fitxer
create-new-folder = Crea una nova carpeta
file-name = Nom del fitxer
file-name = Nom del fixer
folder-name = Nom de la carpeta
file-already-exists = Ja existeix un fitxer amb aquest nom
folder-already-exists = Ja existeix una carpeta amb aquest nom
name-hidden = Els noms que comencin amb "." s'ocultaran.
name-invalid = El nom no pot ser "{ $filename }"
file-already-exists = Ja existeix un fitxer amb aquest nom.
folder-already-exists = Ja existeix una carpeta amb aquest nom.
name-hidden = Els noms que comencin amb "." seran ocults.
name-invalid = El nom no pot ser "{ $filename }".
name-no-slashes = El nom no pot contenir barres.
## Open/Save Dialog
cancel = Cancel·lar
cancel = Cancel·la
create = Crea
open = Obre
open-file = Obre el fitxer
open-file = Obre el fixer
open-folder = Obre la carpeta
open-in-new-tab = Obre en una pestanya nova
open-in-new-window = Obre en una finestra nova
@ -352,14 +352,3 @@ sort-newest-first = Primer més recents
sort-oldest-first = Primer més antics
sort-smallest-to-largest = De petit a gran
sort-largest-to-smallest = De gran a petit
run = Executa
extract-to-title = Extreu a una carpeta
empty-trash-title = Voleu buidar la paperera?
extract-to = Extreu a...
delete = Suprimeix
comment = Gestor de fitxers de l'escriptori COSMIC
copy-to-title = Escolliu el destí de la còpia
copy-to-button-label = Copia
move-to-title = Escolliu el destí del trasllat
move-to-button-label = Mou
keywords = Carpeta;Fitxers;Arxius;Gestor;Explorador;

View file

@ -17,7 +17,7 @@ size = Velikost
## Empty Trash Dialog
empty-trash = Vyprázdnit koš
empty-trash = Vysypat koš
empty-trash-warning = Položky v koši budou trvale smazány
## New File/Folder Dialog
@ -176,7 +176,7 @@ set-executable-and-launch-description = Chcete povolit spouštění souboru „{
set-and-launch = Povolit a spustit
open-with = Otevřít pomocí
other = Ostatní
none = Žádné
none = Žádný
icon-size-and-spacing = Velikost a rozestupy ikon
grid-spacing = Rozestupy mřížky
deleting =
@ -195,8 +195,8 @@ deleted =
[few] položky
*[other] položek
} z koše
emptying-trash = Vyprazdňování koše ({ $progress })...
emptied-trash = Koš vyprázdněn
emptying-trash = Vysypávání koše ({ $progress })...
emptied-trash = Koš vysypán
restoring =
Obnovování { $items } { $items ->
[one] položky
@ -403,7 +403,7 @@ sort-largest-to-smallest = Od největšího po nejmenší
gallery-preview = Náhled galerie
sort = Řazení
sort-a-z = A-Z
empty-trash-title = Vyprázdnit koš?
empty-trash-title = Vysypat koš?
type-to-search-select = Vybere první shodující se soubor nebo složku
pasted-image = Vložený obrázek
pasted-text = Vložený text
@ -417,20 +417,3 @@ move-to-title = Vyberte cíl přesunutí
show-recents = Složka „Nedávné“ v postranním panelu
copy-path = Kopírovat cestu
clear-recents-history = Vymazat historii „Nedávné“
mixed = Různé
context-action = Kontextová akce
context-action-confirm-title = Spustit „{ $name }“?
context-action-confirm-warning =
Tato akce se spustí pro { $items } { $items ->
[one] položku
[few] položky
*[other] položek
}.
run = Spustit
rename-confirm = Přejmenovat
checksum = Kontrolní součet { $kind }
calculate = Vypočítat
error = Chyba
search-application = Hledat podle názvu aplikace
sidebar-label = Popisek v postranním panelu
change-sidebar-label = Změnit popisek

View file

@ -1,43 +1,35 @@
cosmic-files = COSMIC Filhåndtering
cosmic-files = COSMIC Filer
empty-folder = Tom mappe
empty-folder-hidden = Tom mappe (har skjulte emner)
no-results = Fandt ingen resultater
empty-folder-hidden = Tom mappe (har skjulte filer)
no-results = Ingen resultater
filesystem = Filsystem
home = Hjem
networks = Netværk
notification-in-progress = Fil-operationer er igang
trash = Papirkurv
notification-in-progress = Filoperationer er igangværende.
trash = Skraldespand
recents = Seneste
undo = Fortryd
today = I dag
# Desktop view options
desktop-view-options = Valgmuligheder for skrivebordsvisning...
show-on-desktop = Vis på Skrivebord
desktop-folder-content = Skrivebords-mappeindhold
desktop-view-options = Indstillinger for skrivebordsudseende...
show-on-desktop = Vis på Skrivebordet
desktop-folder-content = Indhold på Skrivebordet
mounted-drives = Monterede drev
trash-folder-icon = Papirkurv-ikon
icon-size-and-spacing = Ikonstørrelse og -afstand
trash-folder-icon = Skraldespandsikon
icon-size-and-spacing = Ikonstørrelse og afstand
icon-size = Ikonstørrelse
# List view
name = Navn
modified = Ændret
trashed-on = Kasseret
trashed-on = Smidt ud
size = Størrelse
# Progress footer
details = Detaljer
dismiss = Luk meddelelse
operations-running =
{ $running } { $running ->
[one] operation
*[other] operationer
} kører ({ $percent }%)...
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operationer
} kører ({ $percent }%), { $finished } færdig...
dismiss = Afvis besked
operations-running = { $running } operationer er i gang ({ $percent }%)...
operations-running-finished = { $running } operationer er i gang ({ $percent }%), { $finished } færdiggjort...
pause = Pause
resume = Genoptag
resume = Fortsæt
# Dialogs
@ -48,12 +40,12 @@ create-archive = Opret arkiv
## Empty Trash Dialog
empty-trash = Tøm papirkurv
empty-trash-warning = Emner i Papirkurv-mappen vil blive slettet permanent
empty-trash = Tøm skraldespand
empty-trash-warning = Er du sikker på du vil slette alle objekter i Skraldespanden permanent?
## Mount Error Dialog
mount-error = Ude af stand til at tilgå drev
mount-error = Kan ikke tilgå drev
## New File/Folder Dialog
@ -61,22 +53,22 @@ create-new-file = Opret ny fil
create-new-folder = Opret ny mappe
file-name = Filnavn
folder-name = Mappenavn
file-already-exists = En fil med det navn eksisterer allerede
folder-already-exists = En mappe med det navn eksisterer allerede
name-hidden = Navne begyndende med "." vil være skjulte
name-invalid = Navn må ikke være "{ $filename }"
name-no-slashes = Navn må ikke indeholde skråstreger
file-already-exists = En fil med det navn eksisterer allerede.
folder-already-exists = En mappe med det navn eksisterer allerede.
name-hidden = Navne begyndende med "." vil blive skjult.
name-invalid = Navn kan ikke være "{ $filename }".
name-no-slashes = Navn kan ikke indeholde skråstreg.
## Open/Save Dialog
cancel = Afbryd
cancel = Annullér
create = Opret
open = Åbn
open-file = Åbn fil
open-folder = Åbn mappe
open-in-new-tab = Åbn i nyt faneblad
open-in-new-tab = Åbn i ny fane
open-in-new-window = Åbn i nyt vindue
open-item-location = Åbn emne-placering
open-item-location = Åbn placering for objekt
open-multiple-files = Åbn flere filer
open-multiple-folders = Åbn flere mapper
save = Gem
@ -95,20 +87,20 @@ rename-folder = Omdøb mappe
## Replace Dialog
replace = Erstat
replace-title = "{ $filename }" eksisterer allerede i denne placering
replace-warning = Ønsker du at erstatte den med den, du er ved at gemme? Erstatning af den vil overskrive dens indhold.
replace-warning-operation = Ønsker du at erstatte den? Erstatning af den vil overskrive dens indhold.
replace-title = "{ $filename }" eksisterer allerede på denne placering.
replace-warning = Vil du erstatte den med den du er ved at gemme? Hvis du erstatter den, overskriver du dens indhold.
replace-warning-operation = Vil du erstatte den? Hvis du erstatter den, overskriver du dens indhold.
original-file = Original fil
replace-with = Erstat med
apply-to-all = Anvend på alle
apply-to-all = Anvend for alle
keep-both = Behold begge
skip = Spring over
## Set as Executable and Launch Dialog
set-executable-and-launch = Angiv som eksekverbar og start
set-executable-and-launch-description = Vil du angive "{ $name }" som eksekverbar og starte den?
set-and-launch = Angiv og start
set-executable-and-launch = Indstil som eksekverbar fil og start
set-executable-and-launch-description = Vil du indstille "{ $name }" som en eksekverbar fil og starte den?
set-and-launch = Indstil og start
## Metadata Dialog
@ -247,13 +239,13 @@ settings = Indstillinger
appearance = Udseende
theme = Tema
match-desktop = Følg skrivebordets indstillinger
dark = Mørkt
light = Lyst
match-desktop = Match skrivebord
dark = Mørk
light = Lys
# Context menu
add-to-sidebar = Tilføj til sidebjælke
compress = Komprimer…
extract-here = Uddrag
compress = Komprimér
extract-here = Extract
new-file = Ny fil...
new-folder = Ny mappe...
open-in-terminal = Åbn i terminal
@ -288,15 +280,15 @@ quit = Afslut
edit = Redigér
cut = Klip
copy = Kopiér
paste = Indsæt
select-all = Vælg alle
paste = Sæt ind
select-all = Vælg alt
## View
zoom-in = Zoom ind
default-size = Standardstørrelse
zoom-out = Zoom ud
view = Vis
view = Visning
grid-view = Gitter-visning
list-view = Liste-visning
show-hidden-files = Vis skjulte filer
@ -314,102 +306,3 @@ sort-newest-first = Nyeste først
sort-oldest-first = Ældste først
sort-smallest-to-largest = Mindste til største
sort-largest-to-smallest = Største til mindste
delete = Slet
run = Kør
repository = Pakkearkiv
grid-spacing = Gitter-afstand
extract-password-required = Adgangskode påkrævet
extract-to = Udpak Til...
extract-to-title = Udpak til mappe
empty-trash-title = Tøm papirkurv?
keywords = Mappe;Håndtering;
comment = Filhåndteringsprogram til COSMIC-skrivebordet
copy-to-title = Vælg kopi destination
copy-to-button-label = Kopier
move-to-title = Vælg flytte destination
move-to-button-label = Flyt
deleted =
Slettede { $items } { $items ->
[one] element
*[other] elementer
} fra { trash }
removing-from-recents =
Fjerner { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
type-to-search-enter-path = Indtast stien til mappen eller filen
permanently-deleting =
Sletter permanent { $items } { $items ->
[one] element
*[other] elementer
}
execute-only = Kun kørsel
removed-from-recents =
Fjernede { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
permanently-deleted =
Slettede permanent { $items } { $items ->
[one] element
*[other] elementer
}
write-only = Kun skrivning
remove = Fjern
context-action = Konteksthandling
context-action-confirm-title = Kør “{ $name }”?
context-action-confirm-warning =
Dette vil køre på { $items } { $items ->
[one] item
*[other] items
}.
selected-items = De { $items } valgte elementer
mixed = Blandet
none = Ingen
write-execute = Skriv og kør
read-only = Kun læsning
read-execute = Læs og kør
read-write = Læs og skriv
read-write-execute = Læs, skriv og kør
keep = Behold
support = Support
progress-failed = { $percent } %, mislykkedes
pasted-image = Indsat billede
pasted-text = Indsat tekst
pasted-video = Indsat video
deleting =
Sletter { $items } { $items ->
[one] element
*[other] elementer
} fra { trash } ({ $progress })...
setting-permissions = Indstiller tilladelser for “{ $name }” til { $mode }
set-permissions = Sæt tilladelser for “{ $name }” til { $mode }
show-recents = Seneste-mappe i sidepanelet
type-to-search = Skriv for at søge
type-to-search-recursive = Søger i den aktuelle mappe og alle undermapper
type-to-search-select = Vælger den første matchende fil eller mappe
clear-recents-history = Ryd seneste historik
copy-to = Kopiér til…
delete-permanently = Slet permanent
eject = Skub ud
move-to = Flyt til…
remove-from-recents = Fjern fra seneste
reload-folder = Genindlæs mappe
copy-path = Kopiér sti
rename-confirm = Omdøb
calculate = Udregn
error = Fejl
checksum = { $kind }-kontrolsum
other-apps = Andre applikationer
related-apps = Relaterede applikationer
permanently-delete-question = Slet permanent?
permanently-delete-warning = { $target } vil blive slettet permanent. Denne handling kan ikke fortrydes.
open-with = Åbn med
favorite-path-error = Fejl ved åbning af sti
favorite-path-error-description =
Ude af stand til at åbne "{ $path }"
"{ $path }" Eksisterer muligvis ikke, eller du har måske ikketilladelse til at åbne den
Ønsker du at fjerne den fra sidebjælken?
single-click = Enkelt klik for at åbne

View file

@ -11,7 +11,7 @@ recents = Zuletzt verwendet
undo = Rückgängig
today = Heute
# Optionen für die Desktop-Ansicht
desktop-view-options = Optionen für die Desktop-Ansicht ...
desktop-view-options = Optionen für die Desktop-Ansicht…
show-on-desktop = Auf Desktop anzeigen
desktop-folder-content = Inhalt des Desktop-Ordners
mounted-drives = Eingehängte Laufwerke
@ -31,12 +31,12 @@ operations-running =
{ $running } { $running ->
[one] laufender Vorgang
*[other] laufende Vorgänge
} ({ $percent } %) ...
} ({ $percent } %)...
operations-running-finished =
{ $running } { $running ->
[one] laufender Vorgang
*[other] laufende Vorgänge
} ({ $percent } %), { $finished } abgeschlossen ...
} ({ $percent } %), { $finished } abgeschlossen...
pause = Pause
resume = Fortsetzen
@ -50,7 +50,7 @@ create-archive = Archiv erstellen
## Entpacken-Dialog
extract-password-required = Passwort erforderlich
extract-to = Entpacken nach ...
extract-to = Entpacken nach...
extract-to-title = In Ordner entpacken
## Dialog zum Leeren des Papierkorbs
@ -179,7 +179,7 @@ keep = Behalten
add-network-drive = Netzlaufwerk hinzufügen
connect = Verbinden
connect-anonymously = Anonym verbinden
connecting = Wird verbunden ...
connecting = Wird verbunden...
domain = Domain
enter-server-address = Serveradresse eingeben
network-drive-description =
@ -218,7 +218,7 @@ compressing =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress }) ...
} von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress })...
compressed =
{ $items } { $items ->
[one] Element wurde
@ -231,7 +231,7 @@ copying =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ kopiert ({ $progress }) ...
} von „{ $from }“ nach „{ $to }“ kopiert ({ $progress })...
copied =
{ $items } { $items ->
[one] Element wurde
@ -241,19 +241,19 @@ deleting =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} aus dem { trash } gelöscht ({ $progress }) ...
} aus dem { trash } gelöscht ({ $progress })...
deleted =
{ $items } { $items ->
[one] Element wurde
*[other] Elemente wurden
} aus dem { trash } gelöscht
emptying-trash = { trash } wird geleert ({ $progress }) ...
emptying-trash = { trash } wird geleert ({ $progress })...
emptied-trash = { trash } geleert
extracting =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ entpackt ({ $progress }) ...
} von „{ $from }“ nach „{ $to }“ entpackt ({ $progress })...
extracted =
{ $items } { $items ->
[one] Element wurde
@ -267,7 +267,7 @@ moving =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ verschoben ({ $progress }) ...
} von „{ $from }“ nach „{ $to }“ verschoben ({ $progress })...
moved =
{ $items } { $items ->
[one] Element wurde
@ -289,7 +289,7 @@ restoring =
{ $items } { $items ->
[one] Element wird
*[other] Elemente werden
} aus dem { trash } wiederhergestellt ({ $progress }) ...
} aus dem { trash } wiederhergestellt ({ $progress })...
restored =
{ $items } { $items ->
[one] Element wurde
@ -299,7 +299,7 @@ unknown-folder = unbekannter Ordner
## Öffnen mit
menu-open-with = Öffnen mit ...
menu-open-with = Öffnen mit...
default-app = { $name } (Standard)
## Details anzeigen
@ -311,7 +311,7 @@ item-size = Größe: { $size }
item-created = Erstellt: { $created }
item-modified = Geändert: { $modified }
item-accessed = Zugegriffen: { $accessed }
calculating = Wird berechnet ...
calculating = Wird berechnet...
## Einstellungen
@ -330,18 +330,18 @@ light = Hell
type-to-search = Zum Suchen tippen
type-to-search-recursive = Durchsucht den aktuellen Ordner und alle Unterordner
type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein
type-to-search-enter-path = Gibt den Pfad zu einem Verzeichnis oder einer Datei ein
# Kontextmenü
add-to-sidebar = Zur Seitenleiste hinzufügen
compress = Komprimieren ...
compress = Komprimieren...
delete-permanently = Endgültig löschen
extract-here = Entpacken
new-file = Neue Datei ...
new-folder = Neuer Ordner ...
new-file = Neue Datei...
new-folder = Neuer Ordner...
open-in-terminal = Im Terminal öffnen
move-to-trash = In den Papierkorb verschieben
restore-from-trash = Aus dem Papierkorb wiederherstellen
remove-from-sidebar = Aus der Seitenleiste entfernen
remove-from-sidebar = Von der Seitenleiste entfernen
sort-by-name = Nach Name sortieren
sort-by-modified = Nach Änderung sortieren
sort-by-size = Nach Größe sortieren
@ -349,9 +349,9 @@ sort-by-trashed = Nach Löschzeitpunkt sortieren
## Desktop
change-wallpaper = Hintergrundbild ändern ...
desktop-appearance = Desktop-Aussehen ...
display-settings = Anzeigeeinstellungen ...
change-wallpaper = Hintergrundbild ändern...
desktop-appearance = Desktop-Aussehen...
display-settings = Anzeigeeinstellungen...
# Menü
@ -362,7 +362,7 @@ file = Datei
new-tab = Neuer Tab
new-window = Neues Fenster
reload-folder = Ordner neu laden
rename = Umbenennen ...
rename = Umbenennen...
close-tab = Tab schließen
quit = Beenden
@ -376,28 +376,28 @@ select-all = Alles auswählen
## Ansicht
zoom-in = Hineinzoomen
zoom-in = Vergrößern
default-size = Standardgröße
zoom-out = Herauszoomen
zoom-out = Verkleinern
view = Ansicht
grid-view = Rasteransicht
list-view = Listenansicht
show-hidden-files = Versteckte Dateien anzeigen
list-directories-first = Verzeichnisse zuerst auflisten
gallery-preview = Galerie-Vorschau
menu-settings = Einstellungen ...
menu-about = Über COSMIC Dateien ...
menu-settings = Einstellungen...
menu-about = Über COSMIC Dateien...
## Sortieren
sort = Sortierung
sort = Sortieren
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Neueste zuerst
sort-oldest-first = Älteste zuerst
sort-smallest-to-largest = Von klein nach groß
sort-largest-to-smallest = Von groß nach klein
repository = Repositorium
repository = Repository
empty-trash-title = Papierkorb leeren?
copy-to-title = Kopierziel auswählen
copy-to-button-label = Kopieren
@ -418,28 +418,9 @@ type-to-search-select = Wählt die erste übereinstimmende Datei oder den ersten
pasted-image = Eingefügtes Bild
pasted-text = Eingefügter Text
pasted-video = Eingefügtes Video
copy-to = Kopieren nach ...
copy-to = Kopieren nach...
show-recents = Ordner zuletzt verwendeter Elemente in der Seitenleiste
clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren
comment = Dateimanager für den COSMIC Desktop
keywords = Ordner;Manager;
move-to-button-label = Verschieben
move-to-title = Verschiebeziel auswählen
remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen
move-to = Verschieben nach ...
copy-path = Pfad kopieren
mixed = Gemischt
context-action = Kontextaktion
context-action-confirm-title = „{ $name }“ ausführen?
context-action-confirm-warning =
Dies wird ausgeführt auf { $items } { $items ->
[one] Element
*[other] Elementen
}.
run = Ausführen
rename-confirm = Umbenennen
checksum = { $kind } Prüfsumme
calculate = Berechnen
error = Fehler
sidebar-label = Bezeichnung in der Seitenleiste
search-application = Nach App-Name suchen

View file

@ -1,336 +1,15 @@
empty-folder = Άδειος φάκελος
no-results = Δεν βρέθηκαν αποτελέσματα
trash = Απορρίμματα
trash = Κάδος Ανακύκλωσης
recents = Πρόσφατα
cosmic-files = Αρχεία COSMIC
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά στοιχεία)
cosmic-files = COSMIC Αρχεία
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά αντικείμενα)
filesystem = Σύστημα αρχείων
home = Προσωπικός φάκελος
networks = Δίκτυα
comment = Διαχείριση αρχείων για το περιβάλλον επιφάνειας εργασίας COSMIC
keywords = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager;
networks = Δίκτυο
comment = Αρχεία για το COSMIC περιβάλλον
keywords = Φάκελος;Διαχειριστής;
rename = Μετονομασία...
close-tab = Κλείσιμο καρτέλας
light = Ανοιχτόχρωμο
dark = Σκουρόχρωμο
connect = Σύνδεση
dismiss = Απόρριψη μηνύματος
copy_noun = Αντιγραφή
open-file = Άνοιγμα αρχείου
save = Αποθήκευση
password = Κωδικός πρόσβασης
remove = Αφαίρεση
create = Δημιουργία
pause = Παύση
quit = Τερματισμός
calculating = Υπολογισμός...
keep = Διατήρηση
edit = Επεξεργασία
connecting = Σύνδεση...
copy = Αντιγραφή
theme = Θέμα
appearance = Εμφάνιση
name = Όνομα
resume = Συνέχιση
username = Όνομα χρήστη
delete = Διαγραφή
repository = Αποθετήριο
support = Υποστήριξη
eject = Εξαγωγή
group = Ομάδα
skip = Παράλειψη
paste = Επικόλληση
menu-settings = Ρυθμίσεις...
view = Προβολή
undo = Αναίρεση
details = Λεπτομέρειες
sort-a-z = Α-Ω
extract-here = Αποσυμπίεση
cancel = Ακύρωση
sort-z-a = Ω-Α
open = Άνοιγμα
history = Ιστορικό
domain = Τομέας
sort = Ταξινόμηση
settings = Ρυθμίσεις
pending = Σε εκκρεμότητα
open-folder = Άνοιγμα φακέλου
replace = Αντικατάσταση
cut = Αποκοπή
file = Αρχείο
today = Σήμερα
compress = Συμπίεση...
size = Μέγεθος
related-apps = Σχετικές εφαρμογές
zoom-in = Μεγέθυνση
select-all = Επιλογή όλων
icon-size-and-spacing = Μέγεθος και απόσταση εικονιδίων
new-window = Νέο παράθυρο
zoom-out = Σμίκρυνση
default-size = Προεπιλεγμένο μέγεθος
create-archive = Δημιουργία συμπιεσμένου αρχείου
other-apps = Άλλες εφαρμογές
rename-folder = Μετονομασία φακέλου
folder-name = Όνομα φακέλου
connect-anonymously = Ανώνυμη σύνδεση
replace-with = Αντικατάσταση με
mounted-drives = Προσαρτημένες μονάδες
desktop-view-options = Επιλογές προβολής επιφάνειας εργασίας...
show-on-desktop = Εμφάνιση στην επιφάνεια εργασίας
trash-folder-icon = Εικονίδιο φακέλου απορριμμάτων
open-with = Άνοιγμα με
keep-both = Διατήρηση αμφότερων
icon-size = Μέγεθος εικονιδίων
open-with-title = Πώς θέλετε να ανοίξετε το «{ $name }»;
extract-password-required = Απαιτείται κωδικός πρόσβασης
rename-file = Μετονομασία αρχείου
file-name = Όνομα αρχείου
save-file = Αποθήκευση αρχείου
name-hidden = Τα ονόματα που ξεκινούν με «.» θα αποκρύπτονται
folder-already-exists = Υπάρχει ήδη ένας φάκελος με αυτό το όνομα
empty-trash = Άδειασμα απορριμμάτων
permanently-delete-question = Οριστική διαγραφή;
copy-to-button-label = Αντιγραφή
move-to-button-label = Μετακίνηση
run = Εκτέλεση
copy-to-title = Επιλογή προορισμού αντιγραφής
sort-newest-first = Πρώτα τα νεότερα
default-app = { $name } (προεπιλογή)
renamed = Έγινε μετονομασία από «{ $from }» σε «{ $to }»
read-execute = Ανάγνωση και εκτέλεση
deleted =
Έγινε διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash }
item-modified = Ημερομηνία τροποποίησης: { $modified }
list-view = Προβολή λίστας
reload-folder = Επαναφόρτωση φακέλου
favorite-path-error = Σφάλμα ανοίγματος καταλόγου
progress = { $percent }%
remove-from-sidebar = Αφαίρεση από την πλαϊνή στήλη
restoring =
Ανάκτηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash } ({ $progress })...
network-drive-error = Αδυναμία πρόσβασης σε μονάδα δικτύου
gallery-preview = Προεπισκόπηση συλλογής
sort-smallest-to-largest = Από τα μικρότερα στα μεγαλύτερα
removing-from-recents =
Αφαίρεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { recents }
type-to-search-enter-path = Εισέρχεται στη διαδρομή προς τον κατάλογο ή το αρχείο
emptying-trash = Άδειασμα φακέλου «{ trash }» ({ $progress })...
trashed-on = Ημερομηνία διαγραφής
compressing =
Συμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }» ({ $progress })...
move-to-trash = Μετακίνηση στα απορρίμματα
menu-about = Σχετικά με τα Αρχεία COSMIC...
setting-executable-and-launching = Ορισμός του «{ $name }» ως εκτελέσιμου και εκκίνηση
open-multiple-files = Άνοιγμα πολλαπλών αρχείων
menu-open-with = Άνοιγμα με...
extracted =
Έγινε αποσυμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από το αρχείο «{ $from }» στον φάκελο «{ $to }»
create-new-folder = Δημιουργία νέου φακέλου
original-file = Πρωτότυπο αρχείο
read-write-execute = Ανάγνωση, εγγραφή και εκτέλεση
set-permissions = Έγινε ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
sort-by-size = Ταξινόμηση κατά μέγεθος
item-size = Μέγεθος: { $size }
permanently-deleting =
Οριστική διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
}
read-write = Ανάγνωση και εγγραφή
none = Κανένα
items = Στοιχεία: { $items }
type = Τύπος: { $mime }
compressed =
Έγινε συμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }»
replace-warning = Θέλετε να το αντικαταστήσετε με αυτό που αποθηκεύετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
new-file = Νέο αρχείο...
open-in-terminal = Άνοιγμα σε τερματικό
open-multiple-folders = Άνοιγμα πολλαπλών φακέλων
remember-password = Απομνημόνευση κωδικού πρόσβασης
show-details = Εμφάνιση λεπτομερειών
grid-spacing = Απόσταση πλέγματος
extract-to = Αποσυμπίεση σε...
add-network-drive = Προσθήκη μονάδας δικτύου
copying =
Αντιγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
sort-oldest-first = Πρώτα τα παλαιότερα
create-new-file = Δημιουργία νέου αρχείου
sort-by-trashed = Ταξινόμηση κατά ημερομηνία διαγραφής
replace-warning-operation = Θέλετε να το αντικαταστήσετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
try-again = Δοκιμή ξανά
copied =
Έγινε αντιγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
other = Άλλο
open-in-new-window = Άνοιγμα σε νέο παράθυρο
sort-by-modified = Ταξινόμηση κατά ημερομηνία τροποποίησης
list-directories-first = Παράθεση των καταλόγων πρώτα
read-only = Μόνο ανάγνωση
browse-store = Περιήγηση στο { $store }
enter-server-address = Εισαγάγετε τη διεύθυνση διακομιστή
remove-from-recents = Αφαίρεση από τα πρόσφατα
apply-to-all = Εφαρμογή σε όλα
moving =
Μετακίνηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
change-wallpaper = Αλλαγή ταπετσαρίας...
network-drive-description =
Οι διευθύνσεις διακομιστών αποτελούνται από ένα πρόθεμα πρωτοκόλλου και μια διεύθυνση.
Παραδείγματα: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
Διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash } ({ $progress })...
single-click = Μονό κλικ για άνοιγμα
setting-permissions = Ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
owner = Κάτοχος
creating = Δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
execute-only = Μόνο εκτέλεση
open-item-location = Άνοιγμα τοποθεσίας στοιχείου
set-executable-and-launched = Έγινε ορισμός του «{ $name }» ως εκτελέσιμου και εκκινήθηκε
mount-error = Αδυναμία πρόσβασης στη μονάδα
grid-view = Προβολή πλέγματος
set-and-launch = Ορισμός και εκκίνηση
removed-from-recents =
Έγινε αφαίρεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { recents }
add-to-sidebar = Προσθήκη στην πλαϊνή στήλη
item-created = Ημερομηνία δημιουργίας: { $created }
network-drive-schemes =
Διαθέσιμα πρωτόκολλα,Πρόθεμα
AppleTalk,afp://
File Transfer Protocol,ftp:// ή ftps://
Network File System,nfs://
Server Message Block,smb://
SSH File Transfer Protocol,sftp:// ή ssh://
WebDAV,dav:// ή davs://
set-executable-and-launch = Ορισμός ως εκτελέσιμο και εκκίνηση
restored =
Έγινε ανάκτηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash }
type-to-search-recursive = Κάνει αναζήτηση στον τρέχοντα φάκελο και όλους τους υποφακέλους
progress-paused = { $percent }%, σε παύση
cancelled = Ακυρωμένες
new-folder = Νέος φάκελος...
match-desktop = Συμφωνία με την επιφάνεια εργασίας
operations-running-finished =
Εκτέλεση { $running } { $running ->
[one] διεργασίας
*[other] διεργασιών
} ({ $percent }%), { $finished } ολοκληρωμένες...
sort-by-name = Ταξινόμηση κατά όνομα
edit-history = Ιστορικό επεξεργασιών
show-hidden-files = Εμφάνιση κρυφών αρχείων
progress-failed = { $percent }%, απέτυχε
item-accessed = Ημερομηνία πρόσβασης: { $accessed }
extract-to-title = Αποσυμπίεση σε φάκελο
extracting =
Αποσυμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από το αρχείο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
permanently-deleted =
Έγινε οριστική διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
}
complete = Ολοκληρωμένες
write-execute = Εγγραφή και εκτέλεση
desktop-folder-content = Περιεχόμενο φακέλου επιφάνειας εργασίας
renaming = Μετονομασία από «{ $from }» σε «{ $to }»
set-executable-and-launch-description = Θέλετε να ορίσετε το «{ $name }» ως εκτελέσιμο και να το εκκινήσετε;
no-history = Δεν υπάρχουν στοιχεία στο ιστορικό.
emptied-trash = Έγινε άδειασμα του φακέλου «{ trash }»
sort-largest-to-smallest = Από τα μεγαλύτερα στα μικρότερα
restore-from-trash = Ανάκτηση από τα απορρίμματα
moved =
Έγινε μετακίνηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
progress-cancelled = { $percent }%, ακυρώθηκε
open-in-new-tab = Άνοιγμα σε νέα καρτέλα
unknown-folder = άγνωστος φάκελος
created = Έγινε δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
delete-permanently = Οριστική διαγραφή
write-only = Μόνο εγγραφή
display-settings = Ρυθμίσεις οθόνης...
new-tab = Νέα καρτέλα
failed = Αποτυχημένες
modified = Ημερομηνία τροποποίησης
desktop-appearance = Εμφάνιση επιφάνειας εργασίας...
file-already-exists = Υπάρχει ήδη ένα αρχείο με αυτό το όνομα
permanently-delete-warning = Θα διαγραφούν οριστικά τα εξής: { $target }. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.
favorite-path-error-description =
Αδυναμία ανοίγματος του «{ $path }»
Το «{ $path }» ενδέχεται να μην υπάρχει ή να μην έχετε το δικαίωμα να το ανοίξετε
Θέλετε να το αφαιρέσετε από την πλαϊνή στήλη;
empty-trash-warning = Τα στοιχεία του φακέλου «Απορρίμματα» θα διαγραφούν οριστικά
empty-trash-title = Άδειασμα απορριμμάτων;
type-to-search = Πληκτρολόγηση για αναζήτηση
notification-in-progress = Βρίσκονται σε εξέλιξη διεργασίες αρχείων
name-no-slashes = Το όνομα δεν μπορεί να περιέχει καθέτους
replace-title = Το «{ $filename }» υπάρχει ήδη σε αυτήν την τοποθεσία
name-invalid = Το όνομα δεν μπορεί να είναι «{ $filename }»
operations-running =
Εκτέλεση { $running } { $running ->
[one] διεργασίας
*[other] διεργασιών
} ({ $percent }%)...
context-action-confirm-title = Εκτέλεση του «{ $name }»;
pasted-image = Επικολλημένη εικόνα
pasted-text = Επικολλημένο κείμενο
pasted-video = Επικολλημένο βίντεο
show-recents = Φάκελος «Πρόσφατα» στην πλαϊνή στήλη
move-to = Μετακίνηση σε...
copy-path = Αντιγραφή διαδρομής
move-to-title = Επιλογή προορισμού μετακίνησης
selected-items = { $items } επιλεγμένα στοιχεία
context-action = Ενέργεια περιβάλλοντος
copy-to = Αντιγραφή σε...
mixed = Μικτό
type-to-search-select = Επιλέγει την πρώτη αντιστοιχία αρχείου ή φακέλου
clear-recents-history = Απαλοιφή ιστορικού πρόσφατων
context-action-confirm-warning =
Θα εκτελεστεί σε { $items } { $items ->
[one] στοιχείο
*[other] στοιχεία
}.
rename-confirm = Μετονομασία
checksum = Άθροισμα ελέγχου { $kind }
calculate = Υπολογισμός
error = Σφάλμα
sidebar-label = Ετικέτα πλαϊνής στήλης
search-application = Αναζήτηση με όνομα εφαρμογής
change-sidebar-label = Αλλαγή ετικέτας πλαϊνής στήλης
light = Φωτεινό
dark = Σκοτεινό

View file

@ -1,100 +0,0 @@
save = Save
quit = Quit
name = Name
delete = Delete
cancel = Cancel
run = Run
new-window = New window
theme = Theme
appearance = Appearance
light = Light
repository = Repository
support = Support
dark = Dark
match-desktop = Match desktop
settings = Settings
replace = Replace
file = File
zoom-in = Zoom in
select-all = Select all
zoom-out = Zoom out
default-size = Default size
edit = Edit
copy = Copy
paste = Paste
view = View
undo = Undo
cut = Cut
icon-size-and-spacing = Icon size and spacing
cosmic-files = COSMIC Files
trash = Rubbish
empty-folder-hidden = Empty folder (has hidden items)
no-results = No results found
empty-folder = Empty folder
menu-settings = Settings...
recents = Recents
mounted-drives = Mounted drives
filesystem = Filesystem
home = Home
desktop-view-options = Desktop view options...
show-on-desktop = Show on Desktop
trash-folder-icon = Bin folder icon
desktop-folder-content = Desktop folder content
networks = Networks
today = Today
notification-in-progress = File operations are in progress
comment = File manager for the COSMIC desktop
keywords = Folder;Manager;
size = Size
dismiss = Dismiss message
trashed-on = Binned
create-archive = Create archive
pause = Pause
resume = Resume
grid-spacing = Grid spacing
details = Details
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%), { $finished } finished...
icon-size = Icon size
modified = Modified
operations-running =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%)...
copy-to-title = Select copy destination
open-file = Open file
open-multiple-files = Open multiple files
create-new-folder = Create new folder
create = Create
other-apps = Other applications
open-multiple-folders = Open multiple folders
extract-to = Extract To...
create-new-file = Create new file
open-in-new-window = Open in new window
folder-name = Folder name
browse-store = Browse { $store }
open-item-location = Open item location
mount-error = Unable to access drive
open = Open
extract-to-title = Extract to folder
open-with-title = How do you want to open "{ $name }"?
extract-password-required = Password required
open-folder = Open folder
open-in-new-tab = Open in new tab
file-name = File name
save-file = Save file
file-already-exists = A file with that name already exists
name-hidden = Names starting with "." will be hidden
folder-already-exists = A folder with that name already exists
empty-trash = Empty bin
empty-trash-title = Empty bin?
name-no-slashes = Name cannot contain slashes
name-invalid = Name cannot be "{ $filename }"
copy-to-button-label = Copy
move-to-title = Select move destination
move-to-button-label = Move
context-action = Context action

View file

@ -96,17 +96,9 @@ 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
context-action = Context action
context-action-confirm-title = Run "{$name}"?
context-action-confirm-warning = This will run on {$items} {$items ->
[one] item
*[other] items
}.
run = Run
## Permanently delete Dialog
selected-items = The {$items} selected items
@ -117,10 +109,6 @@ permanently-delete-warning = {$target} will be permanently deleted. This action
## Rename Dialog
rename-file = Rename file
rename-folder = Rename folder
rename-confirm = Rename
## Change Sidebar Label Dialog
sidebar-label = Sidebar label
## Replace Dialog
replace = Replace
@ -143,12 +131,6 @@ 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
### Mode 1 (unusual)
@ -302,7 +284,6 @@ unknown-folder = unknown folder
## Open with
menu-open-with = Open with...
default-app = {$name} (default)
search-application = Search by app name
## Show details
show-details = Show details
@ -313,9 +294,6 @@ item-created = Created: {$created}
item-modified = Modified: {$modified}
item-accessed = Accessed: {$accessed}
calculating = Calculating...
checksum = {$kind} checksum
calculate = Calculate
error = Error
## Settings
settings = Settings
@ -346,12 +324,10 @@ extract-here = Extract
new-file = New file...
new-folder = New folder...
open-in-terminal = Open in terminal
edit-desktop-entry = Edit desktop entry
move-to = Move to...
move-to-trash = Move to trash
restore-from-trash = Restore from trash
remove-from-sidebar = Remove from sidebar
change-sidebar-label = Change sidebar label
sort-by-name = Sort by name
sort-by-modified = Sort by modified
sort-by-size = Sort by size

View file

@ -5,7 +5,7 @@ no-results = No se encontraron resultados
filesystem = Sistema de archivos
home = Inicio
networks = Redes
notification-in-progress = Las operaciones de archivo están en progreso
notification-in-progress = Las operaciones de archivo están en progreso.
trash = Papelera
recents = Recientes
undo = Deshacer

View file

@ -345,4 +345,3 @@ sort-newest-first = جدیدترین در ابتدا
sort-oldest-first = قدیمی‌ترین در ابتدا
sort-smallest-to-largest = کوچک‌ترین به بزرگ‌ترین
sort-largest-to-smallest = بزرگ‌ترین به کوچک‌ترین
run = اجرا

View file

@ -5,7 +5,7 @@ no-results = Ei tuloksia
filesystem = Tiedostojärjestelmä
home = Koti
networks = Verkot
notification-in-progress = Tiedostotoimintoja käynnissä
notification-in-progress = Tiedostotoimintoja käynnissä.
trash = Roskakori
recents = Viimeaikaiset
undo = Kumoa
@ -16,7 +16,7 @@ today = Tänään
desktop-view-options = Työpöytänäkymän asetukset…
show-on-desktop = Näytä työpöydällä
desktop-folder-content = Työpöytäkansion sisältö
mounted-drives = Liitetyt asemat
mounted-drives = Tiedostojärjestelmään liitetyt kovalevyt
trash-folder-icon = Roskakorikansion kuvake
icon-size-and-spacing = Kuvakkeen koko ja välistys
icon-size = Kuvakkeen koko
@ -38,7 +38,7 @@ create-archive = Luo arkisto
## Empty Trash Dialog
empty-trash = Tyhjennä roskakori
empty-trash-warning = Roskakorikansion kohteet poistetaan pysyvästi
empty-trash-warning = Haluatko varmasti tyhjentää koko roskakorin pysyvästi?
## Mount Error Dialog
@ -73,8 +73,8 @@ save-file = Tallenna tiedosto
## Open With Dialog
open-with-title = Miten haluat avata kohteen "{ $name }"?
browse-store = Selaa { $store }a
open-with-title = Kuinka haluat avata kohteen "{ $name }"?
browse-store = Selaa { $store }
## Rename Dialog
@ -88,15 +88,15 @@ replace-title = "{ $filename }" on jo olemassa tässä sijainnissa
replace-warning = Haluatko korvata sen tallentamallasi kohteella? Korvaaminen ylikirjoittaa kohteen sisällön.
replace-warning-operation = Haluatko korvata sen? Korvaaminen ylikirjoittaa sen sisällön.
original-file = Alkuperäinen tiedosto
replace-with = Korvaa käyttäen
apply-to-all = Toteuta kaikkiin
replace-with = Korvaa kohteella
apply-to-all = Sovella kaikkiin
keep-both = Pidä molemmat
skip = Ohita
## Set as Executable and Launch Dialog
set-executable-and-launch = Aseta käynnistettäväksi ja käynnistä
set-executable-and-launch-description = Haluatko asettaa kohteen "{ $name }" käynnistettäväksi ja käynnistää sen?
set-executable-and-launch-description = Haluatko asettaa kohteen "{ $name }" käynnistettväksi ja käynnistää sen?
set-and-launch = Aseta ja käynnistä
## Metadata Dialog
@ -116,9 +116,9 @@ other = Muut
add-network-drive = Lisää verkkolevy
connect = Yhdistä
connect-anonymously = Yhdistä nimettömästi
connecting = Yhdistetään…
connecting = Yhdistää…
domain = Verkkotunnus
enter-server-address = Kirjoita palvelimen osoite
enter-server-address = Syötä palvelimen osoite
network-drive-description =
Palvelinosoitteet sisältävät protokollaetuliitteen sekä osoitteen.
Esimerkkejä: ssh://192.168.0.1, ftp://[2001:db8::1]
@ -126,91 +126,91 @@ network-drive-description =
### Make sure to keep the comma which separates the columns
network-drive-schemes =
Saatavilla olevat yhteyskäytännöt,Etuliite
Saatavissa olevat protokollat,Etuliite
AppleTalk,afp://
File Transfer Protocol,ftp:// tai ftps://
File Transfer Protocol,ftp:// or ftps://
Network File System,nfs://
Server Message Block,smb://
SSH File Transfer Protocol,sftp:// tai ssh://
WebDav,dav:// tai davs://
network-drive-error = Verkkolevy ei saatavilla
SSH File Transfer Protocol,sftp:// or ssh://
WebDav,dav:// or davs://
network-drive-error = Verkkolevy saavuttamattomissa
password = Salasana
remember-password = Muista salasana
try-again = Yritä uudelleen
username = Käyttäjätunnus
username = Käyttäjänimi
## Operations
edit-history = Muokkaa historiaa
history = Historia
no-history = Historia on tyhjä.
pending = Jonossa
failed = Epäonnistuneet
complete = Valmiit
pending = Odottaa käsittelyä
failed = Epäonnistui
complete = Valmis
compressing =
Pakataan { $items } { $items ->
Tiivistetään { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" arkistoon "{ $to }" ({ $progress })…
*[other] kohteita
} lähteestä "{ $from }" arkistoon "{ $to }"
compressed =
Pakattu { $items } { $items ->
Tiivistetty { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" arkistoon "{ $to }"
*[other] kohteet
} lähteestä "{ $from }" arkistoon "{ $to }"
copy_noun = Kopio
creating = Luodaan "{ $name }" kohteen "{ $parent }" alle
created = Luotu "{ $name }" kohteen "{ $parent }" alle
creating = Luodaan kohdetta "{ $name }" kohteen "{ $parent }" alle
created = Luotu kohde "{ $name }" kohteen "{ $parent }" alle
copying =
Kopioidaan { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
*[other] kohteita
} lähteestä "{ $from }" kohteeseen "{ $to }"
copied =
Kopioitu { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" kohteeseen "{ $to }"
emptying-trash = Tyhjennetään { trash } ({ $progress })…
*[other] kohteet
} lähteestä "{ $from }" kohteeseen "{ $to }"
emptying-trash = Tyhjennetään { trash }
emptied-trash = Tyhjennetty { trash }
extracting =
Puretaan { $items } { $items ->
[one] kohde
*[other] kohdetta
} arkistosta "{ $from }" kohteeseen "{ $to }" ({ $progress })…
*[other] kohteet
} arkistosta "{ $from }" kohteeseen "{ $to }"
extracted =
Purettu { $items } { $items ->
[one] kohde
*[other] kohdetta
*[other] kohteet
} arkistosta "{ $from }" kohteeseen "{ $to }"
setting-executable-and-launching = Asetetaan "{ $name }" käynnistettäväksi ja käynnistetään
set-executable-and-launched = Asetettu "{ $name }" käynnistettäväksi ja käynnistetty
moving =
Siirretään { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
*[other] kohteet
} lähteestä "{ $from }" kohteeseen "{ $to }"
moved =
Siirretty { $items } { $items ->
[one] kohde
*[other] kohdetta
} sijainnista "{ $from }" kohteeseen "{ $to }"
*[other] kohteet
} lähteestä "{ $from }" kohteeseen "{ $to }"
renaming = Nimetään kohde "{ $from }" muotoon "{ $to }"
renamed = Nimetty kohde "{ $from }" muotoon "{ $to }"
restoring =
Palautetaan { $items } { $items ->
[one] kohde
*[other] kohdetta
} roskakorista ({ $progress })…
*[other] kohteet
} { trash }sta
restored =
Palautettu { $items } { $items ->
[one] kohde
*[other] kohdetta
} roskakorista
unknown-folder = tuntematon kansio
*[other] kohteet
} { trash }sta
unknown-folder = Tuntematon kansio
## Open with
menu-open-with = Avaa sovelluksella…
menu-open-with = Avaa ohjelmalla…
default-app = { $name } (oletus)
## Show details
@ -225,14 +225,14 @@ settings = Asetukset
appearance = Ulkoasu
theme = Teema
match-desktop = Sama kuin työpöytä
match-desktop = Sovita yhteen työpöydän kanssa
dark = Tumma
light = Vaalea
# Context menu
add-to-sidebar = Lisää sivupalkkiin
compress = Pakkaa…
compress = Pakkaa
extract-here = Pura
new-file = Uusi tiedosto…
new-folder = Uusi kansio…
@ -241,9 +241,9 @@ move-to-trash = Siirrä roskakoriin
restore-from-trash = Palauta roskakorista
remove-from-sidebar = Poista sivupalkista
sort-by-name = Järjestä nimen mukaan
sort-by-modified = Järjestä muokkausajan mukaan
sort-by-modified = Järjestä muokkauspäivämäärän mukaan
sort-by-size = Järjestä koon mukaan
sort-by-trashed = Järjestä poistamisajan mukaan
sort-by-trashed = Järjestä poistamispäivämäärän mukaan
## Desktop
@ -281,9 +281,9 @@ grid-view = Ruudukkonäkymä
list-view = Listanäkymä
show-hidden-files = Näytä piilotetut tiedostot
list-directories-first = Näytä kansiot ensin
gallery-preview = Gallerian esikatselu
gallery-preview = Gallerian esinäkymä
menu-settings = Asetukset…
menu-about = Tietoa COSMICin tiedostonhallinnasta…
menu-about = Tietoa COSMIC Tiedostoista…
## Sort
@ -318,106 +318,3 @@ move-to-button-label = Siirrä
clear-recents-history = Tyhjennä viimeaikaisten historia
copy-path = Kopioi polku
dismiss = Hylkää viesti
operations-running =
{ $running } { $running ->
[one] toiminto
*[other] toimintoa
} käynnissä ({ $percent } %)...
operations-running-finished =
{ $running } { $running ->
[one] toiminto
*[other] toimintoa
} käynnissä ({ $percent } %), { $finished } valmistunut…
pause = Keskeytä
extract-to = Pura sijaintiin…
permanently-delete-warning = { $target } tullaan poistamaan pysyvästi. Tätä toimintoa ei voi perua.
execute-only = Vain suoritus
write-only = Vain kirjoitus
write-execute = Kirjoita ja suorita
read-only = Vain luku
read-execute = Lue ja suorita
read-write = Lue ja kirjoita
read-write-execute = Lue, kirjoita sekä suorita
calculating = Lasketaan…
single-click = Yhden napsautuksen avaus
type-to-search = Kirjoita etsiäksesi
type-to-search-recursive = Etsii nykyisestä kansiosta ja kaikista alikansioista
remove-from-recents = Poista viimeaikaisista
selected-items = { $items } valittua kohdetta
show-recents = Viimeaikaisten kansio sivupalkissa
copy-to = Kopioi…
move-to = Siirrä…
details = Yksityiskohdat
grid-spacing = Ruudukkovälit
none = Ei mitään
favorite-path-error = Virhe avattaessa kansiota
favorite-path-error-description =
Polun { $path } avaaminen ei onnistunut
"{ $path }" ei välttämättä ole olemassa tai oikeutesi eivät riitä sen avaamiseen
Haluatko poistaa sen sivupalkista?
keep = Pidä
repository = Tietovarasto
support = Tuki
progress = { $percent } %
progress-cancelled = { $percent } %, peruttu
progress-failed = { $percent } %, epäonnistui
progress-paused = { $percent } %, keskeytetty
setting-permissions = Asetetaan kohteen "{ $name }" käyttöoikeudeksi { $mode }
set-permissions = Asetettu kohteen { $name } käyttöoikeudeksi { $mode }
permanently-deleting =
Poistetaan pysyvästi { $items } { $items ->
[one] kohde
*[other] kohdetta
}
permanently-deleted =
Poistettu pysyvästi { $items } { $items ->
[one] kohde
*[other] kohdetta
}
items = Kohteita: { $items }
item-accessed = Käytetty: { $accessed }
type-to-search-enter-path = Kirjoittaa polun kansioon tai tiedostoon
eject = Poista asemasta
copy-to-title = Valitse mihin kopioidaan
move-to-title = Valitse mihin siirretään
pasted-image = Liitetty kuva
pasted-text = Liitetty teksti
pasted-video = Liitetty video
type-to-search-select = Valitsee ensimmäisen täsmäävän tiedoston tai kansion
deleting =
Poistetaan { $items } { $items ->
[one] kohde
*[other] kohdetta
} roskakorista ({ $progress })…
deleted =
Poistettu { $items } { $items ->
[one] kohde
*[other] kohdetta
} roskakorista
removing-from-recents =
Poistetaan { $items } { $items ->
[one] kohde
*[other] kohdetta
} viimeaikaisista
removed-from-recents =
Poistettu { $items } { $items ->
[one] kohde
*[other] kohdetta
} viimeaikaisista
mixed = Sekoitettu
context-action = Kontekstitoiminto
context-action-confirm-title = Suoritetaanko "{ $name }"?
context-action-confirm-warning =
Tämä suorittaa { $items } { $items ->
[one] kohteen
*[other] kohdetta
}.
run = Suorita
rename-confirm = Nimeä uudelleen
checksum = { $kind }-tarkistussumma
calculate = Laske
error = Virhe
search-application = Hae sovelluksen nimellä
sidebar-label = Sivupalkin nimike
change-sidebar-label = Muuta sivupalkin nimikettä

View file

@ -80,19 +80,18 @@ cancel = Annuler
create = Créer
open = Ouvrir
open-file = Ouvrir le fichier
open-folder = Ouvrir le dossier
open-folder = Ouvrir dossier
open-in-new-tab = Ouvrir dans un nouvel onglet
open-in-new-window = Ouvrir dans une nouvelle fenêtre
open-item-location = Ouvrir l'emplacement de l'élément
open-multiple-files = Ouvrir plusieurs fichiers
open-multiple-folders = Ouvrir plusieurs dossiers
save = Enregistrer
save-file = Enregistrer le fichier
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
@ -131,11 +130,6 @@ 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
@ -189,7 +183,7 @@ keep = Conserver
## Add Network Drive
add-network-drive = Ajouter un lecteur réseau
connect = Se connecter
connect = Connecter
connect-anonymously = Se connecter anonymement
connecting = Connexion...
domain = Domaine
@ -206,8 +200,8 @@ network-drive-schemes =
File Transfer Protocol,ftp:// or ftps://
Network File System,nfs://
Server Message Block,smb://
SSH File Transfer Protocol,sftp:// ou ssh://
WebDAV,dav:// ou davs://
SSH File Transfer Protocol,sftp:// or ssh://
WebDav,dav:// or davs://
network-drive-error = Impossible d'accéder au lecteur réseau
password = Mot de passe
remember-password = Se souvenir du mot de passe
@ -222,7 +216,7 @@ history = Historique
no-history = Aucun élément dans l'historique.
pending = En attente
progress = { $percent }%
progress-cancelled = { $percent }%, annulé
progress-cancelled = { $percent }%, annulation
progress-paused = { $percent }%, en pause
failed = Échoué
complete = Terminé
@ -259,7 +253,7 @@ deleted =
[one] élément supprimé
*[other] éléments supprimés
} depuis { trash }
emptying-trash = Nettoyage en cours de { trash } ({ $progress })...
emptying-trash = { trash } en cours de nettoyage ({ $progress })...
emptied-trash = { trash } vidée
extracting =
Extraction de { $items } { $items ->
@ -271,7 +265,7 @@ extracted =
[one] élément extrait
*[other] éléments extraits
} depuis "{ $from }" vers "{ $to }"
setting-executable-and-launching = Définition de "{ $name }" comme exécutable et lancement en cours
setting-executable-and-launching = Paramétrage de "{ $name }" comme exécutable et prêt à être lancé
set-executable-and-launched = Défini "{ $name }" comme exécutable et lancé
moving =
Déplacement de { $items } { $items ->
@ -282,7 +276,7 @@ moved =
{ $items } { $items ->
[one] élément déplacé
*[other] éléments déplacés
} de "{ $from }" vers "{ $to }"
} de "{ $from }" à "{ $to }"
permanently-deleting =
Suppression définitive de { $items } { $items ->
[one] élément
@ -310,7 +304,7 @@ unknown-folder = Dossier inconnu
## Open with
menu-open-with = Ouvrir avec...
default-app = { $name } (par défaut)
default-app = { $name } (défaut)
## Show details
@ -349,10 +343,9 @@ extract-here = Extraire
new-file = Nouveau fichier...
new-folder = Nouveau dossier...
open-in-terminal = Ouvrir dans le terminal
edit-desktop-entry = Modifier le fichier .desktop
move-to-trash = Déplacer vers la corbeille
restore-from-trash = Restaurer depuis la corbeille
remove-from-sidebar = Retirer de la barre latérale
remove-from-sidebar = Supprimer de la barre latérale
sort-by-name = Trier par nom
sort-by-modified = Trier par date de modification
sort-by-size = Trier par taille
@ -415,11 +408,11 @@ removing-from-recents =
*[other] éléments
} de { recents }
other-apps = Autres applications
set-permissions = Permissions définies pour "{ $name }" à { $mode }
set-permissions = Définir les permissions pour "{ $name }" à { $mode }
repository = Dépôt
support = Support
eject = Éjecter
remove-from-recents = Retirer des récents
remove-from-recents = Supprimer des récents
empty-trash-title = Vider la corbeille ?
setting-permissions = Définition des permissions pour "{ $name }" à { $mode }
removed-from-recents =
@ -428,9 +421,9 @@ removed-from-recents =
*[other] éléments supprimés
} de { recents }
progress-failed = { $percent }%, échec
type-to-search-select = Sélectionne le premier fichier ou dossier correspondant
type-to-search-select = Sélectionne le premier fichier ou dossier qui convient
pasted-image = Image collée
pasted-text = Texte collé
pasted-text = Text collé
pasted-video = Vidéo collée
copy-to-title = Sélectionner la destination de la copie
copy-to-button-label = Copier
@ -443,19 +436,3 @@ keywords = Dossier;Gestionnaire;
show-recents = Dossier Récents dans la barre latérale
copy-path = Copier le chemin
clear-recents-history = Effacer l'historique des Récents
mixed = Mixte
context-action-confirm-title = Exécuter "{ $name }" ?
context-action-confirm-warning =
Cela exécutera sur { $items } { $items ->
[one] élément
*[other] éléments
}.
run = Exécuter
context-action = Action contextuelle
rename-confirm = Renommer
calculate = Calculer
error = Erreur
checksum = Somme de contrôle { $kind }
sidebar-label = Libellé de la barre latérale
search-application = Chercher par nom d'appli
change-sidebar-label = Changer le libellé de la barre latérale

View file

@ -307,7 +307,7 @@ single-click = Cliceáil amháin le hoscailt
### Appearance
appearance = Dealramh
appearance = Cuma
theme = Téama
match-desktop = Meaitseáil deasc
dark = Dorcha
@ -433,16 +433,3 @@ keywords = Fillteán;Bainisteoir;
show-recents = Fillteán le déanaí sa bharra taoibh
clear-recents-history = Glan stair na n-earraí le déanaí
copy-path = Cóipeáil an chosán
mixed = Measctha
context-action = Gníomh comhthéacsúil
context-action-confirm-title = Rith "{ $name }"?
context-action-confirm-warning =
Rithfidh sé seo ar { $items } { $items ->
[one] mhír
*[other] míreanna
}.
run = Rith
rename-confirm = Athainmnigh
checksum = { $kind } suim sheiceála
calculate = Ríomh
error = Earráid

View file

@ -1,333 +0,0 @@
name = Nome
save = Gardar
cancel = Cancelar
run = Executar
delete = Eliminar
repository = Repositorio
support = Soporte
settings = Axustes
appearance = Apariencia
theme = Tema
match-desktop = Axustar ao escritorio
dark = Escuro
light = Claro
replace = Remplazar
file = Ficheiro
new-window = Nova xanela
quit = Pechar
edit = Editar
undo = Desfacer
cut = Cortar
copy = Copiar
paste = Pegar
select-all = Seleccionar todo
view = Ver
zoom-in = Aumentar Zoom
default-size = Tamaño por defecto
zoom-out = Afastar Zoom
menu-settings = Axustes...
cosmic-files = Ficheiros COSMIC
comment = Xestor de ficheiros para o escritorio COSMIC
keywords = Cartafol;Xestor;
empty-folder = Cartafol baleiro
empty-folder-hidden = Cartafol baleiro (contén elementos agochados)
no-results = Non se atoparon resultados
filesystem = Sistema de ficheiros
home = Inicio
networks = Redes
notification-in-progress = Operacións de ficheiros en progreso
trash = Lixo
recents = Recentes
today = Hoxe
desktop-view-options = Axustes da vista do escritorio…
show-on-desktop = Amosar no Escritorio
desktop-folder-content = Contido do cartafol Escritorio
mounted-drives = Unidades montadas
trash-folder-icon = Icona do cartafol do Lixo
icon-size-and-spacing = Tamaño e espazado das iconas
icon-size = Tamaño de icona
grid-spacing = Espazado da grella
modified = Modificado
trashed-on = No lixo
size = Tamaño
details = Detalles
dismiss = Descartar a mensaxe
operations-running =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%)...
operations-running-finished =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%), { $finished } rematadas...
pause = Pausa
resume = Retomar
create-archive = Crear arquivo comprimido
copy-to-title = Seleccionar o destino da copia
copy-to-button-label = Copiar
extract-password-required = Contrasinal necesario
extract-to = Extraer en…
extract-to-title = Extraer a cartafol
empty-trash = Baleirar Lixo
empty-trash-title = Baleirar o lixo?
empty-trash-warning = Os elementos do cartafol do lixo eliminaranse de forma permanente
mount-error = Non é posible acceder a unidade
move-to-title = Seleccionar o destino do movemento
move-to-button-label = Mover
create-new-file = Crear un novo ficheiro
create-new-folder = Crear novo cartafol
file-name = Nome do ficheiro
folder-name = Nome do cartafol
file-already-exists = Existe un ficheiro co mesmo nome
folder-already-exists = Existe un cartafol co mesmo nome
name-hidden = Os nomes que comecen por "." agocharanse
name-invalid = O nome non pode ser "{ $filename }"
name-no-slashes = O nome non pode conter barras
create = Crear
open = Abrir
open-file = Abrir ficheiro
open-folder = Abrir cartafol
open-in-new-tab = Abrir nunha nova lapela
open-in-new-window = Abrir nunha nova xanela
open-item-location = Abrir ubicación do ítem
open-multiple-files = Abrir multiples ficheiros
open-multiple-folders = Abrir multiples cartafoles
save-file = Gardar ficheiro
open-with-title = Como queres abrir «{ $name }»?
browse-store = Atopar { $store }
other-apps = Outras aplicacións
related-apps = Aplicacións relacionadas
context-action = Acción contextual
context-action-confirm-title = Executar "{ $name }"?
context-action-confirm-warning =
Esto vaise executar en { $items } { $items ->
[un] item
*[outro] items
}.
selected-items = Os { $items } elementos seleccionados
permanently-delete-question = Eliminar permanentemente?
permanently-delete-warning = { $target } será eliminado permanentemente. Esta acción non se poderá desfacer.
rename-file = Renomear ficheiro
rename-folder = Renomear cartafol
rename-confirm = Renomear
replace-title = "{ $filename }" xa existe nesta ubicación
replace-warning = Queres substituílo polo que estás a gardar? Ao substituílo sobrescribirase o seu contido.
replace-warning-operation = Queres substituílo? Ao substituílo sobrescribirase o seu contido.
original-file = Ficheiro orixinal
replace-with = Remplazar con
apply-to-all = Aplicar a todo
keep-both = Manter ambos
skip = Saltar
set-executable-and-launch = Marcar como executable e lanzalo
set-executable-and-launch-description = Queres marcar «{ $name }» como executábel e lanzalo?
set-and-launch = Marcar e executar
open-with = Abrir con
owner = Propietario
group = Grupo
other = Outro
mixed = Mesturar
none = Ningún
execute-only = Só execución
write-only = Só escritura
write-execute = Escritura e execución
read-only = Só lectura
read-execute = Lectura e execución
read-write = Lectura e escritura
read-write-execute = Lectura, escritura e execución
favorite-path-error = Erro abrindo o directorio
favorite-path-error-description =
Non foi posíbel abrir «{ $path }».
É posíbel que «{ $path }» non exista ou que non teñas permisos para abrilo.
Queres retiralo da barra lateral?
remove = Eliminar
keep = Manter
add-network-drive = Engadir unidade de rede
connect = Conectar
connect-anonymously = Conectar de xeito anonimo
connecting = Conectando...
domain = Dominio
enter-server-address = Introducir a dirección do servidor
network-drive-description =
Os enderezos dos servidores inclúen un prefixo de protocolo e un enderezo.
Exemplos: ssh://192.168.0.1, ftp://[2001:db8::1]
network-drive-schemes =
Protocolos dispoñíbeis,Prefixo
AppleTalk,afp://
Protocolo de transferencia de ficheiros,ftp:// ou ftps://
Sistema de ficheiros en rede,nfs://
Server Message Block,smb://
Protocolo de transferencia de ficheiros por SSH,sftp:// ou ssh://
WebDAV,dav:// ou davs://
network-drive-error = Non foi posible acceder a unidade de rede
password = Contrasinal
remember-password = Recordar o contrasinal
try-again = Voltar a intentar
username = Nome de usuario
cancelled = Cancelado
edit-history = Editar historial
history = Historial
no-history = Non hai elementos no historial.
pending = Pendente
progress = { $percent }%
progress-cancelled = { $percent }%, cancelado
progress-failed = { $percent }%, fallou
progress-paused = { $percent }%, pausado
failed = Fallou
complete = Completado
compressing =
Comprimindo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
compressed =
Comprimidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
copy_noun = Copiar
pasted-image = Imaxe pegada
pasted-text = Texto Pegado
pasted-video = Video pegado
creating = Creando "{ $name }" en "{ $parent }"
created = Creado "{ $name }" en "{ $parent }"
copying =
Copiando { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
copied =
Copiados { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
deleted =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} do lixo ({ $progress })
emptying-trash = Baleirando { trash } ({ $progress })...
emptied-trash = Baleirado o { trash }
extracting =
Extraendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
extracted =
Extraídos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
setting-executable-and-launching = Marcando «{ $name }» como executábel e executándoo
set-executable-and-launched = «{ $name }» marcouse como executábel e executouse
setting-permissions = Establecendo os permisos de «{ $name }» en { $mode }
set-permissions = Establecéronse os permisos de «{ $name }» en { $mode }
moving =
Movendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
moved =
Movidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
permanently-deleting =
Eliminando permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
permanently-deleted =
Eliminados permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
removing-from-recents =
Retirando { $items } { $items ->
[one] elemento
*[other] elementos
} dos { recents }
removed-from-recents =
Retirado { $items } { $items ->
[one] elemento
*[other] elementos
} de { recents }
renaming = Renomeando "{ $from }" a "{ $to }"
renamed = Renomeado "{ $from }" a "{ $to }"
restoring =
Restaurando { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash } ({ $progress })...
restored =
Restaurado { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash }
unknown-folder = cartafol descoñecido
menu-open-with = Abrir con...
default-app = { $name } (por defecto)
show-details = Amosar detalles
type = Tipo: { $mime }
items = Elementos: { $items }
item-size = Tamaño: { $size }
item-created = Creado: { $created }
item-modified = Modificado: { $modified }
item-accessed = Accedido: { $accessed }
calculating = Calculando...
checksum = { $kind } checksum
calculate = Calculado
error = Erro
single-click = Clic único para abrir
show-recents = Cartafol de recentes no panel lateral
type-to-search = Escribe para atopar
type-to-search-recursive = Busca no cartafol actual e en todos os subcartafoles
type-to-search-enter-path = Introduce a ruta do cartafol ou ficheiro
type-to-search-select = Selecciona o primeiro ficheiro ou cartafol coincidente
add-to-sidebar = Engadir o panel lateral
clear-recents-history = Limpar historial de recentes
compress = Comprimir...
copy-to = Copiar a...
delete-permanently = Eliminar permanentemente
eject = Expulsar
extract-here = Extraer
new-file = Novo ficheiro...
new-folder = Novo cartafol...
open-in-terminal = Abrir no terminal
move-to = Mover a...
move-to-trash = Mover ao lixo
restore-from-trash = Recuperar do lixo
remove-from-sidebar = Eliminar do panel lateral
sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por modificación
sort-by-size = Ordenar por tamaño
sort-by-trashed = Ordenar pola data de eliminación
remove-from-recents = Eliminar de recentes
change-wallpaper = Cambiar fondo de pantalla...
desktop-appearance = Apariencia do Escritorio...
display-settings = Axustes de pantalla...
new-tab = Nova lapela
reload-folder = Recargar cartafol
rename = Renomear...
close-tab = Pechar lapela
copy-path = Copiar ruta
grid-view = Vista de grella
list-view = Vista de lista
show-hidden-files = Amosar ficheiros ocultos
list-directories-first = Listar primeiro os directorios
gallery-preview = Vista previa da galería
menu-about = Sobre Ficheiros COSMIC...
sort = Ordenar
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Novos primeiro
sort-oldest-first = Antigos primeiro
sort-smallest-to-largest = Do máis pequeno ao máis grande
sort-largest-to-smallest = Do máis grande ao máis pequeno
deleting =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} da { trash } ({ $progress })...

View file

@ -5,7 +5,7 @@ no-results = कोई परिणाम नहीं
filesystem = फाइल सिस्टम
home = होम
networks = नेटवर्क्स
notification-in-progress = फ़ाइल संचालन प्रगति पर हैं
notification-in-progress = फाइल संचालन प्रगति पर है।
trash = कचरा
recents = हाल के
undo = पूर्ववत करें
@ -33,8 +33,8 @@ create-archive = संग्रह बनाएँ
## Empty Trash Dialog
empty-trash = रद्दी साफ़ करें
empty-trash-warning = रद्दी फ़ोल्डर में मौजूद आइटम स्थायी रूप से हटा दिए जाएंगे
empty-trash = कचरा खाली करें
empty-trash-warning = क्या आप वाकई कचरे में सभी आइटमों को स्थायी रूप से हटाना चाहते हैं?
## New File/Folder Dialog
@ -42,11 +42,11 @@ create-new-file = नई फाइल बनाएँ
create-new-folder = नया फ़ोल्डर बनाएँ
file-name = फाइल का नाम
folder-name = फ़ोल्डर का नाम
file-already-exists = उस नाम की फ़ाइल पहले से मौजूद है
folder-already-exists = उस नाम का फ़ोल्डर पहले से मौजूद है
name-hidden = "." से शुरू होने वाले नाम छिपे होंगे
name-invalid = नाम "{ $filename }" नहीं हो सकता
name-no-slashes = नाम में स्लैश नहीं हो सकते
file-already-exists = इसी नाम की फाइल पहले से मौजूद है।
folder-already-exists = इसी नाम का फ़ोल्डर पहले से मौजूद है।
name-hidden = "." से शुरू होने वाले नाम छिपे रहेंगे।
name-invalid = नाम "{ $filename }" मान्य नहीं है।
name-no-slashes = नाम में स्लैश का उपयोग नहीं किया जा सकता है।
## Open/Save Dialog
@ -75,8 +75,8 @@ rename-folder = फ़ोल्डर का नाम बदलें
## Replace Dialog
replace = बदलें
replace-title = { $filename } पहले से इस स्थान पर मौजूद है
replace = प्रतिस्थापित करें
replace-title = { $filename } पहले से इस स्थान पर मौजूद है।
replace-warning = क्या आप इसे प्रतिस्थापित करना चाहते हैं? यदि प्रतिस्थापित किया गया, तो मौजूदा फ़ाइल को ओवरराइट किया जाएगा।
replace-warning-operation = क्या आप इसे बदलना चाहते हैं? प्रतिस्थापित करने पर मौजूदा फ़ाइल ओवरराइट हो जाएगी।
original-file = मूल फ़ाइल
@ -211,13 +211,13 @@ show-details = विवरण दिखाएँ
## Settings
settings = सेटिंग्स
settings = सेटिंग
### Appearance
appearance = रुप-रंग
theme = प्रसंग
match-desktop = डेस्कटॉप से मेल खाएँ
appearance = रूप
theme = थीम
match-desktop = सिस्टम सेटिंग्स से मेल खाएँ
dark = डार्क
light = लाइट
# Context menu
@ -251,27 +251,27 @@ new-tab = नया टैब
new-window = नई विंडो
rename = नाम बदलें...
close-tab = टैब बंद करें
quit = बाहर जाएँ
quit = बंद करें
## Edit
edit = संपादन
cut = काटें
copy = कॉपी करें
edit = संपादित करें
cut = कट करें
copy = नकल करें
paste = चिपकाएँ
select-all = सभी चुनें
## View
zoom-in = बड़ा करें
default-size = मूल आकार
zoom-out = छोटा करें
zoom-in = ज़ूम इन करें
default-size = डिफ़ॉल्ट आकार
zoom-out = ज़ूम आउट करें
view = दृश्य
grid-view = ग्रिड दृश्य
list-view = सूची दृश्य
show-hidden-files = छिपी हुई फाइलें दिखाएँ
list-directories-first = सबसे पहले डाइरेक्ट्री दिखाएँ
menu-settings = सेटिंग्स..।
menu-settings = सेटिंग्स...
menu-about = कॉस्मिक फाइल्स के बारे में...
## Sort
@ -284,98 +284,4 @@ sort-oldest-first = पुराने से नए
sort-smallest-to-largest = छोटे से बड़े
sort-largest-to-smallest = बड़े से छोटे
repository = रिपॉजिटरी
support = समर्थन
read-execute = पढ़ें और निष्पादित करें
deleted =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया गया
*[other] आइटम मिटाए गए
}
dismiss = संदेश खारिज करें
favorite-path-error = निर्देशिका खोलने में त्रुटि
progress = { $percent }%
related-apps = संबंधित ऐप्स
removing-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया जा रहा है
*[other] आइटम हटाए जा रहे हैं
}
remove = हटाएँ
read-write-execute = पढ़ें, लिखें और निष्पादित करें
other-apps = अन्य ऐप्स
pause = विराम
keep = रखें
permanently-deleting =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया जा रहा है
*[other] आइटम स्थायी रूप से मिटाए जा रहे हैं
}
read-write = पढ़ें और लिखें
none = कोई नहीं
resume = फिर से शुरू करें
grid-spacing = ग्रिड स्पेसिंग
extract-to = इस रूप में निकालें..।
delete = हटाएं
read-only = केवल पढ़ने के लिए
deleting =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया जा रहा है
*[other] आइटम मिटाए जा रहे हैं
} ({ $progress })..।
execute-only = केवल निष्पादित करें
details = विवरण
mount-error = ड्राइव तक पहुँचने में असमर्थ
removed-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया गया
*[other] आइटम हटाए गए
}
progress-paused = { $percent }%, रुका हुआ
cancelled = रद्द किया गया
operations-running-finished =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%), { $finished } समाप्त..।
progress-failed = { $percent }%, विफल
extract-to-title = फ़ोल्डर में निकालें
open-with = इससे खोलें
permanently-deleted =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया गया
*[other] आइटम स्थायी रूप से मिटाए गए
}
write-execute = लिखें और निष्पादित करें
extract-password-required = पासवर्ड आवश्यक है
progress-cancelled = { $percent }%, रद्द किया गया
write-only = केवल लिखने के लिए
permanently-delete-warning = { $target } को स्थाई रूप से हटा दिया जाएगा। इस कार्रवाई को पूर्ववत नहीं किया जा सकता है।
favorite-path-error-description =
"{ $path }" को खोला नहीं जा सकता
"{ $path }" मौजूद नहीं हो सकता है या आपके पास इसे खोलने की अनुमति नहीं हो सकती है
क्या आप इसे साइडबार से हटाना चाहते हैं?
empty-trash-title = रद्दी साफ़ करें?
permanently-delete-question = स्थाई रूप से हटाएं?
operations-running =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%)..।
copy-to-title = कॉपी गंतव्य चुनें
copy-to-button-label = कॉपी
move-to-title = मूव गंतव्य चुनें
move-to-button-label = मूव
context-action = संदर्भ क्रिया
context-action-confirm-title = "{ $name }" चलाएँ?
context-action-confirm-warning =
यह { $items } { $items ->
[one] आइटम
*[other] आइटम
} पर चलेगा।
run = चलाएँ
rename-confirm = नाम बदलें
mixed = मिश्रित
pasted-image = चिपकाई गई छवि
pasted-text = चिपकाया गया पाठ
pasted-video = चिपकाया गया वीडियो
support = सहायता

View file

@ -2,7 +2,7 @@ cosmic-files = COSMIC Fájlok
comment = Fájlkezelő a COSMIC asztali környezethez
keywords = mappa;fájl;kezelő;
empty-folder = Üres mappa
empty-folder-hidden = Üres mappa (rejtett elemeket tartalmaz)
empty-folder-hidden = Üres mappa (Rejtett elemeket tartalmaz)
no-results = Nincs találat
filesystem = Fájlrendszer
home = Saját mappa
@ -17,7 +17,7 @@ desktop-view-options = Asztali nézet beállításai…
show-on-desktop = Megjelenítés az asztalon
desktop-folder-content = Asztal mappa tartalma
mounted-drives = Csatolt meghajtók
trash-folder-icon = Kuka ikon
trash-folder-icon = Kuka ikonja
icon-size-and-spacing = Ikonméret és távolság
icon-size = Ikonméret
grid-spacing = Rácsköz
@ -47,18 +47,18 @@ resume = Folytatás
## Compress Dialog
create-archive = Archívum létrehozása
create-archive = Tömörített fájl létrehozása
## Extract Dialog
extract-password-required = Jelszó szükséges
extract-to = Kibontás ide…
extract-to-title = Kibontási cél kiválasztása
extract-to-title = Kibontás mappába
## Empty Trash Dialog
empty-trash = A Kuka ürítése
empty-trash-warning = A Kukában lévő összes elem véglegesen törölve lesz
empty-trash = Kuka ürítése
empty-trash-warning = A kukában lévő összes elem véglegesen törölve lesz
## Mount Error Dialog
@ -93,7 +93,7 @@ save-file = Fájl mentése
## Open With Dialog
open-with-title = Hogyan szeretnéd megnyitni a(z) „{ $name }” fájlt?
open-with-title = Hogyan szeretnéd megnyitni a következőt: „{ $name }”?
browse-store = { $store } böngészése
other-apps = Egyéb alkalmazások
related-apps = Hasonló alkalmazások
@ -103,7 +103,7 @@ related-apps = Hasonló alkalmazások
selected-items = A(z) { $items } kijelölt elem
permanently-delete-question = Végleges törlés?
delete = Törlés
permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem vonható vissza.
permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem visszavonható.
## Rename Dialog
@ -113,7 +113,7 @@ rename-folder = Mappa átnevezése
## Replace Dialog
replace = Csere
replace-title = „{ $filename }” már létezik ezen a helyen
replace-title = „{ $filename }” már létezik
replace-warning = Szeretnéd lecserélni a meglévő fájlt a mentendő fájllal? A cserével felülírod annak tartalmát.
replace-warning-operation = Szeretnéd lecserélni? A csere felülírja annak tartalmát.
original-file = Eredeti fájl
@ -133,7 +133,7 @@ set-and-launch = Beállítás és indítás
open-with = Megnyitás ezzel
owner = Tulajdonos
group = Csoport
other = Mások
other = Többi
### Mode 0
@ -172,7 +172,7 @@ read-write-execute = Olvasás, írás és végrehajtás
favorite-path-error = Hiba a könyvtár megnyitásakor
favorite-path-error-description =
Nem sikerült megnyitni ezt: „{ $path }”
A(z) „{ $path }” útvonal lehet, hogy nem létezik, vagy nincs jogosultságod a megnyitásához
„{ $path }” lehet nem létezik, vagy nincs jogosultságod a megnyitásához
Szeretnéd eltávolítani az oldalsávról?
remove = Eltávolítás
@ -187,9 +187,9 @@ keep = Megtartás
## Add Network Drive
add-network-drive = Hálózati meghajtó hozzáadása
connect = Kapcsolódás
connect-anonymously = Kapcsolódás névtelenül
connecting = Kapcsolódás…
connect = Csatlakozás
connect-anonymously = Csatlakozás névtelenül
connecting = Csatlakozás…
domain = Tartomány
enter-server-address = Add meg a kiszolgáló címét
network-drive-description =
@ -209,7 +209,7 @@ network-drive-schemes =
network-drive-error = Nem érhető el a hálózati meghajtó
password = Jelszó
remember-password = Jelszó megjegyzése
try-again = Próbáld újra
try-again = Újra
username = Felhasználónév
## Operations
@ -217,7 +217,7 @@ username = Felhasználónév
cancelled = Megszakítva
edit-history = Fájlműveleti előzmények
history = Előzmények
no-history = Nem találhatók elemek az előzményekben.
no-history = Nem találhatók elemek az előzményekben
pending = Függőben
progress = { $percent }%
progress-cancelled = { $percent }%, megszakítva
@ -251,13 +251,13 @@ deleting =
{ $items } { $items ->
[one] elem
*[other] elem
} törlése a Kukából ({ $progress })…
} törlése a kukából ({ $progress })…
deleted =
{ $items } { $items ->
[one] elem
*[other] elem
} törölve a Kukából
emptying-trash = { trash } ürítése ({ $progress })…
} törölve a kukából
emptying-trash = { trash } kiürítése ({ $progress })…
emptied-trash = { trash } kiürítve
extracting =
{ $items } { $items ->
@ -309,12 +309,12 @@ restoring =
{ $items } { $items ->
[one] elem
*[other] elem
} visszaállítása a Kukából ({ $progress })…
} visszaállítása a kukából ({ $progress })…
restored =
{ $items } { $items ->
[one] elem
*[other] elem
} visszaállítva a Kukából
} visszaállítva a kukából
unknown-folder = ismeretlen mappa
## Open with
@ -326,11 +326,11 @@ default-app = { $name } (alapértelmezett)
show-details = Részletek megjelenítése
type = Típus: { $mime }
items = Elemek száma: { $items }
items = Elemek: { $items }
item-size = Méret: { $size }
item-created = Létrehozva: { $created }
item-modified = Módosítva: { $modified }
item-accessed = Utolsó hozzáférés: { $accessed }
item-accessed = Hozzáférve: { $accessed }
calculating = Számítás…
## Settings
@ -349,8 +349,8 @@ light = Világos
### Type to Search
type-to-search = Gépeléssel keresés
type-to-search-recursive = Keresés a jelenlegi mappában és az almappákban
type-to-search-enter-path = Elérési út megnyitása
type-to-search-recursive = Keresés a jelenlegi mappában és almappákban
type-to-search-enter-path = Elérési út megadása
# Context menu
add-to-sidebar = Hozzáadás az oldalsávhoz
compress = Tömörítés…
@ -360,8 +360,8 @@ extract-here = Kibontás
new-file = Új fájl…
new-folder = Új mappa…
open-in-terminal = Megnyitás a terminálban
move-to-trash = Áthelyezés a Kukába
restore-from-trash = Visszaállítás a Kukából
move-to-trash = Kukába helyezés
restore-from-trash = Visszaállítás a kukából
remove-from-sidebar = Eltávolítás az oldalsávról
sort-by-name = Név szerinti rendezés
sort-by-modified = Módosítás szerinti rendezés
@ -372,8 +372,8 @@ remove-from-recents = Eltávolítás a legutóbbiak közül
## Desktop
change-wallpaper = Háttérkép cseréje…
desktop-appearance = Asztal megjelenése…
display-settings = Kijelzőbeállítások…
desktop-appearance = Asztali megjelenés…
display-settings = Képernyő-beállítások…
# Menu
@ -406,7 +406,7 @@ grid-view = Rácsnézet
list-view = Listanézet
show-hidden-files = Rejtett fájlok megjelenítése
list-directories-first = Könyvtárak listázása először
gallery-preview = Galéria-előnézet
gallery-preview = Galéria előnézet
menu-settings = Beállítások…
menu-about = A COSMIC Fájlok névjegye…
@ -415,14 +415,14 @@ menu-about = A COSMIC Fájlok névjegye…
sort = Rendezés
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Legújabb elöl
sort-oldest-first = Legrégibb elöl
sort-newest-first = Legújabb előre
sort-oldest-first = Legrégibb előre
sort-smallest-to-largest = Legkisebbtől a legnagyobbig
sort-largest-to-smallest = Legnagyobbtól a legkisebbig
repository = Tároló
support = Támogatás
progress-failed = { $percent }%, sikertelen
empty-trash-title = Kiüríted a Kukát?
empty-trash-title = Kuka ürítése?
type-to-search-select = Kijelöli az első egyező fájlt vagy mappát
pasted-image = Beillesztett kép
pasted-text = Beillesztett szöveg
@ -436,19 +436,3 @@ move-to = Áthelyezés ide…
show-recents = Legutóbbiak mappa megjelenítése az oldalsávban
copy-path = Útvonal másolása
clear-recents-history = Legutóbbiak előzményének törlése
mixed = Vegyes
context-action = Helyi művelet
context-action-confirm-title = Futtatod ezt: „{ $name }”?
context-action-confirm-warning =
Ez a művelet { $items } { $items ->
[one] elemen
*[other] elemen
} fog lefutni.
run = Futtatás
rename-confirm = Átnevezés
checksum = { $kind }-ellenőrzőösszeg
calculate = Számítás
error = Hiba
sidebar-label = Oldalsávcímke
search-application = Keresés alkalmazásnév alapján
change-sidebar-label = Oldalsávcímke módosítása

View file

@ -249,7 +249,7 @@ settings = Pengaturan
single-click = Klik sekali untuk membuka
appearance = Tampilan
theme = Tema
match-desktop = Cocok desktop
match-desktop = Cocokkan desktop
dark = Gelap
light = Terang
type-to-search = Ketik untuk mencari
@ -318,19 +318,3 @@ comment = Pengelola berkas untuk desktop COSMIC
show-recents = Map terbaru di bilah sisi
clear-recents-history = Bersihkan riwayat Terbaru
copy-path = Salin jalur
mixed = Bercampur
context-action = Tindakan konteks
context-action-confirm-title = Jalankan "{ $name }"?
run = Jalankan
context-action-confirm-warning =
Ini akan dijalankan pada { $items } { $items ->
[one] item
*[other] item
}.
rename-confirm = Ganti nama
checksum = { $kind } checksum
calculate = Hitung
error = Galat
sidebar-label = Label bilah sisi
search-application = Cari berdasarkan nama aplikasi
change-sidebar-label = Ubah label bilah sisi

View file

@ -1,5 +1,5 @@
cosmic-files = COSMIC Files
comment = File manager per il desktop COSMIC
comment = File manager di COSMIC
keywords = File;Archivi;Cartelle;Explorer;
empty-folder = Cartella vuota
empty-folder-hidden = Cartella vuota (con elementi nascosti)
@ -7,7 +7,7 @@ no-results = Nessun risultato trovato
filesystem = Filesystem
home = Home
networks = Reti
notification-in-progress = Operazioni sui file in corso
notification-in-progress = Operazioni sui file in corso.
trash = Cestino
recents = Recenti
undo = Annulla
@ -322,7 +322,7 @@ single-click = Click singolo per aprire
appearance = Aspetto
theme = Tema
match-desktop = Abbina al desktop
match-desktop = Sistema
dark = Scuro
light = Chiaro
@ -422,4 +422,3 @@ removed-from-recents =
[one] elemento
*[other] elementi
} da { recents }
run = Esegui

View file

@ -1,322 +1 @@
change-wallpaper = Beddel aɣrab n ugdil…
cosmic-files = Ifuyla COSMIC
empty-folder = Akaram d ilem
empty-folder-hidden = Akaram d ilem (yesɛa iferdisen yeffren)
no-results = Ulac igmaḍ yettwafen
home = Agejdan
networks = Iẓeḍwa
notification-in-progress = Timhalin ɣef ifuyla la tteddunt
trash = Iḍumman
recents = Melmi kan
undo = Ssemmet
today = Ass-a
desktop-view-options = Iɣewwaṛen n tmeẓri n tnarit…
show-on-desktop = Sken deg tnarit
desktop-folder-content = Agbur n ukaram n tnarit
trash-folder-icon = Tignit n ukaram n iḍumman
icon-size-and-spacing = Tiddi n tignit akked tallunt
icon-size = Tiddi n tignit
name = Isem
modified = Ittusnifel
trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman
size = Tiddi
details = Ifatusen
pause = Serǧu
resume = Kemmel
create-archive = Snulfu-d aɣbaṛ
extract-to = Ssef ɣer...
extract-to-title = Ssef ɣer ukaram
empty-trash = Silem iḍumman
rename-folder = Snifel isem n ukaram
filesystem = Anagraw n yifuyla
dismiss = Zgel izen
empty-trash-title = Silem iḍumman?
empty-trash-warning = Iferdisen n ukaram n iḍumman ad ttwakksen i lebda
create-new-file = Snulfu-d afaylu amaynut
create-new-folder = Snulfu-d akaram amaynut
file-name = Isem n ufaylu
folder-name = Isem n ukaram
file-already-exists = Afaylu s yisem-agi yella yakan
folder-already-exists = Akaram s yisem-agi yella yakan
name-hidden = Ismawen ibeddun s "." ad ttwaffren
name-invalid = Isem ur yezmir ara ad yili "{ $filename }"
cancel = Semmet
create = Snulfu-d
open = Ldi
open-file = Ldi afaylu
open-folder = Ldi akaram
open-in-new-tab = Ldi deg yiccer amaynut
open-in-new-window = Ldi deg usfaylu amaynut
open-item-location = Ldi adig n uferdis
open-multiple-files = Ldi aget n ifuyla
mounted-drives = imeɣriyen yettuserkben
mount-error = Ulamek anekcum ɣer umeɣri
operations-running =
{ $running } { $running ->
[one] n temhelt la tteddu
*[other] n temhal la tteddunt
} ({ $percent }%)...
operations-running-finished =
{ $running } { $running ->
[one] n temhelt la tteddu
*[other] n temhal la tteddunt
} ({ $percent }%), { $finished } { $finished ->
[one] tfukk
*[other] fukkent
}...
copy-to-title = Fren taɣerwaḍt n unɣel
copy-to-button-label = Nɣel
move-to-title = Fren taɣerwaḍt n usmutti
move-to-button-label = Smutti
comment = Amsefrak n yifuyla i tnarit COSMIC
keywords = Akaram;Amsefrak;
delete = kkes
replace = Semselsi
support = Tallelt
settings = Iɣewwaṛen
appearance = Timeẓri
theme = Asentel
dark = Aɣmayan
light = Aceɛlal
paste = Senteḍ
select-all = Fren akk
zoom-in = Asemɣeṛ
default-size = Tiddi tamezwert
zoom-out = Asemẓi
view = Wali
menu-settings = Iɣewwaṛen…
save = Sekles
match-desktop = Semṣada akked tnarit
file = Afaylu
new-window = Asfaylu amaynut
quit = Ffeɣ
edit = Ẓreg
cut = Gzem
copy = Nɣel
repository = Asarsay
pasted-image = Tettwasenṭeḍ tugna
pasted-text = Yettwasenṭeḍ uḍris
pasted-video = Tettwasenṭeḍ tvidyut
compress = Skussem…
grid-spacing = Tallunt n iẓiki
extract-password-required = Awal uffir yettwasra
permanently-delete-question = Kkes s wudem imezgi?
permanently-delete-warning = { $target } ad yettwakkes s wudem imezgi. Tigawt-agi ur tezmir ara ad tettwasefsex.
rename-file = Snifel isem n ufaylu
replace-title = "{ $filename }" yella yakan deg wadig-a
replace-warning = Tebɣiḍ ad t-tsemselsiḍ s win ara teskelseḍ? Asemselsi-ines ad yaru sennig ugbur-is.
replace-warning-operation = Tebɣiḍ ad t-tsemselsiḍ? Asemselsi-ines ad yaru sennig ugbur-is.
original-file = Afaylu aneṣli
replace-with = Semselsi s
apply-to-all = Snes i meṛṛa
keep-both = Eǧǧ-iten deg sin
skip = Zgel
set-executable-and-launch = Sbeddet am umselkam syinna senker
set-executable-and-launch-description = Tebɣiḍ ad tesbeddeḍ "{ $name }" am umselkam syinna ad tessenkreḍ?
set-and-launch = Sbadu sakin senker
add-network-drive = Rnu ameɣri n uẓeṭṭa
connect = Qqen
connect-anonymously = Qqen s wudem udrig
connecting = Tuqqna…
domain = Taɣult
enter-server-address = Sekcem tansa n uqeddac
network-drive-description =
Tansiwin n uqeddac gebrent azwir n uneggaf akked tansa.
Imedyaten: ssh://192.168.0.1, ftp://[2001:db8::1]
network-drive-error = Ur izmir ara ad yekcem ɣer umeɣri n uzeṭṭa
password = Awal uffir
remember-password = Cfu ɣef wawal uffir
try-again = ɛreḍ tikelt nniḍen
username = Isem n useqdac
cancelled = Yettwasefsex
edit-history = Ẓreg amazray
history = Amazray
no-history = Ulac iferdisen deg umazray.
pending = Yettṛaǧu
progress-cancelled = { $percent }%, yettwasefsex
progress-failed = { $percent }%, ur yeddi ara
failed = Ur yeddi ara
renaming = Asnifel n yisem "{ $from }" ɣer "{ $to }"
renamed = Yettwasenfel yisem n "{ $from }" ɣer "{ $to }"
unknown-folder = akaram arussin
menu-open-with = Ldi s…
default-app = { $name } (amezwer)
show-details = Sken talqayt
type = Anaw: { $mime }
items = Iferdisen: { $items }
item-size = Tiddi: { $size }
item-created = Yettwarna: { $created }
item-modified = Ittusnifel: { $modified }
item-accessed = Yettwakcem: { $accessed }
calculating = Asiḍen…
single-click = Asiti asuf i ulday
type-to-search = Aru iwakken ad tnadiḍ
type-to-search-recursive = Nadi akaram amiran akked ikaramen inaddawen meṛṛa
type-to-search-enter-path = Sekcem abrid ɣer ukaram neɣ afaylu
add-to-sidebar = Rnu ɣer ufeggag adisan
delete-permanently = Kkes i lebda
grid-view = Askan s iẓiki
list-view = Askan s tebdart
show-hidden-files = Sken ifuyla uffiren
gallery-preview = Taskant n temidelt
menu-about = Ɣef Ifuyla COSMIC…
sort = Asmizzwer
sort-newest-first = Amaynut d amezwaru
sort-oldest-first = Aqbur d amezwaru
sort-smallest-to-largest = Seg umeẓyan akk ɣer umeqqran
sort-largest-to-smallest = Seg umeqqran akk ɣer umeẓyan
open-multiple-folders = Ldi usgit n ikaramen
save-file = Sekles afaylu
open-with-title = Amek tebɣiḍ ad teldiḍ "{ $name }"?
browse-store = Snirem { $store }
other-apps = Isnasen-nniḍen
emptying-trash = Silem { trash } ({ $progress })…
emptied-trash = D ilem { trash }
set-permissions = Sbadu isirigen i "{ $name }" ɣer { $mode }
eject = Ḍeqqer
extract-here = Ssef
new-file = Afaylu amaynut…
new-folder = Akaram amaynut…
open-in-terminal = Ldi deg yixef
move-to-trash = Smutti ɣer tqecwalt n yiḍumman
restore-from-trash = Err-d seg tqecwalt n yiḍumman
remove-from-sidebar = Kkes seg ugalis adisan
sort-by-name = Smizzwer s yisem
sort-by-modified = Asmizzwer s usnifel
sort-by-size = Asmizzwer s tiddi
sort-by-trashed = Asmizzwer s wakud n tukksa
remove-from-recents = Kkes seg ineggura
desktop-appearance = Timeẓri n tnarit…
display-settings = Iɣewwaṛen n ubeqqeḍ...
new-tab = Iccer amaynut
reload-folder = Ales asali n ukaram
rename = Snifel isem...
close-tab = Mdel iccer
list-directories-first = Sken di tazwara ikaramen
open-with = Ldi s
owner = Bab
group = Agraw
other = Ayen nniḍen
none = Ula Yiwen
execute-only = Selkem kan
write-only = Aru kan
write-execute = Aru u selkem
read-only = I tɣuri kan
read-execute = Ɣeṛ u selkem
read-write = Ɣeṛ u aru
read-write-execute = Ɣeṛ, aru, u selkem
favorite-path-error = Tuccḍa deg ulday n ukaram
remove = Kkes
keep = Eǧǧ
progress = { $percent }%
progress-paused = { $percent }%, ibedd
complete = Immed
copy_noun = Nɣel
creating = Asnulfu n "{ $name }" deg "{ $parent }"
created = Yettwarna "{ $name }" deg "{ $parent }"
setting-executable-and-launching = Asbadu n "{ $name }" am umselkam syin ad yettwasekker
set-executable-and-launched = Sbadu "{ $name }" am umselkam syin sekker-it
setting-permissions = Asbadu n tsirag i "{ $name }" ɣer { $mode }
related-apps = Isnasen icudden
favorite-path-error-description =
Ur izmir ara ad yeldi "{ $path }"
"{ $path }" yezmer lḥal ulac-it neɣ ahat ur tesɛiḍ ara tisirag akken ad t-teldiḍ
Tebɣiḍ ad t-tekkseḍ seg ufeggag adisan?
compressing =
La issekkussum { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
compressed =
Yekussem { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }"
copying =
Anɣal { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
copied =
Yettwanɣel { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }"
deleting =
Tukksa { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { trash } ({ $progress })...
deleted =
Yettwakkes { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { trash }
extracting =
Tussfa { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
extracted =
Yettusef { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }"
moving =
Asmutti { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
moved =
Yettwasenkez { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg "{ $from }" ɣer "{ $to }"
permanently-deleting =
Tukksa i lebda { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
}
permanently-deleted =
I lebda yettwakkes { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
}
removing-from-recents =
Tukksa { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { recents }
removed-from-recents =
Yettwakkes { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { recents }
restoring =
Tiririt { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { trash } ({ $progress })…
restored =
Yettwarr-d { $items } { $items ->
[one] n uferdis
*[other] n yiferdisen
} seg { trash }
network-drive-schemes =
Ineggafen iwejden, azwir
AppleTalk,afp://
Aneggaf n usiweḍ n yifuyla,ftp:// neɣ ftps://
Anagraw n yifuyla n uzeṭṭa,nfs://
Iḥder n yizen n uqeddac,smb://
SSH Aneggaf n usiweḍ n yifuyla,sftp:// neɣ ssh://
WebDAV:// neɣ davs://
sort-a-z = A-Ẓ
sort-z-a = Ẓ-A
selected-items = { $items } n yiferdisen yettwafernen
type-to-search-select = Fren afaylu amezwaru neɣ akaram yemṣadan
copy-to = Nɣel ɣer...
move-to = Smutti ɣer…
show-recents = Akaram n melmi kan deg ufeggag adisan
clear-recents-history = Sfeḍ azray n melmi kan
copy-path = Nɣel abrid

View file

@ -22,7 +22,7 @@ name = Аты
modified = Өзгертілген
trashed-on = Қоқыс шелегіне тасталған
size = Өлшемі
details = Толық ақпарат
details = Ақпараты
dismiss = Хабарламаны елемеу
operations-running =
{ $running } { $running ->
@ -246,17 +246,17 @@ item-accessed = Қол жеткізілген: { $accessed }
calculating = Есептеу...
settings = Баптаулар
single-click = Ашу үшін бір рет шерту
appearance = Сыртқы көрініс
theme = Тема
match-desktop = Жұмыс үстеліне сәйкес болу
dark = Қою
appearance = Сыртқы түрі
theme = Тақырып
match-desktop = Жұмыс үстеліне сәйкес келу
dark = Күңгірт
light = Ашық
type-to-search = Іздеу үшін теру
type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді
type-to-search-enter-path = Бумаға немесе файлға жолды енгізеді
type-to-search-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
add-to-sidebar = Бүйірлік панельге қосу
compress = Сығу...
compress = Сығу
delete-permanently = Біржолата өшіру
eject = Шығару
extract-here = Тарқату
@ -318,16 +318,3 @@ keywords = Folder;Manager;Бума;Басқарушы;
show-recents = Бүйір панеліндегі «Жуырдағы құжаттар» бумасы
clear-recents-history = Жуырдағылар тарихын өшіру
copy-path = Орналасқан жолын көшіру
mixed = Аралас
context-action = Контекст әрекеті
context-action-confirm-title = "{ $name }" орындау керек пе?
context-action-confirm-warning =
Бұл { $items } орындалады { $items ->
[one] нәрсеге
*[other] нәрсеге
}.
run = Орындау
rename-confirm = Атын өзгерту
checksum = { $kind } бақылау сомасы
calculate = Есептеу
error = Қате

View file

@ -1,336 +0,0 @@
sort-newest-first = Pêşî ya herî nû
connect = Girê bide
deleted =
{ $items } hat jêbirin{ $items ->
[one] hêman
*[other] hêman
} ji { trash }
dismiss = Peyamê paşguh bike
list-view = Dîtina lîsteyê
copy_noun = Jê bigire
favorite-path-error = Şaşetiya vekirina rêgehê
progress = { $percent }%
open-file = Pelê veke
remove-from-sidebar = Ji benda kêlekê rake
related-apps = Bernameyên têkildar
network-drive-error = Nikaribû bigihêje ajokarê torê
gallery-preview = Pêşdîtina galeriyê
sort-smallest-to-largest = Biçûktir bo mezintir
zoom-in = Nêzîk bike
select-all = Hemûyan hilbijêre
removing-from-recents =
{ $items } tê rakirin { $items ->
[one] hêman
*[other] hêman
} ji { recents }
password = Borînpeyv
cosmic-files = Pelên COSMIC
emptying-trash = { trash } tê valakirin({ $progress })...
new-window = Çarçoveya nû
remove = Rake
zoom-out = Dûr bike
compressing =
{ $items } { $items ->
[one] hêman
*[other] hêman
} tê guvaştin ji "{ $from }" bo "{ $to }" ({ $progress })...
move-to-trash = Bilivîne bo jêbirdankê
menu-about = Derbarê Pelên COSMIC...
file-already-exists = Pelek bi heman navî jixwe heye
setting-executable-and-launching = "{ $name }" wekî pelê xebitandinê tê sazkirin û xebitandin
open-multiple-files = Gelek pelan veke
default-size = Mezinahiya berdest
name-hidden = Navên ku bi "." tê destpêkirin wê werin veşartin
extracted =
{ $items } { $items ->
[one] hêman
*[other] hêman
} hate derxistin ji "{ $from }" bo "{ $to }"
create-new-folder = Peldanka nû biafrîne
folder-already-exists = Peldankek bi heman navî jixwe heye
create-archive = Erşîvê biafrîne
permanently-delete-warning = { $target } wê bi mayînde were jêbirin û nayê vegerandin.
favorite-path-error-description =
Nikaribû "{ $path }" veke.
"{ $path }" dibe ku tune be yan jî mafê te yê
vekirina wê tune be.
Tu dixwazî wê ji benda kêlekê rakî?
empty-trash-warning = Hêmanên di jêbirdankê de wê bi mayînde werin jêbirin
other-apps = Bernameyên din
set-permissions = Maf hatine sazkirin ji bo "{ $name }" bo { $mode }
quit = Biqedîne
sort-by-size = Li gorî mezinahiyê rêz bike
rename = Navê biguhêrîne...
empty-folder-hidden = Peldanka vala (tiştên veşartî tê de hene)
keep = Bihêle
operations-running =
{ $running } { $running ->
[one] pêvajo
*[other] pêvajo
} dixebite ({ $percent }%)...
permanently-deleting =
{ $items } bi mayînde tê jêbirin { $items ->
[one] hêman
*[other] hêman
}
edit = Biguhêrîne
copy = Jê bigire
none = Ne yek
no-results = Tu encam nehatin dîtin
theme = Rûkar
compressed =
{ $items } { $items ->
[one] hêman
*[other] hêman
} hate guvaştin ji "{ $from }" bo "{ $to }"
appearance = Xuyang
rename-folder = Navê peldankê biguherîne
new-file = Pela nû...
close-tab = Rûgerê bigire
open-in-terminal = Di termînalê de veke
open-multiple-folders = Gelek peldankan veke
remember-password = Borînpeyvê bi bîr bîne
username = Navê bikarhêner
light = Ronî
extract-to = Derxîne bo...
add-network-drive = Ajokarê torê tevlî bike
copying =
{ $items } { $items ->
[one] hêman
*[other] hêman
} tê jêgirtin ji "{ $from }" bo "{ $to }" ({ $progress })...
create-new-file = Pelê nû biafrîne
sort-by-trashed = Li gorî demê rêz bike
support = Piştgirî
try-again = Dîsa hewl bide
eject = Biavêje
copied =
{ $items } { $items ->
[one] hêman
*[other] hêman
} hate jêgirtin ji "{ $from }" bo "{ $to }"
open-in-new-window = Di çarçoveyeke nû de veke
empty-folder = Peldanka vala
empty-trash = Jêbirdankê vala bike
sort-by-modified = Li gorî guhertinê rêz bike
list-directories-first = Pêşî rêgehan lîste bike
folder-name = Navê peldankê
browse-store = { $store } bigere
remove-from-recents = Ji dawiyê rake
paste = Pê ve bike
empty-trash-title = Jêbirdankê vala bike?
menu-settings = Sazkarî...
moving =
{ $items } { $items ->
[one] hêman
*[other] hêman
} tê livandin ji "{ $from }" bo "{ $to }" ({ $progress })...
change-wallpaper = Wêneyê dîwêr biguhêrîne...
network-drive-description =
Di nav navnîşanên rajekarê de pêşgira protokolekê û navnîşanek heye.
Mînak: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
{ $items } tê jêbirin { $items ->
[one] hêman
*[other] hêman
} ji { trash } ({ $progress })...
single-click = Tikandina yekane bo vekriinê
view = Dîtin
setting-permissions = Maf tên sazkirin ji bo "{ $name }" bo { $mode }
creating = "{ $name }" tê afirandin di "{ $parent }" de
execute-only = Tenê xebitandin
open-item-location = Cihê hêmanê veke
type-to-search = Bo lêgerînê binivîse
set-executable-and-launched = "{ $name }" wekî pelê xebitandinê hat sazkirin û xebitandin
mounted-drives = Ajokarên siwarkirî
sort-a-z = A-Z
mount-error = Nikare bigihêje ajokerê
extract-here = Derxîne
grid-view = Dîtina torê
add-to-sidebar = Tevlî benda kêlekê bike
dark = Tarî
notification-in-progress = Pêvajoyên pelê didome
home = Rûpela sereke
sort-z-a = Z-A
type-to-search-recursive = Di peldank û binpeldankên heyî de digere
history = Dîrok
progress-paused = { $percent }%, hate rawestandin
desktop-view-options = Vebijêrkên dîtina sermaseyê...
show-on-desktop = Li ser sermaseyê nîşan bide
cancelled = Têkbirî
new-folder = Peldanka nû...
match-desktop = Bi sermaseyê re li hev bîne
name-no-slashes = Nav nabe ku "/" tê de hebe
operations-running-finished =
{ $running } { $running ->
[one] pêvajo
*[other] pêvajo
} dixebite ({ $percent }%), { $finished } qediya...
sort-by-name = Li gorî navê rêz bike
edit-history = Dîrokê biguhêrînê
show-hidden-files = Pelên veşartî nîşan bide
progress-failed = { $percent }%, têk çû
trash-folder-icon = Îkona peldanka jêbirdankê
extract-to-title = Derxîne bo peldankê
icon-size = Mezinahiya îkonê
open-with-title = Tu çawa dixwazî "{ $name }" vekî?
extracting =
{ $items } { $items ->
[one] hêman
*[other] hêman
} tê derxistin ji "{ $from }" bo"{ $to }" ({ $progress })...
settings = Sazkarî
complete = Qediya
permanently-delete-question = Bi mayînde jê bibe?
extract-password-required = Borînpeyv pêdivî ye
pending = Li bendê ye
desktop-folder-content = Naveroka peldanka sermaseyê
no-history = Tu hêman di dîrok de tune ne.
open-folder = Peldankê veke
emptied-trash = { trash } hate valakirin
rename-file = Navê pelê biguherîne
sort-largest-to-smallest = Mezintir bo biçûktir
restore-from-trash = Ji jêbirdankê vegerîne
cut = Jê bike
moved =
{ $items } { $items ->
[one] hêman
*[other] hêman
} hate livandin ji "{ $from }" bo "{ $to }"
progress-cancelled = { $percent }%, hat têkbirin
open-in-new-tab = Di rûgerê nû de veke
file = Pel
file-name = Navê pelê
save-file = Pelê tomar bike
created = "{ $name }" hate afirandin di "{ $parent }" de
delete-permanently = Bi mayînde jê bibe
display-settings = Sazkariyên dîmenderê...
new-tab = Rûgera nû
failed = Têk çû
name-invalid = Nav nabe ku \"{ $filename }\" be
desktop-appearance = Xuyanga sermaseyê...
comment = Rêveberê pelê ji bo sermaseya COSMIC
keywords = Rêveber;Peldank;
filesystem = Pergala pelê
networks = Tor
recents = Dawî
open = Veke
cancel = Têk bibe
trash = Jêbirdank
undo = Vegerîne
today = Îro
name = Nav
modified = Guhertin
size = Mezinahî
details = Hûrgilî
pause = Rawestîne
resume = Bidomîne
copy-to-title = Rêgeha jêgirtinê hilbijêre
copy-to-button-label = Jê bigire
move-to-title = Rêgeha livandinê hilbijêre
move-to-button-label = Bilivîne
create = Afirandin
save = Tomar bike
context-action = Çalakiya naverokê
context-action-confirm-title = "{ $name }" bixebtîne?
context-action-confirm-warning =
Ev ê li ser { $items } { $items ->
[one] hêman
*[other] hêman
} bixebite.
run = Bixebitîne
selected-items = { $items } hêmanên hilbijartî
delete = Jê bibe
rename-confirm = Navê biguherîne
pasted-image = Wêneya pêvekirî
pasted-text = Nivîsa pêvekirî
pasted-video = Vîdiyoya pêvekirî
copy-path = Rêgehê jê bigire
show-recents = Peldanka dawî di benda kêlekê de
clear-recents-history = Dîroka dawî pak bike
compress = Biguvşîne...
copy-to = Jê bigire bo...
move-to = Bilivîne bo...
default-app = { $name } (berdest)
renamed = Nav hate guhertin ji "{ $from }" bo "{ $to }"
read-execute = Xwendin û xebitandin
item-modified = Guhertin: { $modified }
reload-folder = Peldankê ji nû ve bar bike
restoring =
{ $items } tê vegerandin { $items ->
[one] hêman
*[other] hêman
} ji { trash } ({ $progress })...
icon-size-and-spacing = Mezinahiya îkonan û valahiya di navbera wan de
type-to-search-enter-path = Riya rêgehê yan jî pelê têdixîne
menu-open-with = Veke bi riya...
original-file = Pelê resen
read-write-execute = Xwendin, nivîsandin û xebitandin
calculating = Tê jimartin...
item-size = Mezinahî: { $size }
connecting = Tê girêdan...
read-write = Xwendin û nivîsandin
items = Hêman: { $items }
type = Cûre: { $mime }
replace-warning = Tu dixwazî bi ya ku tu niha tomar dikî biguhêrînî? Naveroka berê wê were jêbirin.
show-details = Hûrgiliyan nîşan bide
grid-spacing = Valahiya torê
sort-oldest-first = Pêşî ya herî kevn
repository = Embar
replace-warning-operation = Tu dixwazî biguherînî? Naveroka berê wê were jêbirin.
other = Ên din
read-only = Tenê xwendin
enter-server-address = Navnîşana rajekarê têxîne
connect-anonymously = Bi awayekî anonîm girê bide
group = Kom
apply-to-all = Bo hemûyan bisepîne
skip = Derbas bike
replace-with = Biguhêrîne bi
owner = Xwedî
set-and-launch = Saz bike û dest pê bike
removed-from-recents =
{ $items } hate rakirin { $items ->
[one] hêman
*[other] hêman
} ji { recents }
item-created = Afirandin:{ $created }
network-drive-schemes =
Protokolên berdest,pêşgirê
AppleTalk,afp://
Protokola şandina pelê,ftp:// an ftps://
Pergala pelê torê,nfs://
Astengiya peyama rajekarê,smb://
SSH protokola şandina pelê,sftp:// an ssh://
WebDAV, dav:// an davs://
set-executable-and-launch = Wekî pelê xebitandinê saz bike û bixebitîne
restored =
{ $items } hate vegerandin { $items ->
[one] hêman
*[other] hêman
} ji { trash }
domain = Navper
sort = Rêz bike
item-accessed = Gihîştin: { $accessed }
open-with = Veke bi riya
keep-both = Herduyan bihêle
permanently-deleted =
{ $items } bi mayînde hate jêbirin{ $items ->
[one] hêman
*[other] hêman
}
write-execute = Nivîsandin û xebitandin
renaming = Navê ji "{ $from }" biguhêrîne bo "{ $to }"
set-executable-and-launch-description = Tu dixwazî "{ $name }" wekî pelê xebitandinê saz bikî û bixebtînî?
replace = Biguhêrîne
replace-title = "{ $filename }" jixwe di vê cihê de heye
unknown-folder = Peldanka nenas
write-only = Tenê nivîsandin
trashed-on = Jêbirî
mixed = Tevlihev
type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre
calculate = Bijmêre
error = Şaşetî
checksum = Tevahiya { $kind } kontrol bike
sidebar-label = Nîşanka benda kêlekê
search-application = Li gorî navê sepanê bigere
change-sidebar-label = Nîşanka benda kêlekê biguhêrîne

View file

@ -110,7 +110,7 @@ dismiss = 메시지 무시
copy_noun = 복사
progress = { $percent }%
related-apps = 관련 앱
compress = 압축...
compress = 압축
network-drive-error = 네트워크 드라이브에 접근할 수 없음
icon-size-and-spacing = 아이콘 크기 및 간격
password = 암호
@ -272,18 +272,3 @@ network-drive-schemes =
WebDAV,dav:// 또는 davs://
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
comment = COSMIC 데스크톱용 위한 파일 관리자
keywords = 폴더;관리자;
copy-to-button-label = 복사
move-to-button-label = 이동
clear-recents-history = 최근 기록 비우기
copy-path = 복사 경로
move-to-title = 이동 위치 선택
copy-to-title = 복사 경로 선택
run = 실행
rename-confirm = 이름 변경
mixed = 혼합됨
pasted-image = 붙여넣은 이미지
pasted-text = 붙여넣은 텍스트
pasted-video = 붙여넣은 영상
copy-to = 복사하기...
move-to = 이동하기...

View file

@ -1,5 +1,5 @@
progress = { $percent }%
cosmic-files = COSMIC Failai
cosmic-files = Cosmic Files
empty-folder = Tuščias aplankas
empty-folder-hidden = Tuščias aplankas (turi paslėptų failų)
no-results = Rezultatų nėra
@ -76,7 +76,7 @@ delete = Ištrinti
permanently-delete-warning = { $target } bus ištrintas visam laikui. Šis veiksmas yra negrįžtamas.
rename-file = Pervadinti failą
rename-folder = Pervadinti aplanką
replace = Keisti
replace = Pakeisti
replace-title = „{ $filename }“ jau egzistuoja šioje vietoje
replace-warning = Ar norite pakeisti tai su tuo, kas yra įrašoma? Keičiant bus pakeistas turinys.
replace-warning-operation = Ar norite pakeisti tai? Pakeičiant bus keičiamas turinys.
@ -244,7 +244,7 @@ item-created = Sukurtas: { $created }
item-modified = Modifikuota: { $modified }
item-accessed = Paskutinė prieiga: { $accessed }
calculating = Skaičiuojama...
settings = Nuostatai
settings = Nustatymai
single-click = Vieno paspaudimo atidarymas
appearance = Išvaizda
match-desktop = Pagal darbalaukio temą
@ -252,7 +252,7 @@ type-to-search = Norint ieškoti, pradėkite rašyti
type-to-search-recursive = Paieška dabartiniame aplanke ir jo poaplankiuose
type-to-search-enter-path = Įvedamas aplanko ar failo kelias
add-to-sidebar = Pridėti į šonjuostę
compress = Suspausti...
compress = Suspausti
delete-permanently = Ištrinti visam laikui
eject = Išstumti
extract-here = Išskleisti
@ -276,22 +276,22 @@ new-window = Naujas langas
reload-folder = Perkrauti aplanką
rename = Pervadinti...
close-tab = Uždaryti skirtuką
quit = Išjungti
quit = Išeiti
edit = Redaguoti
cut = Iškirpti
copy = Kopijuoti
paste = Įdėti
select-all = Žymėti viską
zoom-in = Artinti
paste = Įklijuoti
select-all = Pažymėti viską
zoom-in = Priartinti
default-size = Numatytas dydis
zoom-out = Tolinti
zoom-out = Nutolinti
view = Rodymas
grid-view = Tinklelio išdėstymas
list-view = Sąrašo išdėstymas
show-hidden-files = Rodyti paslėptus failus
list-directories-first = Pirmiau pateikti aplankus
gallery-preview = Galerijos peržiūra
menu-settings = Nuostatai...
menu-settings = Nustatymai...
menu-about = Apie COSMIC Files...
sort = Rikiuoti
sort-a-z = A-Ž
@ -302,7 +302,7 @@ sort-smallest-to-largest = Nuo mažiausio iki didžiausio
sort-largest-to-smallest = Nuo didžiausio iki mažiausio
dark = Tamsus
light = Šviesus
comment = COSMIC aplinkos failų tvarkyklė
comment = COSMIC desktop failų tvarkyklė
keywords = Aplankas;Tvarkyklė;
copy-to-title = Pasirinkti kopijavimo vietą
copy-to-button-label = Kopijuoti
@ -317,10 +317,3 @@ clear-recents-history = Išvalyti Neseniai naudotų istoriją
copy-to = Kopijuoti į...
move-to = Perkeltiį į...
copy-path = Kopijuoti kelią
theme = Stilius
context-action = Konteksto veiksmas
context-action-confirm-title = Paleisti „{ $name }“?
run = Paleisti
rename-confirm = Pervadinti
calculate = Skaičiuoti
error = Klaida

View file

@ -98,7 +98,7 @@ permanently-delete-question = Slett permanent
permanently-delete-warning = Er du sukker på om du vil permanent slette { $target }? Dette kan ikke angres.
rename-file = Endre filnavn
rename-folder = Endre mappenavn
replace-title = «{ $filename }» finnes allerede på denne plasseringen
replace-title = "{ $filename }" finnes allerede på denne plasseringen.
replace-warning = Vil du bytte den ut med filen du lagrer? Dette vil overskrive innholdet.
replace-warning-operation = Vil du erstatte den? Dette vil overskrive innholdet.
original-file = Orginalfil
@ -122,8 +122,8 @@ read-write = Kun skrive- og lesbar
read-write-execute = Lesbar, skrivbar og kjørbar
favorite-path-error = Feil ved åpning av mappen
favorite-path-error-description =
Klarte ikke åpne «{ $path }».
«{ $path }» finnes ikke eller så har du ikke tilgang til å åpne den.
Kunne ikke åpne "{ $path }".
Den kan ikke eksistere eller så har du ikke tilgang til å åpne den.
Vil du fjerne den fra sidepanelet?
keep = Behold
@ -140,7 +140,7 @@ zoom-out = Zoom ut
cut = Klipp ut
set-and-launch = Still inn og start
domain = Domene
enter-server-address = Angi tjeneradresse
enter-server-address = Angi serveradresse
network-drive-description =
Serveradresser inkluderer ett protokollprefiks og en addresse.
Examples: ssh://192.168.0.1, ftp://[2001:db8::1]

View file

@ -425,7 +425,7 @@ removed-from-recents =
} uit { recents } verwijderd
remove-from-recents = Uit recente verwijderen
type-to-search-select = Dit selecteert het eerst overeenkomende bestand of map
comment = Bestandsbeheerder voor COSMIC
comment = Bestandsbeheerder voor COSMIC desktop
copy-to-title = Kopieerbestemming aanwijzen
copy-to-button-label = Kopiëren
move-to-button-label = Verplaatsen

View file

@ -1,33 +0,0 @@
appearance = Aparéncia
theme = Tèma
match-desktop = Assortir al burèu
dark = Escur
light = Clar
file = Fichièr
details = Detalhs
name = Nom
cancel = Anullar
repository = Depaus
settings = Configuracion
menu-settings = Configuracion...
quit = Quitar
cosmic-files = Fichièrs de COSMIC
comment = Gestionari de fichièrs pel burèu COSMIC
save-file = Enregistrar lo fichièr
open-with-title = Cossí volètz dobrir « { $name } » ?
other-apps = Autras aplicacions
context-action-confirm-title = Executar « { $name } » ?
run = Executar
delete = Suprimir
replace = Remplaçar
owner = Propietari
group = Grop
write-execute = Escritura e execucion
read-only = Lectura sola
read-execute = Lectura e execucion
read-write = Lectura e execucion
read-write-execute = Lectura, escritura e execucion
remove = Suprimir
connecting = Connexion...
domain = Domeni
password = Senhal

View file

@ -32,7 +32,7 @@ pause = ਵਿਰਾਮ
resume = ਮੁੜ-ਚਾਲੂ
create-archive = ਅਕਾਇਵ ਬਣਾਓ
extract-password-required = ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ
extract-to = ਖਿਲਾਰੋ...
extract-to = Extract To...
extract-to-title = ਫੋਲਡਰ ਵਿੱਚ ਖਿਲਾਰੋ
empty-trash = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰੋ
empty-trash-title = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰਨਾ ਹੈ?
@ -113,7 +113,7 @@ replace-title = "{ $filename }" ਪਹਿਲਾਂ ਹੀ ਇਸ ਟਿਕਾਣ
favorite-path-error = ਡਾਇਰੈਕਟਰੀ ਖੋਲ੍ਹਣ ਦੌਰਾਨ ਗਲਤੀ
add-network-drive = ਨੈੱਟਵਰਕ ਡਰਾਇਵ ਜੋੜੋ
connect-anonymously = ਅਣਪਛਾਤੇ ਵਜੋਂ ਕਨੈਕਟ ਕਰੋ
connecting = ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...
connecting = Connecting...
domain = ਡੋਮੇਨ
enter-server-address = ਸਰਵਰ ਦਾ ਸਿਰਨਾਵਾਂ ਦਿਓ
password = ਪਾਸਵਰਡ
@ -138,15 +138,15 @@ sort-by-modified = ਸੋਧ ਨਾਲ ਲੜੀਬੱਧ
sort-by-size = ਆਕਾਰ ਨਾਲ ਲੜੀਬੱਧ
sort-by-trashed = ਹਟਾਉਣ ਸਮੇਂ ਨਾਲ ਲੜੀਬੱਧ
remove-from-recents = ਸੱਜਰਿਆਂ ਵਿੱਚੋਂ ਹਟਾਓ
change-wallpaper = ਵਾਲਪੇਪਰ ਨੂੰ ਬਦਲੋ...
desktop-appearance = ਡੈਸਕਟਾਪ ਦੀ ਦਿੱਖ...
display-settings = ਡਿਸਪਲੇਅ ਸੈਟਿੰਗਾਂ...
change-wallpaper = Change wallpaper...
desktop-appearance = Desktop appearance...
display-settings = Display settings...
file = ਫ਼ਾਇਲ
new-tab = ਨਵੀਂ ਟੈਬ
new-window = ਨਵੀਂ ਵਿੰਡੋ
rename = ਨਾਂ ਨੂੰ ਬਦਲੋ...
close-tab = ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ
quit = ਸਮਾਪਤ
quit = ਬਾਹਰ
edit = ਸੋਧੋ
cut = ਕੱਟੋ
copy = ਕਾਪੀ ਕਰੋ
@ -188,18 +188,18 @@ item-accessed = ਪਹੁੰਚ: { $accessed }
calculating = ਗਿਣਤੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ...
single-click = ਇੱਕ ਵਾਰ ਕਲਿੱਕ ਕਰਕੇ ਖੋਲ੍ਹੋ
show-recents = ਬਾਹੀ ਵਿੱਚ ਸੱਜਰੇ ਫੋਲਡਰ
match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਾਓ
match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਦਾ
dark = ਗੂੜ੍ਹਾ
light = ਫਿੱਕਾ
light = ਹਲਕਾ
type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ
compress = ਕੰਪਰੈਸ
copy-to = ਇੱਥੇ ਕਾਪੀ ਕਰੋ...
copy-to = Copy to...
delete-permanently = ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਓ
eject = ਬਾਹਰ
extract-here = ਖਿਲਾਰੋ
move-to = ਇੱਥੇ ਭੇਜੋ...
move-to = Move to...
remove-from-sidebar = ਬਾਹੀ ਵਿੱਚੋਂ ਹਟਾਓ
reload-folder = ਫੋਲਡਰ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ
list-view = ਸੂਚੀ ਝਲਕ

View file

@ -1,5 +1,5 @@
cosmic-files = Pliki COSMIC
comment = Menedżer plików dla środowiska COSMIC
comment = Menedżer plików pulpitu COSMIC
keywords = Katalogi;Pliki;Menedżer;
empty-folder = Pusty katalog
empty-folder-hidden = Pusty katalog (z ukrytymi plikami)
@ -27,7 +27,7 @@ modified = Zmodyfikowano
trashed-on = Wyrzucono do kosza
size = Rozmiar
# Progress footer
details = Szczegóły
details = Detale
dismiss = Odrzuć wiadomość
operations-running =
{ $running } bieżące { $running ->
@ -390,7 +390,7 @@ new-window = Nowe okno
reload-folder = Odśwież katalog
rename = Zmień nazwę…
close-tab = Zamknij kartę
quit = Zakończ
quit = Zamknij
## Edit
@ -437,19 +437,3 @@ move-to = Przenieś do…
show-recents = Ostatnie katalogi w panelu bocznym
clear-recents-history = Wyczyść bierzącą historię
copy-path = Skopiuj ścieżkę
mixed = Mieszane
context-action = Akcja sytuacyjna
context-action-confirm-title = Uruchomić "{ $name }"?
context-action-confirm-warning =
Zostanie uruchomionych { $items } { $items ->
[one] element
*[other] elementów
}.
run = Uruchom
rename-confirm = Zmień nazwę
checksum = { $kind } suma kontrolna
calculate = Oblicz
error = Błąd
sidebar-label = Etykieta panelu bocznego
search-application = Wyszukaj po nazwie aplikacji
change-sidebar-label = Zmień etykietę panelu bocznego

View file

@ -109,10 +109,6 @@ permanently-delete-warning = Deseja realmente excluir permanentemente { $target
rename-file = Renomear arquivo
rename-folder = Renomear pasta
## Change Sidebar Label Dialog
sidebar-label = Legenda da barra lateral
## Replace Dialog
replace = Substituir
@ -354,7 +350,7 @@ light = Estilo claro
### Type to Search
type-to-search = Digitar para pesquisar
type-to-search = Digite para pesquisar
type-to-search-recursive = Pesquisa na pasta atual e em todas as subpastas
type-to-search-enter-path = Insere o caminho do diretório ou arquivo
# Context menu
@ -369,7 +365,6 @@ open-in-terminal = Abrir no terminal
move-to-trash = Mover para a lixeira
restore-from-trash = Restaurar da lixeira
remove-from-sidebar = Remover da barra lateral
change-sidebar-label = Alterar legenda da barra lateral
sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por data de modificação
sort-by-size = Ordenar por tamanho
@ -393,7 +388,7 @@ new-window = Nova janela
reload-folder = Recarregar pasta
rename = Renomear...
close-tab = Fechar aba
quit = Encerrar
quit = Sair
## Edit
@ -429,8 +424,8 @@ sort-largest-to-smallest = Do maior para o menor
empty-trash-title = Esvaziar a lixeira?
type-to-search-select = Seleciona o primeiro arquivo ou pasta correspondente
pasted-image = Imagem colada
pasted-text = Texto colado
pasted-video = Vídeo colado
pasted-text = Texto copiado
pasted-video = Vídeo copiado
copy-to-title = Selecione o destino da cópia
copy-to-button-label = Copiar
move-to-title = Selecione o destino da movimentação
@ -438,20 +433,6 @@ move-to-button-label = Mover
copy-to = Copiar para...
move-to = Mover para...
keywords = Pasta;Gerenciador;
show-recents = Pasta de recentes na barra lateral
show-recents = Pasta Recentes na barra lateral
clear-recents-history = Limpar histórico de recentes
copy-path = Copiar caminho
mixed = Misto
context-action = Ação de contexto
context-action-confirm-title = Executar "{ $name }"?
context-action-confirm-warning =
Isso será executado em { $items } { $items ->
[one] item
*[other] itens
}.
run = Executar
rename-confirm = Renomear
checksum = Soma de verificação { $kind }
calculate = Calcular
error = Erro
search-application = Pesquisar por nome de aplicativo

View file

@ -1,12 +1,12 @@
cosmic-files = Ficheiros COSMIC
comment = Gerenciador de arquivos do COSMIC
keywords = Pasta;Gestor;
keywords = Pastas;Gerenciador;Arquivos;Gestor;Explorer;
empty-folder = Pasta vazia
empty-folder-hidden = Pasta vazia (tem ficheiros ocultos)
no-results = Nenhum resultado encontrado
filesystem = Sistema de ficheiros
home = Pasta Pessoal
notification-in-progress = Operações em curso
notification-in-progress = Operações em curso.
trash = Lixo
undo = Anular
# List view
@ -204,7 +204,7 @@ desktop-view-options = Opções de visualização da área de trabalho...
show-on-desktop = Mostrar na área de trabalho
desktop-folder-content = Conteúdo da pasta da área de trabalho
mounted-drives = Dispositivos montados
trash-folder-icon = Ícone da pasta lixo
trash-folder-icon = Ícone do lixo
icon-size-and-spacing = Tamanho e espaçamento do ícone
icon-size = Tamanho do ícone
grid-spacing = Espaçamento entre ícones
@ -352,6 +352,3 @@ sort-newest-first = Mais recentes primeiro
sort-oldest-first = Mais antigos primeiro
sort-smallest-to-largest = Do menor para o maior
sort-largest-to-smallest = Do maior para o menor
context-action-confirm-title = Executar "{ $name }"?
run = Executar
rename-confirm = Renomear

View file

@ -174,7 +174,6 @@ unknown-folder = неизвестная папка
menu-open-with = Открыть с помощью…
default-app = { $name } (по умолчанию)
search-application = Поиск по названию приложения
## Show details
@ -189,14 +188,14 @@ settings = Параметры
### Appearance
appearance = Внешний вид
appearance = Оформление
theme = Тема
match-desktop = Как в системе
dark = Тёмная
light = Светлая
# Context menu
add-to-sidebar = Добавить на боковую панель
compress = Сжать...
compress = Сжать
extract-here = Распаковать
new-file = Новый файл…
new-folder = Новая папка…
@ -219,7 +218,7 @@ new-tab = Новая вкладка
new-window = Новое окно
rename = Переименовать…
close-tab = Закрыть вкладку
quit = Завершить
quit = Выйти
## Edit
@ -272,8 +271,8 @@ extract-to = Распаковать в…
extract-to-title = Распаковать в папку
mount-error = Не удалось получить доступ к диску
create = Создать
open-with-title = Как хотите открыть «{ $name }»?
browse-store = Перейти в { $store }
open-with-title = Как вы хотите открыть «{ $name }»?
browse-store = Искать в { $store }
other-apps = Другие приложения
related-apps = Связанные приложения
selected-items = { $items } выделенных элем.
@ -321,7 +320,7 @@ type-to-search-enter-path = Ввод пути к каталогу или фай
delete-permanently = Удалить навсегда
eject = Извлечь
remove-from-recents = Убрать из недавних
change-wallpaper = Изменить фон...
change-wallpaper = Изменить обои…
desktop-appearance = Параметры оформления…
display-settings = Параметры экрана…
reload-folder = Обновить папку
@ -381,18 +380,3 @@ keywords = Папка;Менеджер;
show-recents = «Недавние документы» в бок. панели
clear-recents-history = Очистить историю недавних
copy-path = Копировать путь
mixed = Смешанные
context-action = Контекстная команда
context-action-confirm-title = Запустить «{ $name }»?
context-action-confirm-warning =
Команда затронет { $items } { $items ->
[one] элемент
*[other] элем.
}.
run = Запустить
rename-confirm = Переименовать
checksum = Хеш-сумма { $kind }
calculate = Вычислить
error = Ошибка
sidebar-label = Подпись в боковой панели
change-sidebar-label = Изменить подпись

View file

@ -1,3 +0,0 @@
cancel = ᱵᱟᱹᱛᱤᱞ
name = ᱧᱩᱛᱩᱢ
save = ᱥᱟᱧᱪᱟᱣ

View file

@ -1,330 +0,0 @@
open-file = Отвори датотеку
quit = Изађи
cancel = Откажи
open = Отвори
run = Покрени
connect = Повежи
save = Сачувај
password = Лозинка
remove = Уклони
appearance = Изглед
username = Корисничко име
light = Светла
dark = Тамна
settings = Подешавања
replace = Замени
size = Величина
sort-newest-first = Најновије прво
default-app = { $name } (подразумевано)
renamed = Преименована „{ $from }“ у „{ $to }“
read-execute = Читање и извршавање
deleted =
Обрисано је { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash }
item-modified = Измењено: { $modified }
dismiss = Одбаци поруку
list-view = Преглед у виду списака
reload-folder = Поново учитај фасциклу
copy_noun = Копија
favorite-path-error = Грешка при отварању директоријума
progress = { $percent }%
remove-from-sidebar = Уклони из бочне траке
related-apps = Повезани програми
restoring =
Враћање { $items } { $items ->
[one] ставке
*[other] ставки
} из { trash } ({ $progress })...
network-drive-error = Немогуће приступити мрежном уређају
gallery-preview = Преглед галерије
sort-smallest-to-largest = Од најмање до највеће
zoom-in = Увећајте приказ
select-all = Означи све
icon-size-and-spacing = Величина и размак иконица
removing-from-recents =
Уклањање { $items } { $items ->
[one] ставке
*[other] ставки
} из { recents }
cosmic-files = Космик датотеке
type-to-search-enter-path = Уноси путању до директоријума или датотеке
trash = Смеће
emptying-trash = Пражњење { trash } ({ $progress })...
trashed-on = Премештено у смеће
new-window = Нови прозор
zoom-out = Умањите приказ
compressing =
Сажимање { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
move-to-trash = Премести у смеће
menu-about = О програму Космик Датотеке...
setting-executable-and-launching = Подешавање „{ $name }“ као извршне датотеке и покретање
open-multiple-files = Отвори више датотека
default-size = Подразумевана величина
menu-open-with = Отвори програмом...
extracted =
Извлачено { $items } { $items ->
[one] ставка
*[other] ставки
} из „{ $from }“ у „{ $to }“
create-new-folder = Направи нову фасциклу
original-file = Изворна датотека
create = Направи
create-archive = Направи архиву
read-write-execute = Читање, уписивање и извршавање
other-apps = Остали програми
set-permissions = Овлашћења за „{ $name }“ су постављена на { $mode }
pause = Паузирај
calculating = Израчунавам…
sort-by-size = Поређај по величини
rename = Преименуј...
empty-folder-hidden = Празна фасцикла (има сакривених ставки)
keep = Задржи
item-size = Величина: { $size }
permanently-deleting =
Трајно брисање { $items } { $items ->
[one] ставке
*[other] ставки
}
edit = Уреди
connecting = Повезујем се...
read-write = Читање и уписивање
copy = Умножи
none = Ништа
items = Ставки: { $items }
no-results = Нису пронађени резултати
theme = Тема
type = Врста: { $mime }
compressed =
Сажето { $items } { $items ->
[one] ставка
*[other] ставки
} из „{ $from }“ у „{ $to }“
replace-warning = Да ли желите да га замените оним који снимате? Замена ће преписати његов садржај.
rename-folder = Преименуј фасциклу
new-file = Нова датотека...
close-tab = Затвори језичак
name = Назив
open-in-terminal = Отвори у терминалу
resume = Настави
open-multiple-folders = Отвори више фасцикла
remember-password = Запамти лозинку
show-details = Прикажи детаље
grid-spacing = Размак мреже
extract-to = Распакуј у...
add-network-drive = Додај мрежни уређај
copying =
Умножавање { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
delete = Обриши
sort-oldest-first = Најстарије прво
repository = Ризница
create-new-file = Направи нову датотеку
sort-by-trashed = Поређај по времену брисања
replace-warning-operation = Да ли желите да га замените? Замена ће преписати његов садржај.
support = Подршка
try-again = Покушај поново
eject = Избаци
copied =
Умножено { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“
other = Друго
open-in-new-window = Отвори у новом прозору
empty-folder = Празна фасцикла
sort-by-modified = Поређај по датуму измене
list-directories-first = Прикажи директоријуме прво
read-only = Само за читање
folder-name = Назив фасцикле
browse-store = Разгледај { $store }
enter-server-address = Унесите адресу сервера
remove-from-recents = Уклони из недавних
connect-anonymously = Повежи се анонимно
group = Група
apply-to-all = Примени на све
skip = Прескочи
paste = Залепи
menu-settings = Подешавања...
moving =
Премештање { $items } { $items ->
[one] датотеке
*[other] датотека
} из „{ $from }“ у „{ $to }“ ({ $progress })...
replace-with = Замени са
recents = Недавно
change-wallpaper = Промени позадину...
network-drive-description =
Адресе сервера укључују префикс протокола и адресу.
Примери: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
Брише се { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash } ({ $progress })...
single-click = Један клик за отварање
view = Преглед
undo = Опозови
setting-permissions = Подешавање овлашћења за „{ $name }“ на { $mode }
owner = Власник
creating = Правим „{ $name }“ у „{ $parent }“
execute-only = Само извршавање
open-item-location = Отвори локацију ставке
details = Детаљи
set-executable-and-launched = Постављено „{ $name }“ као извршну датотеку и покренуто
mounted-drives = Прикључени дискови
sort-a-z = А-Ш
mount-error = Немогуће приступити уређају
extract-here = Извуци
grid-view = Преглед у виду мреже
filesystem = Систем датотека
set-and-launch = Подеси и покрени
removed-from-recents =
Уклоњено { $items } { $items ->
[one] ставке
*[other] ставки
} из { recents }
add-to-sidebar = Додај у страничник
item-created = Направљено: { $created }
network-drive-schemes =
Доступни протоколи,Префикс
ЕплТок,afp://
Протокол за пренос датотека,ftp:// или ftps://
Мрежни систем датотека,nfs://
Серверски блок порука,smb://
SSH протокол за пренос датотека,sftp:// или ssh://
ВебДАВ,dav:// или davs://
home = Лична
set-executable-and-launch = Постави као извршну и покрени
restored =
Враћено { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash }
sort-z-a = Ш-А
type-to-search-recursive = Претражује тренутну фасциклу и све подфасцикле
history = Историјат
progress-paused = { $percent }%, паузирано
desktop-view-options = Могућности приказа радне површине...
show-on-desktop = Прикажи на радној површини
cancelled = Отказано
new-folder = Нова фасцикла...
match-desktop = Прати радну површину
domain = Домен
operations-running-finished =
{ $running } { $running ->
[one] радња покренута
*[other] радње покренуте
} ({ $percent }%), { $finished } завршено...
sort-by-name = Поређај по називу
edit-history = Историјат уређивања
sort = Поређај
show-hidden-files = Прикажи скривене датотеке
progress-failed = { $percent }%, није успело
trash-folder-icon = Иконица фасцикле Смеће
item-accessed = Приступљено: { $accessed }
extract-to-title = Распакуј у фасциклу
open-with = Отвори помоћу
keep-both = Задржи оба
icon-size = Величина иконице
open-with-title = Како желите да отворите „{ $name }“?
extracting =
Извлачење { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
permanently-deleted =
Трајно обрисано { $items } { $items ->
[one] ставке
*[other] ставки
}
complete = Завршено
write-execute = Писање и извршавање
extract-password-required = Потребна је лозинка
pending = У току
desktop-folder-content = Садржај фасцикле радне површине
renaming = Преименовање „{ $from }“ у „{ $to }“
set-executable-and-launch-description = Да ли желите да поставите „{ $name }“ као извршну и покренете је?
no-history = Нема ставки у историјату.
open-folder = Отвори фасциклу
emptied-trash = Опражњено { trash }
rename-file = Преименуј датотеку
sort-largest-to-smallest = Од највеће до најмање
restore-from-trash = Врати из смећа
cut = Исеци
moved =
Премештено { $items } { $items ->
[one] датотеке
*[other] датотека
} из „{ $from }“ у „{ $to }“
progress-cancelled = { $percent }%, отказано
open-in-new-tab = Отвори у новом језичку
unknown-folder = непозната фасцикла
file = Датотека
file-name = Назив датотеке
save-file = Сачувај датотеку
created = Направљено „{ $name }“ у „{ $parent }“
delete-permanently = Трајно обриши
networks = Мреже
write-only = Само писање
today = Данас
display-settings = Подешавања екрана...
new-tab = Нови језичак
failed = Неуспешно
modified = Измењено
desktop-appearance = Изглед радне површине...
file-already-exists = Датотека са овим називом већ постоји
name-hidden = Називи који почињу тачком „.“ ће бити сакривени
folder-already-exists = Фасцикла са овим називом већ постоји
permanently-delete-warning = { $target } ће бити трајно обрисано. Ова радња се не може поништити.
favorite-path-error-description =
Не можемо да отворимо „{ $path }“
„{ $path }“ можда не постоји или немате дозволу за његово отварање
Желите ли да га уклоните из бочне површи?
empty-trash-warning = Ставке у смећу биће трајно обрисане
empty-trash = Испразни смеће
empty-trash-title = Испразнити смеће?
type-to-search = Куцајте за претрагу
notification-in-progress = Радње са датотекама су у току
name-no-slashes = Назив не може садржати косе црте
permanently-delete-question = Трајно обриши?
replace-title = „{ $filename }“ већ постоји на овој локацији
name-invalid = Назив не може бити „{ $filename }“
operations-running =
{ $running } { $running ->
[one] радња покренута
*[other] радње покренуте
} ({ $percent }%)...
comment = Управник датотека за Космик радну површину
keywords = Folder;Manager;Фасцикла;Управник;fascikla;upravnik;
copy-to-title = Изабери одредиште умножавања
copy-to-button-label = Умножи
move-to-title = Изабери одредиште премештања
move-to-button-label = Помери
context-action = Контекстна радња
context-action-confirm-title = Покрени „{ $name }“?
context-action-confirm-warning =
Ово ће се извршити на { $items } { $items ->
[one] ставку
*[other] ставки
}.
selected-items = Изабраних { $items } ставки
mixed = Помешано
pasted-image = Убачена слика
pasted-text = Убачен текст
pasted-video = Убачен видео
show-recents = Недавна фасцикла у бочној површи
type-to-search-select = Обира прву подударајућу датотеку или фасциклу
clear-recents-history = Очисти историју недавних
compress = Сажми...
copy-to = Умножи у...
move-to = Помери у...
copy-path = Умножи путању
rename-confirm = Преименуј

View file

@ -101,7 +101,6 @@ open-with = Öppna med
owner = Ägare
group = Grupp
other = Andra
mixed = Blandade
# Listvy
name = Namn
modified = Ändrad
@ -319,7 +318,7 @@ list-view = Listvy
show-hidden-files = Visa dolda filer
list-directories-first = Lista mappar först
gallery-preview = Galleri förhandsvisning
menu-settings = Inställningar...
menu-settings = Inställningar…
menu-about = Om COSMIC Filer...
## Sortera
@ -410,18 +409,3 @@ move-to = Flytta till...
show-recents = Mapp för senast använda filer i sidofältet
clear-recents-history = Töm historik för Senaste
copy-path = Kopiera sökväg
context-action = Kontextåtgärd
context-action-confirm-title = Kör "{ $name }"?
context-action-confirm-warning =
Detta kommer att köras på { $items } { $items ->
[one] objekt
*[other] objekt
}.
run = Kör
rename-confirm = Byt namn
checksum = { $kind }-kontrollsumma
calculate = Beräkna
error = Fel
sidebar-label = Sidofältsetikett
search-application = Sök på programnamn
change-sidebar-label = Ändra sidofältsetikett

View file

@ -202,9 +202,17 @@ moved =
*[other] ไฟล์
} จาก "{ $from }" สู่ "{ $to }"
renaming = กำลังเปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }"
renamed = เปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }" แล้ว
restoring = กำลังกู้คืน { $items } รายการจาก { trash } ({ $progress })...
restored = กู้คืน { $items } รายการจาก { trash } แล้ว
renamed = Renamed "{ $from }" to "{ $to }"
restoring =
Restoring { $items } { $items ->
[one] item
*[other] items
} from { trash } ({ $progress })...
restored =
Restored { $items } { $items ->
[one] item
*[other] items
} from { trash }
unknown-folder = แฟ้มที่ไม่รู้จัก
## Open with
@ -232,8 +240,8 @@ settings = การตั้งค่า
appearance = ลักษณะ
theme = ธีม
match-desktop = ใช้ตามธีมหน้าจอหลัก
dark = มืด
light = สว่าง
dark = ธีมมืด
light = ธีมสว่าง
# Context menu
add-to-sidebar = เพิ่มเข้าแถบด้านข้าง
compress = บีบอัด
@ -298,4 +306,3 @@ sort-newest-first = ไฟล์ใหม่ก่อน
sort-oldest-first = ไฟล์เก่าก่อน
sort-smallest-to-largest = ขนาดเล็กก่อน
sort-largest-to-smallest = ขนาดใหญ่ก่อน
remove = ลบ

View file

@ -5,7 +5,7 @@ filesystem = Файлова система
home = Домівка
trash = Смітник
recents = Нещодавні
undo = Скасувати
undo = Відмінити
# List view
name = Назва
modified = Змінено
@ -82,11 +82,10 @@ copying =
Копіювання { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» до «{ $to }» ({ $progress })...
} з «{ $from }» в «{ $to }» ({ $progress })...
copied =
Скопійовано { $items } { $items ->
[one] елемент
[few] елементи
*[other] елеменів
} з «{ $from }» в «{ $to }»
emptying-trash = Спорожнення { trash } ({ $progress })...
@ -95,12 +94,11 @@ moving =
Переміщення { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» до «{ $to }» ({ $progress })...
} з «{ $from }» в «{ $to }» ({ $progress })...
moved =
Переміщено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
*[other] елементи
} з «{ $from }» в «{ $to }»
renaming = Перейменування «{ $from }» на «{ $to }»
renamed = Перейменовано «{ $from }» на «{ $to }»
@ -112,8 +110,7 @@ restoring =
restored =
Відновлено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
*[other] елементи
} з { trash }
unknown-folder = невідома тека
@ -158,7 +155,7 @@ new-tab = Нова вкладка
new-window = Нове вікно
rename = Перейменувати...
close-tab = Закрити вкладку
quit = Завершити
quit = Вийти
## Edit
@ -182,7 +179,7 @@ menu-settings = Налаштування...
menu-about = Про Файли COSMIC...
repository = Сховище
support = Підтримка
details = Подробиці
details = Деталі
dismiss = Закрити повідомлення
remove = Вилучити
cancelled = Скасовані
@ -226,7 +223,7 @@ permanently-delete-question = Остаточно видалити?
delete = Видалити
permanently-delete-warning = { $target } буде остаточно видалено. Цю дію не можна скасувати.
set-executable-and-launch = Зробити виконуваним і запустити
set-executable-and-launch-description = Бажаєте зробити «{ $name }» виконуваним і запустити його?
set-executable-and-launch-description = Бажаєте зробити "{ $name }" виконуваним і запустити його?
set-and-launch = Зробити і запустити
open-with = Відкрити за допомогою
owner = Власник
@ -248,7 +245,7 @@ favorite-path-error-description =
Вилучити з бічної панелі?
keep = Залишити
add-network-drive = Додати мережевий диск
connect = З’єднати
connect = З'єднати
connect-anonymously = З'єднатись анонімно
connecting = З'єднання…
domain = Домен
@ -277,13 +274,12 @@ compressing =
Стиснення { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» до «{ $to }» ({ $progress })...
} з "{ $from }" до "{ $to }" ({ $progress })...
compressed =
Стиснуто { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
} з «{ $from }» до «{ $to }»
*[other] елементи
} з "{ $from }" до "{ $to }"
deleting =
Видалення { $items } { $items ->
[one] елемента
@ -292,19 +288,17 @@ deleting =
deleted =
Видалено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементи
} з { trash }
extracting =
Видобування { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» до «{ $to }» ({ $progress })...
} з «{ $from }» в «{ $to }» ({ $progress })...
extracted =
Видобуто { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
*[other] елементи
} з «{ $from }» в «{ $to }»
setting-executable-and-launching = Надання «{ $name }» прав на виконання та запуск
set-executable-and-launched = «{ $name }» надано права на виконання і відкрито
@ -323,7 +317,7 @@ single-click = Відкривати одним клацанням
type-to-search = Введіть для пошуку
type-to-search-recursive = Шукає у поточній теці та всіх підтеках
type-to-search-enter-path = Вводить шлях до каталогу або файлу
compress = Стиснути...
compress = Стиснути
delete-permanently = Остаточно видалити
eject = Безпечно вилучити
extract-here = Видобути
@ -349,8 +343,7 @@ permanently-deleting =
permanently-deleted =
Остаточно вилучено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
*[other] елементи
}
removing-from-recents =
Вилучення { $items } { $items ->
@ -360,14 +353,13 @@ removing-from-recents =
removed-from-recents =
Вилучено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементів
*[other] елементи
} з { recents }
empty-trash-title = Спорожити смітник?
type-to-search-select = Вибирає перший відповідний файл або теку
pasted-image = Вставлене зображення
pasted-text = Вставлений текст
pasted-video = Вставлене видиво
type-to-search-select = Вибирає перший відповідний файл або папку
pasted-image = Вставлене Зображення
pasted-text = Вставлений Текст
pasted-video = Вставлене Видиво
copy-to-button-label = Копіювати
move-to-button-label = Перемістити
copy-to = Копіювати до…
@ -379,19 +371,3 @@ keywords = Тека;Папка;Провідник;Менеджер;Катало
show-recents = Тека «Нещодавні» на бічній панелі
copy-path = Копіювати шлях
clear-recents-history = Очистити нещодавні
context-action-confirm-title = Запустити «{ $name }»?
run = Виконати
context-action-confirm-warning =
Запуститься для { $items } { $items ->
[one] елемента
*[other] елементів
}.
context-action = Контекстна дія
rename-confirm = Перейменувати
mixed = Змішано
checksum = контрольна сума { $kind }
calculate = Вирахувати
error = Помилка
sidebar-label = Назва бічної панелі
search-application = Пошук за назвою застосунку
change-sidebar-label = Змінити назву бічної панелі

View file

@ -11,7 +11,7 @@ recents = 最近访问
undo = 撤销
today = 今天
# Desktop view options
desktop-view-options = 桌面视图选项…
desktop-view-options = 桌面视图选项...
show-on-desktop = 在桌面显示
desktop-folder-content = 桌面文件夹内容
mounted-drives = 已装载驱动器
@ -25,13 +25,13 @@ modified = 修改时间
trashed-on = 删除时间
size = 大小
# Progress footer
details = 详情
details = 详细信息
dismiss = 清除消息
operations-running =
正在进行 { $running } { $running ->
[one] 个操作
*[other] 个操作
}({ $percent }%)…
}({ $percent }%)...
operations-running-finished =
正在进行 { $running } { $running ->
[one] 个操作
@ -50,7 +50,7 @@ create-archive = 创建压缩包
## Extract Dialog
extract-password-required = 需要密码
extract-to = 提取到…
extract-to = 提取到...
extract-to-title = 提取到文件夹
## Empty Trash Dialog
@ -167,10 +167,10 @@ read-write-execute = 读取、写入和执行
## Favorite Path Error Dialog
favorite-path-error = 打开目录时出错
favorite-path-error = 打开路径时出错
favorite-path-error-description =
无法打开“{ $path }” 。
“{ $path }”可能不存在或您没有权限打开它。
无法打开 "{ $path }" 。
"{ $path }" 可能不存在或您没有权限打开它。
您想要从侧边栏中移除它吗?
remove = 移除
@ -267,10 +267,10 @@ extracted =
[one] 项目
*[other] 项目
}到“{ $to }”
setting-executable-and-launching = 设置 “{ $name }” 为可执行文件并启动
set-executable-and-launched = 已设 “{ $name }” 为可执行文件并且启动
setting-permissions = 正在设置 “{ $name }” 的权限为 { $mode }
set-permissions = 设置 “{ $name }” 的权限为 { $mode }
setting-executable-and-launching = 设置 "{ $name }" 为可执行文件并启动
set-executable-and-launched = 已设 "{ $name }" 为可执行文件并且启动
setting-permissions = 正在设置 "{ $name }" 的权限为 { $mode }
set-permissions = 设置 "{ $name }" 的权限为 { $mode }
moving =
正在从“{ $from }”移动 { $items } { $items ->
[one] 项目
@ -302,7 +302,7 @@ removed-from-recents =
*[other] 项目
}
renaming = 正在将“{ $from }”重命名为“{ $to }”
renamed = 已重命名 “{ $from }” 为 “{ $to }”
renamed = 已重命名"{ $from }"为"{ $to }"
restoring =
正在从{ trash }中还原 { $items } { $items ->
[one] 项目
@ -317,7 +317,7 @@ unknown-folder = 未知文件夹
## Open with
menu-open-with = 打开方式…
menu-open-with = 打开方式...
default-app = { $name }(默认)
## Show details
@ -329,7 +329,7 @@ item-size = 文件大小:{ $size }
item-created = 创建于:{ $created }
item-modified = 修改于:{ $modified }
item-accessed = 访问于:{ $accessed }
calculating = 计算中…
calculating = 计算中...
## Settings
@ -348,15 +348,15 @@ light = 亮色模式
type-to-search = 输入即可搜索
type-to-search-recursive = 搜索当前文件夹及其所有子文件夹
type-to-search-enter-path = 输入文件夹或文件目录
type-to-search-enter-path = 输入文件夹或文件路径
# Context menu
add-to-sidebar = 加入侧边栏
compress = 压缩…
delete-permanently = 永久删除
eject = 弹出
extract-here = 解压到此处
new-file = 新建文件…
new-folder = 新建文件夹…
new-file = 新建文件...
new-folder = 新建文件夹...
open-in-terminal = 在终端模拟器中打开
move-to-trash = 移动到回收站
restore-from-trash = 从回收站中还原
@ -369,9 +369,9 @@ remove-from-recents = 从最近访问中移除
## Desktop
change-wallpaper = 更改壁纸…
desktop-appearance = 桌面外观…
display-settings = 显示设置…
change-wallpaper = 更改壁纸...
desktop-appearance = 桌面外观...
display-settings = 显示设置...
# Menu
@ -382,7 +382,7 @@ file = 文件
new-tab = 新建标签
new-window = 新建窗口
reload-folder = 刷新文件夹
rename = 重命名…
rename = 重命名...
close-tab = 关闭标签
quit = 退出
@ -405,7 +405,7 @@ list-view = 列表视图
show-hidden-files = 显示隐藏文件
list-directories-first = 优先列出目录
gallery-preview = 图库预览
menu-settings = 设置…
menu-settings = 设置...
menu-about = 关于 COSMIC 文件…
## Sort
@ -435,20 +435,4 @@ comment = COSMIC 桌面的文件管理器
keywords = 文件夹;管理器;
clear-recents-history = 清除最近访问历史
copy-path = 复制文件路径
show-recents = 侧边栏中的最近访问
mixed = 混合
context-action-confirm-title = 运行 “{ $name }” ?
run = 运行
context-action-confirm-warning =
该行动将会在 { $items } { $items ->
[one] 项目
*[other] 项目
} 上运行。
context-action = 环境行动
rename-confirm = 重命名
calculate = 计算
error = 错误
checksum = { $kind } 校验和
sidebar-label = 侧边栏标签
search-application = 使用应用名称搜索
change-sidebar-label = 更改侧边栏标签
show-recents = 侧边栏显示最近访问

View file

@ -1,18 +1,18 @@
cosmic-files = COSMIC 檔案
cosmic-files = COSMIC 檔案總管
empty-folder = 空資料夾
empty-folder-hidden = 空資料夾(包含隱藏項目)
no-results = 找不到結果
filesystem = 檔案系統
home = 家目錄
home = 主目錄
networks = 網路
notification-in-progress = 檔案操作正在進行中
notification-in-progress = 檔案操作正在進行中。
trash = 垃圾桶
recents = 最近使用
undo = 復原
today = 今天
# List view
name = 名稱
modified = 修改時間
modified = 修改日期
size = 大小
# Dialogs
@ -25,7 +25,7 @@ create-archive = 建立壓縮檔案
## Empty Trash Dialog
empty-trash = 清空垃圾桶
empty-trash-warning = 垃圾桶中的項目將被永久刪除
empty-trash-warning = 你確定要永久刪除垃圾桶中的所有項目嗎?
## New File/Folder Dialog
@ -33,16 +33,16 @@ create-new-file = 建立新檔案
create-new-folder = 建立新資料夾
file-name = 檔案名稱
folder-name = 資料夾名稱
file-already-exists = 相同名稱的檔案已經存在
folder-already-exists = 相同名稱的資料夾已經存在
name-hidden = 以「.」開頭的名稱將會被隱藏
name-invalid = 名稱不能是「{ $filename }」
name-no-slashes = 名稱不能包含斜線
file-already-exists = 已存在同名檔案。
folder-already-exists = 已存在同名資料夾。
name-hidden = 以「.」開頭的名稱將會被隱藏。
name-invalid = 名稱不能是 「{ $filename }」。
name-no-slashes = 名稱不能包含斜線。
## Open/Save Dialog
cancel = 取消
create = 創作
create = 建立
open = 開啟
open-file = 開啟檔案
open-folder = 開啟資料夾
@ -62,12 +62,12 @@ rename-folder = 重新命名資料夾
## Replace Dialog
replace = 取代
replace-title = 「{ $filename }」已存在於此位置
replace-title = 檔案「{ $filename }」已存在於此位置。
replace-warning = 你要取代它嗎?取代將覆蓋其內容。
replace-warning-operation = 你要取代它嗎?取代將覆蓋其內容。
original-file = 原始檔案
replace-with = 取代為
apply-to-all = 套用至全部
apply-to-all = 套用至所有項目
keep-both = 保留兩者
skip = 跳過
@ -95,13 +95,13 @@ network-drive-description =
伺服器地址包括協定前綴和地址。
範例:ssh://192.168.0.1, ftp://[2001:db8::1]
network-drive-schemes =
可用協定,前綴
AppleTalk,afp://
檔案傳輸協定,ftp:// 或 ftps://
網路檔案系統,nfs://
伺服器訊息區塊,smb://
SSH 檔案傳輸協定,sftp:// 或 ssh://
WebDav,dav:// 或 davs://
可用協定,前綴
AppleTalk,afp://
檔案傳輸協定,ftp:// 或 ftps://
網路檔案系統,nfs://
伺服器訊息區塊,smb://
SSH 檔案傳輸協定,sftp:// 或 ssh://
WebDav,dav:// 或 davs://
network-drive-error = 無法存取網路磁碟機
password = 密碼
remember-password = 記住密碼
@ -112,7 +112,7 @@ username = 使用者名稱
edit-history = 編輯歷史
history = 歷史紀錄
no-history = 無歷史記錄項目。
no-history = 沒有歷史項目。
pending = 待處理
failed = 失敗
complete = 完成
@ -133,56 +133,56 @@ copying =
正在複製 { $items } { $items ->
[one] 項目
*[other] 項目
}從「{ $from }」到「{ $to }」({ $progress })...
} 從「{ $from }」到「{ $to }」({ $progress })...
copied =
已複製 { $items } { $items ->
[one] 項目
*[other] 項目
}從「{ $from }」到「{ $to }」
emptying-trash = 正在清空 { trash }({ $progress })…
emptied-trash = 已經清空 { trash }
emptying-trash = 正在清空{ trash }({ $progress })…
emptied-trash = 已清空{ trash }
extracting =
正在解壓縮 { $items } 項目 { $items ->
[one] 項目
*[other] 項目
}從「{ $from }」至「{ $to }」({ $progress })...
} 從「{ $from }」到「{ $to }」({ $progress })...
extracted =
已解壓縮 { $items } 項目 { $items ->
[one] 項目
*[other] 項目
}從「{ $from }」到「{ $to }」
} 從 { $from } 到 { $to }
moving =
正在移動 { $items } { $items ->
[one] 項目
*[other] 項目
}從「{ $from }」到「{ $to }」({ $progress })...
} 從「{ $from }」到「{ $to }」({ $progress })...
moved =
已經移動 { $items } { $items ->
已移動 { $items } { $items ->
[one] 項目
*[other] 項目
} 從「{ $from }」至「{ $to }」
renaming = 正在將「{ $from }」重新命名為「{ $to }」
renamed = 已將「{ $from }」重新命名為「{ $to }」
} 從「{ $from }」到「{ $to }」
renaming = 正在重新命名「{ $from }」為「{ $to }」
renamed = 已重新命名 { $from } 為 { $to }
restoring =
正在還原 { $items } 項目 { $items ->
[one] 項目
*[other] 項目
}自 { trash } ({ $progress })...
} 從{ trash }({ $progress })...
restored =
已經還原 { $items } 項目 { $items ->
已還原 { $items } 項目 { $items ->
[one] 項目
*[other] 項目
}從 { trash }
unknown-folder = 不明資料夾
} 從{ trash }
unknown-folder = 未知資料夾
## Open with
menu-open-with = 開啟檔案...
menu-open-with = 開啟方式...
default-app = { $name } (預設)
## Show details
show-details = 顯示詳細資訊
show-details = 顯示詳細資料
## Settings
@ -192,17 +192,17 @@ settings = 設定
appearance = 外觀
theme = 主題
match-desktop = 符合桌面
dark = 深色
light = 淺色
match-desktop = 與桌面一致
dark = 暗色模式
light = 亮色模式
# Context menu
add-to-sidebar = 加入側邊欄
compress = 壓縮…
extract-here = 解壓縮
new-file = 新增檔案...
new-folder = 新增資料夾...
compress = 壓縮
extract-here = 解壓縮至此
new-file = 新檔案...
new-folder = 新資料夾...
open-in-terminal = 在終端機中開啟
move-to-trash = 丟入垃圾桶
move-to-trash = 移動至垃圾桶
restore-from-trash = 從垃圾桶還原
remove-from-sidebar = 從側邊欄移除
sort-by-name = 依名稱排序
@ -215,11 +215,11 @@ sort-by-size = 依大小排序
## File
file = 檔案
new-tab = 新增分頁
new-window = 新增視窗
new-tab = 新分頁
new-window = 新視窗
rename = 重新命名...
close-tab = 關閉分頁
quit = 結束
quit = 退出
## Edit
@ -238,164 +238,46 @@ view = 檢視
grid-view = 網格檢視
list-view = 列表檢視
show-hidden-files = 顯示隱藏檔案
list-directories-first = 目錄優先列出
list-directories-first = 優先列出目錄
menu-settings = 設定...
menu-about = 關於 COSMIC 檔案...
menu-about = 關於 COSMIC 檔案總管...
## Sort
sort = 排序
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = 最新優先
sort-oldest-first = 最舊優先
sort-smallest-to-largest = 從小到大
sort-largest-to-smallest = 從大到小
sort-newest-first = 最新的在前
sort-oldest-first = 最舊的在前
sort-smallest-to-largest = 由小至大
sort-largest-to-smallest = 由大至小
deleted =
已經刪除 { $items } { $items ->
已刪除 { $items } { $items ->
[one] 項目
*[other] 項目
}從 { trash }
}從{ trash }
permanently-deleting =
正在永久刪除 { $items } { $items ->
[one] 項目
*[other] 項目
[one] 项目
*[other] 项目
}
permanently-deleted =
已經永久刪除 { $items } { $items ->
[one] 項目
*[other] 項目
已永久刪除 { $items } { $items ->
[one] 项目
*[other] 项目
}
removing-from-recents =
正在從 { recents } 中移除 { $items } { $items ->
[one] 項目
*[other] 項目
正在從{ recents }中移除 { $items } { $items ->
[one] 项目
*[other] 项目
}
deleting =
正在刪除 { $items } { $items ->
[one] 项目
*[other] 项目
}從 { trash }({ $progress })…
}從{ trash }({ $progress })…
removed-from-recents =
已經從 { recents } 中移除 { $items } { $items ->
[one] 項目
*[other] 項目
已從{ recents }中移除 { $items } { $items ->
[one] 项目
*[other] 项目
}
repository = 軟體庫
desktop-view-options = 桌面檢視選項...
show-on-desktop = 顯示在桌面
desktop-folder-content = 桌面資料夾內容
mounted-drives = 已經掛載的磁碟機
trash-folder-icon = 垃圾桶圖示
trashed-on = 丟入時間
icon-size-and-spacing = 圖示大小與間距
icon-size = 圖示大小
grid-spacing = 網格間距
details = 詳細資訊
dismiss = 關閉訊息
delete = 刪除
remove = 移除
support = 支援
cancelled = 已取消
keywords = 資料夾;管理器;
empty-trash-title = 清空垃圾桶?
pause = 暫停
resume = 繼續
extract-password-required = 需要密碼
extract-to = 解壓縮至...
extract-to-title = 解壓縮至資料夾
mount-error = 無法存取磁碟機
open-with-title = 您要如何開啟「{ $name }」?
browse-store = 瀏覽 { $store }
other-apps = 其他應用程式
related-apps = 相關應用程式
permanently-delete-question = 永久刪除?
set-executable-and-launch = 設定為可以執行並啟動
read-only = 唯讀
read-execute = 讀取和執行
read-write = 讀取和寫入
read-write-execute = 讀取、寫入和執行
favorite-path-error = 開啟目錄時發生錯誤
set-executable-and-launch-description = 您是否要將「{ $name }」設為可執行並啟動它?
set-and-launch = 設定並啟動
none = 無
execute-only = 僅執行
write-only = 僅寫入
write-execute = 寫入和執行
operations-running =
{ $running } { $running ->
[one] 個操作
*[other] 個操作
}正在執行({ $percent }%)...
operations-running-finished =
{ $running } { $running ->
[one] 個操作
*[other] 個操作
}正在執行({ $percent }%), { $finished } 個已經完成...
permanently-delete-warning = 「{ $target }」將被永久刪除。此操作無法復原。
open-with = 開啟檔案
selected-items = 已經選定 { $items } 個項目
copy-to-title = 選擇複製目的地
copy-to-button-label = 複製
move-to-title = 選擇移動目的地
move-to-button-label = 移動
keep = 保留
progress = { $percent }%
progress-cancelled = { $percent }%,已經取消
progress-failed = { $percent }%,失敗
progress-paused = { $percent }%,已經暫停
favorite-path-error-description =
無法開啟「{ $path }」
「{ $path }」可能不存在,或您可能沒有權限開啟它。
您是否要將它從側邊欄移除?
comment = COSMIC 桌面檔案管理器
pasted-image = 已經貼上的圖片
pasted-text = 已經貼上的文字
pasted-video = 已經貼上的影片
sort-by-trashed = 依丟入時間排序
calculating = 計算中...
single-click = 點按以開啟
type-to-search = 輸入進行搜尋
type-to-search-recursive = 搜尋目前資料夾及全部子資料夾
type-to-search-enter-path = 輸入目錄或檔案的目錄
delete-permanently = 永久刪除
eject = 彈出
remove-from-recents = 從最近項目中移除
change-wallpaper = 變更桌布...
desktop-appearance = 桌面外觀...
display-settings = 顯示設定...
reload-folder = 重新載入資料夾
gallery-preview = 圖庫預覽
type = 類型:{ $mime }
items = 項目:{ $items }
item-size = 大小:{ $size }
item-created = 建立時間:{ $created }
item-modified = 修改時間:{ $modified }
item-accessed = 存取時間:{ $accessed }
type-to-search-select = 選取第一個符合條件的檔案或資料夾
copy-to = 複製至...
move-to = 移動至...
show-recents = 側邊欄中的最近使用資料夾
clear-recents-history = 清除最近使用歷史記錄
copy-path = 複製路徑
setting-executable-and-launching = 設定「{ $name }」為可以執行並進行啟動
set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟動
setting-permissions = 設定「{ $name }」的權限至 { $mode }
set-permissions = 設定「{ $name }」的權限至 { $mode }
mixed = 混合
context-action = 環境行動
context-action-confirm-title = 執行「{ $name }」嗎?
context-action-confirm-warning =
該行動將會在 { $items } { $items ->
[one] 項目
*[other] 項目
} 上執行。
run = 執行
rename-confirm = 重新命名
checksum = { $kind } 核對和
calculate = 計算
error = 錯誤
sidebar-label = 側邊欄標籤
search-application = 依應用程式名稱搜尋
change-sidebar-label = 變更側邊欄標籤

View file

@ -12,10 +12,6 @@ cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
bin-src := cargo-target-dir / 'release' / name
bin-dst := base-dir / 'bin' / name
thumbnailer-bin-name := name + '-thumbnailer'
thumbnailer-bin-src := cargo-target-dir / 'release' / thumbnailer-bin-name
thumbnailer-bin-dst := base-dir / 'bin' / thumbnailer-bin-name
applet-name := name + '-applet'
applet-src := cargo-target-dir / 'release' / applet-name
applet-dst := base-dir / 'bin' / applet-name
@ -28,10 +24,6 @@ metainfo := APPID + '.metainfo.xml'
metainfo-src := 'target/xdgen' / metainfo
metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo
thumbnailer := APPID + '.thumbnailer'
thumbnailer-src := 'res' / thumbnailer
thumbnailer-dst := clean(rootdir / prefix) / 'share' / 'thumbnailers' / thumbnailer
icons-src := 'res' / 'icons' / 'hicolor'
icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor'
@ -53,10 +45,13 @@ clean-dist: clean clean-vendor
build-debug *args:
cargo build {{args}}
cargo build --package {{applet-name}} {{args}}
cargo build --package {{thumbnailer-bin-name}} {{args}}
# Compiles with release profile
build-release *args: (build-debug '--release' args)
# Compiles applet with release profile
build-release-applet *args:
cargo build --package {{applet-name}} --release {{args}}
# Compiles release profile with vendored dependencies
build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
@ -97,11 +92,9 @@ heaptrack *args:
# Installs files
install:
install -Dm0755 {{bin-src}} {{bin-dst}}
install -Dm0755 {{thumbnailer-bin-src}} {{thumbnailer-bin-dst}}
install -Dm0755 {{applet-src}} {{applet-dst}}
install -Dm0644 {{desktop-src}} {{desktop-dst}}
install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
install -Dm0644 {{thumbnailer-src}} {{thumbnailer-dst}}
for size in `ls {{icons-src}}`; do \
install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
done
@ -112,13 +105,13 @@ install-applet:
# Uninstalls installed files
uninstall:
rm -f {{bin-dst}} {{thumbnailer-bin-dst}} {{applet-dst}} {{thumbnailer-dst}}
rm -f {{bin-dst}} {{applet-dst}}
# Vendor dependencies locally
vendor:
#!/usr/bin/env bash
mkdir -p .cargo
cargo vendor --locked --sync Cargo.toml | head -n -1 > .cargo/config.toml
cargo vendor --sync Cargo.toml | head -n -1 > .cargo/config.toml
echo 'directory = "vendor"' >> .cargo/config.toml
echo >> .cargo/config.toml
echo '[env]' >> .cargo/config.toml

View file

@ -1,4 +0,0 @@
[Thumbnailer Entry]
TryExec=cosmic-files-thumbnailer
Exec=cosmic-files-thumbnailer %o --size %s %i
MimeType=application/vnd.microsoft.portable-executable;application/x-msdownload;

View file

@ -1,3 +0,0 @@
[toolchain]
channel = "1.93.0"
components = ["clippy", "rustfmt"]

View file

@ -1 +0,0 @@
imports_granularity = "Module"

2092
src/app.rs

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,17 @@
use crate::mime_icon::mime_for_path;
use crate::operation::{Controller, OpReader, OperationError, OperationErrorType};
use crate::{
mime_icon::mime_for_path,
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
};
use chrono::TimeZone;
use chrono::{Datelike, Timelike};
use cosmic::iced::futures;
use jiff::Zoned;
use jiff::civil::DateTime;
use jiff::tz::TimeZone;
use std::collections::HashSet;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::{
collections::HashSet,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
time::SystemTime,
};
use zip::result::ZipError;
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
@ -45,7 +48,7 @@ pub fn extract(
new_dir: &Path,
password: &Option<String>,
controller: &Controller,
) -> Result<(Vec<PathBuf>, HashSet<PathBuf>), OperationError> {
) -> Result<(), OperationError> {
let mime = mime_for_path(path, None, false);
let password = password.as_deref();
match mime.essence_str() {
@ -55,15 +58,13 @@ pub fn extract(
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default())
.map_err(|e| OperationError::from_err(e, controller))?;
}
"application/x-tar" => OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default()),
.map_err(|e| OperationError::from_err(e, controller))?,
"application/zip" => fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
@ -77,7 +78,7 @@ pub fn extract(
OperationError::from_kind(OperationErrorType::PasswordRequired, controller)
}
_ => OperationError::from_err(e, controller),
}),
})?,
#[cfg(feature = "bzip2")]
"application/x-bzip"
| "application/x-bzip-compressed-tar"
@ -87,8 +88,7 @@ pub fn extract(
.map(bzip2::read::BzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default()),
.map_err(|e| OperationError::from_err(e, controller))?,
#[cfg(feature = "lzma-rust2")]
"application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller.clone())
@ -96,14 +96,14 @@ pub fn extract(
.map(|reader| lzma_rust2::XzReader::new(reader, true))
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default())
.map_err(|e| OperationError::from_err(e, controller))?;
}
_ => Err(OperationError::from_err(
format!("unsupported mime type {mime:?}"),
controller,
)),
))?,
}
Ok(())
}
// From https://docs.rs/zip/latest/zip/read/struct.ZipArchive.html#method.extract, with cancellation and progress added
@ -112,9 +112,8 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
directory: P,
password: Option<&str>,
controller: Controller,
) -> zip::result::ZipResult<(Vec<PathBuf>, HashSet<PathBuf>)> {
use std::ffi::OsString;
use std::fs;
) -> zip::result::ZipResult<()> {
use std::{ffi::OsString, fs};
use zip::result::ZipError;
fn make_writable_dir_all<T: AsRef<Path>>(
@ -191,8 +190,6 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
let mut target = Vec::with_capacity(file.size() as usize);
file.read_to_end(&mut target)?;
// File no longer needed, drop to allow reading target on windows
drop(file);
#[cfg(unix)]
{
@ -203,15 +200,11 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
#[cfg(windows)]
{
let Ok(target) = String::from_utf8(target) else {
return Err(ZipError::InvalidArchive(
"Invalid UTF-8 as symlink target".into(),
));
return Err(ZipError::InvalidArchive("Invalid UTF-8 as symlink target"));
};
let target_is_dir_from_archive = match password {
None => archive.by_name(&target),
Some(pwd) => archive.by_name_decrypt(&target, pwd.as_bytes()),
}
.map_or(false, |x| x.is_dir());
let target = target.into_boxed_str();
let target_is_dir_from_archive =
archive.shared.files.contains_key(&target) && is_dir(&target);
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
let target_is_dir = if target_is_dir_from_archive {
true
@ -285,29 +278,32 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
}
}
Ok((written_files, target_dirs))
// Flush files to disk
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
Ok(())
}
fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option<SystemTime> {
let dt = DateTime::new(
date_time.year() as i16,
date_time.month() as i8,
date_time.day() as i8,
date_time.hour() as i8,
date_time.minute() as i8,
date_time.second() as i8,
0,
)
.ok()?;
TimeZone::system()
.to_ambiguous_zoned(dt)
.later()
.ok()
let date = chrono::NaiveDate::from_ymd_opt(
date_time.year() as i32,
date_time.month() as u32,
date_time.day() as u32,
)?;
let time = chrono::NaiveTime::from_hms_opt(
date_time.hour() as u32,
date_time.minute() as u32,
date_time.second() as u32,
)?;
let naive = chrono::NaiveDateTime::new(date, time);
chrono::Local
.from_local_datetime(&naive)
.latest()
.map(SystemTime::from)
}
pub fn system_time_to_zip_date_time(system_time: SystemTime) -> Option<zip::DateTime> {
let date_time = Zoned::try_from(system_time).ok()?;
let date_time: chrono::DateTime<chrono::Local> = system_time.into();
zip::DateTime::from_date_and_time(
date_time.year() as u16,

View file

@ -1,73 +0,0 @@
// Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
/// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
pub fn channel<Message>() -> (Sender<Message>, Receiver<Message>) {
let channel = Arc::new(Channel {
queue: Mutex::new(VecDeque::default()),
notify: tokio::sync::Notify::const_new(),
closed: AtomicBool::new(false),
});
(Sender(channel.clone()), Receiver(channel))
}
/// A channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
struct Channel<Message> {
pub(self) queue: Mutex<VecDeque<Message>>,
/// Set when a new message has been stored.
pub(self) notify: tokio::sync::Notify,
/// Set when the receiver is dropped.
pub(self) closed: AtomicBool,
}
pub struct Sender<Message>(Arc<Channel<Message>>);
impl<Message> Sender<Message> {
pub fn send(&self, message: Message) {
self.0.queue.lock().unwrap().push_back(message);
self.0.notify.notify_one();
}
}
impl<Message> Drop for Sender<Message> {
fn drop(&mut self) {
self.0.closed.store(true, Ordering::SeqCst);
self.0.notify.notify_one();
}
}
pub struct Receiver<Message>(Arc<Channel<Message>>);
impl<Message> Receiver<Message> {
/// Returns a value until the sender is dropped.
pub async fn recv(&self) -> Option<Message> {
loop {
{
let mut queue = self.0.queue.lock().unwrap();
if let Some(value) = queue.pop_front() {
if queue.capacity() - queue.len() > 32 {
let capacity = queue.len().next_power_of_two();
queue.shrink_to(capacity);
}
drop(queue);
return Some(value);
}
}
if self.0.closed.load(Ordering::SeqCst) {
return None;
}
self.0.notify.notified().await;
}
}
pub fn try_recv(&self) -> Option<Message> {
self.0.queue.lock().unwrap().pop_front()
}
}

View file

@ -2,10 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use std::borrow::Cow;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::str;
use std::{
borrow::Cow,
error::Error,
path::{Path, PathBuf},
str,
};
use url::Url;
#[derive(Clone, Copy, Debug)]
@ -126,15 +128,10 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
// Assume the kind is Copy if not provided by the mime type
let mut kind = ClipboardKind::Copy;
let mut paths = Vec::new();
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('#')
}) {
for line in text.lines() {
let url = Url::parse(line)?;
match url.to_file_path() {
Ok(path) => paths.push(path),
@ -144,7 +141,6 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
}
"x-special/gnome-copied-files" => {
let text = str::from_utf8(&data)?;
for (i, line) in text.lines().enumerate() {
if i == 0 {
kind = match line {

View file

@ -1,20 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::any::TypeId;
use std::num::NonZeroU16;
use std::path::PathBuf;
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
use cosmic::cosmic_config::{self, CosmicConfigEntry};
use cosmic::iced::Subscription;
use cosmic::{Application, theme};
use cosmic::{
Application,
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
iced::Subscription,
theme,
};
use serde::{Deserialize, Serialize};
use crate::FxOrderMap;
use crate::app::App;
use crate::tab::{HeadingOptions, Location, View};
pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
use crate::{
FxOrderMap,
app::App,
tab::{HeadingOptions, Location, View},
};
pub const CONFIG_VERSION: u64 = 1;
@ -70,11 +70,6 @@ pub enum Favorite {
name: String,
path: PathBuf,
},
/// A path with a custom name chosen by the user
Named {
path: PathBuf,
name: String,
},
}
impl Favorite {
@ -103,38 +98,6 @@ impl Favorite {
Self::Videos => dirs::video_dir(),
Self::Path(path) => Some(path.clone()),
Self::Network { path, .. } => Some(path.clone()),
Self::Named { path, .. } => Some(path.clone()),
}
}
/// Name shown in the sidebar, or `None` if the path has no usable file name
pub fn display_name(&self) -> Option<String> {
match self {
Self::Home => Some(crate::fl!("home")),
Self::Named { name, .. } | Self::Network { name, .. } => Some(name.clone()),
_ => self
.path_opt()?
.file_name()
.and_then(|x| x.to_str())
.map(ToString::to_string),
}
}
/// Return this favorite with a custom sidebar label chosen by the user
pub fn with_label(&self, name: &str) -> Self {
match self {
Self::Network { uri, path, .. } => Self::Network {
uri: uri.clone(),
name: name.to_string(),
path: path.clone(),
},
other => match other.path_opt() {
Some(path) => Self::Named {
path,
name: name.to_string(),
},
None => other.clone(),
},
}
}
}
@ -150,7 +113,6 @@ pub enum TypeToSearch {
#[serde(default)]
pub struct State {
pub sort_names: FxOrderMap<String, (HeadingOptions, bool)>,
pub dialog_sort_name: (HeadingOptions, bool),
}
impl Default for State {
@ -162,7 +124,6 @@ impl Default for State {
(HeadingOptions::Modified, false),
)
})),
dialog_sort_name: (HeadingOptions::Modified, false),
}
}
}
@ -203,17 +164,11 @@ pub struct Config {
pub app_theme: AppTheme,
pub dialog: DialogConfig,
pub desktop: DesktopConfig,
pub context_actions: Vec<ContextActionPreset>,
pub thumb_cfg: ThumbCfg,
pub favorites: Vec<Favorite>,
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,
}
@ -265,7 +220,6 @@ impl Default for Config {
app_theme: AppTheme::System,
desktop: DesktopConfig::default(),
dialog: DialogConfig::default(),
context_actions: Vec::new(),
thumb_cfg: ThumbCfg::default(),
favorites: vec![
Favorite::Home,
@ -278,97 +232,11 @@ 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 {
@ -515,66 +383,3 @@ pub const TIME_CONFIG_ID: &str = "com.system76.CosmicAppletTime";
pub struct TimeConfig {
pub military_time: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn favorite_with_label_converts_path_to_named() {
let favorite = Favorite::Path(PathBuf::from("/some/dir"));
assert_eq!(
favorite.with_label("Custom"),
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_with_label_updates_network_in_place() {
let favorite = Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "example.com".to_string(),
path: PathBuf::from("/run/mount/example"),
};
assert_eq!(
favorite.with_label("Custom"),
Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "Custom".to_string(),
path: PathBuf::from("/run/mount/example"),
}
);
}
#[test]
fn favorite_with_label_converts_special_folder_to_named() {
let home = dirs::home_dir().unwrap();
assert_eq!(
Favorite::Home.with_label("Custom"),
Favorite::Named {
path: home,
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_display_name() {
assert_eq!(
Favorite::Path(PathBuf::from("/some/dir")).display_name(),
Some("dir".to_string())
);
assert_eq!(Favorite::Path(PathBuf::from("/")).display_name(), None);
assert_eq!(
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
.display_name(),
Some("Custom".to_string())
);
}
}

View file

@ -1,80 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::mime_app;
use crate::spawn_detached::spawn_detached;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum ContextActionSelection {
#[default]
#[serde(alias = "any")]
Any,
#[serde(alias = "files")]
Files,
#[serde(alias = "folders")]
Folders,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default)]
pub struct ContextActionPreset {
pub name: String,
pub confirm: bool,
pub selection: ContextActionSelection,
pub steps: Vec<String>,
}
impl ContextActionPreset {
pub fn matches_selection(&self, selected: usize, selected_dir: usize) -> bool {
if selected == 0 {
return false;
}
match self.selection {
ContextActionSelection::Any => true,
ContextActionSelection::Files => selected_dir == 0,
ContextActionSelection::Folders => selected_dir == selected,
}
}
pub fn run(&self, paths: &[PathBuf]) {
if self.steps.is_empty() {
log::warn!("context action {:?} has no steps", self.name);
return;
}
for step in &self.steps {
let Some(commands) = mime_app::exec_to_command(step, &self.name, None, paths) else {
log::warn!(
"failed to parse context action {:?}: invalid Exec {:?}",
self.name,
step
);
return;
};
for mut command in commands {
if let Err(err) = spawn_detached(&mut command) {
log::warn!(
"failed to run context action {:?} step {:?}: {}",
self.name,
step,
err
);
return;
}
}
}
}
}
pub fn run(actions: &[ContextActionPreset], action: usize, paths: &[PathBuf]) {
if let Some(preset) = actions.get(action) {
preset.run(paths);
} else {
log::warn!("invalid context action index `{action}`");
}
}

View file

@ -1,47 +1,58 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::app::cosmic::Cosmic;
use cosmic::app::{Core, Task, context_drawer};
use cosmic::cosmic_config::ConfigSet;
use cosmic::iced::core::SmolStr;
use cosmic::iced::core::widget::operation;
use cosmic::iced::futures::{self, SinkExt};
use cosmic::iced::keyboard::key::{Named, Physical};
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper};
use cosmic::iced::widget::scrollable;
use cosmic::iced::widget::scrollable::AbsoluteOffset;
use cosmic::iced::{
self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window,
use cosmic::{
Application, ApplicationExt, Element,
app::{Core, Task, context_drawer, cosmic::Cosmic},
cosmic_config, cosmic_theme, executor,
iced::{
self, Alignment, Event, Length, Size, Subscription,
core::SmolStr,
event,
futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named},
mouse, stream,
widget::scrollable,
window,
},
iced_core::widget::operation,
iced_widget::scrollable::AbsoluteOffset,
iced_winit::{self, SurfaceIdWrapper},
theme,
widget::{
self, Operation,
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
segmented_button,
},
};
use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{Action as MenuAction, KeyBind};
use cosmic::widget::{self, Operation, segmented_button};
use cosmic::{Application, ApplicationExt, Element, cosmic_config, cosmic_theme, executor, theme};
use mime_guess::{Mime, mime};
use notify_debouncer_full::notify::{self, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
use notify_debouncer_full::{
DebouncedEvent, Debouncer, RecommendedCache, new_debouncer,
notify::{self, RecommendedWatcher},
};
use recently_used_xbel::update_recently_used;
use rustc_hash::{FxHashMap, FxHashSet};
use std::any::TypeId;
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
use std::time::{self, Instant};
use std::{env, fmt, fs};
use std::{
any::TypeId,
collections::{HashMap, VecDeque},
env, fmt, fs,
path::PathBuf,
time::{self, Instant},
};
use crate::app::{
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
use crate::{
app::{
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
},
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch},
fl, home_dir,
key_bind::key_binds,
localize::LANGUAGE_SORTER,
menu,
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
tab::{self, ItemMetadata, Location, SearchLocation, Tab},
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
};
use crate::config::{
Config, DialogConfig, State, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch,
};
use crate::key_bind::key_binds;
use crate::localize::LANGUAGE_SORTER;
use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage};
use crate::tab::{self, HeadingOptions, ItemMetadata, Location, SearchLocation, Tab};
use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default};
use crate::{fl, home_dir, menu, mime_icon};
#[derive(Clone, Debug)]
pub struct DialogMessage(cosmic::Action<Message>);
@ -192,7 +203,7 @@ impl<T: AsRef<str>> From<T> for DialogLabel {
impl<'a, M: Clone + 'static> From<&'a DialogLabel> for Element<'a, M> {
fn from(label: &'a DialogLabel) -> Self {
let mut iced_spans: Vec<cosmic::iced::core::text::Span<'_, ()>> =
let mut iced_spans: Vec<cosmic::iced_core::text::Span<'_, ()>> =
Vec::with_capacity(label.spans.len());
for span in &label.spans {
iced_spans.push(cosmic::iced::widget::span(&span.text).underline(span.underline));
@ -254,7 +265,6 @@ impl<M: Send + 'static> Dialog<M> {
crate::localize::localize();
let (config_handler, config) = Config::load();
let (state_handler, state) = State::load();
let mut settings = window::Settings {
decorations: false,
@ -289,8 +299,6 @@ impl<M: Send + 'static> Dialog<M> {
window_id,
config_handler,
config,
state_handler,
state,
};
let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags));
@ -445,8 +453,6 @@ struct Flags {
#[allow(dead_code)]
config_handler: Option<cosmic_config::Config>,
config: Config,
state_handler: Option<cosmic_config::Config>,
state: State,
}
/// Messages that are used specifically by our [`App`].
@ -462,7 +468,7 @@ enum Message {
Escape,
Filename(String),
Filter(usize),
Key(Modifiers, Key, Physical, Option<SmolStr>),
Key(Modifiers, Key, Option<SmolStr>),
ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems),
Mouse(window::Id, mouse::Button),
@ -476,12 +482,12 @@ enum Message {
SearchActivate,
SearchClear,
SearchInput(String),
Surface(cosmic::surface::Action<Message>),
Surface(cosmic::surface::Action),
#[allow(clippy::enum_variant_names)]
TabMessage(tab::Message),
TabRescan(
Location,
Option<Box<tab::Item>>,
Option<tab::Item>,
Vec<tab::Item>,
Option<Vec<PathBuf>>,
),
@ -509,7 +515,7 @@ impl From<AppMessage> for Message {
AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn,
AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut,
AppMessage::NewItem(_entity_opt, true) => Self::NewFolder,
AppMessage::Surface(action) => Self::Surface(action.map(Self::from)),
AppMessage::Surface(action) => Self::Surface(action),
unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode");
Self::None
@ -549,6 +555,7 @@ struct App {
title: String,
accept_label: DialogLabel,
choices: Vec<DialogChoice>,
context_menu_window: Option<window::Id>,
context_page: ContextPage,
dialog_pages: VecDeque<DialogPage>,
dialog_text_input: widget::Id,
@ -580,7 +587,7 @@ impl App {
space_s,
space_l,
..
} = theme::spacing();
} = theme::active().cosmic().spacing;
let is_condensed = self.core().is_condensed();
let mut col = widget::column::with_capacity(2).spacing(space_xxs);
@ -588,7 +595,6 @@ impl App {
col = col.push(
widget::text_input("", filename)
.id(self.filename_id.clone())
.double_click_select_delimiter('.')
.on_input(Message::Filename)
.on_submit(|_| Message::Save(false)),
);
@ -711,7 +717,7 @@ impl App {
match (selected.next(), selected.next()) {
// At least two selected items
(Some(_), Some(_)) => Some(self.tab.multi_preview_view(None)),
(Some(_), Some(_)) => Some(self.tab.multi_preview_view()),
// Exactly one selected item
(Some(item), None) => Some(item.preview_view(None, military_time)),
// No selected items
@ -737,17 +743,11 @@ 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, items)) => {
#[cfg(feature = "gvfs")]
let mut items = items;
#[cfg(not(feature = "gvfs"))]
let items = items;
Ok((parent_item_opt, mut items)) => {
#[cfg(feature = "gvfs")]
{
let mounter_paths: Box<[_]> = mounter_items
@ -798,7 +798,7 @@ impl App {
};
search_location.map(|search_location| {
(
return (
Location::Search(
search_location,
term,
@ -806,7 +806,7 @@ impl App {
Instant::now(),
),
true,
)
);
})
}
None => match &self.tab.location {
@ -836,10 +836,9 @@ impl App {
fn update_config(&mut self) -> Task<Message> {
self.core.window.show_context = self.flags.config.dialog.show_details;
let config = self.flags.config.dialog_tab();
self.tab.config.view = config.view;
self.tab.config = self.flags.config.dialog_tab();
self.update_nav_model();
self.update(Message::TabMessage(tab::Message::Config(config)))
self.update(Message::TabMessage(tab::Message::Config(self.tab.config)))
}
fn with_dialog_config<F: Fn(&mut DialogConfig)>(&mut self, f: F) -> Task<Message> {
@ -878,20 +877,31 @@ impl App {
}
}
fn close_context_menus(&mut self) -> Task<Message> {
self.tab.location_context_menu_index = None;
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
Task::none()
}
fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default();
if self.flags.config.show_recents {
nav_model = nav_model.insert(|b| {
b.text(fl!("recents"))
.icon(widget::icon::from_name("document-open-recent-symbolic"))
.data(Location::Recents)
});
}
nav_model = nav_model.insert(|b| {
b.text(fl!("recents"))
.icon(widget::icon::from_name("document-open-recent-symbolic"))
.data(Location::Recents)
});
for favorite in &self.flags.config.favorites {
if let Some(path) = favorite.path_opt() {
let Some(name) = favorite.display_name() else {
let name = if matches!(favorite, Favorite::Home) {
fl!("home")
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
continue;
};
nav_model = nav_model.insert(move |b| {
@ -1041,7 +1051,8 @@ impl Application for App {
None,
);
tab.mode = tab::Mode::Dialog(flags.kind.clone());
(tab.sort_name, tab.sort_direction) = flags.state.dialog_sort_name;
tab.sort_name = tab::HeadingOptions::Modified;
tab.sort_direction = false;
let key_binds = key_binds(&tab.mode);
@ -1051,6 +1062,7 @@ impl Application for App {
title,
accept_label: DialogLabel::from(accept_label),
choices: Vec::new(),
context_menu_window: None,
context_page: ContextPage::Preview(None, PreviewKind::Selected),
dialog_pages: VecDeque::new(),
dialog_text_input: widget::Id::new("Dialog Text Input"),
@ -1110,7 +1122,7 @@ impl Application for App {
}
fn dialog(&self) -> Option<Element<'_, Message>> {
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
//TODO: should gallery view just be a dialog?
if self.tab.gallery {
@ -1194,9 +1206,7 @@ impl Application for App {
.icon(widget::icon::from_name("dialog-question").size(64))
.body(fl!("replace-warning"))
.primary_action(
widget::button::suggested(fl!("replace"))
.on_press(Message::DialogComplete)
.id(REPLACE_BUTTON_ID.clone()),
widget::button::suggested(fl!("replace")).on_press(Message::DialogComplete),
)
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
@ -1321,6 +1331,15 @@ impl Application for App {
return Task::none();
}
if self.tab.location_context_menu_index.is_some() {
self.tab.location_context_menu_index = None;
return Task::none();
}
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
if self.tab.edit_location.is_some() {
// Close location editing if enabled
self.tab.edit_location = None;
@ -1379,10 +1398,7 @@ impl Application for App {
Message::Config(config) => {
if config != self.flags.config {
log::info!("update config");
// Don't overwrite military time
let military_time = self.flags.config.tab.military_time;
self.flags.config = config;
self.flags.config.tab.military_time = military_time;
return self.update_config();
}
}
@ -1435,16 +1451,16 @@ impl Application for App {
}
return self.rescan_tab(None);
}
Message::Key(modifiers, key, physical_key, text) => {
Message::Key(modifiers, key, text) => {
for (key_bind, action) in &self.key_binds {
if key_bind.matches(modifiers, &key, Some(&physical_key)) {
if key_bind.matches(modifiers, &key) {
return self.update(Message::from(action.message()));
}
}
// Check key binds from accept label
if let Some(key_bind) = &self.accept_label.key_bind_opt
&& key_bind.matches(modifiers, &key, Some(&physical_key))
&& key_bind.matches(modifiers, &key)
{
return self.update(if self.flags.kind.save() {
Message::Save(false)
@ -1555,7 +1571,7 @@ impl Application for App {
Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) {
return Task::none();
return self.close_context_menus();
}
}
Message::NewFolder => {
@ -1715,7 +1731,7 @@ impl Application for App {
)));
}
Message::SearchActivate => {
let mut tasks = vec![];
let mut tasks = vec![self.close_context_menus()];
if self.search_get().is_none() {
tasks.push(self.search_set(Some(String::new())));
@ -1726,7 +1742,7 @@ impl Application for App {
return Task::batch(tasks);
}
Message::SearchClear => {
return self.search_set(None);
return Task::batch([self.close_context_menus(), self.search_set(None)]);
}
Message::SearchInput(input) => {
return self.search_set(Some(input));
@ -1762,9 +1778,76 @@ impl Application for App {
self.rescan_tab(selection_paths),
]));
}
tab::Command::Surface(action) => {
let action = action.map(Message::TabMessage);
commands.push(self.update(Message::Surface(action)));
tab::Command::ContextMenu(point_opt, parent_id) => {
#[cfg(feature = "wayland")]
match point_opt {
Some(point) => {
if crate::is_wayland() {
// Open context menu
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
Anchor, Gravity,
};
use cosmic::iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use cosmic::iced::Rectangle;
let window_id = window::Id::unique();
self.context_menu_window = Some(window_id);
let autosize_id = widget::Id::unique();
commands.push(self.update(Message::Surface(
cosmic::surface::action::app_popup(
move |app: &mut Self| -> SctkPopupSettings {
let anchor_rect = Rectangle {
x: point.x as i32,
y: point.y as i32,
width: 1,
height: 1,
};
let positioner = SctkPositioner {
size: None,
anchor_rect,
anchor: Anchor::None,
gravity: Gravity::BottomRight,
reactive: true,
..Default::default()
};
SctkPopupSettings {
parent: parent_id
.unwrap_or(app.flags.window_id),
id: window_id,
positioner,
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
}
},
Some(Box::new(move |app: &Self| {
widget::autosize::autosize(
menu::context_menu(
&app.tab,
&app.key_binds,
&app.modifiers,
false, // Paste not used in dialogs
)
.map(Message::TabMessage)
.map(cosmic::Action::App),
autosize_id.clone(),
)
.into()
})),
),
)));
}
}
None => {
if let Some(window_id) = self.context_menu_window.take() {
commands.push(self.update(Message::Surface(
cosmic::surface::action::destroy_popup(window_id),
)));
}
}
}
}
tab::Command::Iced(iced_command) => {
commands.push(iced_command.0.map(|tab_message| {
@ -1799,17 +1882,6 @@ impl Application for App {
self.auto_scroll_speed = None;
}
}
tab::Command::SetSort(_, heading_options, direction) => {
self.flags.state.dialog_sort_name = (heading_options, direction);
if let Some(state_handler) = self.flags.state_handler.as_ref()
&& let Err(err) = state_handler.set::<&(HeadingOptions, bool)>(
"dialog_sort_name",
&self.flags.state.dialog_sort_name,
)
{
log::warn!("Failed to save dialog sort name: {err:?}");
}
}
unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode");
}
@ -1847,13 +1919,11 @@ impl Application for App {
items.retain(|item| {
// Directories are always shown
item.metadata.is_dir()
// Check for mime type match (first because it is faster)
|| mimes.iter().any(|filter_mime| {
if filter_mime.subtype() == mime::STAR {
filter_mime.type_() == item.mime.type_()
} else {
*filter_mime == item.mime
|| mime_icon::is_mime_subclass_of(&item.mime, filter_mime)
}
})
// Check for glob match (last because it is slower)
@ -1882,16 +1952,6 @@ impl Application for App {
if self.search_get().is_some() {
return widget::text_input::focus(self.search_id.clone());
}
if let DialogKind::SaveFile { filename } = &self.flags.kind {
return Task::batch([
widget::text_input::focus(self.filename_id.clone()),
widget::text_input::select_until_last(
self.filename_id.clone(),
filename,
'.',
),
]);
}
return widget::text_input::focus(self.filename_id.clone());
}
}
@ -1930,7 +1990,9 @@ impl Application for App {
});
}
Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Surface(action));
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
}
}
@ -1939,7 +2001,7 @@ impl Application for App {
/// Creates a view after each update.
fn view(&self) -> Element<'_, Message> {
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
let mut col = widget::column::with_capacity(2);
@ -1960,7 +2022,7 @@ impl Application for App {
col = col.push(
self.tab
.view(&self.key_binds, &self.modifiers, false, &[])
.view(&self.key_binds, &self.modifiers, false)
.map(Message::TabMessage),
);
@ -1978,14 +2040,11 @@ impl Application for App {
},
Event::Keyboard(KeyEvent::KeyPressed {
key,
physical_key,
modifiers,
text,
..
}) => match status {
event::Status::Ignored => {
Some(Message::Key(modifiers, key, physical_key, text))
}
event::Status::Ignored => Some(Message::Key(modifiers, key, text)),
event::Status::Captured => {
if key == Key::Named(Named::Escape) {
Some(Message::Escape)

View file

@ -1,10 +1,11 @@
use cosmic::iced::core::keyboard::key::Named;
use cosmic::iced::keyboard::Key;
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
use cosmic::{
iced::keyboard::Key,
iced_core::keyboard::key::Named,
widget::menu::key_bind::{KeyBind, Modifier},
};
use std::collections::HashMap;
use crate::app::Action;
use crate::tab;
use crate::{app::Action, tab};
//TODO: load from config
pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
@ -30,16 +31,12 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
bind!([], Key::Named(Named::F5), Reload);
bind!([], Key::Named(Named::Home), SelectFirst);
bind!([], Key::Named(Named::End), SelectLast);
bind!([], Key::Named(Named::PageDown), ItemPageDown);
bind!([], Key::Named(Named::PageUp), ItemPageUp);
bind!([Shift], Key::Named(Named::ArrowDown), ItemDown);
bind!([Shift], Key::Named(Named::ArrowLeft), ItemLeft);
bind!([Shift], Key::Named(Named::ArrowRight), ItemRight);
bind!([Shift], Key::Named(Named::ArrowUp), ItemUp);
bind!([Shift], Key::Named(Named::Home), SelectFirst);
bind!([Shift], Key::Named(Named::End), SelectLast);
bind!([Shift], Key::Named(Named::PageDown), ItemPageDown);
bind!([Shift], Key::Named(Named::PageUp), ItemPageUp);
bind!([Ctrl, Shift], Key::Character("n".into()), NewFolder);
bind!([], Key::Named(Named::Enter), Open);
bind!([Ctrl], Key::Character(" ".into()), Preview);

View file

@ -1,7 +1,9 @@
use cosmic::widget;
use image::ImageReader;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
};
/// Bytes per pixel in RGBA format (Red, Green, Blue, Alpha = 4 bytes)
pub const RGBA_BYTES_PER_PIXEL: u64 = 4;

View file

@ -1,23 +1,15 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::app::Settings;
use cosmic::iced::Limits;
use std::path::PathBuf;
use std::{env, fs, process};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use crate::app::{App, Flags};
use crate::config::{Config, State};
use crate::tab::Location;
use cosmic::{app::Settings, iced::Limits};
use std::{env, fs, path::PathBuf, process};
use app::{App, Flags};
pub mod app;
mod archive;
pub mod channel;
pub mod clipboard;
use config::Config;
pub mod config;
mod context_action;
pub mod dialog;
mod key_bind;
pub(crate) mod large_image;
@ -30,15 +22,16 @@ mod mounter;
mod mouse_area;
pub mod operation;
mod spawn_detached;
use tab::Location;
mod zoom;
use crate::config::State;
pub mod tab;
mod thumbnail_cacher;
mod thumbnailer;
pub(crate) mod trash;
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()
}
@ -79,22 +72,7 @@ pub fn is_wayland() -> bool {
/// Runs application in desktop mode
#[rustfmt::skip]
pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
let log_format = tracing_subscriber::fmt::format()
.pretty()
.without_time()
.with_line_number(true)
.with_file(true)
.with_target(false)
.with_thread_names(true);
let log_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(std::io::stderr)
.event_format(log_format);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
.with(log_layer)
.init();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
localize::localize();
@ -129,21 +107,7 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
/// Runs application with these settings
#[rustfmt::skip]
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let log_format = tracing_subscriber::fmt::format()
.pretty()
.with_line_number(true)
.with_file(true)
.with_target(false)
.with_thread_names(true);
let log_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(std::io::stderr)
.event_format(log_format);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(log_layer)
.init();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
localize::localize();
@ -193,7 +157,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
}
if daemonize {
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
#[cfg(all(unix, not(target_os = "redox")))]
match fork::daemon(true, true) {
Ok(fork::Fork::Child) => (),
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),

View file

@ -1,10 +1,11 @@
use cosmic::iced::{core as iced_core, widget as iced_widget};
use cosmic::{iced_core, iced_widget};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
renderer,
};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub fn loaded_image<'a, Message: 'static, Theme>(
handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle,

View file

@ -1,10 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
use icu::collator::options::CollatorOptions;
use icu::collator::preferences::CollationNumericOrdering;
use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences};
use i18n_embed::{
DefaultLocalizer, LanguageLoader, Localizer,
fluent::{FluentLanguageLoader, fluent_language_loader},
};
use icu::collator::{
Collator, CollatorBorrowed, CollatorPreferences, options::CollatorOptions,
preferences::CollationNumericOrdering,
};
use icu::locale::Locale;
use rust_embed::RustEmbed;
use std::sync::LazyLock;

View file

@ -6,6 +6,5 @@ use tikv_jemallocator::Jemalloc;
static GLOBAL: Jemalloc = Jemalloc;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = jxl_oxide::integration::register_image_decoding_hook();
cosmic_files::main()
}

View file

@ -1,28 +1,48 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{Element, theme};
use cosmic::app::Core;
use cosmic::iced::keyboard::Modifiers;
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
use cosmic::widget::{self, responsive_menu_bar};
use cosmic::{
Element,
app::Core,
iced::{
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
keyboard::Modifiers,
},
theme,
widget::{
self, Row, button, column, container, divider,
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
responsive_menu_bar, space, text,
},
};
use i18n_embed::LanguageLoader;
use mime_guess::Mime;
use std::collections::HashMap;
use std::sync::LazyLock;
use std::{collections::HashMap, sync::LazyLock};
use crate::app::{Action, Message};
use crate::config::{Config, ContextActionPreset};
use crate::fl;
use crate::tab::{
self, HeadingOptions, ItemMetadata, Location, LocationMenuAction, SearchLocation, Tab,
use crate::{
app::{Action, Message},
config::Config,
fl,
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
};
use crate::trash::{Trash, TrashExt};
static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
macro_rules! menu_button {
($($x:expr),+ $(,)?) => (
button::custom(
Row::with_children(
[$(Element::from($x)),+]
)
.height(Length::Fixed(24.0))
.align_y(Alignment::Center)
)
.padding([theme::active().cosmic().spacing.space_xxs, 16])
.width(Length::Fill)
.class(theme::Button::MenuItem)
);
}
const fn menu_button_optional(
label: String,
action: Action,
@ -35,46 +55,72 @@ const fn menu_button_optional(
}
}
/// A menu action dispatched to the tab the menu was opened on.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TabAction(pub Action);
impl MenuAction for TabAction {
type Message = tab::Message;
fn message(&self) -> tab::Message {
tab::Message::ContextAction(self.0)
}
}
pub fn context_menu<'a>(
tab: &Tab,
key_binds: &HashMap<KeyBind, Action>,
modifiers: &Modifiers,
clipboard_paste_available: bool,
context_actions: &[ContextActionPreset],
) -> Vec<menu::Tree<tab::Message>> {
let menu_item =
|label: String, action: Action| menu::Item::Button(label, None, TabAction(action));
let menu_item_disabled =
|label: String, action: Action| menu::Item::ButtonDisabled(label, None, TabAction(action));
) -> Element<'a, tab::Message> {
let find_key = |action: &Action| -> String {
for (key_bind, key_action) in key_binds {
if action == key_action {
return key_bind.to_string();
}
}
String::new()
};
fn key_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
}
}
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
color.alpha *= 0.5;
TextStyle {
color: Some(color.into()),
}
}
let menu_item = |label, action| {
let key = find_key(&action);
menu_button!(
text::body(label),
space::horizontal(),
text::body(key).class(theme::Text::Custom(key_style))
)
.on_press(tab::Message::ContextAction(action))
};
let menu_item_disabled = |label, action: Action| {
let key = find_key(&action);
menu_button!(
text::body(label).class(theme::Text::Custom(disabled_style)),
space::horizontal(),
text::body(key).class(theme::Text::Custom(disabled_style))
)
};
// Allow paste when clipboard has data and we're in a location that supports it
let can_paste = clipboard_paste_available && tab.location.supports_paste();
let (sort_name, sort_direction, _) = tab.sort_options();
let sort_item = |label: String, variant| {
let entry = menu::Entry::new(label, TabAction(Action::ToggleSort(variant)));
menu::Item::Entry(if sort_name == variant {
let icon_name = if sort_direction {
"view-sort-ascending-symbolic"
} else {
"view-sort-descending-symbolic"
};
entry.icon(widget::icon::from_name(icon_name).size(14).handle())
} else {
entry.reserve_icon()
})
let sort_item = |label, variant| {
menu_item(
format!(
"{} {}",
label,
match (sort_name == variant, sort_direction) {
(true, true) => "\u{2B07}",
(true, false) => "\u{2B06}",
_ => "",
}
),
Action::ToggleSort(variant),
)
.into()
};
let mut selected_dir = 0;
@ -83,7 +129,6 @@ pub fn context_menu<'a>(
let mut selected_desktop_entry = None;
let mut selected_types: Vec<Mime> = vec![];
let mut selected_mount_point = 0;
let mut any_trash_item = false;
if let Some(items) = tab.items_opt() {
for item in items {
if item.selected {
@ -96,17 +141,15 @@ pub fn context_menu<'a>(
Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => {
selected_trash_only = true
}
Some(Location::Path(path))
Some(Location::Path(path)) => {
if selected == 1
&& path.extension().and_then(|s| s.to_str()) == Some("desktop") =>
{
selected_desktop_entry = Some(&**path);
&& path.extension().and_then(|s| s.to_str()) == Some("desktop")
{
selected_desktop_entry = Some(&**path);
}
}
_ => (),
}
if matches!(&item.metadata, ItemMetadata::Trash { .. }) {
any_trash_item = true;
}
selected_types.push(item.mime.clone());
}
}
@ -114,14 +157,6 @@ pub fn context_menu<'a>(
selected_types.sort_unstable();
selected_types.dedup();
selected_trash_only = selected_trash_only && selected == 1;
let context_action_items = |selected: usize, selected_dir: usize| {
context_actions
.iter()
.enumerate()
.filter(|(_, action)| action.matches_selection(selected, selected_dir))
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)))
.collect::<Vec<_>>()
};
// Parse the desktop entry if it is the only selection
#[cfg(feature = "desktop")]
let selected_desktop_entry = selected_desktop_entry.and_then(|path| {
@ -135,7 +170,7 @@ pub fn context_menu<'a>(
}
});
let mut children: Vec<menu::Item<TabAction, String>> = Vec::new();
let mut children: Vec<Element<_>> = Vec::new();
match (&tab.mode, &tab.location) {
(
tab::Mode::App | tab::Mode::Desktop,
@ -147,175 +182,142 @@ pub fn context_menu<'a>(
| Location::Network(_, _, Some(_)),
) => {
if selected_trash_only {
children.push(menu_item(fl!("open"), Action::Open));
if !Trash::is_empty() {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash));
children.push(menu_item(fl!("open"), Action::Open).into());
if !trash::os_limited::is_empty().unwrap_or(true) {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
}
} else if let Some(entry) = selected_desktop_entry {
children.push(menu_item(fl!("open"), Action::Open));
children.push(menu_item(fl!("open"), Action::Open).into());
#[cfg(feature = "desktop")]
{
children.extend(
entry
.desktop_actions
.into_iter()
.enumerate()
.map(|(i, action)| menu_item(action.name, Action::ExecEntryAction(i))),
);
children.extend(entry.desktop_actions.into_iter().enumerate().map(
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
));
}
children.push(menu_item(fl!("edit-desktop-entry"), Action::EditDesktopEntry));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("rename"), Action::Rename));
children.push(menu_item(fl!("cut"), Action::Cut));
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath));
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
} else {
children.push(menu_item(fl!("copy"), Action::Copy));
children.push(menu_item(fl!("copy"), Action::Copy).into());
}
// Should this simply bypass trash and remove the shortcut?
children.push(menu_item(fl!("move-to-trash"), Action::Delete));
let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() {
children.push(menu::Item::Divider);
children.extend(action_items);
}
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
} else if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open));
children.push(menu_item(fl!("open"), Action::Open).into());
}
if selected == 1 {
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith));
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith).into());
if selected_dir == 1 {
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
children
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
}
}
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
children.push(menu_item(
fl!("open-item-location"),
Action::OpenItemLocation,
));
if tab.location.is_recents() {
children.push(
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
);
}
// All selected items are directories
if selected == selected_dir && matches!(tab.mode, tab::Mode::App) {
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab));
children.push(menu_item(
fl!("open-in-new-window"),
Action::OpenInNewWindow,
));
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
children
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
}
let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() {
children.push(menu::Item::Divider);
children.extend(action_items);
}
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
if selected_mount_point == 0 {
children.push(menu_item(fl!("rename"), Action::Rename));
children.push(menu_item(fl!("cut"), Action::Cut));
children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
}
if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath));
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
} else {
children.push(menu_item(fl!("copy"), Action::Copy));
children.push(menu_item(fl!("copy"), Action::Copy).into());
}
if selected_mount_point == 0 {
children.push(menu_item(fl!("move-to"), Action::MoveTo));
children.push(menu_item(fl!("move-to"), Action::MoveTo).into());
}
children.push(menu_item(fl!("copy-to"), Action::CopyTo));
children.push(menu_item(fl!("copy-to"), Action::CopyTo).into());
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES;
selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m));
if selected_types.is_empty() {
children.push(menu_item(fl!("extract-here"), Action::ExtractHere));
children.push(menu_item(fl!("extract-to"), Action::ExtractTo));
children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into());
children.push(menu_item(fl!("extract-to"), Action::ExtractTo).into());
}
children.push(menu_item(fl!("compress"), Action::Compress));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("compress"), Action::Compress).into());
children.push(divider::horizontal::light().into());
//TODO: Print?
children.push(menu_item(fl!("show-details"), Action::Preview));
if any_trash_item {
children.push(menu::Item::Divider);
children.push(menu_item(
fl!("restore-from-trash"),
Action::RestoreFromTrash,
));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
} else {
if matches!(tab.mode, tab::Mode::App) {
children.push(menu::Item::Divider);
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar));
}
children.push(menu::Item::Divider);
if tab.location.is_recents() {
children.push(menu_item(
fl!("remove-from-recents"),
Action::RemoveFromRecents,
));
children.push(menu::Item::Divider);
}
if selected_mount_point == 0 {
if modifiers.shift() && !modifiers.control() {
children.push(
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete)
.into(),
);
} else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete));
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject));
children.push(menu_item(fl!("show-details"), Action::Preview).into());
if matches!(tab.mode, tab::Mode::App) {
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
}
children.push(divider::horizontal::light().into());
if tab.location.is_recents() {
children.push(
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
);
children.push(divider::horizontal::light().into());
}
if selected_mount_point == 0 {
if modifiers.shift() && !modifiers.control() {
children.push(
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(),
);
} else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject).into());
}
} else {
//TODO: need better designs for menu with no selection
//TODO: have things like properties but they apply to the folder?
if tab.location != Location::Recents {
children.push(menu_item(fl!("new-folder"), Action::NewFolder));
children.push(menu_item(fl!("new-file"), Action::NewFile));
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
children.push(menu_item(fl!("new-file"), Action::NewFile).into());
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
children.push(divider::horizontal::light().into());
}
if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll));
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
}
if can_paste {
children.push(menu_item(fl!("paste"), Action::Paste));
children.push(menu_item(fl!("paste"), Action::Paste).into());
} else {
children.push(menu_item_disabled(fl!("paste"), Action::Paste));
children.push(menu_item_disabled(fl!("paste"), Action::Paste).into());
}
//TODO: only show if cosmic-settings is found?
if matches!(tab.mode, tab::Mode::Desktop) {
children.push(menu::Item::Divider);
children.push(menu_item(
fl!("change-wallpaper"),
Action::CosmicSettingsWallpaper,
));
children.push(menu_item(
fl!("desktop-appearance"),
Action::CosmicSettingsDesktop,
));
children.push(menu_item(
fl!("display-settings"),
Action::CosmicSettingsDisplays,
));
children.push(divider::horizontal::light().into());
children.push(
menu_item(fl!("change-wallpaper"), Action::CosmicSettingsWallpaper).into(),
);
children.push(
menu_item(fl!("desktop-appearance"), Action::CosmicSettingsDesktop).into(),
);
children.push(
menu_item(fl!("display-settings"), Action::CosmicSettingsDisplays).into(),
);
}
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
// TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
if matches!(tab.location, Location::Desktop(..)) {
children.push(menu::Item::Divider);
children.push(menu_item(
fl!("desktop-view-options"),
Action::DesktopViewOptions,
));
children.push(divider::horizontal::light().into());
children.push(
menu_item(fl!("desktop-view-options"), Action::DesktopViewOptions).into(),
);
}
}
}
@ -330,25 +332,24 @@ pub fn context_menu<'a>(
) => {
if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open));
children.push(menu_item(fl!("open"), Action::Open).into());
}
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
children.push(menu_item(
fl!("open-item-location"),
Action::OpenItemLocation,
));
children.push(
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
);
}
children.push(menu::Item::Divider);
children.push(menu_item(fl!("show-details"), Action::Preview));
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("show-details"), Action::Preview).into());
} else {
if dialog_kind.save() {
children.push(menu_item(fl!("new-folder"), Action::NewFolder));
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
}
if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll));
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
}
if !children.is_empty() {
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
}
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -358,14 +359,14 @@ pub fn context_menu<'a>(
(_, Location::Network(..)) => {
if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open));
children.push(menu_item(fl!("open"), Action::Open).into());
}
} else {
if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll));
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
}
if !children.is_empty() {
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
}
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -374,26 +375,18 @@ pub fn context_menu<'a>(
}
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll));
}
if !Trash::is_empty() {
if !children.is_empty() {
children.push(menu::Item::Divider);
}
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash));
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
}
if !children.is_empty() {
children.push(menu::Item::Divider);
children.push(divider::horizontal::light().into());
}
if selected > 0 {
children.push(menu_item(fl!("show-details"), Action::Preview));
children.push(menu::Item::Divider);
children.push(menu_item(
fl!("restore-from-trash"),
Action::RestoreFromTrash,
));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
children.push(menu_item(fl!("show-details"), Action::Preview).into());
children.push(divider::horizontal::light().into());
children
.push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into());
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
} else {
// TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
@ -403,11 +396,26 @@ pub fn context_menu<'a>(
}
}
let key_binds: HashMap<KeyBind, TabAction> = key_binds
.iter()
.map(|(key_bind, action)| (key_bind.clone(), TabAction(*action)))
.collect();
menu::items(&key_binds, children)
container(column::with_children(children))
.padding(1)
//TODO: move style to libcosmic
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
}
pub fn dialog_menu(
@ -551,7 +559,7 @@ pub fn dialog_menu(
])
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360))
.spacing(theme::spacing().space_xxxs.into())
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
.into()
}
@ -606,7 +614,7 @@ pub fn menu_bar<'a>(
responsive_menu_bar()
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360))
.spacing(theme::spacing().space_xxxs.into())
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
.into_element(
core,
key_binds,
@ -756,33 +764,50 @@ pub fn menu_bar<'a>(
)
}
pub fn location_context_menu(ancestor_index: usize) -> Vec<menu::Tree<tab::Message>> {
pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> {
//TODO: only add some of these when in App mode
menu::items(
&HashMap::new(),
vec![
menu::Item::Button(
fl!("open-in-new-tab"),
None,
let children = [
menu_button!(text::body(fl!("open-in-new-tab")))
.on_press(tab::Message::LocationMenuAction(
LocationMenuAction::OpenInNewTab(ancestor_index),
),
menu::Item::Button(
fl!("open-in-new-window"),
None,
))
.into(),
menu_button!(text::body(fl!("open-in-new-window")))
.on_press(tab::Message::LocationMenuAction(
LocationMenuAction::OpenInNewWindow(ancestor_index),
),
menu::Item::Divider,
menu::Item::Button(
fl!("show-details"),
None,
))
.into(),
divider::horizontal::light().into(),
menu_button!(text::body(fl!("show-details")))
.on_press(tab::Message::LocationMenuAction(
LocationMenuAction::Preview(ancestor_index),
),
menu::Item::Divider,
menu::Item::Button(
fl!("add-to-sidebar"),
None,
))
.into(),
divider::horizontal::light().into(),
menu_button!(text::body(fl!("add-to-sidebar")))
.on_press(tab::Message::LocationMenuAction(
LocationMenuAction::AddToSidebar(ancestor_index),
),
],
)
))
.into(),
];
container(column::with_children(children))
.padding(1)
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
}

View file

@ -1,171 +1,155 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use bstr::{BString, ByteSlice, ByteVec};
#[cfg(feature = "desktop")]
use cosmic::desktop;
use cosmic::widget;
pub use mime_guess::Mime;
#[cfg(feature = "desktop")]
use notify_debouncer_full::notify;
use rustc_hash::{FxHashMap, FxHashSet};
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock, atomic};
use std::time::{self, Instant};
use std::{fs, io, process};
use rustc_hash::FxHashMap;
use std::{
cmp::Ordering,
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
process,
time::Instant,
};
#[cfg(feature = "desktop")]
pub async fn watch(mut emitter: impl FnMut() + 'static + Send) {
let watcher_result = notify_debouncer_full::new_debouncer(
time::Duration::from_millis(250),
Some(time::Duration::from_millis(250)),
move |event_res: notify_debouncer_full::DebounceEventResult| {
let Ok(events) = event_res else {
return;
};
if events.iter().any(|event| {
event.kind.is_create() || event.kind.is_modify() || event.kind.is_remove()
}) {
emitter();
}
},
);
if let Ok(mut watcher) = watcher_result {
let system_paths = cosmic_mime_apps::list_paths();
let local_paths = (|| {
let base_dirs = xdg::BaseDirectories::new();
let Some(home) = base_dirs.get_config_home() else {
return Err(std::io::Error::other("XDG config home not set"));
};
let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") else {
return Err(std::io::Error::other("XDG_CURRENT_DESKTOP unset"));
};
let default_mimeapps = home.join("mimeapps.list");
let desktop_mimeapps =
home.join([&desktop.to_ascii_lowercase(), "-mimeapps.list"].concat());
Ok([desktop_mimeapps, default_mimeapps])
})()
.ok();
for path in system_paths
.iter()
.chain(local_paths.as_ref().into_iter().flatten())
{
_ = watcher.watch(path.as_path(), notify::RecursiveMode::NonRecursive);
}
std::future::pending().await
}
}
// Supported exec key field codes
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"];
// Deprecated field codes. The spec advises to ignore these handlers.
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"];
pub fn exec_to_command(
exec: &str,
entry_name: &str,
entry_path: Option<&Path>,
path_opt: &[impl AsRef<OsStr>],
) -> Option<Vec<process::Command>> {
let arguments = shlex::split(exec)?;
let args_vec = shlex::split(exec)?;
let program = args_vec.first()?;
// Skip program to make indexing easier
let args_vec = &args_vec[1..];
if arguments.is_empty() {
tracing::error!("command does not contain any arguments");
return None;
}
let mut commands = Vec::new();
let paths = path_opt
// Base Command instance(s)
// 1. We may need to launch multiple of the same process.
// 2. Each of those processes will need to be passed args from exec.
// 3. Each of those args may appear in any order.
// 4. Arg order should be preserved.
//
// So, we'll go through exec in two passes. The first pass handles paths (%f etc) and args up
// to the field code followed by the second which passes extra, non-% args to each processes.
//
// While it'd be marginally faster to process everything in one pass, that's problematic:
// 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck
// doesn't know we'll only use it once)
// 2. We have to keep track of which modifier (%f etc) we've used/seen already
// 3. We have to keep track of which processes received non-modifier args which gets messy fast
// 4. `exec` is likely small so looping over it twice is not a big deal
let field_code_pos = args_vec
.iter()
.map(AsRef::as_ref)
.map(Some)
// Add a single `None` if no path was given.
.chain(std::iter::repeat_n(
None,
if path_opt.is_empty() { 1 } else { 0 },
));
.position(|arg| EXEC_HANDLERS.contains(&arg.as_str()));
let args_handler = field_code_pos.and_then(|i| args_vec.get(i));
// msrv
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}"));
// Number of args before the field code.
// This won't be an off by one err below because take is not zero indexed.
let field_code_pos = field_code_pos.unwrap_or_default();
let mut processes = match args_handler.map(String::as_str) {
Some("%f") => {
let mut processes = Vec::with_capacity(path_opt.len());
for path in paths {
let mut batch_process = false;
let mut args = Vec::with_capacity(arguments.len());
let mut field_code_used = false;
for argument in arguments.iter().skip(1) {
let mut new_argument = BString::new(Vec::with_capacity(argument.capacity()));
let mut chars = argument.chars();
while let Some(char) = chars.next() {
// https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html
if char == '%' {
match chars.next() {
Some('%') => new_argument.push_char(char),
Some('c') => new_argument.push_str(entry_name),
Some('k') => {
if let Some(path) = entry_path {
new_argument.push_str(path.as_os_str().as_bytes());
}
}
// %f and %u behave the same in a file manager.
Some('f' | 'u') => {
if let Some(path) = path
&& !field_code_used
{
// TODO: files on remote file systems should be copied to a temporary local file.
batch_process = true;
field_code_used = true;
new_argument.push_str(path.as_bytes());
}
}
// %F and %U behave the same in a file manager.
Some('F') | Some('U') => {
if !field_code_used && new_argument.is_empty() {
field_code_used = true;
for path in path_opt.iter().map(AsRef::as_ref) {
args.push(BString::new(path.as_bytes().to_owned()));
}
}
}
_ => (),
}
} else {
new_argument.push_char(char);
for path in path_opt.iter().map(AsRef::as_ref) {
// TODO: %f and %F need to handle non-file URLs (see spec)
if from_file_or_dir(path).is_none() {
log::warn!("Desktop file expects a file path instead of a URL: {path:?}");
}
// Passing multiple paths to %f should open an instance per path
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(AsRef::as_ref)
.take(field_code_pos)
.chain(std::iter::once(path)),
);
processes.push(process);
}
if !new_argument.is_empty() {
args.push(new_argument);
}
processes
}
Some("%F") => {
// TODO: %f and %F need to handle non-file URLs (see spec)
for invalid in path_opt
.iter()
.map(AsRef::as_ref)
.filter(|&path| from_file_or_dir(path).is_none())
{
log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}");
}
let mut command = process::Command::new(&arguments[0]);
// Launch one instance with all args
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(path_opt.iter().map(AsRef::as_ref)),
);
for arg in args {
match arg.to_os_str() {
Ok(arg) => {
command.arg(arg);
}
Err(_) => {
tracing::error!("invalid string encoding in command");
vec![process]
}
Some("%u") => path_opt
.iter()
.map(|path| {
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(std::iter::once(path.as_ref())),
);
process
})
.collect(),
Some("%U") => {
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(path_opt.iter().map(AsRef::as_ref)),
);
vec![process]
}
Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"),
None => vec![process::Command::new(program)],
};
// Pass 2: Add remaining arguments that are not % to each process
for arg in args_vec.iter().skip(field_code_pos) {
match arg.as_str() {
// Consume path field codes or fail on codes we don't handle yet
field_code if arg.starts_with('%') => {
if !EXEC_HANDLERS.contains(&field_code)
&& !DEPRECATED_HANDLERS.contains(&field_code)
{
log::warn!("unsupported Exec code {field_code:?} in {exec:?}");
return None;
}
}
}
commands.push(command);
if !batch_process {
break;
arg => {
for process in &mut processes {
process.arg(arg);
}
}
}
}
#[cfg(debug_assertions)]
for command in &commands {
for command in &processes {
log::debug!(
"Parsed program {} with args: {:?}",
command.get_program().to_string_lossy(),
@ -173,14 +157,13 @@ pub fn exec_to_command(
);
}
Some(commands)
Some(processes)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MimeAppMatch {
Exact,
Related,
Other,
fn from_file_or_dir(path: impl AsRef<Path>) -> Option<url::Url> {
url::Url::from_file_path(&path)
.ok()
.or_else(|| url::Url::from_directory_path(&path).ok())
}
#[derive(Clone, Debug)]
@ -189,42 +172,14 @@ pub struct MimeApp {
pub path: Option<PathBuf>,
pub name: String,
pub exec: Option<String>,
icon_name: Box<str>,
icon: std::sync::OnceLock<widget::icon::Handle>,
is_default: Arc<RwLock<FxHashSet<Box<str>>>>,
no_display: Arc<AtomicBool>,
pub icon: widget::icon::Handle,
pub is_default: bool,
}
impl MimeApp {
//TODO: move to libcosmic, support multiple files
pub fn command<O: AsRef<OsStr>>(&self, path_opt: &[O]) -> Option<Vec<process::Command>> {
exec_to_command(
self.exec.as_deref()?,
&self.name,
self.path.as_deref(),
path_opt,
)
}
pub fn is_default(&self, mime: &Mime) -> bool {
self.is_default.read().unwrap().contains(mime.essence_str())
}
pub fn no_display(&self) -> bool {
self.no_display.load(atomic::Ordering::Relaxed)
}
pub fn icon(&self) -> widget::icon::Handle {
self.icon
.get_or_init(|| {
let name = &*self.icon_name;
if name.starts_with('/') {
cosmic::widget::icon::from_path(PathBuf::from(name))
} else {
cosmic::widget::icon::from_name(name).size(32).handle()
}
})
.clone()
exec_to_command(self.exec.as_deref()?, path_opt)
}
}
@ -235,10 +190,38 @@ impl AsRef<str> for MimeApp {
}
}
#[cfg(feature = "desktop")]
impl From<&desktop::DesktopEntryData> for MimeApp {
fn from(app: &desktop::DesktopEntryData) -> Self {
Self {
id: app.id.clone(),
path: app.path.clone(),
name: app.name.clone(),
exec: app.exec.clone(),
icon: match &app.icon {
desktop::fde::IconSource::Name(name) => {
widget::icon::from_name(name.as_str()).size(32).handle()
}
desktop::fde::IconSource::Path(path) => widget::icon::from_path(path.clone()),
},
is_default: false,
}
}
}
#[cfg(feature = "desktop")]
fn filename_eq(path_opt: &Option<PathBuf>, filename: &str) -> bool {
path_opt
.as_ref()
.and_then(|path| path.file_name())
.is_some_and(|x| x == filename)
}
pub struct MimeAppCache {
apps: Vec<Arc<MimeApp>>,
cache: FxHashMap<Mime, Vec<Arc<MimeApp>>>,
terminals: Vec<Arc<MimeApp>>,
apps: Vec<MimeApp>,
cache: FxHashMap<Mime, Vec<MimeApp>>,
icons: FxHashMap<Mime, Box<[widget::icon::Handle]>>,
terminals: Vec<MimeApp>,
}
impl MimeAppCache {
@ -246,273 +229,154 @@ impl MimeAppCache {
let mut mime_app_cache = Self {
apps: Vec::new(),
cache: FxHashMap::default(),
icons: FxHashMap::default(),
terminals: Vec::new(),
};
mime_app_cache.reload();
mime_app_cache
}
pub fn get_apps_for_mime(
&self,
mime_type: &Mime,
include_other: bool,
) -> Vec<(&Arc<MimeApp>, MimeAppMatch)> {
let mut results = Vec::new();
let mut dedupe = FxHashSet::default();
// start with exact matches
results.extend(
self.get(mime_type)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Exact)),
);
let include_mime = match mime_type.type_().as_str() {
"audio" => Some("video/mp4".parse::<Mime>().expect("video/mp4 mime")),
"text" => Some(mime_guess::mime::TEXT_PLAIN),
_ => None,
};
if let Some(mime) = include_mime {
results.extend(
self.get(&mime)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Exact)),
);
}
// grab matches based off of subclass / parent mime type
if let Some(parent_types) = crate::mime_icon::parent_mime_types(mime_type) {
for parent_type in parent_types {
results.extend(
self.get(&parent_type)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Related)),
);
}
}
if include_other {
results.extend({
let mut apps = self
.apps()
.iter()
.filter(|mime_app| !mime_app.no_display())
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Other))
.collect::<Vec<_>>();
apps.sort_by(|(a, _), (b, _)| {
crate::localize::LANGUAGE_SORTER.compare(&a.name, &b.name)
});
apps
});
}
results
}
#[cfg(not(feature = "desktop"))]
pub fn reload(&mut self) {}
/// Reload mime types and their known app associations and defaults.
// Only available when using desktop feature of libcosmic, which only works on Unix-likes
#[cfg(feature = "desktop")]
pub fn reload(&mut self) {
use crate::localize::LANGUAGE_SORTER;
use crate::mime_icon;
use cosmic::desktop::fde;
use std::borrow::Cow;
let start = Instant::now();
self.apps.clear();
self.cache.clear();
self.icons.clear();
self.terminals.clear();
//TODO: get proper locale?
let locale = &[];
// Load desktop applications by supported mime types
//TODO: hashmap for all apps by id?
let all_apps: Box<[_]> = desktop::load_applications(locale, false, None).collect();
for app in &all_apps {
//TODO: just collect apps that can be executed with a file argument?
if !app.mime_types.is_empty() {
self.apps.push(MimeApp::from(app));
}
for mime in &app.mime_types {
let apps = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(1));
if !apps.iter().any(|x| x.id == app.id) {
apps.push(MimeApp::from(app));
}
}
for category in &app.categories {
if category == "TerminalEmulator" {
self.terminals.push(MimeApp::from(app));
break;
}
}
}
let mut list = cosmic_mime_apps::List::default();
let paths = cosmic_mime_apps::list_paths();
list.load_from_paths(&paths);
let locales = fde::get_languages_from_env();
let desktop_entries = fde::Iter::new(fde::default_paths()).entries(Some(&locales));
let mime_icon_cache = mime_icon::MIME_ICON_CACHE.lock().unwrap();
let shared_mime_info = &mime_icon_cache.shared_mime_info;
let mut aliased_mimes = FxHashMap::default();
for desktop_entry in desktop_entries {
let name = desktop_entry
.name(&locales)
.unwrap_or_else(|| Cow::Borrowed(desktop_entry.id()));
let app = Arc::new(MimeApp {
id: desktop_entry.appid.clone(),
path: Some(desktop_entry.path.clone()),
name: name.into(),
exec: desktop_entry.exec().map(String::from),
icon_name: desktop_entry.icon().unwrap_or_default().into(),
icon: std::sync::OnceLock::new(),
is_default: Arc::new(RwLock::default()),
no_display: Arc::new(AtomicBool::new(false)),
});
tracing::info!(target: "mime-apps", id = app.id, "detected desktop entry");
self.apps.push(app.clone());
if desktop_entry
.categories()
.into_iter()
.flatten()
.any(|c| c == "TerminalEmulator")
{
self.terminals.push(app.clone());
}
// Cache associations defined by the desktop entry.
let mime_types = desktop_entry.mime_type().unwrap_or_else(Vec::new);
let associated_mime_types = mime_types.iter().filter_map(|m| {
m.parse::<Mime>().ok().map(|mime| {
if let Some(unaliased) = shared_mime_info.unalias_mime_type(&mime) {
aliased_mimes.insert(unaliased.clone(), mime);
return unaliased;
}
mime
})
});
for mime in associated_mime_types {
let apps = self.cache.entry(mime.clone()).or_default();
if apps.iter().all(|cached_app| cached_app.id != app.id) {
apps.push(app.clone());
}
}
}
// Cache added associations from mimeapps lists.
for (mut added_mime, added_apps) in &list.added_associations {
let _unaliased;
if let Some(unaliased) = shared_mime_info.unalias_mime_type(added_mime) {
aliased_mimes.insert(unaliased.clone(), added_mime.clone());
_unaliased = unaliased;
added_mime = &_unaliased;
}
for added_app in added_apps {
if let Some(app) = self
.apps
.iter()
.find(|cached| cached.id.as_str() == added_app.as_ref())
{
let apps = self.cache.entry(added_mime.clone()).or_default();
if apps.iter().all(|cached_app| cached_app.id != app.id) {
apps.push(app.clone());
for (mime, filenames) in list
.added_associations
.iter()
.chain(list.default_apps.iter())
{
for filename in filenames {
log::trace!("add {mime}={filename}");
let apps = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(1));
if !apps.iter().any(|x| filename_eq(&x.path, filename)) {
if let Some(app) = all_apps.iter().find(|&x| filename_eq(&x.path, filename)) {
apps.push(MimeApp::from(app));
} else {
log::info!(
"failed to add association for {mime:?}: application {filename:?} not found"
);
}
}
}
}
// Remove associations
for (mut removed_mime, removed_apps) in &list.removed_associations {
let _unaliased;
if let Some(unaliased) = shared_mime_info.unalias_mime_type(removed_mime) {
aliased_mimes.insert(unaliased.clone(), removed_mime.clone());
_unaliased = unaliased;
removed_mime = &_unaliased;
}
for removed_app in removed_apps {
if let Some(app) = self
.apps
.iter()
.find(|cached| cached.id.as_str() == removed_app.as_ref())
&& let Some(apps) = self.cache.get_mut(removed_mime)
{
apps.retain(|cached_app| cached_app.id != app.id);
for (mime, filenames) in list.removed_associations.iter() {
for filename in filenames {
log::trace!("remove {mime}={filename}");
if let Some(apps) = self.cache.get_mut(mime) {
apps.retain(|x| !filename_eq(&x.path, filename));
}
}
}
// Fetch defaults and sort apps by their default precedence.
for (mime, mut apps) in std::mem::take(&mut self.cache).into_iter() {
let defaults = list.default_app_for(&mime);
let aliased_defaults = aliased_mimes
.get(&mime)
.and_then(|mime| list.default_app_for(mime));
let cache = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(apps.len()));
// Sort cached apps for this mime by default precedence.
for default in defaults
.into_iter()
.flatten()
.chain(aliased_defaults.into_iter().flatten())
{
let default = default.strip_suffix(".desktop").unwrap_or(default.as_ref());
let mut found_any = false;
apps.retain(|app| {
let found = app.id.as_str() == default;
for (mime, filenames) in list.default_apps.iter() {
for filename in filenames {
log::trace!("default {mime}={filename}");
if let Some(apps) = self.cache.get_mut(mime) {
let mut found = false;
for app in apps.iter_mut() {
if filename_eq(&app.path, filename) {
app.is_default = true;
found = true;
} else {
app.is_default = false;
}
}
if found {
app.is_default
.write()
.unwrap()
.insert(mime.essence_str().into());
cache.push(app.clone());
found_any = true;
break;
}
!found
});
if !found_any && let Some(app) = self.apps.iter().find(|app| app.id == default) {
app.is_default
.write()
.unwrap()
.insert(mime.essence_str().into());
cache.push(app.clone());
log::debug!(
"failed to set default for {mime:?}: application {filename:?} not found"
);
}
}
// Sort remaining apps by name
apps.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.name, &b.name));
cache.extend_from_slice(&apps);
tracing::debug!(target: "mime-apps", mime = mime.essence_str(), apps = ?(cache.iter().map(|app| &*app.id).collect::<Vec<&str>>()), "mime defaults found")
}
let associated: rustc_hash::FxHashSet<&str> = self
.cache
.values()
.flatten()
.map(|app| app.id.as_str())
.collect();
for app in &self.apps {
app.no_display.store(
!associated.contains(app.id.as_str()),
atomic::Ordering::Relaxed,
);
// Sort apps by name
self.apps
.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
for apps in self.cache.values_mut() {
apps.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
}
// Copy icons to special cache
//TODO: adjust dropdown API so this is no longer needed
self.icons.extend(self.cache.iter().map(|(mime, apps)| {
(
mime.clone(),
apps.iter().map(|app| app.icon.clone()).collect(),
)
}));
let elapsed = start.elapsed();
tracing::info!(target: "mime-apps", "loaded mime app cache in {elapsed:?}");
log::info!("loaded mime app cache in {elapsed:?}");
}
pub fn apps(&self) -> &[Arc<MimeApp>] {
pub fn apps(&self) -> &[MimeApp] {
&self.apps
}
pub fn get(&self, key: &Mime) -> &[Arc<MimeApp>] {
pub fn get(&self, key: &Mime) -> &[MimeApp] {
self.cache.get(key).map_or(&[], Vec::as_slice)
}
pub fn icons(&self, key: &Mime) -> Vec<widget::icon::Handle> {
self.cache
.get(key)
.map_or_else(Vec::new, |apps| apps.iter().map(|app| app.icon()).collect())
pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] {
self.icons.get(key).map_or(&[], Box::as_ref)
}
fn get_default_terminal(&self) -> Option<String> {
@ -530,19 +394,14 @@ impl MimeAppCache {
.map(|string| string.trim().replace(".desktop", ""))
}
pub fn terminal(&self) -> Option<&Arc<MimeApp>> {
pub fn terminal(&self) -> Option<&MimeApp> {
//TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec
// The current approach works but might not adhere to the spec (yet)
// Look for and return preferred terminals
//TODO: fallback order beyond cosmic-term?
// 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(),
];
let mut preference_order = vec!["com.system76.CosmicTerm".to_string()];
if let Some(id) = self.get_default_terminal() {
preference_order.insert(0, id);
@ -616,43 +475,11 @@ impl Default for MimeAppCache {
mod tests {
use super::exec_to_command;
#[test]
fn keys_within_words() {
let exec = "/usr/bin/foo --option=%f";
let paths = ["file1"];
let commands = exec_to_command(exec, "keys_within_words", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
assert_eq!(
"--option=file1",
command.get_args().next().unwrap().to_str().unwrap()
);
}
#[test]
fn no_path_f_field_code() {
let exec = "/usr/bin/foo %f";
let paths: [&str; 0] = [];
let commands = exec_to_command(exec, "no_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
assert_eq!(0, command.get_args().len());
}
#[test]
fn one_path_f_field_code() {
let exec = "/usr/bin/foo %f";
let paths = ["file1"];
let commands = exec_to_command(exec, "one_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -667,40 +494,31 @@ mod tests {
#[test]
#[allow(non_snake_case)]
fn one_path_F_field_code() {
let exec = "/usr/bin/cosmic-term -w %F";
let paths = ["/home/user"];
let commands = exec_to_command(exec, "one_path_F_field_code", None, &paths)
.expect("Should parse valid exec");
let exec = "/usr/bin/bar %F";
let paths = ["cat"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!(
"/usr/bin/cosmic-term",
command.get_program().to_str().unwrap()
);
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap());
assert_eq!("cat", command.get_args().next().unwrap().to_str().unwrap());
}
#[test]
fn one_path_u_field_code() {
let exec = "/usr/bin/cosmic-term -w %u";
let paths = ["/home/user"];
let commands = exec_to_command(exec, "one_path_u_field_code", None, &paths)
.expect("Should parse valid exec");
let exec = "/usr/bin/foobar %u";
let paths = ["/home/josh/krumpli"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!("/usr/bin/foobar", command.get_program().to_str().unwrap());
assert_eq!(
"/usr/bin/cosmic-term",
command.get_program().to_str().unwrap()
*paths.first().unwrap(),
command.get_args().next().unwrap().to_str().unwrap()
);
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
}
#[test]
@ -708,8 +526,7 @@ mod tests {
fn one_path_U_field_code() {
let exec = "/usr/bin/rmrfbye %U";
let paths = ["/"];
let commands = exec_to_command(exec, "one_path_U_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -725,8 +542,7 @@ mod tests {
"/usr/share/games/psp/miku.iso",
"/usr/share/games/psp/eternia.iso",
];
let commands = exec_to_command(exec, "mult_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) {
@ -746,8 +562,7 @@ mod tests {
"/usr/share/games/doom2/hr.wad",
"/usr/share/games/doom2/hrmus.wad",
];
let commands = exec_to_command(exec, "mult_path_F_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -769,8 +584,7 @@ mod tests {
"https://redox-os.org/",
"https://system76.com/",
];
let commands = exec_to_command(exec, "mult_path_u_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) {
@ -793,8 +607,7 @@ mod tests {
"frieren01.mkv",
"rtmp://example.org/this/video/doesnt/exist.avi",
];
let commands = exec_to_command(exec, "mult_path_U_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -822,8 +635,7 @@ mod tests {
"@@u",
];
let paths = ["file1.rs", "file2.rs"];
let commands = exec_to_command(exec, "flatpak_style_exec", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -846,8 +658,7 @@ mod tests {
"file:///usr/share/games/roguelike/mods/mod1",
"file:///usr/share/games/roguelike/mods/mod2",
];
let commands = exec_to_command(exec, "multiple_field_codes", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -880,8 +691,7 @@ mod tests {
];
let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"];
let args_trailing = ["@@"];
let commands = exec_to_command(exec, "sandwiched_field_code", None, &paths)
.expect("Should parse valid exec");
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();

View file

@ -3,9 +3,11 @@
use cosmic::widget::icon;
use mime_guess::Mime;
use rustc_hash::FxHashMap;
use std::fs;
use std::path::Path;
use std::sync::{LazyLock, Mutex};
use std::{
fs,
path::Path,
sync::{LazyLock, Mutex},
};
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
@ -15,21 +17,20 @@ struct MimeIconKey {
size: u16,
}
#[derive(Default)]
pub struct MimeIconCache {
struct MimeIconCache {
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
#[cfg(unix)]
pub shared_mime_info: xdg_mime::SharedMimeInfo,
shared_mime_info: xdg_mime::SharedMimeInfo,
}
impl MimeIconCache {
#[cfg(not(unix))]
pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
None
pub fn new() -> Self {
Self {
cache: FxHashMap::default(),
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
}
}
#[cfg(unix)]
fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
self.cache
.entry(key)
.or_insert_with_key(|key| {
@ -38,7 +39,7 @@ impl MimeIconCache {
return None;
}
let icon_name = icon_names.remove(0);
let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size);
let mut named = icon::from_name(icon_name).size(key.size);
if !icon_names.is_empty() {
let fallback_names =
icon_names.into_iter().map(std::borrow::Cow::from).collect();
@ -49,20 +50,9 @@ impl MimeIconCache {
.clone()
}
}
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
pub static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
LazyLock::new(|| Mutex::new(MimeIconCache::default()));
#[cfg(not(unix))]
pub fn mime_for_path(
path: impl AsRef<Path>,
metadata_opt: Option<&fs::Metadata>,
remote: bool,
) -> Mime {
mime_guess::from_path(path).first_or_octet_stream()
}
#[cfg(unix)]
pub fn mime_for_path(
path: impl AsRef<Path>,
metadata_opt: Option<&fs::Metadata>,
@ -72,7 +62,6 @@ pub fn mime_for_path(
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
// Try the shared mime info cache first
let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
gb.zero_size(false);
if remote {
if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
gb.file_name(file_name);
@ -107,28 +96,12 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
match mime_icon_cache.get(MimeIconKey { mime, size }) {
Some(handle) => handle,
None => icon::from_name(FALLBACK_MIME_ICON)
.prefer_svg(true)
.size(size)
.handle(),
None => icon::from_name(FALLBACK_MIME_ICON).size(size).handle(),
}
}
#[cfg(not(unix))]
pub fn parent_mime_types(_mime: &Mime) -> Option<Vec<Mime>> {
None
}
#[cfg(unix)]
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
}
pub fn is_mime_subclass_of(mime_type: &Mime, base: &Mime) -> bool {
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
mime_icon_cache
.shared_mime_info
.mime_type_subclass(mime_type, base)
}

View file

@ -1,32 +1,21 @@
use cosmic::iced::futures::SinkExt;
use cosmic::iced::{Subscription, stream};
use cosmic::{Task, widget};
use gio::glib;
use gio::prelude::*;
use std::any::TypeId;
use std::cell::Cell;
use std::future::pending;
use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc;
use cosmic::{
Task,
iced::{Subscription, futures::SinkExt, stream},
widget,
};
use gio::{glib, prelude::*};
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
use tokio::sync::{Mutex, mpsc};
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
use crate::config::IconSizes;
use crate::err_str;
use crate::tab::{self, ChecksumState, DirSize, ItemMetadata, ItemThumbnail, Location};
use crate::{
config::IconSizes,
err_str,
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
};
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
// Attributes requested when listing a network directory.
const SCAN_ATTRIBUTES: &str = "standard::name,\
standard::display-name,\
standard::type,\
standard::size,\
standard::icon,\
standard::is-hidden,\
time::modified";
fn resolve_uri(uri: &str) -> (String, gio::File) {
let file = gio::File::for_uri(uri);
// Resolve the target-uri if it exists
@ -128,27 +117,9 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
Box::from([])
};
// `filesystem::remote` belongs to the filesystem namespace, which `enumerate_children`
// never fills in, so it has to be queried once for the directory being listed.
let remote = file
.query_filesystem_info(
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
)
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
.unwrap_or_else(|err| {
log::warn!("failed to get GIO filesystem info for {uri}: {err}");
// Assume remote, so per-entry work is skipped rather than retried
true
});
let mut items = Vec::new();
for info_res in file
.enumerate_children(
SCAN_ATTRIBUTES,
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
)
.enumerate_children("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE)
.map_err(err_str)?
{
let info = info_res.map_err(err_str)?;
@ -160,17 +131,13 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: what is the best way to resolve shortcuts?
let location = Location::Network(uri, display_name.clone(), file.child(&name).path());
let metadata = if force_dir {
ItemMetadata::SimpleDir { entries: 0 }
} else {
let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) {
let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED);
let is_dir = matches!(info.file_type(), gio::FileType::Directory);
let size_opt = (!is_dir).then_some(info.size() as u64);
let mut children_opt = None;
// Counting children costs a directory listing per entry, which is far too
// expensive on a remote filesystem
if is_dir && !remote {
if is_dir {
if let Some(path) = file.child(&name).path() {
//TODO: calculate children in the background (and make it cancellable?)
match std::fs::read_dir(&path) {
@ -190,8 +157,9 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
mtime,
size_opt,
children_opt,
is_dir,
}
} else {
ItemMetadata::SimpleDir { entries: 0 }
};
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
@ -231,7 +199,6 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
metadata,
hidden,
location_opt: Some(location),
image_dimensions: None,
mime,
icon_handle_grid,
icon_handle_list,
@ -246,7 +213,6 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: scan directory size on gvfs mounts?
dir_size: DirSize::NotDirectory,
cut: false,
checksums: ChecksumState::default(),
});
}
Ok(items)
@ -263,10 +229,7 @@ fn dir_info(uri: &str) -> Result<(String, String, Option<PathBuf>), glib::Error>
Ok((resolved_uri, info.display_name().into(), file.path()))
}
fn mount_op(
uri: String,
event_tx: std::sync::Weak<crate::channel::Sender<Event>>,
) -> gio::MountOperation {
fn mount_op(uri: String, event_tx: mpsc::UnboundedSender<Event>) -> gio::MountOperation {
let mount_op = gio::MountOperation::new();
mount_op.connect_ask_password(
move |mount_op, message, default_user, default_domain, flags| {
@ -289,9 +252,9 @@ fn mount_op(
.then_some(false),
};
let (auth_tx, mut auth_rx) = mpsc::channel(1);
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_tx));
}
event_tx
.send(Event::NetworkAuth(uri.clone(), auth, auth_tx))
.unwrap();
//TODO: async recv?
if let Some(auth) = auth_rx.blocking_recv() {
if auth.anonymous_opt == Some(true) {
@ -394,45 +357,37 @@ impl Item {
pub struct Gvfs {
command_tx: mpsc::UnboundedSender<Cmd>,
event_rx: Arc<crate::channel::Receiver<Event>>,
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
}
impl Gvfs {
pub fn new() -> Self {
//TODO: switch to using gvfs-zbus which will better integrate with async rust
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
let (event_tx, event_rx) = crate::channel::channel();
let event_tx = Arc::new(event_tx);
let (event_tx, event_rx) = mpsc::unbounded_channel();
std::thread::spawn(move || {
let main_loop = glib::MainLoop::new(None, false);
main_loop.context().spawn_local(async move {
let event_tx = Arc::downgrade(&event_tx);
let monitor = gio::VolumeMonitor::get();
{
let event_tx = event_tx.clone();
monitor.connect_mount_changed(move |_monitor, mount| {
log::info!("mount changed {}", MountExt::name(mount));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
{
let event_tx = event_tx.clone();
monitor.connect_mount_added(move |_monitor, mount| {
log::info!("mount added {}", MountExt::name(mount));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
{
let event_tx = event_tx.clone();
monitor.connect_mount_removed(move |_monitor, mount| {
log::info!("mount removed {}", MountExt::name(mount));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
@ -440,27 +395,21 @@ impl Gvfs {
let event_tx = event_tx.clone();
monitor.connect_volume_changed(move |_monitor, volume| {
log::info!("volume changed {}", VolumeExt::name(volume));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
{
let event_tx = event_tx.clone();
monitor.connect_volume_added(move |_monitor, volume| {
log::info!("volume added {}", VolumeExt::name(volume));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
{
let event_tx = event_tx.clone();
monitor.connect_volume_removed(move |_monitor, volume| {
log::info!("volume removed {}", VolumeExt::name(volume));
if let Some(event_tx) = event_tx.upgrade() {
event_tx.send(Event::Changed);
}
event_tx.send(Event::Changed).unwrap();
});
}
@ -470,11 +419,7 @@ impl Gvfs {
items_tx.send(items(&monitor, sizes)).await.unwrap();
}
Cmd::Rescan => {
let Some(event_tx) = event_tx.upgrade() else {
return;
};
event_tx.send(Event::Items(items(&monitor, IconSizes::default())));
event_tx.send(Event::Items(items(&monitor, IconSizes::default()))).unwrap();
}
Cmd::Mount(mounter_item, complete_tx) => {
let MounterItem::Gvfs(ref item) = mounter_item else {
@ -526,9 +471,6 @@ impl Gvfs {
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
.unwrap_or(true);
}
let Some(event_tx) = event_tx.upgrade() else {
return;
};
event_tx.send(Event::MountResult(updated_item, match res {
Ok(()) => {
_ = complete_tx.send(Ok(()));
@ -540,7 +482,7 @@ impl Gvfs {
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
_ => Err(format!("{err}"))
}}
}));
})).unwrap();
},
);
break;
@ -556,9 +498,6 @@ impl Gvfs {
gio::Cancellable::NONE,
move |res| {
log::info!("network drive {uri}: result {res:?}");
let Some(event_tx) = event_tx.upgrade() else {
return;
};
event_tx.send(Event::NetworkResult(uri, match res {
Ok(()) => {
_ = result_tx.send(Ok(()));
@ -569,7 +508,7 @@ impl Gvfs {
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
_ => Err(format!("{err}"))
}}
}));
})).unwrap();
}
);
}
@ -593,9 +532,6 @@ impl Gvfs {
// FIXME sometimes a uri can be mounted and then not recognized as mounted...
// seems to be related to uri with a path
items_tx.blocking_send(network_scan(&uri, sizes)).unwrap();
let Some(event_tx) = event_tx.upgrade() else {
return;
};
event_tx.send(Event::NetworkResult(resolved_uri, match res {
Ok(()) => {
Ok(true)
@ -604,7 +540,7 @@ impl Gvfs {
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
_ => Err(format!("{err}"))
}
}));
})).unwrap();
}
);
} else {
@ -660,7 +596,7 @@ impl Gvfs {
});
Self {
command_tx,
event_rx: Arc::new(event_rx),
event_rx: Arc::new(Mutex::new(event_rx)),
}
}
}
@ -689,7 +625,7 @@ impl Mounter for Gvfs {
)
}
fn network_drive(&self, uri: String) -> Task<bool> {
fn network_drive(&self, uri: String) -> Task<()> {
let command_tx = self.command_tx.clone();
Task::perform(
async move {
@ -698,15 +634,9 @@ impl Mounter for Gvfs {
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
res_rx.await
},
|result| match result {
Ok(Ok(())) => true,
Ok(Err(err)) => {
|x| {
if let Err(err) = x {
log::error!("{err:?}");
false
}
Err(err) => {
log::error!("{err:?}");
false
}
},
)
@ -740,7 +670,7 @@ impl Mounter for Gvfs {
let event_rx = self.event_rx.clone();
struct Wrapper {
command_tx: mpsc::UnboundedSender<Cmd>,
event_rx: Arc<crate::channel::Receiver<Event>>,
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
}
impl Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
@ -764,7 +694,7 @@ impl Mounter for Gvfs {
MounterMessage,
>| async move {
command_tx.send(Cmd::Rescan).unwrap();
while let Some(event) = event_rx.recv().await {
while let Some(event) = event_rx.lock().await.recv().await {
match event {
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
Event::Items(items) => {

View file

@ -1,13 +1,13 @@
use cosmic::iced::Subscription;
use cosmic::{Task, widget};
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};
use cosmic::{Task, iced::Subscription, widget};
use std::{
collections::BTreeMap,
fmt,
path::PathBuf,
sync::{Arc, LazyLock},
};
use tokio::sync::mpsc;
use crate::config::IconSizes;
use crate::tab;
use crate::{config::IconSizes, tab};
#[cfg(feature = "gvfs")]
mod gvfs;
@ -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,7 +103,6 @@ impl MounterItem {
pub type MounterItems = Vec<MounterItem>;
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum MounterMessage {
Items(MounterItems),
MountResult(MounterItem, Result<bool, String>),
@ -115,7 +114,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<bool>;
fn network_drive(&self, uri: String) -> Task<()>;
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<()>;

View file

@ -3,17 +3,21 @@
use std::time::Instant;
use crate::tab::DOUBLE_CLICK_DURATION;
use cosmic::iced::core::border::Border;
use cosmic::iced::core::event::Event;
use cosmic::iced::core::mouse::{self, click};
use cosmic::iced::core::renderer::{self, Quad, Renderer as _};
use cosmic::iced::core::widget::{Operation, Tree, tree};
use cosmic::iced::core::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
overlay, touch,
use cosmic::{
Element, Renderer, Theme,
iced_core::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
border::Border,
event::Event,
layout,
mouse::{self, click},
overlay,
renderer::{self, Quad, Renderer as _},
touch,
widget::{Operation, Tree, tree},
},
widget::Id,
};
use cosmic::widget::Id;
use cosmic::{Element, Renderer, Theme};
/// Emit messages on mouse events.
#[allow(missing_debug_implementations)]
@ -214,9 +218,8 @@ impl<'a, Message, F> OnAutoScroll<'a, Message> for F where F: Fn(Option<f32>) ->
pub trait OnMouseButton<'a, Message>: Fn(Option<Point>) -> Message + 'a {}
impl<'a, Message, F> OnMouseButton<'a, Message> for F where F: Fn(Option<Point>) -> Message + 'a {}
pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Option<Message> + 'a {}
impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Option<Message> + 'a
{}
pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Message + 'a {}
impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Message + 'a {}
pub trait OnResize<'a, Message>: Fn(Rectangle) -> Message + 'a {}
impl<'a, Message, F> OnResize<'a, Message> for F where F: Fn(Rectangle) -> Message + 'a {}
@ -392,7 +395,7 @@ where
update(
self,
event,
&event,
layout,
cursor,
shell,
@ -485,7 +488,7 @@ where
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut cosmic::iced::core::clipboard::DndDestinationRectangles,
dnd_rectangles: &mut cosmic::iced_core::clipboard::DndDestinationRectangles,
) {
self.content.as_widget().drag_destinations(
&state.children[0],
@ -564,16 +567,6 @@ fn update<Message: Clone>(
state.last_position = position_in;
}
if state.drag_initiated.is_some()
&& matches!(
event,
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. })
)
{
shell.request_redraw();
}
if let Event::Mouse(mouse::Event::CursorMoved { position }) = event {
let virtual_position = Point::new(
viewport.x - layout_bounds.x + position.x,
@ -650,7 +643,6 @@ fn update<Message: Clone>(
{
state.drag_initiated = None;
state.prev_click = None;
shell.request_redraw();
if let Some(message) = widget.on_drag_end.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds)));
}
@ -670,9 +662,7 @@ fn update<Message: Clone>(
state.prev_click = None;
return;
}
if state.drag_initiated.take().is_some() {
shell.request_redraw();
}
state.drag_initiated = None;
if let Some(message) = widget.on_release.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds)));
@ -799,13 +789,12 @@ fn update<Message: Clone>(
}
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
let rect_opt = drag_rect.intersection(&layout_bounds).map(|mut rect| {
rect.x -= layout_bounds.x;
rect.y -= layout_bounds.y;
rect
});
if let Some(message) = message(rect_opt) {
shell.publish(message);
}
shell.publish(message(drag_rect.intersection(&layout_bounds).map(
|mut rect| {
rect.x -= layout_bounds.x;
rect.y -= layout_bounds.y;
rect
},
)));
}
}

View file

@ -1,11 +1,7 @@
use atomic_float::AtomicF32;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::sync::Arc;
use std::sync::atomic::{self, AtomicU16};
use std::sync::{Arc, Mutex};
use tokio::sync::Notify;
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ControllerState {
Cancelled,
Failed,
@ -15,8 +11,8 @@ pub enum ControllerState {
#[derive(Debug)]
struct ControllerInner {
state: AtomicU16,
progress: AtomicF32,
state: Mutex<ControllerState>,
progress: Mutex<f32>,
notify: Notify,
}
@ -31,8 +27,8 @@ impl Default for Controller {
Self {
primary: true,
inner: Arc::new(ControllerInner {
state: AtomicU16::new(ControllerState::Running.into()),
progress: AtomicF32::new(0.0),
state: Mutex::new(ControllerState::Running),
progress: Mutex::new(0.0),
notify: Notify::new(),
}),
}
@ -54,24 +50,19 @@ impl Controller {
}
pub fn progress(&self) -> f32 {
self.inner.progress.load(atomic::Ordering::Relaxed)
*self.inner.progress.lock().unwrap()
}
pub fn set_progress(&self, progress: f32) {
self.inner
.progress
.swap(progress, atomic::Ordering::Relaxed);
*self.inner.progress.lock().unwrap() = progress;
}
pub fn state(&self) -> ControllerState {
ControllerState::try_from(self.inner.state.load(atomic::Ordering::Relaxed))
.unwrap_or(ControllerState::Failed)
*self.inner.state.lock().unwrap()
}
pub fn set_state(&self, state: ControllerState) {
self.inner
.state
.store(state.into(), atomic::Ordering::Relaxed);
*self.inner.state.lock().unwrap() = state;
self.inner.notify.notify_waiters();
}
@ -95,35 +86,6 @@ impl Controller {
self.set_state(ControllerState::Paused);
}
/// Returns when the state is paused.
///
/// Use this to pause futures.
pub async fn until_paused(&self) {
loop {
if matches!(self.state(), ControllerState::Paused) {
return;
}
self.inner.notify.notified().await;
}
}
/// Returns when state is neither paused, cancelled, nor failed.
///
/// Use this to resume futures.
pub async fn until_unpaused(&self) {
loop {
if !matches!(
self.state(),
ControllerState::Paused | ControllerState::Cancelled | ControllerState::Failed
) {
return;
}
self.inner.notify.notified().await;
}
}
pub fn unpause(&self) {
if !self.is_cancelled() | !self.is_failed() {
self.set_state(ControllerState::Running);

View file

@ -1,15 +1,20 @@
use crate::app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID};
use crate::config::IconSizes;
use crate::spawn_detached::spawn_detached;
use crate::{archive, fl, tab};
use cosmic::iced::futures::channel::mpsc::Sender;
use cosmic::iced::futures::{self, SinkExt, StreamExt, stream};
use std::borrow::Cow;
use std::fmt::Formatter;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::{
app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID},
archive,
config::IconSizes,
fl,
spawn_detached::spawn_detached,
tab,
};
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
use std::{
borrow::Cow,
fmt::Formatter,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::sync::{Mutex as TokioMutex, mpsc};
use walkdir::WalkDir;
use zip::AesMode::Aes256;
@ -17,9 +22,6 @@ use zip::AesMode::Aes256;
pub use self::controller::{Controller, ControllerState};
pub mod controller;
pub use notifiers::*;
mod notifiers;
pub use self::reader::OpReader;
pub mod reader;
@ -34,7 +36,7 @@ async fn handle_replace(
conflict_count: usize,
) -> ReplaceResult {
let item_from = match tab::item_from_path(file_from, IconSizes::default()) {
Ok(ok) => Box::new(ok),
Ok(ok) => ok,
Err(err) => {
log::warn!("{err}");
return ReplaceResult::Cancel;
@ -42,7 +44,7 @@ async fn handle_replace(
};
let item_to = match tab::item_from_path(file_to, IconSizes::default()) {
Ok(ok) => Box::new(ok),
Ok(ok) => ok,
Err(err) => {
log::warn!("{err}");
return ReplaceResult::Cancel;
@ -109,7 +111,7 @@ async fn copy_or_move(
);
// Handle duplicate file names by renaming paths
let from_to_pairs_iter = paths
let mut from_to_pairs: Vec<(PathBuf, PathBuf)> = paths
.into_iter()
.zip(std::iter::repeat(to.as_path()))
.filter_map(|(from, to)| {
@ -127,46 +129,36 @@ async fn copy_or_move(
//TODO: how to handle from missing file name?
None
}
});
})
.collect();
// Attempt quick and simple renames
//TODO: allow rename to be used for directories in recursive context?
if matches!(method, Method::Move { .. }) {
from_to_pairs.retain(|(from, to)| {
//TODO: show replace dialog here?
if to.exists() {
return true;
}
let from_to_pairs: Vec<(PathBuf, PathBuf)> = if matches!(method, Method::Move { .. }) {
from_to_pairs_iter
.map(|(from, to)| async move {
//TODO: show replace dialog here?
if to.exists() {
return Some((from, to));
//TODO: use compio::fs::rename?
match fs::rename(from, to) {
Ok(()) => {
log::info!("renamed {} to {}", from.display(), to.display());
false
}
match compio::fs::rename(&from, &to).await {
Ok(()) => {
log::info!("renamed {} to {}", from.display(), to.display());
None
}
Err(err) => {
log::info!(
"failed to rename {} to {}, fallback to recursive move: {}",
from.display(),
to.display(),
err
);
Some((from, to))
}
Err(err) => {
log::info!(
"failed to rename {} to {}, fallback to recursive move: {}",
from.display(),
to.display(),
err
);
true
}
})
.collect::<cosmic::iced::futures::stream::FuturesOrdered<_>>()
.fold(Vec::new(), |mut pairs, pair| async move {
if let Some(pair) = pair {
pairs.push(pair);
}
pairs
})
.await
} else {
from_to_pairs_iter.collect()
};
}
});
}
let mut context = Context::new(controller.clone());
@ -224,7 +216,7 @@ pub async fn sync_to_disk(
}
}))
.buffer_unordered(32)
.collect::<()>()
.collect::<Vec<_>>()
.await;
// Sync directories to disk
@ -234,7 +226,7 @@ pub async fn sync_to_disk(
}
}))
.buffer_unordered(16)
.collect::<()>()
.collect::<Vec<_>>()
.await;
}
@ -770,12 +762,13 @@ impl Operation {
OperationError::from_err(e, &controller)
})?;
if let Ok(modified) = metadata.modified()
&& let Some(last_modified) =
if let Ok(modified) = metadata.modified() {
if let Some(last_modified) =
archive::system_time_to_zip_date_time(modified)
{
zip_options =
zip_options.last_modified_time(last_modified);
{
zip_options =
zip_options.last_modified_time(last_modified);
}
}
#[cfg(unix)]
@ -967,13 +960,11 @@ impl Operation {
password,
} => {
let controller_clone = controller.clone();
compio::runtime::spawn(async move {
let extracted = compio::runtime::spawn_blocking(move || {
compio::runtime::spawn_blocking(
move || -> Result<OperationSelection, OperationError> {
let controller = controller_clone;
let total_paths = paths.len();
let mut op_sel = OperationSelection::default();
let mut written_files = Vec::new();
let mut target_dirs = std::collections::HashSet::new();
for (i, path) in paths.iter().enumerate() {
futures::executor::block_on(async {
controller
@ -997,32 +988,16 @@ impl Operation {
op_sel.ignored.push(path.clone());
op_sel.selected.push(new_dir.clone());
let (files, dirs) = crate::archive::extract(
path,
&new_dir,
&password,
&controller,
)?;
written_files.extend(files);
target_dirs.extend(dirs);
crate::archive::extract(path, &new_dir, &password, &controller)?;
}
}
Ok::<_, OperationError>((op_sel, written_files, target_dirs))
})
.await
.map_err(wrap_compio_spawn_error)??;
let (op_sel, written_files, target_dirs) = extracted;
if !written_files.is_empty() || !target_dirs.is_empty() {
sync_to_disk(written_files, target_dirs).await;
}
Ok::<_, OperationError>(op_sel)
})
.await
.map_err(wrap_compio_spawn_error)?
Ok(op_sel)
},
)
}
.await
.map_err(wrap_compio_spawn_error)?,
Self::Move {
paths,
to,
@ -1138,9 +1113,7 @@ impl Operation {
#[cfg(target_os = "macos")]
Self::Restore { .. } => {
// TODO: add support for macos
return Err(OperationError::from_msg(
"Restoring from trash is not supported on macos",
));
return OperationError::from_msg("Restoring from trash is not supported on macos");
}
#[cfg(not(target_os = "macos"))]
Self::Restore { items } => {
@ -1156,26 +1129,10 @@ impl Operation {
paths.push(item.original_path());
// Items with .trashinfo id use standard restore; sub-items use manual move
if item
.id
.to_str()
.map_or(false, |s| s.ends_with(".trashinfo"))
{
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
.await
.map_err(wrap_compio_spawn_error)?
.map_err(|e| OperationError::from_err(e, &controller))?;
} else {
let from = PathBuf::from(&item.id);
let to = item.original_path();
if let Some(parent) = to.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| OperationError::from_err(e, &controller))?;
}
std::fs::rename(&from, &to)
.map_err(|e| OperationError::from_err(e, &controller))?;
}
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
.await
.map_err(wrap_compio_spawn_error)?
.map_err(|e| OperationError::from_err(e, &controller))?;
}
Ok(OperationSelection {
ignored: Vec::new(),
@ -1224,10 +1181,8 @@ impl Operation {
.map_err(|s| OperationError::from_state(s, &controller))?;
let controller_clone = controller.clone();
let path_clone = path.clone();
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
let controller = controller_clone;
let path = path_clone;
//TODO: what to do on non-Unix systems?
#[cfg(unix)]
{
@ -1242,10 +1197,7 @@ impl Operation {
.await
.map_err(wrap_compio_spawn_error)?
.map_err(|e| OperationError::from_err(e, &controller))?;
Ok(OperationSelection {
ignored: Vec::new(),
selected: vec![path],
})
Ok(OperationSelection::default())
}
};
@ -1272,23 +1224,28 @@ fn wrap_compio_spawn_error(err: Box<dyn std::any::Any + Send>) -> OperationError
#[cfg(test)]
mod tests {
use std::fs::{self, File};
use std::io;
use std::path::PathBuf;
use std::{
fs::{self, File},
io,
path::PathBuf,
};
use cosmic::iced::futures::channel::mpsc;
use cosmic::iced::futures::{StreamExt, future};
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
use log::debug;
use test_log::test;
use tokio::sync;
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
use crate::app::test_utils::{
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, filter_files,
simple_fs,
use crate::{
app::{
DialogPage, Message,
test_utils::{
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
filter_files, simple_fs,
},
},
fl,
};
use crate::app::{DialogPage, Message};
use crate::fl;
/// Simple wrapper around `[Operation::Copy]`
pub async fn operation_copy(

View file

@ -1,58 +0,0 @@
// Copyright 2026 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::path::{Path, PathBuf};
use std::sync::{Arc, LazyLock, Mutex};
use tokio::sync::Notify;
/// Monitor files which are being written to.
pub struct FileWritingNotifier {
data: Vec<PathBuf>,
notify: Arc<Notify>,
}
static ACTIVELY_WRITING: LazyLock<Mutex<FileWritingNotifier>> = LazyLock::new(|| {
Mutex::new(FileWritingNotifier {
data: Vec::new(),
notify: Arc::new(Notify::new()),
})
});
/// Append path that is being written to.
pub fn actively_writing_add(path: PathBuf) {
ACTIVELY_WRITING.lock().unwrap().data.push(path);
}
/// Remove path to file that has finished writing and notify waiters.
pub fn actively_writing_remove(path: &Path) {
let mut guard = ACTIVELY_WRITING.lock().unwrap();
guard.data.retain(|p| p != path);
guard.notify.notify_waiters();
}
/// Wait until the actively-writing queue is empty or a file has been removed.
pub async fn actively_writing_tick() {
let notify = (|| {
let guard = ACTIVELY_WRITING.lock().unwrap();
if !guard.data.is_empty() {
return Some(guard.notify.clone());
}
None
})();
if let Some(notify) = notify {
notify.notified().await
}
}
/// Check if a file is being written to. Avoid thumbnail generation until after it is finished.
pub fn is_actively_writing_to(path: &Path) -> bool {
ACTIVELY_WRITING
.lock()
.unwrap()
.data
.iter()
.any(|p| p == path)
}

View file

@ -1,5 +1,4 @@
use std::path::Path;
use std::{fs, io};
use std::{fs, io, path::Path};
use crate::operation::OperationError;

View file

@ -1,39 +1,15 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
use crate::operation::{OperationError, sync_to_disk};
use anyhow::Context as AnyhowContext;
use compio::BufResult;
use compio::buf::{IntoInner, IoBuf};
use compio::driver::ToSharedFd;
use compio::driver::op::AsyncifyFd;
use compio::io::{AsyncReadAt, AsyncWriteAt};
use cosmic::iced::futures;
#[cfg(feature = "gvfs")]
use futures::{FutureExt, StreamExt};
use std::cell::Cell;
use std::error::Error;
use std::fs;
use std::future::Future;
use std::ops::ControlFlow;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::time::Instant;
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
use walkdir::WalkDir;
#[cfg(feature = "gvfs")]
use gio::prelude::FileExtManual;
use crate::operation::{OperationError, sync_to_disk};
#[derive(thiserror::Error, Debug)]
pub enum GioCopyError {
#[error("controller state")]
Controller(OperationError),
#[cfg(feature = "gvfs")]
#[error("gio copy failed")]
GLib(#[from] glib::Error),
}
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
pub enum Method {
Copy,
@ -337,28 +313,136 @@ impl Op {
})
}
async fn run(&mut self, ctx: &mut Context, progress: Progress) -> Result<bool, Box<dyn Error>> {
async fn run(
&mut self,
ctx: &mut Context,
mut progress: Progress,
) -> Result<bool, Box<dyn Error>> {
if self.skipped.normal.get() || (self.is_cleanup && self.skipped.cleanup.get()) {
return Ok(true);
}
match self.kind {
OpKind::Copy => {
crate::operation::actively_writing_add(self.to.clone());
let result = self.copy(ctx, progress).await;
if result.is_err() {
_ = compio::fs::remove_file(&self.to).await;
// Remove `to` if overwriting and it is an existing file
if self.to.is_file() {
match ctx.replace(self).await? {
ControlFlow::Continue(to) => {
self.to = to;
}
ControlFlow::Break(ret) => {
return Ok(ret);
}
}
}
crate::operation::actively_writing_remove(&self.to);
return result;
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
async {
compio::fs::OpenOptions::new()
.read(true)
.open(&self.from)
.await
},
compio::fs::metadata(&self.from),
// This is atomic and ensures `to` is not created by any other process
async {
compio::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&self.to)
.await
}
)?;
progress.total_bytes = Some(metadata.len());
(ctx.on_progress)(self, &progress);
if let Err(err) = to_file.set_permissions(metadata.permissions()).await {
// This error is not propagated upwards as some filesystems do not support setting permissions
log::warn!(
"failed to set permissions for {}: {}",
self.to.display(),
err
);
}
// Prevent spamming the progress callbacks.
let mut last_progress_update = Instant::now();
// io_uring/IOCP requires transferring ownership of the buffer to the kernel.
let mut buf_in = std::mem::take(&mut ctx.buf);
// Track where the current read/write position is at.
let mut pos = 0;
loop {
let BufResult(result, buf_out) = from_file.read_at(buf_in, pos).await;
let count = match result {
Ok(0) => {
ctx.buf = buf_out;
break;
}
Ok(count) => count,
Err(why) => {
ctx.buf = buf_out;
return Err(why.into());
}
};
let BufResult(result, buf_out_slice) =
to_file.write_at(buf_out.slice(..count), pos).await;
let buf_out = buf_out_slice.into_inner();
if let Err(why) = result {
ctx.buf = buf_out;
return Err(why.into());
}
progress.current_bytes += count as u64;
pos += count as u64;
// Avoid spamming progress messages too early.
let current = Instant::now();
if current.duration_since(last_progress_update).as_millis() > 49 {
last_progress_update = current;
(ctx.on_progress)(self, &progress);
// Also check if the progress was cancelled.
if let Err(state) = ctx.controller.check().await {
ctx.buf = buf_out;
return Err(OperationError::from_state(state, &ctx.controller).into());
}
}
buf_in = buf_out;
}
let mut times = fs::FileTimes::new();
{
use std::os::unix::prelude::MetadataExt;
log::info!("{}", metadata.mtime());
}
if let Ok(time) = dbg!(metadata.modified()) {
times = times.set_modified(time);
}
if let Ok(time) = dbg!(metadata.accessed()) {
times = times.set_accessed(time);
}
//TODO: upstream set_times implementation to compio?
{
use compio::driver::{ToSharedFd, op::AsyncifyFd};
let op =
AsyncifyFd::new(to_file.to_shared_fd(), move |file: &std::fs::File| {
BufResult(file.set_times(times).map(|_| 0), ())
});
match compio::runtime::submit(op).await.0.map(|_| ()) {
Ok(()) => {
log::info!("set times for {} to {:?}", self.to.display(), times);
}
Err(err) => {
log::warn!("failed to set times for {}: {}", self.to.display(), err);
}
}
}
}
OpKind::Move { cross_device_copy } => {
// Do not clean up if cross_device_copy is set
if cross_device_copy {
self.skipped.cleanup.set(true);
}
// Remove `to` if overwriting and it is an existing file
if self.to.is_file() {
match ctx.replace(self).await? {
@ -436,286 +520,4 @@ impl Op {
}
Ok(true)
}
async fn copy(
&mut self,
ctx: &mut Context,
mut progress: Progress,
) -> Result<bool, Box<dyn Error>> {
// Remove `to` if overwriting and it is an existing file
if self.to.is_file() {
match ctx.replace(self).await? {
ControlFlow::Continue(to) => {
self.to = to;
}
ControlFlow::Break(ret) => {
return Ok(ret);
}
}
}
let (from_file_open_result, metadata, to_file_open_result) = cosmic::iced::futures::join!(
async {
compio::fs::OpenOptions::new()
.read(true)
.open(&self.from)
.await
.with_context(|| format!("failed to open {} for reading", self.from.display(),))
},
async { compio::fs::metadata(&self.from).await.ok() },
// This is atomic and ensures `to` is not created by any other process
async {
compio::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&self.to)
.await
}
);
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);
if let Some(metadata) = metadata.as_ref()
&& let Err(why) = to_file.set_permissions(metadata.permissions()).await
{
// This error is not propagated upwards as some filesystems do not support setting permissions
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
}
}
// Prevent spamming the progress callbacks.
let mut last_progress_update = Instant::now();
// io_uring/IOCP requires transferring ownership of the buffer to the kernel.
let mut buf_in = std::mem::take(&mut ctx.buf);
// Track where the current read/write position is at.
let mut pos = 0;
loop {
let BufResult(result, buf_out) = from_file.read_at(buf_in, pos).await;
let count = match result {
Ok(0) => {
buf_in = buf_out;
break;
}
Ok(count) => count,
Err(why) => {
ctx.buf = buf_out;
tracing::error!("failed to read: {:?}", why);
_ = futures::future::join(from_file.close(), to_file.close()).await;
return Err(why).context("failed to read")?;
}
};
let BufResult(result, buf_out_slice) =
to_file.write_at(buf_out.slice(..count), pos).await;
let buf_out = buf_out_slice.into_inner();
if let Err(why) = result {
#[cfg(feature = "gvfs")]
if let std::io::ErrorKind::Unsupported = why.kind() {
ctx.buf = buf_out;
_ = futures::future::join(from_file.close(), to_file.close()).await;
return self
.gio_file_copy(ctx, progress)
.await
.map(|_| true)
.map_err(Into::into);
}
tracing::error!("failed to write: {:?}", why);
ctx.buf = buf_out;
_ = futures::future::join(from_file.close(), to_file.close()).await;
return Err(why).context("failed to write")?;
}
progress.current_bytes += count as u64;
pos += count as u64;
// Avoid spamming progress messages too early.
let current = Instant::now();
if current.duration_since(last_progress_update).as_millis() > 49 {
last_progress_update = current;
(ctx.on_progress)(self, &progress);
// Also check if the progress was cancelled.
if let Err(state) = ctx.controller.check().await {
ctx.buf = buf_out;
tracing::warn!(
"operation to copy from {:?} to {:?} cancelled",
self.from,
self.to
);
_ = futures::future::join(from_file.close(), to_file.close()).await;
return Err(OperationError::from_state(state, &ctx.controller).into());
}
}
buf_in = buf_out;
}
ctx.buf = buf_in;
if let Some(metadata) = metadata.as_ref() {
let mut times = fs::FileTimes::new();
if let Ok(time) = metadata.modified() {
times = times.set_modified(time);
}
if let Ok(time) = metadata.accessed() {
times = times.set_accessed(time);
}
//TODO: upstream set_times implementation to compio?
let op = AsyncifyFd::new(to_file.to_shared_fd(), move |file: &std::fs::File| {
BufResult(file.set_times(times).map(|_| 0), ())
});
match compio::runtime::submit(op).await.0.map(|_| ()) {
Ok(()) => {
tracing::info!("set times for {} to {:?}", self.to.display(), times);
}
Err(why) => {
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
tracing::error!(?why, "failed to set times for {}", self.to.display());
}
}
}
}
_ = to_file.close().await;
Ok(true)
}
/// Fallback mechanism in the event that unsupported I/O error errors occur.
/// Fixes unsupported errors when copying large files over MTP.
/// TODO: Find what Gio.File does to work around this.
#[cfg(feature = "gvfs")]
async fn gio_file_copy(
&self,
ctx: &mut Context,
mut progress: Progress,
) -> Result<(), GioCopyError> {
_ = compio::fs::remove_file(&self.to).await;
let from = gio::File::for_path(&self.from);
let to = gio::File::for_path(&self.to);
let (progress_tx, mut progress_rx) = tokio::sync::mpsc::unbounded_channel();
let (tx, rx) = tokio::sync::oneshot::channel();
let (pause_tx, mut pause_rx) = tokio::sync::watch::channel(false);
let task = compio::runtime::spawn_blocking(move || {
let glib_context = glib::MainContext::new();
let glib_loop = glib::MainLoop::new(Some(&glib_context), false);
glib_context.with_thread_default(move || {
let glib_loop2 = glib_loop.clone();
glib::MainContext::ref_thread_default().spawn_local(async move {
// Create a future for copying the file with `gio::File`. This also creates a progress stream.
let (gio_copy_fut, mut progress_stream) = from.copy_future(
&to,
gio::FileCopyFlags::OVERWRITE | gio::FileCopyFlags::ALL_METADATA,
glib::Priority::LOW,
);
let mut copy_fut = gio_copy_fut
.map(|result| result.map_err(GioCopyError::GLib))
.fuse();
let progress_fut = std::pin::pin!(async {
while let Some((current_bytes, _)) = progress_stream.next().await {
_ = progress_tx.send(current_bytes);
}
drop(progress_tx);
futures::future::pending::<()>().await;
});
let mut progress_fut = progress_fut.fuse();
let mut pause_rx2 = pause_rx.clone();
loop {
let until_paused = std::pin::pin!(pause_rx.wait_for(|paused| *paused));
futures::select! {
_ = &mut progress_fut => {},
result = &mut copy_fut => {
_ = tx.send(result.map(|_| ()));
glib_loop2.quit();
return;
}
_ = until_paused.fuse() => {
_ = pause_rx2.wait_for(|paused| !*paused).await;
}
}
}
});
glib_loop.run();
})
});
let mut last_progress_update = Instant::now();
let mut task = task.fuse();
let mut rx = rx.fuse();
loop {
let until_paused = std::pin::pin!(ctx.controller.until_paused());
futures::select! {
value = progress_rx.recv().fuse() => {
if let Some(current_bytes) = value {
progress.current_bytes = current_bytes as u64;
let current = Instant::now();
if current.duration_since(last_progress_update).as_millis() > 49 {
last_progress_update = current;
(ctx.on_progress)(self, &progress);
// Also check if the progress was cancelled.
if let Err(state) = ctx.controller.check().await {
tracing::warn!(
"operation to copy from {:?} to {:?} cancelled",
self.from,
self.to
);
return Err::<(), GioCopyError>(GioCopyError::Controller(
OperationError::from_state(state, &ctx.controller),
));
}
}
}
}
result = rx => return result.unwrap(),
_ = task => (),
_ = until_paused.fuse() => {
// Pauses an active copy while the controller state is paused.
_ = pause_tx.send(true);
ctx.controller.until_unpaused().await;
_ = pause_tx.send(false);
}
}
}
}
}

1959
src/tab.rs

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,15 @@
use image::DynamicImage;
use md5::{Digest, Md5};
use rustc_hash::FxHashMap;
use std::error::Error;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::time::UNIX_EPOCH;
use std::{
error::Error,
fs::{self, File},
io::{self, BufReader, BufWriter},
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
sync::LazyLock,
time::UNIX_EPOCH,
};
use tempfile::NamedTempFile;
use url::Url;
@ -92,7 +93,6 @@ impl ThumbnailCacher {
}
pub fn update_with_temp_file(&self, temp_file: NamedTempFile) -> Result<&Path, Box<dyn Error>> {
#[cfg(unix)]
fs::set_permissions(temp_file.path(), fs::Permissions::from_mode(0o600))?;
self.update_thumbnail_text_metadata(temp_file.path())?;
fs::rename(temp_file.path(), &self.thumbnail_path)?;
@ -127,7 +127,6 @@ impl ThumbnailCacher {
pub fn create_fail_marker(&self) -> Result<(), Box<dyn Error>> {
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
fs::create_dir_all(dir)?;
#[cfg(unix)]
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
}

View file

@ -1,16 +1,15 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "desktop")]
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)]
@ -175,25 +174,3 @@ pub fn thumbnailer(mime: &Mime) -> Vec<Thumbnailer> {
let thumbnailer_cache = THUMBNAILER_CACHE.lock().unwrap();
thumbnailer_cache.get(mime)
}
#[cfg(all(test, feature = "desktop"))]
mod tests {
use super::Thumbnailer;
use std::path::Path;
#[test]
fn command_expands_thumbnailer_arguments() {
let thumbnailer = Thumbnailer {
exec: "cosmic-files-thumbnailer %o --size %s %i".to_string(),
};
let command = thumbnailer
.command(Path::new("input.exe"), Path::new("output.png"), 128)
.expect("valid thumbnailer command");
assert_eq!(command.get_program(), "cosmic-files-thumbnailer");
assert_eq!(
command.get_args().collect::<Vec<_>>(),
["output.png", "--size", "128", "input.exe"]
);
}
}

View file

@ -1,234 +0,0 @@
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};
fn percent_decode(s: &str) -> Option<String> {
let (mut r, mut b) = (String::with_capacity(s.len()), s.bytes());
while let Some(c) = b.next() {
if c == b'%' {
let (hi, lo) = (b.next()?, b.next()?);
let h = |x: u8| match x {
b'0'..=b'9' => Some(x - b'0'),
b'a'..=b'f' => Some(x - b'a' + 10),
b'A'..=b'F' => Some(x - b'A' + 10),
_ => None,
};
r.push((h(hi)? << 4 | h(lo)?) as char);
} else {
r.push(c as char);
}
}
Some(r)
}
pub trait TrashExt {
fn is_empty() -> bool {
true
}
fn entries() -> usize {
0
}
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
Err(trash::Error::Unknown {
description: "reading trash folders not supported on this platform".into(),
})
}
fn scan(_sizes: IconSizes) -> Vec<Item> {
log::warn!("viewing trash not supported on this platform");
Vec::new()
}
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() {
"user-trash"
} else {
"user-trash-full"
})
.size(icon_size)
.handle()
}
fn icon_symbolic(icon_size: u16) -> widget::icon::Handle {
widget::icon::from_name(if Self::is_empty() {
"user-trash-symbolic"
} else {
"user-trash-full-symbolic"
})
.size(icon_size)
.handle()
}
}
/// Derive the actual filesystem path of a trashed item from its .trashinfo path
/// (or return the id directly if it's already a filesystem path).
pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
let id_path = Path::new(&item.id);
if id_path.extension().map_or(false, |e| e == "trashinfo") {
let trash_root = id_path.parent()?.parent()?;
let file_name = id_path.file_stem()?;
Some(trash_root.join("files").join(file_name))
} else {
Some(PathBuf::from(&item.id))
}
}
/// For a path inside a trash `files/` directory, reconstruct the original path
/// by reading the parent `.trashinfo` file.
///
/// Given `~/.local/share/Trash/files/folder/sub/file.txt`:
/// - The top-level trashed item is `folder`
/// - 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 = trash_files_dir(p)?;
let root = files.parent()?;
let top = p.strip_prefix(files).ok()?.components().next()?;
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);
if !rel.as_os_str().is_empty() {
result.push(rel);
}
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 {
trash_files_dir(path).is_some()
}
pub struct Trash;
// This config statement is from trash::os_limited
#[cfg(any(
target_os = "windows",
all(
unix,
not(target_os = "macos"),
not(target_os = "ios"),
not(target_os = "android")
)
))]
impl TrashExt for Trash {
fn is_empty() -> bool {
trash::os_limited::is_empty().unwrap_or(true)
}
fn entries() -> usize {
match trash::os_limited::list() {
Ok(entries) => entries.len(),
Err(_err) => 0,
}
}
// Not available on Windows only
#[cfg(not(target_os = "windows"))]
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
trash::os_limited::trash_folders()
}
fn scan(sizes: IconSizes) -> Vec<Item> {
use crate::localize::LANGUAGE_SORTER;
use crate::tab::item_from_trash_entry;
use std::cmp::Ordering;
let entries = match trash::os_limited::list() {
Ok(entry) => entry,
Err(err) => {
log::warn!("failed to read trash items: {err}");
return Vec::new();
}
};
let mut items: Vec<_> = entries
.into_iter()
.filter_map(|entry| {
let metadata = trash::os_limited::metadata(&entry)
.inspect_err(|err| {
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
})
.ok()?;
Some(item_from_trash_entry(entry, metadata, sizes))
})
.collect();
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
});
items
}
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
let entries = match trash::os_limited::list() {
Ok(entries) => entries,
Err(err) => {
log::warn!("failed to read trash items: {err}");
return;
}
};
for entry in entries {
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
}) {
let name = entry.name.to_string_lossy();
if regex.is_match(&name) && !callback(SearchItem::Trash(entry, metadata)) {
break;
}
}
}
}
}
// This config statement is from trash::os_limited, inverted
#[cfg(not(any(
target_os = "windows",
all(
unix,
not(target_os = "macos"),
not(target_os = "ios"),
not(target_os = "android")
)
)))]
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);
}
}

View file

@ -1,7 +1,6 @@
use std::num::NonZeroU16;
use crate::config::IconSizes;
use crate::tab::View;
use crate::{config::IconSizes, tab::View};
static DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap();
static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap();