From fb731a6ba46c05f333306cf2ac857b4207e9b8eb Mon Sep 17 00:00:00 2001 From: iam54r1n4 Date: Fri, 20 Jan 2023 15:17:57 -0800 Subject: [PATCH] Add deep_link package to the source(this package modified for this program) --- src-tauri/src/deep_link/linux.rs | 143 +++++++++++++++++++++++++++++ src-tauri/src/deep_link/macos.rs | 11 +++ src-tauri/src/deep_link/mod.rs | 26 ++++++ src-tauri/src/deep_link/windows.rs | 104 +++++++++++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 src-tauri/src/deep_link/linux.rs create mode 100644 src-tauri/src/deep_link/macos.rs create mode 100644 src-tauri/src/deep_link/mod.rs create mode 100644 src-tauri/src/deep_link/windows.rs diff --git a/src-tauri/src/deep_link/linux.rs b/src-tauri/src/deep_link/linux.rs new file mode 100644 index 0000000..f4bf1cc --- /dev/null +++ b/src-tauri/src/deep_link/linux.rs @@ -0,0 +1,143 @@ +use std::{ + fs::{create_dir_all, remove_file, File}, + io::{Error, ErrorKind, Read, Result, Write}, + os::unix::net::{UnixListener, UnixStream}, + process::Command, +}; + +use dirs_next::data_dir; + +use super::ID; + +pub async fn register(scheme: &str, handler: F) -> Result<()> +where +F: FnMut(String) -> Fut + Send + 'static, +Fut: Future + Send + 'static, +{ + listen(handler); + + let mut target = data_dir() + .ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))? + .join("applications"); + + create_dir_all(&target)?; + + let exe = tauri_utils::platform::current_exe()?; + + let file_name = format!( + "{}-handler.desktop", + exe.file_name() + .ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Couldn't get file name of curent executable.", + ))? + .to_string_lossy() + ); + + target.push(&file_name); + + let mime_types = format!("x-scheme-handler/{};", scheme); + + let mut file = File::create(&target)?; + file.write_all( + format!( + include_str!("template.desktop"), + name = ID + .get() + .expect("Called register() before prepare()") + .split('.') + .last() + .unwrap(), + exec = exe.to_string_lossy(), + mime_types = mime_types + ) + .as_bytes(), + )?; + + target.pop(); + + Command::new("update-desktop-database") + .arg(target) + .status()?; + + Command::new("xdg-mime") + .args(["default", &file_name, scheme]) + .status()?; + + Ok(()) +} + +pub fn unregister(_scheme: &str) -> Result<()> { + let mut target = + data_dir().ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))?; + + target.push("applications"); + target.push(format!( + "{}-handler.desktop", + tauri_utils::platform::current_exe()? + .file_name() + .ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Couldn't get file name of curent executable.", + ))? + .to_string_lossy() + )); + + remove_file(&target)?; + + Ok(()) +} + +pub fn listen(mut handler: F) { + std::thread::spawn(move || { + let addr = format!( + "/tmp/{}-deep-link.sock", + ID.get().expect("listen() called before prepare()") + ); + + let listener = UnixListener::bind(addr).expect("Can't create listener"); + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut buffer = String::new(); + if let Err(io_err) = stream.read_to_string(&mut buffer) { + log::error!("Error reading incoming connection: {}", io_err.to_string()); + }; + + handler(dbg!(buffer)); + } + Err(err) => { + log::error!("Incoming connection failed: {}", err); + continue; + } + } + } + }); +} + +pub fn prepare(identifier: &str) { + let addr = format!("/tmp/{}-deep-link.sock", identifier); + + match UnixStream::connect(&addr) { + Ok(mut stream) => { + if let Err(io_err) = + stream.write_all(std::env::args().nth(1).unwrap_or_default().as_bytes()) + { + log::error!( + "Error sending message to primary instance: {}", + io_err.to_string() + ); + }; + std::process::exit(0); + } + Err(err) => { + log::error!("Error creating socket listener: {}", err.to_string()); + if err.kind() == ErrorKind::ConnectionRefused { + let _ = remove_file(&addr); + } + } + }; + ID.set(identifier.to_string()) + .expect("prepare() called more than once with different identifiers."); +} diff --git a/src-tauri/src/deep_link/macos.rs b/src-tauri/src/deep_link/macos.rs new file mode 100644 index 0000000..ba12a94 --- /dev/null +++ b/src-tauri/src/deep_link/macos.rs @@ -0,0 +1,11 @@ +pub fn register( + identifier: &str, + scheme: &str, + handler: F, +) -> Result<(), std::io::Error> { + unimplemented!() +} + +pub fn unregister(scheme: &str) -> Result<(), std::io::Error> { + unimplemented!() +} diff --git a/src-tauri/src/deep_link/mod.rs b/src-tauri/src/deep_link/mod.rs new file mode 100644 index 0000000..165dbfe --- /dev/null +++ b/src-tauri/src/deep_link/mod.rs @@ -0,0 +1,26 @@ +use once_cell::sync::OnceCell; + +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +pub use linux::*; + +#[cfg(target_os = "windows")] +mod windows; +#[cfg(target_os = "windows")] +pub use windows::*; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +pub use macos::*; + +static ID: OnceCell = OnceCell::new(); + +/// This function is meant for use-cases where the default [`prepare`] function can't be used. +/// +/// # Errors +/// If ID was already set this functions returns an error containing the ID as String. +pub fn set_identifier(identifier: &str) -> Result<(), String> { + ID.set(identifier.to_string()) +} diff --git a/src-tauri/src/deep_link/windows.rs b/src-tauri/src/deep_link/windows.rs new file mode 100644 index 0000000..e93f7c1 --- /dev/null +++ b/src-tauri/src/deep_link/windows.rs @@ -0,0 +1,104 @@ +use std::{ + io::{BufRead, BufReader, Write}, + path::Path, process::Output, future::IntoFuture, +}; + +use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; +use warp::Future; +use ::futures::executor::block_on; +use winreg::{enums::HKEY_CURRENT_USER, RegKey}; +use super::ID; + +// Consider adding a function to register without starting the listener. +// Plugin needs linux and macOS support before making decisions. + +pub async fn register( + scheme: &str, + handler: F, +) -> Result<(), std::io::Error> +where +F: FnMut(String) -> Fut + Send + 'static, +Fut: Future + Send + 'static, +{ + listen(handler); + + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let base = Path::new("Software").join("Classes").join(scheme); + + let exe = tauri_utils::platform::current_exe()? + .to_string_lossy() + .replace("\\\\?\\", ""); + + let (key, _) = hkcu.create_subkey(&base)?; + key.set_value( + "", + &format!( + "URL:{}", + ID.get().expect("register() called before prepare()") + ), + )?; + key.set_value("URL Protocol", &"")?; + + let (icon, _) = hkcu.create_subkey(base.join("DefaultIcon"))?; + icon.set_value("", &format!("{},0", &exe))?; + + let (cmd, _) = hkcu.create_subkey(base.join("shell").join("open").join("command"))?; + + cmd.set_value("", &format!("{} \"%1\"", &exe))?; + + Ok(()) +} + +pub fn unregister(scheme: &str) -> Result<(), std::io::Error> { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let base = Path::new("Software").join("Classes").join(scheme); + + hkcu.delete_subkey_all(base)?; + + Ok(()) +} + +pub fn listen(mut handler: F) -> () +where +F: FnMut(String) -> Fut + Send + 'static , +Fut: Future + Send + 'static, +{ + let task_to_do = async move { + let listener = + LocalSocketListener::bind(ID.get().expect("listen() called before prepare()").as_str()) + .expect("Can't create listener"); + + for conn in listener.incoming().filter_map(|c| { + c.map_err(|error| log::error!("Incoming connection failed: {}", error)) + .ok() + }) + { + let mut conn = BufReader::new(conn); + let mut buffer = String::new(); + if let Err(io_err) = conn.read_line(&mut buffer) { + log::error!("Error reading incoming connection: {}", io_err.to_string()); + }; + buffer.pop(); + + handler(buffer).await; + } + }; + + std::thread::spawn(move || block_on(task_to_do)); +} + +pub fn prepare(identifier: &str) { + if let Ok(mut conn) = LocalSocketStream::connect(identifier) { + if let Err(io_err) = conn.write_all(std::env::args().nth(1).unwrap_or_default().as_bytes()) + { + log::error!( + "Error sending message to primary instance: {}", + io_err.to_string() + ); + }; + let _ = conn.write_all(b"\n"); + std::process::exit(0); + }; + ID.set(identifier.to_string()) + .expect("prepare() called more than once with different identifiers."); +}