Skip to content

Commit

Permalink
feat: 添加预览选择文件弹窗中的文件
Browse files Browse the repository at this point in the history
  • Loading branch information
GuoJikun committed Jan 6, 2025
1 parent 7f5668d commit e754595
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 90 deletions.
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ calamine = "0.26.1"
csv = "1.3.1"
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
docx-rs = "0.4.17"
regex = "1.11.1"

[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2"
Expand Down
226 changes: 136 additions & 90 deletions src-tauri/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,21 @@ use tauri::{
webview::PageLoadEvent, AppHandle, Error as TauriError, Manager, WebviewUrl, WebviewWindowBuilder
};
use windows::{
core::{w, Error as WError, IUnknown, Interface, VARIANT},
core::{w, Error as WError, Interface, VARIANT},
Win32::{
Foundation::{BOOL, HWND, LPARAM, LRESULT, WPARAM},
System::{
Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize, IDispatch, IServiceProvider, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER, CLSCTX_SERVER, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, COINIT_MULTITHREADED
},
SystemServices::SFGAO_FILESYSTEM,
Variant,
CoCreateInstance, CoInitializeEx, CoUninitialize, IDispatch, IServiceProvider, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER, COINIT_APARTMENTTHREADED
},
SystemServices::SFGAO_FILESYSTEM,
Variant::{self}
},
UI::{
Controls::{self, LVM_GETITEMCOUNT, LVM_GETNEXTITEM, LVM_GETSELECTEDCOUNT, LVNI_SELECTED},
Accessibility::{CUIAutomation, IUIAutomation, IUIAutomationSelectionPattern, UIA_NamePropertyId, UIA_SelectionPatternId},
Input::KeyboardAndMouse,
Shell::{
IFileDialog, IShellBrowser, IShellFolder, IShellItemArray, IShellView, IShellWindows,
ShellWindows, SIGDN_DESKTOPABSOLUTEPARSING, SIGDN_FILESYSPATH, SVGIO_SELECTION,
SWFO_NEEDDISPATCH, IFileSaveDialog, FileOpenDialog, IFileOpenDialog, FileSaveDialog
IShellBrowser, IShellItemArray, IShellView, IShellWindows, ShellWindows, SIGDN_DESKTOPABSOLUTEPARSING, SIGDN_FILESYSPATH, SVGIO_SELECTION, SWFO_NEEDDISPATCH
},
WindowsAndMessaging
},
Expand All @@ -46,6 +44,7 @@ struct Selected;
impl Selected {
pub fn new() -> Option<String> {
let path = Self::get_selected_file();
println!("path: {:?}", path);
return path;
}

Expand All @@ -54,8 +53,12 @@ impl Selected {
return match focused_type.as_str() {
"explorer" => unsafe { Self::get_select_file_from_explorer().ok() },
"desktop" => unsafe { Self::get_select_file_from_desktop().ok() },
#[cfg(debug_assertions)]
"dialog" => Self::get_select_file_from_dialog().ok(),
"dialog" => {
match Self::get_select_file_from_dialog() {
Ok(result) => Some(result),
Err(err) => None
}
},
_ => None,
};
}
Expand Down Expand Up @@ -91,7 +94,7 @@ impl Selected {

let hwnd_gfw = WindowsAndMessaging::GetForegroundWindow();
let shell_windows: IShellWindows =
CoCreateInstance(&ShellWindows, None, CLSCTX_SERVER).unwrap();
CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER).unwrap();
let result_hwnd =
WindowsAndMessaging::FindWindowExW(hwnd_gfw, None, w!("ShellTabWindowClass"), None)
.unwrap();
Expand All @@ -101,23 +104,14 @@ impl Selected {

for i in 0..count {
let variant = VARIANT::from(i);
let window: IDispatch = shell_windows.Item(&variant).unwrap();
let mut service_provider: Option<IServiceProvider> = None;
window
.query(
&IServiceProvider::IID,
&mut service_provider as *mut _ as *mut _,
)
.ok()
.unwrap();
if service_provider.is_none() {
let dispatch: IDispatch = shell_windows.Item(&variant).unwrap();

let shell_browser = Self::dispath2browser(dispatch);

if shell_browser.is_none() {
continue;
}
let shell_browser = service_provider
.unwrap()
.QueryService::<IShellBrowser>(&IShellBrowser::IID)
.unwrap();

let shell_browser = shell_browser.unwrap();
// 调用 GetWindow 可能会阻塞 GUI 消息
let phwnd = shell_browser.GetWindow().unwrap();
if hwnd_gfw.0 != phwnd.0 && result_hwnd.0 != phwnd.0 {
Expand Down Expand Up @@ -147,8 +141,13 @@ impl Selected {
let mut target_path = String::new();
let hwnd_gfw = WindowsAndMessaging::GetForegroundWindow(); // 获取当前活动窗口句柄
log::info!("hwnd_gfw: {:?}", hwnd_gfw);
let shell_windows: IShellWindows =
CoCreateInstance(&ShellWindows, None, CLSCTX_SERVER).unwrap();
let shell_windows: Result<IShellWindows, WError> =
CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER);
if shell_windows.is_err() {
log::info!("shell_windows 不存在");
tx.send(target_path.clone()).unwrap();
}
let shell_windows = shell_windows.unwrap();

let pvar_loc: VARIANT = Variant::VariantInit();

Expand All @@ -162,64 +161,103 @@ impl Selected {
windows::Win32::UI::Shell::SWC_DESKTOP,
&mut phwnd,
SWFO_NEEDDISPATCH,
)
.unwrap();
let mut service_provider: Option<IServiceProvider> = None;
dispatch
.query(
&IServiceProvider::IID,
&mut service_provider as *mut _ as *mut _,
)
.ok()
.unwrap();

let shell_browser = service_provider
.unwrap()
.QueryService::<IShellBrowser>(&IShellBrowser::IID)
.unwrap();

let shell_view = shell_browser.QueryActiveShellView().unwrap();
target_path = Self::get_selected_file_path_from_shellview(shell_view);
);
if dispatch.is_err() {
log::info!("dispatch 不存在");
tx.send(target_path.clone()).unwrap();
}

let shell_browser = Self::dispath2browser(dispatch.unwrap());
if shell_browser.is_none() {
log::info!("shell_browser 不存在");
tx.send(target_path.clone()).unwrap();
}
let shell_browser = shell_browser.unwrap();

let phwnd = shell_browser.GetWindow().unwrap();
let top = WindowsAndMessaging::GetAncestor(phwnd, WindowsAndMessaging::GA_ROOT);

if !hwnd_gfw.eq(&top) {
log::info!("top hwnd 不相等");
tx.send(target_path.clone()).unwrap();
}

let shell_view = shell_browser.QueryActiveShellView();
if shell_view.is_err() {
log::info!("shell_view 不存在");
tx.send(target_path.clone()).unwrap();
}

target_path = Self::get_selected_file_path_from_shellview(shell_view.unwrap());
CoUninitialize();
tx.send(target_path).unwrap();
});
let target_path = rx.recv().unwrap();
Ok(target_path)
}



fn get_select_file_from_dialog() -> Result<String, WError> {
let mut target_path = String::new();
let fw_hwnd = unsafe {
WindowsAndMessaging::GetForegroundWindow()
};
println!("fw_hwnd: {:?}", fw_hwnd);

// 获取选中文件窗口的 Text
let seleced_file_hwnd = unsafe {
WindowsAndMessaging::FindWindowExW(fw_hwnd, None, w!("ComboBoxEx32"), None)

let defview = unsafe {
let mut tmp: Option<HWND> = None;
let _ = WindowsAndMessaging::EnumChildWindows(fw_hwnd, Some(Self::dialog_defview_proc), LPARAM(&mut tmp as *mut _ as isize));
tmp
};
println!("seleced_file_hwnd: {:?}", seleced_file_hwnd);
if seleced_file_hwnd.is_err() {

if defview.is_none() {
log::info!("defview 不存在");
return Ok(target_path);
}
let seleced_file_hwnd = seleced_file_hwnd.unwrap();
let mut real_selected_file_hwnd: Option<HWND> = None;
let _ = unsafe {
WindowsAndMessaging::EnumChildWindows(seleced_file_hwnd, Some(Self::get_select_file_from_dialog_proc), LPARAM(&mut real_selected_file_hwnd as *const _ as _))
let defview = defview.unwrap();

let listview = unsafe {
WindowsAndMessaging::FindWindowExW(defview, None, w!("DirectUIHWND"), None)
};
println!("real_selected_file_hwnd: {:?}", real_selected_file_hwnd);
if real_selected_file_hwnd.is_none() {
return Ok(target_path);
if listview.is_err() {
log::info!("listview(DirectUIHWND) 不存在");
return Ok(target_path);
}
let real_selected_file_hwnd = real_selected_file_hwnd.unwrap();
let seleced_file_title = win::get_window_text(real_selected_file_hwnd);
let listview = listview.unwrap();
let seleced_file_title = unsafe {
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
// 通过 ui automation 获取选中文件
let automation: IUIAutomation = CoCreateInstance(
&CUIAutomation,
None,
CLSCTX_INPROC_SERVER
)?;
// 获取列表元素
let list_element = automation.ElementFromHandle(listview)?;

// 获取选中项
let selection_pattern = list_element.GetCurrentPattern(UIA_SelectionPatternId)?;
let selection = selection_pattern.cast::<IUIAutomationSelectionPattern>()?;

// 获取选中的元素
let selected = selection.GetCurrentSelection()?;
let count = selected.Length()?;
let mut file_name = String::new();
if count > 0 {
// 获取第一个选中项
let item = selected.GetElement(0)?;
// 获取文件名
let name = item.GetCurrentPropertyValue(UIA_NamePropertyId)?;
println!("name: {:?}", name);
file_name = name.to_string();
}
file_name
};
println!("seleced_file_title: {:?}", seleced_file_title);

// 获取搜索框的 Text
let mut breadcrumb_parent_hwnd: Option<HWND> = None;
let _ = unsafe {
WindowsAndMessaging::EnumChildWindows(fw_hwnd, Some(Self::get_select_file_from_dialog_proc), LPARAM(&mut breadcrumb_parent_hwnd as *const _ as _))
WindowsAndMessaging::EnumChildWindows(fw_hwnd, Some(Self::breadcrumb_proc), LPARAM(&mut breadcrumb_parent_hwnd as *const _ as isize))
};
if breadcrumb_parent_hwnd.is_none() {
return Ok(target_path);
Expand All @@ -232,48 +270,56 @@ impl Selected {
return Ok(target_path);
}
let breadcrumb_hwnd = breadcrumb_hwnd.unwrap();
let breadcrumb_title = win::get_window_text(breadcrumb_hwnd);
let mut breadcrumb_title = win::get_window_text(breadcrumb_hwnd);
println!("breadcrumb_title: {:?}", breadcrumb_title);
let re = regex::Regex::new(r"[A-Z]:\\.*").unwrap();
if let Some(cap) = re.find(&breadcrumb_title) {
breadcrumb_title = cap.as_str().to_string();
}

target_path = format!("{}\\{}", breadcrumb_title.replace("地址:", ""), seleced_file_title);
target_path = format!("{}\\{}", breadcrumb_title, seleced_file_title);
println!("target_path: {:?}", target_path);

Ok(target_path)
}
unsafe extern "system" fn get_select_file_from_dialog_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {

unsafe extern "system" fn dialog_defview_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
let list_view = lparam.0 as *mut Option<HWND>;
let class_name = win::get_window_class_name(hwnd);
if class_name.contains("Breadcrumb Parent") | class_name.contains("Edit") | class_name.contains("SHELLDLL_DefView") {
if class_name.contains("SHELLDLL_DefView") {
*list_view = Some(hwnd);
return BOOL(0);
}
BOOL(1)
}
unsafe extern "system" fn breadcrumb_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
let list_view = lparam.0 as *mut Option<HWND>;
let class_name = win::get_window_class_name(hwnd);
if class_name.contains("Breadcrumb Parent") {
*list_view = Some(hwnd);
return BOOL(0);
}
BOOL(1)
}

unsafe fn get_shellview_from_hwnd(hwnd: HWND) -> Option<IShellView> {
// 定义 WM 消息
const SFVM_GETSERVICEPROVIDER: u32 = WindowsAndMessaging::WM_USER + 7;
unsafe fn dispath2browser(dispatch: IDispatch) -> Option<IShellBrowser> {

let mut service_provider: Option<IServiceProvider> = None;
WindowsAndMessaging::SendMessageW(
hwnd,
SFVM_GETSERVICEPROVIDER,
WPARAM(0),
LPARAM(&mut service_provider as *mut _ as isize)
);

println!("service_provider: {:?}", service_provider);

if let Some(provider) = service_provider {
// 获取 IShellBrowser
if let Ok(browser) = provider.QueryService::<IShellBrowser>(&IShellBrowser::IID) {
// 获取 IShellView
return browser.QueryActiveShellView().ok();
}
dispatch
.query(
&IServiceProvider::IID,
&mut service_provider as *mut _ as *mut _,
)
.ok()
.unwrap();
if service_provider.is_none() {
return None;
}


None
let shell_browser = service_provider
.unwrap()
.QueryService::<IShellBrowser>(&IShellBrowser::IID)
.ok();
shell_browser
}

unsafe fn get_selected_file_path_from_shellview(shell_view: IShellView) -> String {
Expand Down
4 changes: 4 additions & 0 deletions src/views/preview/code.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const getLanguage = (extension: string) => {
ext = 'ts'
} else if (['markdown'].includes(extension)) {
ext = 'md'
} else if (['json5', 'json'].includes(extension)) {
ext = 'json'
} else if (extension === 'ps1') {
ext = 'powershell'
}
return ext
}
Expand Down

0 comments on commit e754595

Please sign in to comment.