Files
DeskCore/DesktopToggle.cs
T
2026-10-03 19:51:56 +00:00

273 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Automation;
namespace DeskCore;
/// <summary>
/// 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
/// </summary>
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;
}
/// <summary>Muss sehr schnell sein: nur Fensterklasse und zwischengespeicherte Icon-Positionen prüfen.</summary>
private static IntPtr Proc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0) return CallNextHookEx(_hook, nCode, wParam, lParam);
var info = Marshal.PtrToStructure<MSLLHOOKSTRUCT>(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));
/// <summary>Liegt unter dem Punkt direkt der Desktop (kein Fenster darüber)?</summary>
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");
}
/// <summary>Abgefangenen Rechtsklick an Windows weitergeben (wird als "eingespeist" markiert und nicht erneut abgefangen).</summary>
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<INPUT>());
}
private static void OnDoubleClick(POINT pt)
{
if (!IsEmptyDesktopSpot(pt)) return;
Toggle();
}
/// <summary>Liegt der Punkt auf dem Desktop – und zwar nicht auf einem Icon?</summary>
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; }
}
/// <summary>Desktop-Icons und Boxen umschalten (auch per Befehl --toggle-desktop).</summary>
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();
}
/// <summary>Beim Beenden: ausgeblendete Desktop-Icons wieder zeigen, damit niemand vor einem leeren Desktop sitzt.</summary>
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);
}
/// <summary>Das Fenster mit den Desktop-Icons (liegt je nach Zustand unter Progman oder einem WorkerW).</summary>
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;
}
/// <summary>Sind die Desktop-Icons gerade sichtbar?</summary>
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);
}