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);
}