234 lines
12 KiB
C#
234 lines
12 KiB
C#
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
|
||
namespace DeskCore;
|
||
|
||
/// <summary>Ein Icon auf dem echten Windows-Desktop (Bildschirm-Pixel).</summary>
|
||
public record DesktopIcon(string Path, int X, int Y, int IconSize, int CellWidth);
|
||
|
||
/// <summary>
|
||
/// Liest Positionen und Größe der echten Desktop-Icons über die offizielle Shell-Schnittstelle (IFolderView)
|
||
/// aus – ohne etwas in den Explorer einzuschleusen. Nur auf dem UI-Thread (STA) aufrufen.
|
||
/// </summary>
|
||
public static class DesktopIcons
|
||
{
|
||
/// <summary>Alle Desktop-Icons mit Position – oder leer, wenn der Desktop gerade nicht erreichbar ist.</summary>
|
||
public static List<DesktopIcon> Read()
|
||
{
|
||
var result = new List<DesktopIcon>();
|
||
try
|
||
{
|
||
var view = DesktopFolderView(out var listView);
|
||
if (view is null || listView == IntPtr.Zero) return result;
|
||
var view2 = view as IFolderView2;
|
||
int iconSize = 48;
|
||
if (view2 is not null && view2.GetViewModeAndIconSize(out _, out var size) == 0) iconSize = size;
|
||
view.GetSpacing(out var spacing);
|
||
var origin = new POINT();
|
||
ClientToScreen(listView, ref origin);
|
||
|
||
view.ItemCount(SVGIO_ALLVIEW, out int count);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
if (view.Item(i, out var pidl) != 0 || pidl == IntPtr.Zero) continue;
|
||
try
|
||
{
|
||
if (view.GetItemPosition(pidl, out var pos) != 0) continue;
|
||
string path = "";
|
||
if (view2 is not null && view2.GetItem(i, typeof(IShellItem).GUID, out var obj) == 0 && obj is IShellItem item)
|
||
{
|
||
if (item.GetDisplayName(SIGDN_FILESYSPATH, out var p) == 0)
|
||
{
|
||
path = Marshal.PtrToStringUni(p) ?? "";
|
||
Marshal.FreeCoTaskMem(p);
|
||
}
|
||
Marshal.ReleaseComObject(item);
|
||
}
|
||
result.Add(new DesktopIcon(path, origin.X + pos.X, origin.Y + pos.Y, iconSize, spacing.X));
|
||
}
|
||
finally { Marshal.FreeCoTaskMem(pidl); }
|
||
}
|
||
}
|
||
catch { }
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rechteck des Icon-Bildes in Bildschirm-Pixeln. Die Position aus IFolderView ist bereits die linke obere
|
||
/// Ecke des Bildes (gemessen: Eintrag beginnt (Abstand − Bildgröße) / 2 weiter links, 3 px weiter oben).
|
||
/// </summary>
|
||
public static (int L, int T, int Size) IconRect(DesktopIcon i) => (i.X, i.Y + IconTopOffset, i.IconSize);
|
||
|
||
/// <summary>Feinkorrektur nach unten (Windows zeichnet das Bild mit kleinem Rand).</summary>
|
||
public static int IconTopOffset { get; set; } = 3;
|
||
|
||
/// <summary>Gesamte Fläche eines Eintrags (Bild + Beschriftung), z. B. für Klick-Tests.</summary>
|
||
public static (int L, int T, int W, int H) CellRect(DesktopIcon i)
|
||
{
|
||
int pad = Math.Max(0, (i.CellWidth - i.IconSize) / 2);
|
||
return (i.X - pad, i.Y, i.CellWidth, i.IconSize + 44);
|
||
}
|
||
|
||
/// <summary>Zuletzt gelesene Icons (für schnelle Prüfungen aus dem Maus-Hook, ohne COM-Aufruf).</summary>
|
||
public static volatile IReadOnlyList<DesktopIcon> Cached = [];
|
||
|
||
/// <summary>Zwischenspeicher auffrischen (UI-Thread). Gibt true zurück, wenn sich etwas geändert hat.</summary>
|
||
public static bool RefreshCache()
|
||
{
|
||
var fresh = Read();
|
||
bool changed = fresh.Count != Cached.Count || fresh.Where((x, n) => x != Cached[n]).Any();
|
||
Cached = fresh;
|
||
return changed;
|
||
}
|
||
|
||
public static bool HitsIcon(int x, int y) => Cached.Any(i =>
|
||
{
|
||
var (l, t, w, h) = CellRect(i);
|
||
return x >= l && x < l + w && y >= t && y < t + h;
|
||
});
|
||
|
||
// ---------- Desktop-Ansicht finden ----------
|
||
|
||
private static IFolderView? DesktopFolderView(out IntPtr listView)
|
||
{
|
||
listView = IntPtr.Zero;
|
||
var shellWindows = (IShellWindows)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_ShellWindows)!)!;
|
||
try
|
||
{
|
||
object loc = CSIDL_DESKTOP, empty = new object();
|
||
var disp = shellWindows.FindWindowSW(ref loc, ref empty, SWC_DESKTOP, out _, SWFO_NEEDDISPATCH);
|
||
if (disp is not IServiceProvider sp) return null;
|
||
var sidBrowser = SID_STopLevelBrowser;
|
||
var iidBrowser = typeof(IShellBrowser).GUID;
|
||
if (sp.QueryService(ref sidBrowser, ref iidBrowser, out var browserObj) != 0) return null;
|
||
var browser = (IShellBrowser)browserObj;
|
||
if (browser.QueryActiveShellView(out var shellView) != 0 || shellView is null) return null;
|
||
shellView.GetWindow(out var defView);
|
||
listView = FindWindowEx(defView, IntPtr.Zero, "SysListView32", null);
|
||
return shellView as IFolderView;
|
||
}
|
||
finally { Marshal.ReleaseComObject(shellWindows); }
|
||
}
|
||
|
||
// ---------- COM-Schnittstellen (nur die benötigten Methoden in vtable-Reihenfolge) ----------
|
||
|
||
private const int CSIDL_DESKTOP = 0, SWC_DESKTOP = 8, SWFO_NEEDDISPATCH = 1, SVGIO_ALLVIEW = 2;
|
||
private const uint SIGDN_FILESYSPATH = 0x80058000;
|
||
private static readonly Guid CLSID_ShellWindows = new("9BA05972-F6A8-11CF-A442-00A0C90A8F39");
|
||
private static readonly Guid SID_STopLevelBrowser = new("4C96BE40-915C-11CF-99D3-00AA004AE837");
|
||
|
||
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
|
||
|
||
[ComImport, Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
|
||
private interface IShellWindows
|
||
{
|
||
int Count { get; }
|
||
[return: MarshalAs(UnmanagedType.IDispatch)] object Item(object index);
|
||
[return: MarshalAs(UnmanagedType.IUnknown)] object _NewEnum();
|
||
void Register([MarshalAs(UnmanagedType.IDispatch)] object pid, int hwnd, int swClass, out int plCookie);
|
||
void RegisterPending(int lThreadId, ref object pvarloc, ref object pvarlocRoot, int swClass, out int plCookie);
|
||
void Revoke(int lCookie);
|
||
void OnNavigate(int lCookie, ref object pvarLoc);
|
||
void OnActivated(int lCookie, short fActive);
|
||
[return: MarshalAs(UnmanagedType.IDispatch)]
|
||
object FindWindowSW(ref object pvarLoc, ref object pvarLocRoot, int swClass, out int pHWND, int swfwOptions);
|
||
}
|
||
|
||
[ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IServiceProvider
|
||
{
|
||
[PreserveSig] int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
|
||
}
|
||
|
||
[ComImport, Guid("000214E2-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IShellBrowser
|
||
{
|
||
void GetWindow(out IntPtr phwnd);
|
||
void ContextSensitiveHelp(bool fEnterMode);
|
||
void InsertMenusSB(IntPtr hmenuShared, IntPtr lpMenuWidths);
|
||
void SetMenuSB(IntPtr hmenuShared, IntPtr holemenuRes, IntPtr hwndActiveObject);
|
||
void RemoveMenusSB(IntPtr hmenuShared);
|
||
void SetStatusTextSB(IntPtr pszStatusText);
|
||
void EnableModelessSB(bool fEnable);
|
||
void TranslateAcceleratorSB(IntPtr pmsg, ushort wID);
|
||
void BrowseObject(IntPtr pidl, uint wFlags);
|
||
void GetViewStateStream(uint grfMode, out IntPtr ppStrm);
|
||
void GetControlWindow(uint id, out IntPtr phwnd);
|
||
void SendControlMsg(uint id, uint uMsg, IntPtr wParam, IntPtr lParam, out IntPtr pret);
|
||
[PreserveSig] int QueryActiveShellView(out IShellView ppshv);
|
||
}
|
||
|
||
[ComImport, Guid("000214E3-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IShellView
|
||
{
|
||
void GetWindow(out IntPtr phwnd);
|
||
}
|
||
|
||
[ComImport, Guid("CDE725B0-CCC9-4519-917E-325D72FAB4CE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IFolderView
|
||
{
|
||
[PreserveSig] int GetCurrentViewMode(out uint pViewMode);
|
||
[PreserveSig] int SetCurrentViewMode(uint ViewMode);
|
||
[PreserveSig] int GetFolder(ref Guid riid, out IntPtr ppv);
|
||
[PreserveSig] int Item(int iItemIndex, out IntPtr ppidl);
|
||
[PreserveSig] int ItemCount(uint uFlags, out int pcItems);
|
||
[PreserveSig] int Items(uint uFlags, ref Guid riid, out IntPtr ppv);
|
||
[PreserveSig] int GetSelectionMarkedItem(out int piItem);
|
||
[PreserveSig] int GetFocusedItem(out int piItem);
|
||
[PreserveSig] int GetItemPosition(IntPtr pidl, out POINT ppt);
|
||
[PreserveSig] int GetSpacing(out POINT ppt);
|
||
}
|
||
|
||
[ComImport, Guid("1AF3A467-214F-4298-908E-06B03E0B39F9"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IFolderView2
|
||
{
|
||
// IFolderView
|
||
[PreserveSig] int GetCurrentViewMode(out uint pViewMode);
|
||
[PreserveSig] int SetCurrentViewMode(uint ViewMode);
|
||
[PreserveSig] int GetFolder(ref Guid riid, out IntPtr ppv);
|
||
[PreserveSig] int Item(int iItemIndex, out IntPtr ppidl);
|
||
[PreserveSig] int ItemCount(uint uFlags, out int pcItems);
|
||
[PreserveSig] int Items(uint uFlags, ref Guid riid, out IntPtr ppv);
|
||
[PreserveSig] int GetSelectionMarkedItem(out int piItem);
|
||
[PreserveSig] int GetFocusedItem(out int piItem);
|
||
[PreserveSig] int GetItemPosition(IntPtr pidl, out POINT ppt);
|
||
[PreserveSig] int GetSpacing(out POINT ppt);
|
||
[PreserveSig] int GetDefaultSpacing(out POINT ppt);
|
||
[PreserveSig] int GetAutoArrange();
|
||
[PreserveSig] int SelectItem(int iItem, uint dwFlags);
|
||
[PreserveSig] int SelectAndPositionItems(uint cidl, IntPtr apidl, IntPtr apt, uint dwFlags);
|
||
// IFolderView2
|
||
[PreserveSig] int SetGroupBy(IntPtr key, bool fAscending);
|
||
[PreserveSig] int GetGroupBy(IntPtr pkey, out bool pfAscending);
|
||
[PreserveSig] int SetViewProperty(IntPtr pidl, IntPtr propkey, IntPtr propvar);
|
||
[PreserveSig] int GetViewProperty(IntPtr pidl, IntPtr propkey, IntPtr ppropvar);
|
||
[PreserveSig] int SetTileViewProperties(IntPtr pidl, IntPtr pszPropList);
|
||
[PreserveSig] int SetExtendedTileViewProperties(IntPtr pidl, IntPtr pszPropList);
|
||
[PreserveSig] int SetText(int iType, IntPtr pwszText);
|
||
[PreserveSig] int SetCurrentFolderFlags(uint dwMask, uint dwFlags);
|
||
[PreserveSig] int GetCurrentFolderFlags(out uint pdwFlags);
|
||
[PreserveSig] int GetSortColumnCount(out int pcColumns);
|
||
[PreserveSig] int SetSortColumns(IntPtr rgSortColumns, int cColumns);
|
||
[PreserveSig] int GetSortColumns(IntPtr rgSortColumns, int cColumns);
|
||
[PreserveSig] int GetItem(int iItem, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
|
||
[PreserveSig] int GetVisibleItem(int iStart, bool fPrevious, out int piItem);
|
||
[PreserveSig] int GetSelectedItem(int iStart, out int piItem);
|
||
[PreserveSig] int GetSelection(bool fNoneImpliesFolder, out IntPtr ppsia);
|
||
[PreserveSig] int GetSelectionState(IntPtr pidl, out uint pdwFlags);
|
||
[PreserveSig] int InvokeVerbOnSelection(IntPtr pszVerb);
|
||
[PreserveSig] int SetViewModeAndIconSize(int uViewMode, int iImageSize);
|
||
[PreserveSig] int GetViewModeAndIconSize(out int puViewMode, out int piImageSize);
|
||
}
|
||
|
||
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||
private interface IShellItem
|
||
{
|
||
[PreserveSig] int BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
|
||
[PreserveSig] int GetParent(out IShellItem ppsi);
|
||
[PreserveSig] int GetDisplayName(uint sigdnName, out IntPtr ppszName);
|
||
}
|
||
|
||
[DllImport("user32.dll")] private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
|
||
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string? cls, string? title);
|
||
}
|