Clean up mime app handling and make it possible to set default application, part of #325
This commit is contained in:
parent
691719ade7
commit
ceab7835ad
37 changed files with 306 additions and 114 deletions
89
Cargo.lock
generated
89
Cargo.lock
generated
|
|
@ -158,6 +158,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
|
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "almost"
|
name = "almost"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -880,6 +886,39 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cached"
|
||||||
|
version = "0.54.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"cached_proc_macro",
|
||||||
|
"cached_proc_macro_types",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"once_cell",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cached_proc_macro"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.96",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cached_proc_macro_types"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calloop"
|
name = "calloop"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
@ -1229,6 +1268,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"cosmic-mime-apps",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
|
|
@ -1299,6 +1339,17 @@ dependencies = [
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cosmic-mime-apps"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/pop-os/cosmic-mime-apps.git#a5aefbd2e914682c151f3b8054dd711e7f57941d"
|
||||||
|
dependencies = [
|
||||||
|
"freedesktop-desktop-entry 0.7.7",
|
||||||
|
"mime 0.3.17",
|
||||||
|
"quick-xml 0.37.2",
|
||||||
|
"xdg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-protocols"
|
name = "cosmic-protocols"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -2138,6 +2189,23 @@ dependencies = [
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "freedesktop-desktop-entry"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "016f6ee9509f11c985aa402451f4ee900d1fafeb501a4c3d734ebecfc1130e05"
|
||||||
|
dependencies = [
|
||||||
|
"cached",
|
||||||
|
"dirs 5.0.1",
|
||||||
|
"gettext-rs",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"strsim 0.11.1",
|
||||||
|
"textdistance",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
"xdg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "freedesktop_entry_parser"
|
name = "freedesktop_entry_parser"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -2567,6 +2635,10 @@ name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
|
@ -3537,7 +3609,7 @@ dependencies = [
|
||||||
"cosmic-theme",
|
"cosmic-theme",
|
||||||
"css-color",
|
"css-color",
|
||||||
"derive_setters",
|
"derive_setters",
|
||||||
"freedesktop-desktop-entry",
|
"freedesktop-desktop-entry 0.5.2",
|
||||||
"iced",
|
"iced",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
|
|
@ -4923,6 +4995,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.37.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.38"
|
||||||
|
|
@ -5881,6 +5962,12 @@ dependencies = [
|
||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textdistance"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ vergen = { version = "8", features = ["git", "gitcl"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4", features = ["unstable-locales"] }
|
chrono = { version = "0.4", features = ["unstable-locales"] }
|
||||||
|
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
freedesktop_entry_parser = "1.3"
|
freedesktop_entry_parser = "1.3"
|
||||||
|
|
@ -67,7 +68,7 @@ features = ["multi-window", "tokio", "winit"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bzip2", "desktop", "gvfs", "liblzma", "notify", "wgpu"]
|
default = ["bzip2", "desktop", "gvfs", "liblzma", "notify", "wgpu"]
|
||||||
desktop = ["libcosmic/desktop", "dep:xdg"]
|
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||||
gvfs = ["dep:gio", "dep:glib"]
|
gvfs = ["dep:gio", "dep:glib"]
|
||||||
jemalloc = ["dep:tikv-jemallocator"]
|
jemalloc = ["dep:tikv-jemallocator"]
|
||||||
notify = ["dep:notify-rust"]
|
notify = ["dep:notify-rust"]
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ complete = انتهى
|
||||||
copy_noun = ينسخ
|
copy_noun = ينسخ
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = افتح باستخدام
|
menu-open-with = افتح باستخدام
|
||||||
default-app = {$name} (المبدئي)
|
default-app = {$name} (المبدئي)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ restored = Адноўлена {$items} {$items ->
|
||||||
unknown-folder = невядомая папка
|
unknown-folder = невядомая папка
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Адкрыць з дапамогай
|
menu-open-with = Адкрыць з дапамогай
|
||||||
default-app = {$name} (па змаўчанні)
|
default-app = {$name} (па змаўчанні)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ complete = Hotovo
|
||||||
copy_noun = Kopírovat
|
copy_noun = Kopírovat
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Otevřít v
|
menu-open-with = Otevřít v
|
||||||
default-app = {$name} (výchozí)
|
default-app = {$name} (výchozí)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ restored = Genoprettet {$items} {$items ->
|
||||||
unknown-folder = ukendt mappe
|
unknown-folder = ukendt mappe
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Åbn med...
|
menu-open-with = Åbn med...
|
||||||
default-app = {$name} (standardindstilling)
|
default-app = {$name} (standardindstilling)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ restored = {$items} {$items ->
|
||||||
unknown-folder = unbekannter Ordner
|
unknown-folder = unbekannter Ordner
|
||||||
|
|
||||||
## Öffnen mit
|
## Öffnen mit
|
||||||
open-with = Öffnen mit
|
menu-open-with = Öffnen mit
|
||||||
default-app = {$name} (Standard)
|
default-app = {$name} (Standard)
|
||||||
|
|
||||||
## Details anzeigen
|
## Details anzeigen
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ set-executable-and-launch-description = Do you want to set "{$name}" as executab
|
||||||
set-and-launch = Set and launch
|
set-and-launch = Set and launch
|
||||||
|
|
||||||
## Metadata Dialog
|
## Metadata Dialog
|
||||||
|
open-with = Open with
|
||||||
owner = Owner
|
owner = Owner
|
||||||
group = Group
|
group = Group
|
||||||
other = Other
|
other = Other
|
||||||
|
|
@ -196,7 +197,7 @@ restored = Restored {$items} {$items ->
|
||||||
unknown-folder = unknown folder
|
unknown-folder = unknown folder
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Open with...
|
menu-open-with = Open with...
|
||||||
default-app = {$name} (default)
|
default-app = {$name} (default)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ restored = Se ha restaurado {$items} {$items ->
|
||||||
unknown-folder = carpeta desconocida
|
unknown-folder = carpeta desconocida
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Abrir con
|
menu-open-with = Abrir con
|
||||||
default-app = {$name} (predeterminado)
|
default-app = {$name} (predeterminado)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ complete = Completadas
|
||||||
copy_noun = Copia
|
copy_noun = Copia
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Abrir con
|
menu-open-with = Abrir con
|
||||||
default-app = {$name} (por defecto)
|
default-app = {$name} (por defecto)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ restored = Palautettu {$items} {$items ->
|
||||||
unknown-folder = Tuntematon kansio
|
unknown-folder = Tuntematon kansio
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Avaa ohjelmalla…
|
menu-open-with = Avaa ohjelmalla…
|
||||||
default-app = {$name} (oletus)
|
default-app = {$name} (oletus)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ restored = {$items} {$items ->
|
||||||
unknown-folder = Dossier inconnu
|
unknown-folder = Dossier inconnu
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Ouvrir avec
|
menu-open-with = Ouvrir avec
|
||||||
default-app = {$name} (défaut)
|
default-app = {$name} (défaut)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ restored = {$items} {$items ->
|
||||||
unknown-folder = अज्ञात फ़ोल्डर
|
unknown-folder = अज्ञात फ़ोल्डर
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = इसके साथ खोलें
|
menu-open-with = इसके साथ खोलें
|
||||||
default-app = {$name} (डिफ़ॉल्ट)
|
default-app = {$name} (डिफ़ॉल्ट)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ undo = Visszavonás
|
||||||
unknown-folder = ismeretlen mappa
|
unknown-folder = ismeretlen mappa
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Megnyitás ezzel
|
menu-open-with = Megnyitás ezzel
|
||||||
default-app = {$name} (alapértelmezett)
|
default-app = {$name} (alapértelmezett)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ restored = Ripristinato {$items} {$items ->
|
||||||
unknown-folder = cartella sconosciuta
|
unknown-folder = cartella sconosciuta
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Apri con
|
menu-open-with = Apri con
|
||||||
default-app = {$name} (default)
|
default-app = {$name} (default)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ undo = 元に戻す
|
||||||
unknown-folder = 不明なフォルダー
|
unknown-folder = 不明なフォルダー
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = 別のアプリケーションで開く
|
menu-open-with = 別のアプリケーションで開く
|
||||||
default-app = {$name} (デフォルト)
|
default-app = {$name} (デフォルト)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ restored = {$items} {$items ->
|
||||||
unknown-folder = ಅಜ್ಞಾತ ಫೋಲ್ಡರ್
|
unknown-folder = ಅಜ್ಞಾತ ಫೋಲ್ಡರ್
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = ಇದರೊಂದಿಗೆ ತೆರೆಯಿರಿ
|
menu-open-with = ಇದರೊಂದಿಗೆ ತೆರೆಯಿರಿ
|
||||||
default-app = {$name} (ಸ್ಥೂಲ)
|
default-app = {$name} (ಸ್ಥೂಲ)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ failed = 실패
|
||||||
complete = 완료
|
complete = 완료
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = 다른 앱으로 열기
|
menu-open-with = 다른 앱으로 열기
|
||||||
default-app = {$name} (기본)
|
default-app = {$name} (기본)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ restored = {$items} {$items ->
|
||||||
unknown-folder = Onbekende map
|
unknown-folder = Onbekende map
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Openen met...
|
menu-open-with = Openen met...
|
||||||
default-app = {$name} (standaard)
|
default-app = {$name} (standaard)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ restored = Przywrócono {$items} {$items ->
|
||||||
unknown-folder = nieznany katalog
|
unknown-folder = nieznany katalog
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Otwórz za pomocą…
|
menu-open-with = Otwórz za pomocą…
|
||||||
default-app = {$name} (domyślnie)
|
default-app = {$name} (domyślnie)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ restored = Restaurado {$items} {$items ->
|
||||||
unknown-folder = pasta desconhecida
|
unknown-folder = pasta desconhecida
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Abrir com...
|
menu-open-with = Abrir com...
|
||||||
default-app = {$name} (padrão)
|
default-app = {$name} (padrão)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ undo = Desfazer
|
||||||
unknown-folder = pasta desconhecida
|
unknown-folder = pasta desconhecida
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Abrir com...
|
menu-open-with = Abrir com...
|
||||||
default-app = {$name} (predefinição)
|
default-app = {$name} (predefinição)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ restored = Restaurat {$items} {$items ->
|
||||||
unknown-folder = dosar necunoscut
|
unknown-folder = dosar necunoscut
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Deschide cu...
|
menu-open-with = Deschide cu...
|
||||||
default-app = {$name} (implicit)
|
default-app = {$name} (implicit)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ restored = Восстановлено {$items} {$items ->
|
||||||
unknown-folder = неизвестная папка
|
unknown-folder = неизвестная папка
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Открыть с помощью
|
menu-open-with = Открыть с помощью
|
||||||
default-app = {$name} (по умолчанию)
|
default-app = {$name} (по умолчанию)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ undo = Späť
|
||||||
unknown-folder = neznámy priečinok
|
unknown-folder = neznámy priečinok
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Otvoriť s
|
menu-open-with = Otvoriť s
|
||||||
default-app = {$name} (Predvolené)
|
default-app = {$name} (Predvolené)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ restored = Återställt {$items} {$items ->
|
||||||
unknown-folder = okänd katalog
|
unknown-folder = okänd katalog
|
||||||
|
|
||||||
## Öppna med
|
## Öppna med
|
||||||
open-with = Öppna med...
|
menu-open-with = Öppna med...
|
||||||
default-app = {$name} (default)
|
default-app = {$name} (default)
|
||||||
|
|
||||||
## Visa detaljer
|
## Visa detaljer
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ restored = Restored {$items} {$items ->
|
||||||
unknown-folder = แฟ้มที่ไม่รู้จัก
|
unknown-folder = แฟ้มที่ไม่รู้จัก
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = เปิดด้วย...
|
menu-open-with = เปิดด้วย...
|
||||||
default-app = {$name} (ค่าเริ่มต้น)
|
default-app = {$name} (ค่าเริ่มต้น)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ restored = {$items} öge "{$trash}"ten "{$to}" dizinine geri yüklenildi ({$prog
|
||||||
unknown-folder = bilinmeyen klasör
|
unknown-folder = bilinmeyen klasör
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Birlikte aç...
|
menu-open-with = Birlikte aç...
|
||||||
default-app = {$name} (varsayılan)
|
default-app = {$name} (varsayılan)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ undo = Скасувати
|
||||||
unknown-folder = невідома тека
|
unknown-folder = невідома тека
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = Відкрити за допомогою
|
menu-open-with = Відкрити за допомогою
|
||||||
default-app = {$name} (типово)
|
default-app = {$name} (типово)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ restored = 已从 {trash} 还原 {$items} 个项目
|
||||||
unknown-folder = 未知文件夹
|
unknown-folder = 未知文件夹
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = 打开方式...
|
menu-open-with = 打开方式...
|
||||||
default-app = {$name} (默认)
|
default-app = {$name} (默认)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ restored = 已還原 {$items} 項目 {$items ->
|
||||||
unknown-folder = 未知資料夾
|
unknown-folder = 未知資料夾
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
open-with = 開啟方式...
|
menu-open-with = 開啟方式...
|
||||||
default-app = {$name} (預設)
|
default-app = {$name} (預設)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
|
||||||
81
src/app.rs
81
src/app.rs
|
|
@ -444,7 +444,6 @@ pub enum DialogPage {
|
||||||
OpenWith {
|
OpenWith {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
mime: mime_guess::Mime,
|
mime: mime_guess::Mime,
|
||||||
apps: Vec<mime_app::MimeApp>,
|
|
||||||
selected: usize,
|
selected: usize,
|
||||||
store_opt: Option<mime_app::MimeApp>,
|
store_opt: Option<mime_app::MimeApp>,
|
||||||
},
|
},
|
||||||
|
|
@ -514,6 +513,7 @@ pub struct App {
|
||||||
dialog_text_input: widget::Id,
|
dialog_text_input: widget::Id,
|
||||||
key_binds: HashMap<KeyBind, Action>,
|
key_binds: HashMap<KeyBind, Action>,
|
||||||
margin: HashMap<window::Id, (f32, f32, f32, f32)>,
|
margin: HashMap<window::Id, (f32, f32, f32, f32)>,
|
||||||
|
mime_app_cache: mime_app::MimeAppCache,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||||
network_drive_connecting: Option<(MounterKey, String)>,
|
network_drive_connecting: Option<(MounterKey, String)>,
|
||||||
|
|
@ -592,7 +592,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try mime apps, which should be faster than xdg-open
|
// Try mime apps, which should be faster than xdg-open
|
||||||
for app in mime_app::mime_apps(&mime) {
|
for app in self.mime_app_cache.get(&mime) {
|
||||||
let Some(mut command) = app.command(Some(path.clone().into())) else {
|
let Some(mut command) = app.command(Some(path.clone().into())) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -1431,21 +1431,24 @@ impl App {
|
||||||
entity_opt: &Option<Entity>,
|
entity_opt: &Option<Entity>,
|
||||||
kind: &'a PreviewKind,
|
kind: &'a PreviewKind,
|
||||||
context_drawer: bool,
|
context_drawer: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, tab::Message> {
|
||||||
let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut children = Vec::with_capacity(1);
|
let mut children = Vec::with_capacity(1);
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
match kind {
|
match kind {
|
||||||
PreviewKind::Custom(PreviewItem(item)) => {
|
PreviewKind::Custom(PreviewItem(item)) => {
|
||||||
children.push(item.preview_view(IconSizes::default()));
|
children.push(item.preview_view(Some(&self.mime_app_cache), IconSizes::default()));
|
||||||
}
|
}
|
||||||
PreviewKind::Location(location) => {
|
PreviewKind::Location(location) => {
|
||||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||||
if let Some(items) = tab.items_opt() {
|
if let Some(items) = tab.items_opt() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.location_opt.as_ref() == Some(location) {
|
if item.location_opt.as_ref() == Some(location) {
|
||||||
children.push(item.preview_view(tab.config.icon_sizes));
|
children.push(item.preview_view(
|
||||||
|
Some(&self.mime_app_cache),
|
||||||
|
tab.config.icon_sizes,
|
||||||
|
));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -1459,7 +1462,10 @@ impl App {
|
||||||
if let Some(items) = tab.items_opt() {
|
if let Some(items) = tab.items_opt() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.selected {
|
if item.selected {
|
||||||
children.push(item.preview_view(tab.config.icon_sizes));
|
children.push(item.preview_view(
|
||||||
|
Some(&self.mime_app_cache),
|
||||||
|
tab.config.icon_sizes,
|
||||||
|
));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -1467,7 +1473,10 @@ impl App {
|
||||||
}
|
}
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
if let Some(item) = &tab.parent_item_opt {
|
if let Some(item) = &tab.parent_item_opt {
|
||||||
children.push(item.preview_view(tab.config.icon_sizes));
|
children.push(item.preview_view(
|
||||||
|
Some(&self.mime_app_cache),
|
||||||
|
tab.config.icon_sizes,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1573,6 +1582,7 @@ impl Application for App {
|
||||||
dialog_text_input: widget::Id::unique(),
|
dialog_text_input: widget::Id::unique(),
|
||||||
key_binds,
|
key_binds,
|
||||||
margin: HashMap::new(),
|
margin: HashMap::new(),
|
||||||
|
mime_app_cache: mime_app::MimeAppCache::new(),
|
||||||
modifiers: Modifiers::empty(),
|
modifiers: Modifiers::empty(),
|
||||||
mounter_items: HashMap::new(),
|
mounter_items: HashMap::new(),
|
||||||
network_drive_connecting: None,
|
network_drive_connecting: None,
|
||||||
|
|
@ -1688,7 +1698,7 @@ impl Application for App {
|
||||||
NavMenuAction::Open(entity),
|
NavMenuAction::Open(entity),
|
||||||
));
|
));
|
||||||
items.push(cosmic::widget::menu::Item::Button(
|
items.push(cosmic::widget::menu::Item::Button(
|
||||||
fl!("open-with"),
|
fl!("menu-open-with"),
|
||||||
None,
|
None,
|
||||||
NavMenuAction::OpenWith(entity),
|
NavMenuAction::OpenWith(entity),
|
||||||
));
|
));
|
||||||
|
|
@ -2016,11 +2026,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
DialogPage::OpenWith {
|
DialogPage::OpenWith {
|
||||||
path,
|
path,
|
||||||
apps,
|
mime,
|
||||||
selected,
|
selected,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if let Some(app) = apps.get(selected) {
|
if let Some(app) = self.mime_app_cache.get(&mime).get(selected) {
|
||||||
if let Some(mut command) = app.command(Some(path.clone().into())) {
|
if let Some(mut command) = app.command(Some(path.clone().into())) {
|
||||||
match spawn_detached(&mut command) {
|
match spawn_detached(&mut command) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
|
@ -2345,7 +2355,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::OpenTerminal(entity_opt) => {
|
Message::OpenTerminal(entity_opt) => {
|
||||||
if let Some(terminal) = mime_app::terminal() {
|
if let Some(terminal) = self.mime_app_cache.terminal() {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
|
|
@ -2471,12 +2481,13 @@ impl Application for App {
|
||||||
return self.update(Message::DialogPush(DialogPage::OpenWith {
|
return self.update(Message::DialogPush(DialogPage::OpenWith {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
mime: item.mime.clone(),
|
mime: item.mime.clone(),
|
||||||
apps: item.open_with.clone(),
|
|
||||||
selected: 0,
|
selected: 0,
|
||||||
store_opt: "x-scheme-handler/mime"
|
store_opt: "x-scheme-handler/mime"
|
||||||
.parse::<mime_guess::Mime>()
|
.parse::<mime_guess::Mime>()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|mime| mime_app::mime_apps(&mime).first().cloned()),
|
.and_then(|mime| {
|
||||||
|
self.mime_app_cache.get(&mime).first().cloned()
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2905,9 +2916,11 @@ impl Application for App {
|
||||||
App::exec_entry_action(entry, action);
|
App::exec_entry_action(entry, action);
|
||||||
}
|
}
|
||||||
tab::Command::Iced(iced_command) => {
|
tab::Command::Iced(iced_command) => {
|
||||||
commands.push(iced_command.0.map(move |tab_message| {
|
commands.push(
|
||||||
message::app(Message::TabMessage(Some(entity), tab_message))
|
iced_command.0.map(move |x| {
|
||||||
}));
|
message::app(Message::TabMessage(Some(entity), x))
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
tab::Command::MoveToTrash(paths) => {
|
tab::Command::MoveToTrash(paths) => {
|
||||||
self.operation(Operation::Delete { paths });
|
self.operation(Operation::Delete { paths });
|
||||||
|
|
@ -2942,6 +2955,10 @@ impl Application for App {
|
||||||
self.context_page = ContextPage::Preview(Some(entity), kind);
|
self.context_page = ContextPage::Preview(Some(entity), kind);
|
||||||
self.set_show_context(true);
|
self.set_show_context(true);
|
||||||
}
|
}
|
||||||
|
tab::Command::SetOpenWith(mime, id) => {
|
||||||
|
//TODO: this will block for a few ms, run in background?
|
||||||
|
self.mime_app_cache.set_default(mime, id);
|
||||||
|
}
|
||||||
tab::Command::WindowDrag => {
|
tab::Command::WindowDrag => {
|
||||||
if let Some(window_id) = &self.window_id_opt {
|
if let Some(window_id) = &self.window_id_opt {
|
||||||
commands.push(window::drag(*window_id));
|
commands.push(window::drag(*window_id));
|
||||||
|
|
@ -3282,13 +3299,12 @@ impl Application for App {
|
||||||
return self.update(Message::DialogPush(DialogPage::OpenWith {
|
return self.update(Message::DialogPush(DialogPage::OpenWith {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
mime: item.mime.clone(),
|
mime: item.mime.clone(),
|
||||||
apps: item.open_with.clone(),
|
|
||||||
selected: 0,
|
selected: 0,
|
||||||
store_opt: "x-scheme-handler/mime"
|
store_opt: "x-scheme-handler/mime"
|
||||||
.parse::<mime_guess::Mime>()
|
.parse::<mime_guess::Mime>()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|mime| {
|
.and_then(|mime| {
|
||||||
mime_app::mime_apps(&mime).first().cloned()
|
self.mime_app_cache.get(&mime).first().cloned()
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -3530,14 +3546,17 @@ impl Application for App {
|
||||||
if let Some(items) = tab.items_opt() {
|
if let Some(items) = tab.items_opt() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.selected {
|
if item.selected {
|
||||||
actions.extend(item.preview_header())
|
actions.extend(item.preview_header().into_iter().map(|element| {
|
||||||
|
element.map(move |x| Message::TabMessage(Some(entity), x))
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
context_drawer::context_drawer(
|
context_drawer::context_drawer(
|
||||||
self.preview(entity_opt, kind, true),
|
self.preview(entity_opt, kind, true)
|
||||||
Message::ToggleContextPage(ContextPage::Preview(*entity_opt, kind.clone())),
|
.map(move |x| Message::TabMessage(Some(entity), x)),
|
||||||
|
Message::ToggleContextPage(ContextPage::Preview(Some(entity), kind.clone())),
|
||||||
)
|
)
|
||||||
.header_actions(actions)
|
.header_actions(actions)
|
||||||
}
|
}
|
||||||
|
|
@ -3556,7 +3575,7 @@ impl Application for App {
|
||||||
if tab.gallery {
|
if tab.gallery {
|
||||||
return Some(
|
return Some(
|
||||||
tab.gallery_view()
|
tab.gallery_view()
|
||||||
.map(move |tab_message| Message::TabMessage(Some(entity), tab_message)),
|
.map(move |x| Message::TabMessage(Some(entity), x)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3878,7 +3897,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
DialogPage::OpenWith {
|
DialogPage::OpenWith {
|
||||||
path,
|
path,
|
||||||
apps,
|
mime,
|
||||||
selected,
|
selected,
|
||||||
store_opt,
|
store_opt,
|
||||||
..
|
..
|
||||||
|
|
@ -3889,7 +3908,7 @@ impl Application for App {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut column = widget::list_column();
|
let mut column = widget::list_column();
|
||||||
for (i, app) in apps.iter().enumerate() {
|
for (i, app) in self.mime_app_cache.get(mime).iter().enumerate() {
|
||||||
column = column.add(
|
column = column.add(
|
||||||
widget::button::custom(
|
widget::button::custom(
|
||||||
widget::row::with_children(vec![
|
widget::row::with_children(vec![
|
||||||
|
|
@ -4026,8 +4045,14 @@ impl Application for App {
|
||||||
let dialog = widget::dialog()
|
let dialog = widget::dialog()
|
||||||
.title(fl!("replace-title", filename = to.name.as_str()))
|
.title(fl!("replace-title", filename = to.name.as_str()))
|
||||||
.body(fl!("replace-warning-operation"))
|
.body(fl!("replace-warning-operation"))
|
||||||
.control(to.replace_view(fl!("original-file"), IconSizes::default()))
|
.control(
|
||||||
.control(from.replace_view(fl!("replace-with"), IconSizes::default()))
|
to.replace_view(fl!("original-file"), IconSizes::default())
|
||||||
|
.map(|x| Message::TabMessage(None, x)),
|
||||||
|
)
|
||||||
|
.control(
|
||||||
|
from.replace_view(fl!("replace-with"), IconSizes::default())
|
||||||
|
.map(|x| Message::TabMessage(None, x)),
|
||||||
|
)
|
||||||
.primary_action(widget::button::suggested(fl!("replace")).on_press(
|
.primary_action(widget::button::suggested(fl!("replace")).on_press(
|
||||||
Message::ReplaceResult(ReplaceResult::Replace(*apply_to_all)),
|
Message::ReplaceResult(ReplaceResult::Replace(*apply_to_all)),
|
||||||
));
|
));
|
||||||
|
|
@ -4365,7 +4390,9 @@ impl Application for App {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Some(WindowKind::DesktopViewOptions) => self.desktop_view_options(),
|
Some(WindowKind::DesktopViewOptions) => self.desktop_view_options(),
|
||||||
Some(WindowKind::Preview(entity_opt, kind)) => self.preview(entity_opt, kind, false),
|
Some(WindowKind::Preview(entity_opt, kind)) => self
|
||||||
|
.preview(entity_opt, kind, false)
|
||||||
|
.map(|x| Message::TabMessage(*entity_opt, x)),
|
||||||
None => {
|
None => {
|
||||||
//TODO: distinct views per monitor in desktop mode
|
//TODO: distinct views per monitor in desktop mode
|
||||||
return self.view_main().map(|message| match message {
|
return self.view_main().map(|message| match message {
|
||||||
|
|
|
||||||
|
|
@ -471,17 +471,17 @@ impl App {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview<'a>(&'a self, kind: &'a PreviewKind) -> Element<'a, AppMessage> {
|
fn preview<'a>(&'a self, kind: &'a PreviewKind) -> Element<'a, tab::Message> {
|
||||||
let mut children = Vec::with_capacity(1);
|
let mut children = Vec::with_capacity(1);
|
||||||
match kind {
|
match kind {
|
||||||
PreviewKind::Custom(PreviewItem(item)) => {
|
PreviewKind::Custom(PreviewItem(item)) => {
|
||||||
children.push(item.preview_view(IconSizes::default()));
|
children.push(item.preview_view(None, IconSizes::default()));
|
||||||
}
|
}
|
||||||
PreviewKind::Location(location) => {
|
PreviewKind::Location(location) => {
|
||||||
if let Some(items) = self.tab.items_opt() {
|
if let Some(items) = self.tab.items_opt() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.location_opt.as_ref() == Some(location) {
|
if item.location_opt.as_ref() == Some(location) {
|
||||||
children.push(item.preview_view(self.tab.config.icon_sizes));
|
children.push(item.preview_view(None, self.tab.config.icon_sizes));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -493,7 +493,7 @@ impl App {
|
||||||
if let Some(items) = self.tab.items_opt() {
|
if let Some(items) = self.tab.items_opt() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.selected {
|
if item.selected {
|
||||||
children.push(item.preview_view(self.tab.config.icon_sizes));
|
children.push(item.preview_view(None, self.tab.config.icon_sizes));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -501,7 +501,7 @@ impl App {
|
||||||
}
|
}
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
if let Some(item) = &self.tab.parent_item_opt {
|
if let Some(item) = &self.tab.parent_item_opt {
|
||||||
children.push(item.preview_view(self.tab.config.icon_sizes));
|
children.push(item.preview_view(None, self.tab.config.icon_sizes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -814,14 +814,14 @@ impl Application for App {
|
||||||
actions.extend(
|
actions.extend(
|
||||||
item.preview_header()
|
item.preview_header()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|element| element.map(Message::from)),
|
.map(|element| element.map(Message::TabMessage)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(
|
Some(
|
||||||
context_drawer::context_drawer(
|
context_drawer::context_drawer(
|
||||||
self.preview(kind).map(Message::from),
|
self.preview(kind).map(Message::TabMessage),
|
||||||
Message::Preview,
|
Message::Preview,
|
||||||
)
|
)
|
||||||
.header_actions(actions),
|
.header_actions(actions),
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ pub fn context_menu<'a>(
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||||
}
|
}
|
||||||
if selected == 1 {
|
if selected == 1 {
|
||||||
children.push(menu_item(fl!("open-with"), Action::OpenWith).into());
|
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith).into());
|
||||||
if selected_dir == 1 {
|
if selected_dir == 1 {
|
||||||
children
|
children
|
||||||
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
||||||
|
|
@ -531,7 +531,7 @@ pub fn menu_bar<'a>(
|
||||||
Action::Open,
|
Action::Open,
|
||||||
(selected > 0 && selected_dir == 0) || (selected_dir == 1 && selected == 1),
|
(selected > 0 && selected_dir == 0) || (selected_dir == 1 && selected == 1),
|
||||||
),
|
),
|
||||||
menu_button_optional(fl!("open-with"), Action::OpenWith, selected == 1),
|
menu_button_optional(fl!("menu-open-with"), Action::OpenWith, selected == 1),
|
||||||
menu::Item::Divider,
|
menu::Item::Divider,
|
||||||
menu_button_optional(fl!("rename"), Action::Rename, selected > 0),
|
menu_button_optional(fl!("rename"), Action::Rename, selected > 0),
|
||||||
menu::Item::Divider,
|
menu::Item::Divider,
|
||||||
|
|
|
||||||
104
src/mime_app.rs
104
src/mime_app.rs
|
|
@ -5,9 +5,8 @@
|
||||||
use cosmic::desktop;
|
use cosmic::desktop;
|
||||||
use cosmic::widget;
|
use cosmic::widget;
|
||||||
pub use mime_guess::Mime;
|
pub use mime_guess::Mime;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering, collections::HashMap, env, ffi::OsString, path::PathBuf, process, sync::Mutex,
|
cmp::Ordering, collections::HashMap, env, ffi::OsString, fs, io, path::PathBuf, process,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,6 +51,13 @@ impl MimeApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This allows usage of MimeApp in a dropdown
|
||||||
|
impl AsRef<str> for MimeApp {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
impl From<&desktop::DesktopEntryData> for MimeApp {
|
impl From<&desktop::DesktopEntryData> for MimeApp {
|
||||||
fn from(app: &desktop::DesktopEntryData) -> Self {
|
fn from(app: &desktop::DesktopEntryData) -> Self {
|
||||||
|
|
@ -82,6 +88,7 @@ fn filename_eq(path_opt: &Option<PathBuf>, filename: &str) -> bool {
|
||||||
|
|
||||||
pub struct MimeAppCache {
|
pub struct MimeAppCache {
|
||||||
cache: HashMap<Mime, Vec<MimeApp>>,
|
cache: HashMap<Mime, Vec<MimeApp>>,
|
||||||
|
icons: HashMap<Mime, Vec<widget::icon::Handle>>,
|
||||||
terminals: Vec<MimeApp>,
|
terminals: Vec<MimeApp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +96,7 @@ impl MimeAppCache {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut mime_app_cache = Self {
|
let mut mime_app_cache = Self {
|
||||||
cache: HashMap::new(),
|
cache: HashMap::new(),
|
||||||
|
icons: HashMap::new(),
|
||||||
terminals: Vec::new(),
|
terminals: Vec::new(),
|
||||||
};
|
};
|
||||||
mime_app_cache.reload();
|
mime_app_cache.reload();
|
||||||
|
|
@ -106,6 +114,7 @@ impl MimeAppCache {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
self.cache.clear();
|
self.cache.clear();
|
||||||
|
self.icons.clear();
|
||||||
self.terminals.clear();
|
self.terminals.clear();
|
||||||
|
|
||||||
//TODO: get proper locale?
|
//TODO: get proper locale?
|
||||||
|
|
@ -254,37 +263,88 @@ impl MimeAppCache {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy icons to special cache
|
||||||
|
//TODO: adjust dropdown API so this is no longer needed
|
||||||
|
for (mime, apps) in self.cache.iter() {
|
||||||
|
self.icons.insert(
|
||||||
|
mime.clone(),
|
||||||
|
apps.iter().map(|app| app.icon.clone()).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
log::info!("loaded mime app cache in {:?}", elapsed);
|
log::info!("loaded mime app cache in {:?}", elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, key: &Mime) -> Vec<MimeApp> {
|
pub fn get(&self, key: &Mime) -> &[MimeApp] {
|
||||||
self.cache.get(key).map_or_else(Vec::new, |x| x.clone())
|
static EMPTY: Vec<MimeApp> = Vec::new();
|
||||||
|
self.cache.get(key).unwrap_or_else(|| &EMPTY)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static MIME_APP_CACHE: Lazy<Mutex<MimeAppCache>> = Lazy::new(|| Mutex::new(MimeAppCache::new()));
|
pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] {
|
||||||
|
static EMPTY: Vec<widget::icon::Handle> = Vec::new();
|
||||||
|
self.icons.get(key).unwrap_or_else(|| &EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mime_apps(mime: &Mime) -> Vec<MimeApp> {
|
pub fn terminal(&self) -> Option<&MimeApp> {
|
||||||
let mime_app_cache = MIME_APP_CACHE.lock().unwrap();
|
//TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec
|
||||||
mime_app_cache.get(mime)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn terminal() -> Option<MimeApp> {
|
// Look for and return preferred terminals
|
||||||
let mime_app_cache = MIME_APP_CACHE.lock().unwrap();
|
//TODO: fallback order beyond cosmic-term?
|
||||||
|
for id in &["com.system76.CosmicTerm"] {
|
||||||
|
for terminal in self.terminals.iter() {
|
||||||
|
if &terminal.id == id {
|
||||||
|
return Some(terminal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec
|
// Return whatever was the first terminal found
|
||||||
|
self.terminals.first()
|
||||||
|
}
|
||||||
|
|
||||||
// Look for and return preferred terminals
|
#[cfg(not(feature = "desktop"))]
|
||||||
//TODO: fallback order beyond cosmic-term?
|
pub fn set_default(&mut self, mime: Mime, id: String) {
|
||||||
for id in &["com.system76.CosmicTerm"] {
|
log::warn!(
|
||||||
for terminal in mime_app_cache.terminals.iter() {
|
"failed to set default handler for {mime:?} to {id:?}: desktop feature not enabled"
|
||||||
if &terminal.id == id {
|
);
|
||||||
return Some(terminal.clone());
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
pub fn set_default(&mut self, mime: Mime, mut id: String) {
|
||||||
|
let Some(path) = cosmic_mime_apps::local_list_path() else {
|
||||||
|
log::warn!("failed to find mimeapps.list path");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut list = cosmic_mime_apps::List::default();
|
||||||
|
match fs::read_to_string(&path) {
|
||||||
|
Ok(string) => {
|
||||||
|
list.load_from(&string);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if err.kind() != io::ErrorKind::NotFound {
|
||||||
|
log::warn!("failed to read {path:?}: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let suffix = ".desktop";
|
||||||
|
if !id.ends_with(suffix) {
|
||||||
|
id.push_str(suffix);
|
||||||
|
}
|
||||||
|
list.set_default_app(mime, id);
|
||||||
|
|
||||||
|
let mut string = list.to_string();
|
||||||
|
string.push('\n');
|
||||||
|
match fs::write(&path, string) {
|
||||||
|
Ok(()) => {
|
||||||
|
self.reload();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to write {path:?}: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return whatever was the first terminal found
|
|
||||||
mime_app_cache.terminals.first().cloned()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,6 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
icon_handle_list_condensed,
|
icon_handle_list_condensed,
|
||||||
open_with: Vec::new(),
|
|
||||||
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||||
button_id: widget::Id::unique(),
|
button_id: widget::Id::unique(),
|
||||||
pos_opt: Cell::new(None),
|
pos_opt: Cell::new(None),
|
||||||
|
|
|
||||||
65
src/tab.rs
65
src/tab.rs
|
|
@ -58,14 +58,13 @@ use tokio::sync::mpsc;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{self, Action, PreviewItem, PreviewKind},
|
app::{Action, PreviewItem, PreviewKind},
|
||||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||||
config::{DesktopConfig, IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID},
|
config::{DesktopConfig, IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID},
|
||||||
dialog::DialogKind,
|
dialog::DialogKind,
|
||||||
fl,
|
fl,
|
||||||
localize::{LANGUAGE_CHRONO, LANGUAGE_SORTER},
|
localize::{LANGUAGE_CHRONO, LANGUAGE_SORTER},
|
||||||
menu,
|
menu, mime_app,
|
||||||
mime_app::{mime_apps, MimeApp},
|
|
||||||
mime_icon::{mime_for_path, mime_icon},
|
mime_icon::{mime_for_path, mime_icon},
|
||||||
mounter::MOUNTERS,
|
mounter::MOUNTERS,
|
||||||
mouse_area,
|
mouse_area,
|
||||||
|
|
@ -459,8 +458,6 @@ pub fn item_from_entry(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let open_with = mime_apps(&mime);
|
|
||||||
|
|
||||||
let children = if metadata.is_dir() {
|
let children = if metadata.is_dir() {
|
||||||
//TODO: calculate children in the background (and make it cancellable?)
|
//TODO: calculate children in the background (and make it cancellable?)
|
||||||
match fs::read_dir(&path) {
|
match fs::read_dir(&path) {
|
||||||
|
|
@ -490,7 +487,6 @@ pub fn item_from_entry(
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
icon_handle_list_condensed,
|
icon_handle_list_condensed,
|
||||||
open_with,
|
|
||||||
thumbnail_opt: None,
|
thumbnail_opt: None,
|
||||||
button_id: widget::Id::unique(),
|
button_id: widget::Id::unique(),
|
||||||
pos_opt: Cell::new(None),
|
pos_opt: Cell::new(None),
|
||||||
|
|
@ -720,7 +716,6 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
icon_handle_list_condensed,
|
icon_handle_list_condensed,
|
||||||
open_with: Vec::new(),
|
|
||||||
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||||
button_id: widget::Id::unique(),
|
button_id: widget::Id::unique(),
|
||||||
pos_opt: Cell::new(None),
|
pos_opt: Cell::new(None),
|
||||||
|
|
@ -904,7 +899,6 @@ pub fn scan_desktop(
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
icon_handle_list_condensed,
|
icon_handle_list_condensed,
|
||||||
open_with: Vec::new(),
|
|
||||||
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||||
button_id: widget::Id::unique(),
|
button_id: widget::Id::unique(),
|
||||||
pos_opt: Cell::new(None),
|
pos_opt: Cell::new(None),
|
||||||
|
|
@ -1027,6 +1021,7 @@ pub enum Command {
|
||||||
OpenInNewWindow(PathBuf),
|
OpenInNewWindow(PathBuf),
|
||||||
OpenTrash,
|
OpenTrash,
|
||||||
Preview(PreviewKind),
|
Preview(PreviewKind),
|
||||||
|
SetOpenWith(Mime, String),
|
||||||
WindowDrag,
|
WindowDrag,
|
||||||
WindowToggleMaximize,
|
WindowToggleMaximize,
|
||||||
}
|
}
|
||||||
|
|
@ -1073,6 +1068,7 @@ pub enum Message {
|
||||||
SelectAll,
|
SelectAll,
|
||||||
SelectFirst,
|
SelectFirst,
|
||||||
SelectLast,
|
SelectLast,
|
||||||
|
SetOpenWith(Mime, String),
|
||||||
SetSort(HeadingOptions, bool),
|
SetSort(HeadingOptions, bool),
|
||||||
Thumbnail(PathBuf, ItemThumbnail),
|
Thumbnail(PathBuf, ItemThumbnail),
|
||||||
ToggleShowHidden,
|
ToggleShowHidden,
|
||||||
|
|
@ -1318,7 +1314,6 @@ pub struct Item {
|
||||||
pub icon_handle_grid: widget::icon::Handle,
|
pub icon_handle_grid: widget::icon::Handle,
|
||||||
pub icon_handle_list: widget::icon::Handle,
|
pub icon_handle_list: widget::icon::Handle,
|
||||||
pub icon_handle_list_condensed: widget::icon::Handle,
|
pub icon_handle_list_condensed: widget::icon::Handle,
|
||||||
pub open_with: Vec<MimeApp>,
|
|
||||||
pub thumbnail_opt: Option<ItemThumbnail>,
|
pub thumbnail_opt: Option<ItemThumbnail>,
|
||||||
pub button_id: widget::Id,
|
pub button_id: widget::Id,
|
||||||
pub pos_opt: Cell<Option<(usize, usize)>>,
|
pub pos_opt: Cell<Option<(usize, usize)>>,
|
||||||
|
|
@ -1343,7 +1338,7 @@ impl Item {
|
||||||
self.mime.type_() == mime::IMAGE || self.mime.type_() == mime::TEXT
|
self.mime.type_() == mime::IMAGE || self.mime.type_() == mime::TEXT
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview<'a>(&'a self, sizes: IconSizes) -> Element<'a, app::Message> {
|
fn preview<'a>(&'a self, sizes: IconSizes) -> Element<'a, Message> {
|
||||||
let spacing = cosmic::theme::active().cosmic().spacing;
|
let spacing = cosmic::theme::active().cosmic().spacing;
|
||||||
// This loads the image only if thumbnailing worked
|
// This loads the image only if thumbnailing worked
|
||||||
let icon = widget::icon::icon(self.icon_handle_grid.clone())
|
let icon = widget::icon::icon(self.icon_handle_grid.clone())
|
||||||
|
|
@ -1376,23 +1371,23 @@ impl Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preview_header(&self) -> Vec<Element<app::Message>> {
|
pub fn preview_header(&self) -> Vec<Element<Message>> {
|
||||||
let mut row = Vec::with_capacity(3);
|
let mut row = Vec::with_capacity(3);
|
||||||
row.push(
|
row.push(
|
||||||
widget::button::icon(widget::icon::from_name("go-previous-symbolic"))
|
widget::button::icon(widget::icon::from_name("go-previous-symbolic"))
|
||||||
.on_press(app::Message::TabMessage(None, Message::ItemLeft))
|
.on_press(Message::ItemLeft)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
row.push(
|
row.push(
|
||||||
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
||||||
.on_press(app::Message::TabMessage(None, Message::ItemRight))
|
.on_press(Message::ItemRight)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
if self.can_gallery() {
|
if self.can_gallery() {
|
||||||
if let Some(_path) = self.path_opt() {
|
if let Some(_path) = self.path_opt() {
|
||||||
row.push(
|
row.push(
|
||||||
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
||||||
.on_press(app::Message::TabMessage(None, Message::Gallery(true)))
|
.on_press(Message::Gallery(true))
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1400,7 +1395,11 @@ impl Item {
|
||||||
row
|
row
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preview_view<'a>(&'a self, sizes: IconSizes) -> Element<'a, app::Message> {
|
pub fn preview_view<'a>(
|
||||||
|
&'a self,
|
||||||
|
mime_app_cache_opt: Option<&'a mime_app::MimeAppCache>,
|
||||||
|
sizes: IconSizes,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
let cosmic_theme::Spacing {
|
let cosmic_theme::Spacing {
|
||||||
space_xxxs,
|
space_xxxs,
|
||||||
space_m,
|
space_m,
|
||||||
|
|
@ -1422,6 +1421,24 @@ impl Item {
|
||||||
mime = self.mime.to_string()
|
mime = self.mime.to_string()
|
||||||
)));
|
)));
|
||||||
let mut settings = Vec::new();
|
let mut settings = Vec::new();
|
||||||
|
if let Some(mime_app_cache) = mime_app_cache_opt {
|
||||||
|
let mime_apps = mime_app_cache.get(&self.mime);
|
||||||
|
if !mime_apps.is_empty() {
|
||||||
|
settings.push(
|
||||||
|
widget::settings::item::builder(fl!("open-with")).control(
|
||||||
|
widget::dropdown(
|
||||||
|
mime_apps,
|
||||||
|
mime_apps.iter().position(|x| x.is_default),
|
||||||
|
|index| {
|
||||||
|
let mime_app = &mime_apps[index];
|
||||||
|
Message::SetOpenWith(self.mime.clone(), mime_app.id.clone())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.icons(mime_app_cache.icons(&self.mime)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
match &self.metadata {
|
match &self.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path { metadata, children } => {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
|
|
@ -1509,9 +1526,10 @@ impl Item {
|
||||||
column = column.push(details);
|
column = column.push(details);
|
||||||
|
|
||||||
if let Some(path) = self.path_opt() {
|
if let Some(path) = self.path_opt() {
|
||||||
column = column.push(widget::button::standard(fl!("open")).on_press(
|
column = column.push(
|
||||||
app::Message::TabMessage(None, Message::Open(Some(path.to_path_buf()))),
|
widget::button::standard(fl!("open"))
|
||||||
));
|
.on_press(Message::Open(Some(path.to_path_buf()))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !settings.is_empty() {
|
if !settings.is_empty() {
|
||||||
|
|
@ -1525,11 +1543,7 @@ impl Item {
|
||||||
column.into()
|
column.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_view<'a>(
|
pub fn replace_view<'a>(&'a self, heading: String, sizes: IconSizes) -> Element<'a, Message> {
|
||||||
&'a self,
|
|
||||||
heading: String,
|
|
||||||
sizes: IconSizes,
|
|
||||||
) -> Element<'a, app::Message> {
|
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut row = widget::row().spacing(space_xxxs);
|
let mut row = widget::row().spacing(space_xxxs);
|
||||||
|
|
@ -2831,6 +2845,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::SetOpenWith(mime, id) => {
|
||||||
|
commands.push(Command::SetOpenWith(mime, id));
|
||||||
|
}
|
||||||
Message::SetSort(heading_option, dir) => {
|
Message::SetSort(heading_option, dir) => {
|
||||||
if !matches!(self.location, Location::Search(..)) {
|
if !matches!(self.location, Location::Search(..)) {
|
||||||
self.sort_name = heading_option;
|
self.sort_name = heading_option;
|
||||||
|
|
@ -4432,7 +4449,7 @@ impl Tab {
|
||||||
dnd_dest.into()
|
dnd_dest.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view<'a>(&'a self, key_binds: &'a HashMap<KeyBind, Action>) -> Element<Message> {
|
pub fn view<'a>(&'a self, key_binds: &'a HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||||
widget::responsive(|size| self.view_responsive(key_binds, size)).into()
|
widget::responsive(|size| self.view_responsive(key_binds, size)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue