From 2f3e1c5bf202c3d6e9940958ca511dfd01e407be Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 12:21:12 +0200 Subject: [PATCH] fix(desktop): resolve folder via IObjectWithSite on blank-area View online (#148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-clicking empty space in a synced directory invokes the command with no selection array — Invoke silently returned, so the menu item appeared but did nothing. Implement IObjectWithSite on the handler: Explorer now hands us its site, and a selection-less invoke queries SID_SFolderView for the IFolderView and resolves the current folder's path. Verified with cargo check --target x86_64-pc-windows-gnu. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- desktop/crates/cloudreve-sync/Cargo.toml | 1 + .../src/shellext/context_menu/view_online.rs | 66 ++++++++++++++----- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/desktop/crates/cloudreve-sync/Cargo.toml b/desktop/crates/cloudreve-sync/Cargo.toml index 212fc405..72839b79 100644 --- a/desktop/crates/cloudreve-sync/Cargo.toml +++ b/desktop/crates/cloudreve-sync/Cargo.toml @@ -64,6 +64,7 @@ features = [ "Win32_System_CorrelationVector", "Win32_Storage_FileSystem", "Win32_System_Com", + "Win32_System_Ole", "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_Shell_Common", diff --git a/desktop/crates/cloudreve-sync/src/shellext/context_menu/view_online.rs b/desktop/crates/cloudreve-sync/src/shellext/context_menu/view_online.rs index e4d99017..e83a210f 100644 --- a/desktop/crates/cloudreve-sync/src/shellext/context_menu/view_online.rs +++ b/desktop/crates/cloudreve-sync/src/shellext/context_menu/view_online.rs @@ -2,19 +2,20 @@ use crate::drive::manager::DriveManager; use crate::{drive::commands::ManagerCommand, utils::app::AppRoot}; use rust_i18n::t; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use windows::{ - Win32::{Foundation::*, System::Com::*, UI::Shell::*}, + Win32::{Foundation::*, System::Com::*, System::Ole::*, UI::Shell::*}, core::*, }; -#[implement(IExplorerCommand)] +/// shlguid.h: SID_SFolderView — resolves IFolderView from the command site. +const SID_S_FOLDER_VIEW: GUID = GUID::from_u128(0xcde725b0_ccc9_4519_917e_325d72fab4ce); + +#[implement(IExplorerCommand, IObjectWithSite)] pub struct ViewOnlineCommandHandler { drive_manager: Arc, app_root: AppRoot, - - #[allow(dead_code)] - site: Option, + site: Mutex>, } impl ViewOnlineCommandHandler { @@ -22,7 +23,31 @@ impl ViewOnlineCommandHandler { Self { drive_manager, app_root, - site: None, + site: Mutex::new(None), + } + } + + fn send_view_online(&self, path: PathBuf) { + tracing::debug!(target: "shellext::context_menu", path = %path.display(), "View online requested"); + let command_tx = self.drive_manager.get_command_sender(); + if let Err(e) = command_tx.send(ManagerCommand::ViewOnline { path }) { + tracing::error!(target: "shellext::context_menu", error = %e, "Failed to send ViewOnline command"); + } + } +} + +impl IObjectWithSite_Impl for ViewOnlineCommandHandler_Impl { + fn SetSite(&self, punksite: Option<&IUnknown>) -> Result<()> { + *self.site.lock().unwrap() = punksite.cloned(); + Ok(()) + } + + fn GetSite(&self, riid: *const GUID, ppvsite: *mut *mut core::ffi::c_void) -> Result<()> { + let site = self.site.lock().unwrap(); + if let Some(site) = site.as_ref() { + unsafe { site.query(riid, ppvsite) }.ok() + } else { + Err(Error::from(E_FAIL)) } } } @@ -82,17 +107,26 @@ impl IExplorerCommand_Impl for ViewOnlineCommandHandler_Impl { // Get the first item let item = items.GetItemAt(0)?; let display_name = item.GetDisplayName(SIGDN_FILESYSPATH)?; - let path_str = display_name.to_string()?; - let path = PathBuf::from(path_str.clone()); - - tracing::debug!(target: "shellext::context_menu", path = %path_str, "View online requested"); + let path = PathBuf::from(display_name.to_string()?); - // Send command through channel to async processor - let command_tx = self.drive_manager.get_command_sender(); + self.send_view_online(path); + } + } else { + // Folder-background invoke carries no selection; resolve the + // current folder through the site Explorer gave us in SetSite. + let site = self.site.lock().unwrap().clone(); + let Some(site) = site else { + return Ok(()); + }; + unsafe { + let service_provider: IServiceProvider = site.cast()?; + let folder_view: IFolderView = + service_provider.QueryService(&SID_S_FOLDER_VIEW)?; + let item: IShellItem = folder_view.GetFolder()?; + let display_name = item.GetDisplayName(SIGDN_FILESYSPATH)?; + let path = PathBuf::from(display_name.to_string()?); - if let Err(e) = command_tx.send(ManagerCommand::ViewOnline { path: path.clone() }) { - tracing::error!(target: "shellext::context_menu", error = %e, "Failed to send ViewOnline command"); - } + self.send_view_online(path); } }