From 92454d4d455c32d3f9d1e5a7130d59e4993d0991 Mon Sep 17 00:00:00 2001 From: Douglas Andreani Date: Thu, 4 Jan 2024 18:50:56 -0300 Subject: [PATCH 1/3] feat: add dependencies for windows support --- Cargo.lock | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 +++++++++++ 2 files changed, 100 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ab92c120..eaeccdc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,8 @@ dependencies = [ "toml", "ureq", "walkdir", + "widestring", + "windows", "yes-or-no", ] @@ -1366,6 +1368,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -1397,6 +1405,82 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "yes-or-no" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3c91f409..577ae0dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,22 @@ ureq = "2.4.0" freedesktop_entry_parser = "1.1" lexical-core = "0.7.6" +[target.'cfg(target_os = "windows")'.dependencies] +widestring = "1.0.2" +[dependencies.windows] +version = "0.52" +features = [ + "Data_Xml_Dom", + "Win32_Foundation", + "Win32_Security", + "Win32_System_Threading", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Com", + "Win32_System_Ole", + "Win32_System_Variant", + "Win32_UI_Shell", +] + [build-dependencies] bicycle = { git = "https://github.com/BrainiumLLC/bicycle", rev = "28080e0c6fa4067d9dd1b0f2b7322b6b32178e1f" } hit = "0.2.0" From 4649e3b7029af4a2b3b2f899bf6a777a67bf4f56 Mon Sep 17 00:00:00 2001 From: Douglas Andreani Date: Thu, 4 Jan 2024 18:51:20 -0300 Subject: [PATCH 2/3] feat: implement windows support --- src/os/mod.rs | 10 ++++- src/os/windows/info.rs | 14 ++++++ src/os/windows/mod.rs | 89 +++++++++++++++++++++++++++++++++++++++ src/os/windows/windows.rs | 67 +++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/os/windows/info.rs create mode 100644 src/os/windows/mod.rs create mode 100644 src/os/windows/windows.rs diff --git a/src/os/mod.rs b/src/os/mod.rs index cd9f9a16..3220c319 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -12,8 +12,14 @@ mod linux; #[cfg(target_os = "linux")] pub use self::linux::*; -#[cfg(not(any(target_os = "macos", target_os = "linux")))] -compile_error!("Host platform not yet supported by cargo-mobile! We'd love if you made a PR to add support for this platform ❤️"); + +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "windows")] +pub use self::windows::*; + + // TODO: we should probably expose common functionality throughout `os` in a // less ad-hoc way... since it's really easy to accidentally break things. diff --git a/src/os/windows/info.rs b/src/os/windows/info.rs new file mode 100644 index 00000000..baa89926 --- /dev/null +++ b/src/os/windows/info.rs @@ -0,0 +1,14 @@ +use crate::{os::Info, util}; +use once_cell_regex::regex; + +pub fn check() -> Result { + util::run_and_search( + &mut bossy::Command::impure_parse("ver"), + regex!(r"\[Microsoft Windows (?P.*)\]"), + |_output, caps| caps.name("version").unwrap().as_str().to_owned(), + ) + .map(|version| Info { + name: "Windows".to_owned(), + version, + }) +} diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs new file mode 100644 index 00000000..82dbe8e6 --- /dev/null +++ b/src/os/windows/mod.rs @@ -0,0 +1,89 @@ +use std::{io, fmt::{Display, self}, ffi::{OsString, OsStr}, path::{PathBuf, Path}}; + +use self::windows::{detect_type_editor, FileType}; + +pub (super) mod info; +mod windows; + +#[derive(Debug)] +pub enum DetectEditorError { + NoDefaultEditorSet, + ExecFieldMissing, +} + + +impl Display for DetectEditorError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NoDefaultEditorSet => write!(f, "No default editor is set: registry queries for \".rs\" and \".txt\" both failed"), + Self::ExecFieldMissing => write!(f, "Exec field on desktop entry was not found"), + } + } +} + +#[derive(Debug)] +pub enum OpenFileError { + LaunchFailed(bossy::Error), + CommandParsingFailed, +} + +impl Display for OpenFileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::LaunchFailed(e) => write!(f, "Launch failed: {}", e), + Self::CommandParsingFailed => write!(f, "Command parsing failed"), + } + } +} + +#[derive(Debug)] +pub struct Application { + exec_command: OsString, +} + + +impl Application { + pub fn detect_editor() -> Result { + if let Ok(command) = detect_type_editor(FileType::Rust) { + return Ok(Self{exec_command: command}) + } else { + return match detect_type_editor(FileType::Text) { + Ok(c) => Ok(Self{exec_command: c}), + Err(_) => Err(DetectEditorError::NoDefaultEditorSet) + } + } + } + + pub fn open_file(&self, path: impl AsRef) -> Result<(), OpenFileError> { + let path = path.as_ref(); + let command_parts = vec![&self.exec_command, path.as_os_str()]; + + bossy::Command::impure(&command_parts[0]) + .with_args(&command_parts[1..]) + .run_and_detach() + .map_err(OpenFileError::LaunchFailed) + } +} + +pub fn open_file_with( + application: impl AsRef, + path: impl AsRef, +) -> bossy::Result<()> { + let application = application.as_ref(); + let path = path.as_ref(); + + let command_parts = vec![application.to_os_string(), path.to_os_string()]; + + bossy::Command::impure(&command_parts[0]) + .with_args(&command_parts[1..]) + .run_and_detach() +} + +// We use "sh" in order to access "command -v", as that is a bultin command on sh. +// Linux does not require a binary "command" in path, so this seems the way to go. +#[cfg(target_os = "windows")] +pub fn command_path(name: &str) -> bossy::Result { + bossy::Command::impure("powershell") + .with_args(&["-Command", name]) + .run_and_wait_for_output() +} diff --git a/src/os/windows/windows.rs b/src/os/windows/windows.rs new file mode 100644 index 00000000..740cac1d --- /dev/null +++ b/src/os/windows/windows.rs @@ -0,0 +1,67 @@ +use std::ffi::OsString; + +use widestring::U16String; +use windows::Win32::Foundation::E_POINTER; +use windows::Win32::Foundation::S_FALSE; + +use windows::Win32::Foundation::S_OK; + +use windows::Win32::UI::Shell::*; + +use windows::core::PCWSTR; +use windows::core::PWSTR; + + +pub enum FileType { + Rust, + Text +} + +pub enum WindowsError { + BufferTooSmall, + False, + Unknown, + UndefinedType +} + + +fn get_editor(extension: &str) -> Result { + + let mut r_ptr = [0u16; 1024]; + let mut size = 1024; + + let extension = U16String::from_str(extension); + + //safety: we cannot overrun the buffer since we are using the NOTRUNCATE OPTION with a known buffer size + let result = unsafe { + let hresult = AssocQueryStringW( + ASSOCF_NOTRUNCATE | ASSOCF_REMAPRUNDLL | ASSOCF_INIT_FOR_FILE | ASSOCF_INIT_FIXED_PROGID, + ASSOCSTR_EXECUTABLE, + PCWSTR::from_raw(extension.as_ptr()), + PCWSTR::null(), + PWSTR::from_raw(r_ptr.as_mut_ptr()), + &mut size); + + match hresult { + S_OK => Ok(U16String::from_vec(r_ptr).to_os_string()), + + S_FALSE => Err(WindowsError::False), + E_POINTER => Err(WindowsError::BufferTooSmall), + windows::core::HRESULT(-2147023741) => Err(WindowsError::UndefinedType), + _ => Err(WindowsError::Unknown) + } + + }; + + result + +} + +pub fn detect_type_editor(t: FileType) -> Result { + + match t { + FileType::Rust => get_editor(".rs"), + FileType::Text => get_editor(".txt"), + } + +} \ No newline at end of file From 1951d6977d532ff70c28a579e3c61921ae70ec58 Mon Sep 17 00:00:00 2001 From: Douglas Andreani Date: Thu, 4 Jan 2024 18:56:17 -0300 Subject: [PATCH 3/3] chore: formatt code accordingly --- src/os/mod.rs | 3 --- src/os/windows/mod.rs | 30 +++++++++++++++++++----------- src/os/windows/windows.rs | 25 +++++++++++-------------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/os/mod.rs b/src/os/mod.rs index 3220c319..8d3c8323 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -12,15 +12,12 @@ mod linux; #[cfg(target_os = "linux")] pub use self::linux::*; - #[cfg(target_os = "windows")] mod windows; #[cfg(target_os = "windows")] pub use self::windows::*; - - // TODO: we should probably expose common functionality throughout `os` in a // less ad-hoc way... since it's really easy to accidentally break things. #[derive(Debug)] diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 82dbe8e6..3dcf0984 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -1,8 +1,13 @@ -use std::{io, fmt::{Display, self}, ffi::{OsString, OsStr}, path::{PathBuf, Path}}; +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Display}, + io, + path::{Path, PathBuf}, +}; use self::windows::{detect_type_editor, FileType}; -pub (super) mod info; +pub(super) mod info; mod windows; #[derive(Debug)] @@ -11,11 +16,13 @@ pub enum DetectEditorError { ExecFieldMissing, } - impl Display for DetectEditorError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NoDefaultEditorSet => write!(f, "No default editor is set: registry queries for \".rs\" and \".txt\" both failed"), + Self::NoDefaultEditorSet => write!( + f, + "No default editor is set: registry queries for \".rs\" and \".txt\" both failed" + ), Self::ExecFieldMissing => write!(f, "Exec field on desktop entry was not found"), } } @@ -41,16 +48,17 @@ pub struct Application { exec_command: OsString, } - impl Application { pub fn detect_editor() -> Result { if let Ok(command) = detect_type_editor(FileType::Rust) { - return Ok(Self{exec_command: command}) + return Ok(Self { + exec_command: command, + }); } else { return match detect_type_editor(FileType::Text) { - Ok(c) => Ok(Self{exec_command: c}), - Err(_) => Err(DetectEditorError::NoDefaultEditorSet) - } + Ok(c) => Ok(Self { exec_command: c }), + Err(_) => Err(DetectEditorError::NoDefaultEditorSet), + }; } } @@ -75,8 +83,8 @@ pub fn open_file_with( let command_parts = vec![application.to_os_string(), path.to_os_string()]; bossy::Command::impure(&command_parts[0]) - .with_args(&command_parts[1..]) - .run_and_detach() + .with_args(&command_parts[1..]) + .run_and_detach() } // We use "sh" in order to access "command -v", as that is a bultin command on sh. diff --git a/src/os/windows/windows.rs b/src/os/windows/windows.rs index 740cac1d..fa96e273 100644 --- a/src/os/windows/windows.rs +++ b/src/os/windows/windows.rs @@ -11,22 +11,19 @@ use windows::Win32::UI::Shell::*; use windows::core::PCWSTR; use windows::core::PWSTR; - pub enum FileType { Rust, - Text + Text, } pub enum WindowsError { BufferTooSmall, False, Unknown, - UndefinedType + UndefinedType, } - fn get_editor(extension: &str) -> Result { - let mut r_ptr = [0u16; 1024]; let mut size = 1024; @@ -35,33 +32,33 @@ fn get_editor(extension: &str) -> Result { //safety: we cannot overrun the buffer since we are using the NOTRUNCATE OPTION with a known buffer size let result = unsafe { let hresult = AssocQueryStringW( - ASSOCF_NOTRUNCATE | ASSOCF_REMAPRUNDLL | ASSOCF_INIT_FOR_FILE | ASSOCF_INIT_FIXED_PROGID, + ASSOCF_NOTRUNCATE + | ASSOCF_REMAPRUNDLL + | ASSOCF_INIT_FOR_FILE + | ASSOCF_INIT_FIXED_PROGID, ASSOCSTR_EXECUTABLE, PCWSTR::from_raw(extension.as_ptr()), PCWSTR::null(), PWSTR::from_raw(r_ptr.as_mut_ptr()), - &mut size); + &mut size, + ); match hresult { S_OK => Ok(U16String::from_vec(r_ptr).to_os_string()), - + S_FALSE => Err(WindowsError::False), E_POINTER => Err(WindowsError::BufferTooSmall), windows::core::HRESULT(-2147023741) => Err(WindowsError::UndefinedType), - _ => Err(WindowsError::Unknown) + _ => Err(WindowsError::Unknown), } - }; result - } pub fn detect_type_editor(t: FileType) -> Result { - match t { FileType::Rust => get_editor(".rs"), FileType::Text => get_editor(".txt"), } - -} \ No newline at end of file +}