From 39481ecf69b84c48b6b0e6530b0384cfd7a3b7c0 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:54:17 +0200 Subject: [PATCH] feat: open with Does not work yet --- Cargo.lock | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/app.rs | 2 ++ src/message.rs | 7 +++++++ src/utils.rs | 26 ++++++++++++++++++++++++++ src/view.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 133 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 449f598..15203e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -955,10 +955,12 @@ dependencies = [ "constcat", "derivative", "directories", + "freedesktop-desktop-entry", "futures", "i18n-embed", "i18n-embed-fl", "libcosmic", + "memchr", "mime 0.3.17", "nucleo", "os_pipe", @@ -1777,6 +1779,22 @@ dependencies = [ "num", ] +[[package]] +name = "freedesktop-desktop-entry" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e6dc222403f11572b39d551948ce4e9ff860a9bdf3307bb6018d4454a8e034" +dependencies = [ + "dirs", + "gettext-rs", + "log", + "memchr", + "strsim", + "textdistance", + "thiserror", + "xdg", +] + [[package]] name = "freedesktop-icons" version = "0.2.6" @@ -1959,6 +1977,26 @@ dependencies = [ "wasi", ] +[[package]] +name = "gettext-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6716b8a0db461a2720b850ba1623e5b69e4b1aa0224cf5e1fb23a0fe49e65c" +dependencies = [ + "gettext-sys", + "locale_config", +] + +[[package]] +name = "gettext-sys" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b8797f28f2dabfbe2caadb6db4f7fd739e251b5ede0a2ba49e506071edcf67" +dependencies = [ + "cc", + "temp-dir", +] + [[package]] name = "gif" version = "0.12.0" @@ -4860,6 +4898,12 @@ dependencies = [ "slotmap", ] +[[package]] +name = "temp-dir" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f227968ec00f0e5322f9b8173c7a0cbcff6181a0a5b28e9892491c286277231" + [[package]] name = "tempfile" version = "3.13.0" @@ -4882,6 +4926,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textdistance" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d321c8576c2b47e43953e9cce236550d4cd6af0a6ce518fe084340082ca6037b" + [[package]] name = "thiserror" version = "1.0.64" diff --git a/Cargo.toml b/Cargo.toml index ae47c0d..6beaaf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ tracing-journald = "0.3" constcat = "0.5" nucleo = "0.5" futures = "0.3" +freedesktop-desktop-entry = "0.7.4" +memchr = "2.7.4" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" diff --git a/src/app.rs b/src/app.rs index c28fdbf..1cdfa60 100644 --- a/src/app.rs +++ b/src/app.rs @@ -387,6 +387,8 @@ impl cosmic::Application for AppState { config_set!(horizontal, horizontal); } }, + AppMsg::Open(_) => todo!(), + AppMsg::OpenWith { entry, desktop_entry } => todo!(), } Command::none() } diff --git a/src/message.rs b/src/message.rs index 0bf0b1f..da38acf 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,5 @@ use cosmic::iced::window::Id; +use freedesktop_desktop_entry::DesktopEntry; use crate::{ clipboard::{self, ClipboardMessage}, @@ -24,6 +25,12 @@ pub enum AppMsg { ShowQrCode(Entry), ReturnToClipboard, Config(ConfigMsg), + Open(Entry), + OpenWith { + entry: Entry, + desktop_entry: DesktopEntry<'static>, + }, + } #[derive(Clone, Debug)] diff --git a/src/utils.rs b/src/utils.rs index 9867437..36b64fc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -99,3 +99,29 @@ pub fn remove_dir_contents(dir: &Path) { let _ = inner(dir); } + +pub fn find_x_scheme_handler<'a>(a: &'a str) -> Option<&'a str> { + // Use memchr to find the first occurrence of ':' in the input string. + if let Some(colon_index) = memchr::memchr(b':', a.as_bytes()) { + // Check if the colon is followed by "//" to validate the scheme. + if a[colon_index..].starts_with("://") { + // If valid, return the scheme as a slice from the start up to the colon. + return Some(&a[..colon_index]); + } + } + // If no scheme is found, return None. + None +} + +#[test] +fn find_x_scheme_handler_test() { + assert_eq!( + find_x_scheme_handler("https://github.com/wiiznokes/clipboard-manager"), + Some("https") + ); + + assert_eq!( + find_x_scheme_handler("ddg://query%20terms"), + Some("ddg") + ); +} diff --git a/src/view.rs b/src/view.rs index c30a8b7..b9b9d51 100644 --- a/src/view.rs +++ b/src/view.rs @@ -15,12 +15,14 @@ use cosmic::{ button::{self}, column, container, context_menu, flex_row, grid, icon::{self, Handle}, - image, menu, mouse_area, row, text, text_input, toggler, Column, Container, Icon, + image, menu, mouse_area, row, text, text_input, toggler, Column, Container, Icon, Lazy, MouseArea, Space, Text, TextEditor, }, Element, }; +use freedesktop_desktop_entry as fde; + use anyhow::{anyhow, bail, Result}; use crate::{ @@ -29,7 +31,7 @@ use crate::{ db::{Content, Entry}, fl, message::{AppMsg, ConfigMsg}, - utils::{formatted_value, horizontal_padding, vertical_padding}, + utils::{find_x_scheme_handler, formatted_value, horizontal_padding, vertical_padding}, }; impl AppState { @@ -327,6 +329,43 @@ impl AppState { btn.width(Length::Fill).into() }; + let open_with = Lazy::new(entry, |entry| { + println!("lazy"); + + let mut mimes = vec![entry.mime.as_ref()]; + + if let Ok(Content::Text(content)) = entry.get_content() { + if let Some(m) = find_x_scheme_handler(content) { + mimes.push(m); + } + } + + let res = fde::DesktopEntry::from_paths::<&str>( + fde::Iter::new(fde::default_paths()), + Some(&[]), + ) + .filter_map(|e| { + e.ok().and_then(|e| { + e.mime_type() + .unwrap_or_default() + .iter() + .any(|e| mimes.contains(e)) + .then_some( + button(text(e.appid.clone())) + .on_press(AppMsg::OpenWith { + entry: (*entry).clone(), + desktop_entry: e, + }) + .width(Length::Fill) + .into(), + ) + }) + }) + .collect::>(); + + Column::with_children(res) + }); + context_menu( btn, Some(vec![ @@ -339,8 +378,11 @@ impl AppState { menu::Tree::new( button::text(fl!("show_qr_code")) .on_press(AppMsg::ShowQrCode(entry.clone())) - .width(Length::Fill) - .style(Button::Destructive), + .width(Length::Fill), + ), + menu::Tree::with_children( + text("Open with").width(Length::Fill), + vec![menu::Tree::new(open_with)], ), ]), )