using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Automation; namespace DeskCore; /// /// Maus-Funktionen direkt auf dem Windows-Desktop – ohne etwas in den Explorer einzuschleusen: /// • Doppelklick auf freie Stelle: Desktop-Icons und Boxen aus-/einblenden ("Schnell verstecken") /// • Rechte Maustaste auf freier Stelle ziehen: neue Box aufziehen (normaler Rechtsklick bleibt unverändert) /// • Mausrad über dem Desktop: Desktop-Seite wechseln /// public static class DesktopToggle { private static IntPtr _hook; private static LowLevelMouseProc? _proc; // Referenz halten, sonst räumt der GC den Callback weg private static long _lastDownTick; private static POINT _lastDownPt; // Box aufziehen private static bool _rPending, _rDrawing, _updatePosted; private static POINT _rStart, _rCurrent; private static long _lastWheelTick; private static AppSettings S => AppSettings.Current; public static void Apply() { bool needed = S.QuickHide || S.DrawFences || (S.PageWheel && S.Pages.Count > 1); if (needed) Start(); else Stop(); } private static void Start() { if (_hook != IntPtr.Zero) return; _proc = Proc; using var module = Process.GetCurrentProcess().MainModule!; _hook = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(module.ModuleName), 0); } public static void Stop() { if (_hook == IntPtr.Zero) return; UnhookWindowsHookEx(_hook); _hook = IntPtr.Zero; } /// Muss sehr schnell sein: nur Fensterklasse und zwischengespeicherte Icon-Positionen prüfen. private static IntPtr Proc(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode < 0) return CallNextHookEx(_hook, nCode, wParam, lParam); var info = Marshal.PtrToStructure(lParam); bool injected = (info.flags & LLMHF_INJECTED) != 0; int msg = wParam.ToInt32(); var dispatcher = Application.Current?.Dispatcher; switch (msg) { case WM_LBUTTONDOWN when S.QuickHide: { long now = Environment.TickCount64; bool isDouble = now - _lastDownTick <= GetDoubleClickTime() && Math.Abs(info.pt.x - _lastDownPt.x) <= 4 && Math.Abs(info.pt.y - _lastDownPt.y) <= 4; if (isDouble) { _lastDownTick = 0; var pt = info.pt; dispatcher?.BeginInvoke(() => OnDoubleClick(pt)); } else { _lastDownTick = now; _lastDownPt = info.pt; } break; } case WM_RBUTTONDOWN when !injected && S.DrawFences && IsDesktopAt(info.pt) && !DesktopIcons.HitsIcon(info.pt.x, info.pt.y): // Rechtsklick auf freie Desktop-Fläche abfangen – bleibt es ein Klick, wird er unten nachgereicht _rPending = true; _rDrawing = false; _rStart = _rCurrent = info.pt; return new IntPtr(1); case WM_MOUSEMOVE when _rPending: _rCurrent = info.pt; if (!_rDrawing && (Math.Abs(info.pt.x - _rStart.x) > 8 || Math.Abs(info.pt.y - _rStart.y) > 8)) { _rDrawing = true; dispatcher?.BeginInvoke(() => DrawBoxOverlay.Begin()); } if (_rDrawing && !_updatePosted) { _updatePosted = true; dispatcher?.BeginInvoke(() => { _updatePosted = false; DrawBoxOverlay.Update(PixelRect(_rStart, _rCurrent)); }); } break; case WM_RBUTTONUP when _rPending && !injected: { _rPending = false; var rect = PixelRect(_rStart, info.pt); if (_rDrawing) dispatcher?.BeginInvoke(() => DrawBoxOverlay.Finish(rect)); else dispatcher?.BeginInvoke(ReplayRightClick); // normales Desktop-Kontextmenü return new IntPtr(1); } case WM_MOUSEWHEEL when S.PageWheel && S.Pages.Count > 1 && !injected: { if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) break; // Strg+Rad = Icon-Größe (Windows) long now = Environment.TickCount64; if (now - _lastWheelTick < 350 || !IsDesktopAt(info.pt)) break; _lastWheelTick = now; int delta = (short)((info.mouseData >> 16) & 0xFFFF); dispatcher?.BeginInvoke(() => FenceManager.NextPage(delta < 0 ? 1 : -1)); break; } } return CallNextHookEx(_hook, nCode, wParam, lParam); } private static Rect PixelRect(POINT a, POINT b) => new(Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Abs(a.x - b.x), Math.Abs(a.y - b.y)); /// Liegt unter dem Punkt direkt der Desktop (kein Fenster darüber)? private static bool IsDesktopAt(POINT pt) { var cls = ClassName(WindowFromPoint(pt)); return cls is "WorkerW" or "Progman" or "SHELLDLL_DefView" || (cls == "SysListView32" && ClassName(GetParent(WindowFromPoint(pt))) == "SHELLDLL_DefView"); } /// Abgefangenen Rechtsklick an Windows weitergeben (wird als "eingespeist" markiert und nicht erneut abgefangen). private static void ReplayRightClick() { var inputs = new[] { new INPUT { type = INPUT_MOUSE, mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_RIGHTDOWN } }, new INPUT { type = INPUT_MOUSE, mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_RIGHTUP } }, }; SendInput((uint)inputs.Length, inputs, Marshal.SizeOf()); } private static void OnDoubleClick(POINT pt) { if (!IsEmptyDesktopSpot(pt)) return; Toggle(); } /// Liegt der Punkt auf dem Desktop – und zwar nicht auf einem Icon? private static bool IsEmptyDesktopSpot(POINT pt) { var hwnd = WindowFromPoint(pt); var cls = ClassName(hwnd); if (cls is "WorkerW" or "Progman" or "SHELLDLL_DefView") return true; // Icons gerade ausgeblendet if (cls != "SysListView32" || ClassName(GetParent(hwnd)) != "SHELLDLL_DefView") return false; // Auf ein Icon geklickt? Dann ist es ein normaler Programmstart, nichts tun. try { var el = AutomationElement.FromPoint(new Point(pt.x, pt.y)); for (var e = el; e is not null; e = TreeWalker.RawViewWalker.GetParent(e)) { var type = e.Current.ControlType; if (type == ControlType.ListItem) return false; if (type == ControlType.List) break; } return true; } catch { return false; } } /// Desktop-Icons und Boxen umschalten (auch per Befehl --toggle-desktop). public static void Toggle() { var view = DesktopView(); var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null); bool iconsVisible = list != IntPtr.Zero && IsWindowVisible(list); if (view != IntPtr.Zero) SendMessage(view, WM_COMMAND, new IntPtr(0x7402), IntPtr.Zero); // "Desktopsymbole anzeigen" umschalten FenceManager.SetHidden(iconsVisible); DesktopTint.Refresh(); } /// Beim Beenden: ausgeblendete Desktop-Icons wieder zeigen, damit niemand vor einem leeren Desktop sitzt. public static void RestoreOnExit() { if (!FenceManager.Hidden) return; var view = DesktopView(); var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null); if (view != IntPtr.Zero && list != IntPtr.Zero && !IsWindowVisible(list)) SendMessage(view, WM_COMMAND, new IntPtr(0x7402), IntPtr.Zero); } /// Das Fenster mit den Desktop-Icons (liegt je nach Zustand unter Progman oder einem WorkerW). public static IntPtr DesktopView() { var progman = FindWindow("Progman", null); var view = FindWindowEx(progman, IntPtr.Zero, "SHELLDLL_DefView", null); if (view != IntPtr.Zero) return view; IntPtr worker = IntPtr.Zero; while ((worker = FindWindowEx(IntPtr.Zero, worker, "WorkerW", null)) != IntPtr.Zero) { view = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null); if (view != IntPtr.Zero) return view; } return IntPtr.Zero; } /// Sind die Desktop-Icons gerade sichtbar? public static bool IconsVisible() { var view = DesktopView(); var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null); return list != IntPtr.Zero && IsWindowVisible(list); } private static string ClassName(IntPtr hwnd) { if (hwnd == IntPtr.Zero) return ""; var sb = new StringBuilder(64); GetClassName(hwnd, sb, sb.Capacity); return sb.ToString(); } private const int WH_MOUSE_LL = 14, WM_COMMAND = 0x0111, VK_CONTROL = 0x11; private const int WM_MOUSEMOVE = 0x0200, WM_LBUTTONDOWN = 0x0201, WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205, WM_MOUSEWHEEL = 0x020A; private const uint LLMHF_INJECTED = 0x1, INPUT_MOUSE = 0, MOUSEEVENTF_RIGHTDOWN = 0x0008, MOUSEEVENTF_RIGHTUP = 0x0010; private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x, y; } [StructLayout(LayoutKind.Sequential)] private struct MSLLHOOKSTRUCT { public POINT pt; public uint mouseData, flags, time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct MOUSEINPUT { public int dx, dy; public uint mouseData, dwFlags, time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Explicit)] private struct INPUT { [FieldOffset(0)] public uint type; [FieldOffset(8)] public MOUSEINPUT mi; // 64-Bit: Union beginnt bei Offset 8 } [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("user32.dll")] private static extern uint GetDoubleClickTime(); [DllImport("user32.dll")] private static extern short GetKeyState(int vk); [DllImport("user32.dll")] private static extern uint SendInput(uint n, INPUT[] inputs, int size); [DllImport("user32.dll")] private static extern IntPtr WindowFromPoint(POINT p); [DllImport("user32.dll")] private static extern IntPtr GetParent(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr hWnd, StringBuilder name, int max); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindow(string? cls, string? title); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string? cls, string? title); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); }