Repository: obhq/obliteration (language: rust)

Issue:
Implement open_file for Windows
feature not yet implemented when selecting firmware, it's a bit weird but I can select where to store data but when trying to select the firmware location I get the error that the feature isn't implemented 

![Image](https://github.com/user-attachments/assets/84236383-e49e-4a84-ad4d-c2335a3889d9)


Discussion / hints:
Only directory browsing is implemented on Windows at the moment. I'll implement file browsing tonight if you can help on testing but if you want to check out where we currently are I recommend using Linux with Wayland since it is the only OS that has all implementation.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index dcbe87bf7..7222b9901 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1702,6 +1702,7 @@ dependencies = [
  "wayland-backend",
  "wayland-client",
  "wayland-protocols",
+ "windows",
  "windows-sys 0.59.0",
  "winit",
  "winres",
diff --git a/gui/Cargo.toml b/gui/Cargo.toml
index 5cb952ab4..a5b4a01a4 100644
--- a/gui/Cargo.toml
+++ b/gui/Cargo.toml
@@ -68,6 +68,11 @@ xdg = "2.5.2"
 [target.'cfg(target_os = "windows")'.dependencies]
 ash = { version = "0.37.3", features = ["linked"], default-features = false }
 i-slint-renderer-skia = { version = "=1.9.0", features = ["vulkan"] }
+windows = { version = "0.58.0", features = [
+    "Win32_System_Com",
+    "Win32_UI_Shell",
+    "Win32_UI_Shell_Common",
+] }
 windows-sys = { version = "0.59.0", features = [
     "Win32",
     "Win32_Foundation",
diff --git a/gui/src/ui/windows/dialogs.rs b/gui/src/ui/windows/dialogs.rs
index a93675d13..1b267e49b 100644
--- a/gui/src/ui/windows/dialogs.rs
+++ b/gui/src/ui/windows/dialogs.rs
@@ -1,9 +1,19 @@
 use crate::ui::{DesktopWindow, FileType};
-use futures::channel::oneshot;
 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
-use std::future::Future;
+use std::ffi::OsString;
 use std::num::NonZero;
+use std::os::windows::ffi::OsStringExt;
 use std::path::PathBuf;
+use windows::core::{w, PCWSTR};
+use windows::Win32::Foundation::HWND;
+use windows::Win32::System::Com::{
+    CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED,
+    COINIT_SPEED_OVER_MEMORY,
+};
+use windows::Win32::UI::Shell::Common::COMDLG_FILTERSPEC;
+use windows::Win32::UI::Shell::{
+    FileOpenDialog, IFileOpenDialog, FOS_NOCHANGEDIR, SIGDN_FILESYSPATH,
+};
 use windows_sys::Win32::System::Com::CoTaskMemFree;
 use windows_sys::Win32::UI::Controls::Dialogs::{GetOpenFileNameW, OPENFILENAMEW};
 use windows_sys::Win32::UI::Shell::{
@@ -15,24 +25,59 @@ pub async fn open_file<T: DesktopWindow>(
     title: impl AsRef<str>,
     ty: FileType,
 ) -> Option<PathBuf> {
-    todo!()
+    let parent = get_hwnd(parent);
+    let title: Vec<u16> = title
+        .as_ref()
+        .encode_utf16()
+        .chain(std::iter::once(0))
+        .collect();
+    let browse = move || unsafe {
+        // Setup CLSID_FileOpenDialog.
+        let browser: IFileOpenDialog = CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL).unwrap();
+        let mut opts = browser.GetOptions().unwrap();
+        let filter = match ty {
+            FileType::Firmware => COMDLG_FILTERSPEC {
+                pszName: w!("Firmware Dump"),
+                pszSpec: w!("*.obf"),
+            },
+        };
+
+        opts |= FOS_NOCHANGEDIR;
+
+        browser.SetFileTypes(&[filter]).unwrap();
+        browser.SetOptions(opts).unwrap();
+        browser.SetTitle(PCWSTR(title.as_ptr())).unwrap();
+
+        // Show CLSID_FileOpenDialog.
+        let item = match browser.Show(HWND(parent.get() as _)) {
+            Ok(_) => browser.GetResult().unwrap(),
+            Err(_) => return None,
+        };
+
+        // Get file path.
+        let buf = item.GetDisplayName(SIGDN_FILESYSPATH).unwrap();
+        let path = OsString::from_wide(buf.as_wide());
+
+        CoTaskMemFree(buf.0 as _);
+
+        Some(PathBuf::from(path))
+    };
+
+    spawn_modal(browse).await
 }
 
 pub async fn open_dir<T: DesktopWindow>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
-    let hwnd = get_hwnd(parent);
-
-    let title_wide: Vec<u16> = title
+    let parent = get_hwnd(parent);
+    let title: Vec<u16> = title
         .as_ref()
         .encode_utf16()
         .chain(std::iter::once(0))
         .collect();
-
-    spawn_dialog(move || unsafe {
+    let browse = move || unsafe {
         let mut bi: BROWSEINFOW = std::mem::zeroed();
-        bi.hwndOwner = hwnd.get() as _;
-
-        bi.lpszTitle = title_wide.as_ptr();
 
+        bi.hwndOwner = parent.get() as _;
+        bi.lpszTitle = title.as_ptr();
         bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
 
         let pidl = SHBrowseForFolderW(&mut bi);
@@ -53,30 +98,33 @@ pub async fn open_dir<T: DesktopWindow>(parent: &T, title: impl AsRef<str>) -> O
         } else {
             None
         }
-    })
-    .await
+    };
+
+    spawn_modal(browse).await
 }
 
-fn get_hwnd<T: DesktopWindow>(parent: &T) -> NonZero<isize> {
-    let parent = parent.handle();
-    let parent = parent.window_handle().unwrap();
-    let RawWindowHandle::Win32(win) = parent.as_ref() else {
+fn get_hwnd<T: DesktopWindow>(win: &T) -> NonZero<isize> {
+    let win = win.handle();
+    let win = win.window_handle().unwrap();
+    let RawWindowHandle::Win32(win) = win.as_ref() else {
         unreachable!();
     };
 
     win.hwnd
 }
 
-fn spawn_dialog<F>(dialog_fn: F) -> impl Future<Output = Option<PathBuf>>
+async fn spawn_modal<R, F>(f: F) -> R
 where
-    F: Send + 'static + FnOnce() -> Option<PathBuf>,
+    R: Send + 'static,
+    F: FnOnce() -> R + Send + 'static,
 {
-    let (tx, rx) = oneshot::channel();
+    let (tx, rx) = futures::channel::oneshot::channel();
 
-    std::thread::spawn(move || {
-        let res = dialog_fn();
-        tx.send(res).unwrap();
+    std::thread::spawn(move || unsafe {
+        CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY).unwrap();
+        assert!(tx.send(f()).is_ok());
+        CoUninitialize();
     });
 
-    async move { rx.await.unwrap_or(None) }
+    rx.await.unwrap()
 }


Test patch: