Desktop-suite.ahk für Windows 10 IoT 2021 LTSC: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 3: | Zeile 3: | ||
[[file:desktop-suite.ahk für Windows 10 IoT 2021 LTSC.png|frameless|upright=3.0 | [[file:desktop-suite.ahk für Windows 10 IoT 2021 LTSC.png|frameless|upright=3.0|desktop-suite.ahk für Windows 10 IoT 2021 LTSC - Screenshot]] | ||
Dieses Script ist speziell für [[Windows#Windows_10_IoT_Enterprise_LTSC_2021|Windows 10 IoT 2021 LTSC]]. Wenn man es mit einer Anderen Windows Version nutzen will, muss man es für die jeweilige Version überarbeiten. Und man benötigt auch die entsprechende [[Windows#Autohotkey|AutoHotKey]] Version, welche zur jeweiligen Windows Version passt. | Dieses Script ist speziell für [[Windows#Windows_10_IoT_Enterprise_LTSC_2021|Windows 10 IoT 2021 LTSC]]. Wenn man es mit einer Anderen Windows Version nutzen will, muss man es für die jeweilige Version überarbeiten. Und man benötigt auch die entsprechende [[Windows#Autohotkey|AutoHotKey]] Version, welche zur jeweiligen Windows Version passt. | ||
Version vom 16. Juli 2026, 22:31 Uhr
Hauptseite > Technik > EDV > Windows > Windows 10 IoT 2021 LTSC
Dieses Script ist speziell für Windows 10 IoT 2021 LTSC. Wenn man es mit einer Anderen Windows Version nutzen will, muss man es für die jeweilige Version überarbeiten. Und man benötigt auch die entsprechende AutoHotKey Version, welche zur jeweiligen Windows Version passt.
Anleitung: Desktop-Suite
Was ist das?
Ein AutoHotkey-v2-Skript, das drei Dinge in einem bündelt: eine Desktop-Leiste in der Taskleiste zum Anzeigen und Wechseln der virtuellen Windows-Desktops, ein Autotile, das Fenster automatisch in Spalten oder einem Quad-Layout anordnet, und einen Dark/Light-Umschalter für das Windows-Farbschema. Dazu kommt ein Always-on-Top-Schalter für einzelne Fenster.
Voraussetzungen
Du brauchst AutoHotkey v2 (installiert) und die VirtualDesktopAccessor.dll unter C:\Tools\VirtualDesktopAccessor\VirtualDesktopAccessor.dll. Liegt die DLL woanders, passe die Zeile DllPath := ... ganz oben im Skript an. Ohne die DLL startet das Skript nicht (es meldet sich mit einer Fehlermeldung).
Starten
Doppelklick auf desktop-suite.ahk. Es erscheint das grüne AHK-Symbol im Infobereich und links neben der Uhr die Desktop-Leiste mit den nummerierten Knöpfen. Soll das Skript bei jedem Windows-Start mitlaufen: Win+R → shell:startup → Enter, und eine Verknüpfung der Datei in diesen Ordner legen.
Nach jeder Änderung am Skript: Rechtsklick aufs AHK-Symbol im Infobereich → Reload Script.
Virtuelle Desktops
Die Leiste zeigt alle Desktops als Knöpfe; der aktive leuchtet blau. Klick auf einen Knopf wechselt dorthin. Legst du in Windows Desktops an oder löscht welche (Win+Tab), passt sich die Leiste automatisch an. Bei Vollbild-Anwendungen (Videos, Spiele) blendet sie sich selbst aus.
| Kürzel | Wirkung |
|---|---|
| Win + 1…9 | zu Desktop 1–9 wechseln |
| Win + Shift + 1…9 | aktives Fenster dorthin schicken, du bleibst hier |
| Win + Alt + 1…9 | Fenster verschieben und mitspringen |
| Win + 0 | Desktop-Leiste ein-/ausblenden |
Autotile
Das Autotile ordnet alle „normalen" Fenster des Bildschirms automatisch an — minimierte Fenster, Dialoge und Fenster auf anderen Desktops bleiben unberührt. Solange es an ist, fügen sich neu geöffnete Fenster von selbst ein und geschlossene machen Platz.
| Kürzel | Wirkung |
|---|---|
| Strg + Shift + Win + Z | Autotile an, Layout Spalten (alle nebeneinander) |
| Strg + Shift + Win + J | Autotile an, Layout Quad (links eine große Hälfte, Rest rechts gestapelt — bei 3 Fenstern: halb + zwei Viertel) |
| Strg + Shift + Win + Y | Autotile aus |
| Strg + Shift + Win + N | aktives Fenster eine Position weiterschieben |
| Strg + Shift + Win + B | aktives Fenster eine Position zurückschieben |
Tipp: Im Quad-Layout bestimmt die Position 1 den großen Platz links — mit N/B schiebst du das gewünschte Fenster dorthin. Die Abstände lassen sich oben im Skript über GAP_INNER und GAP_OUTER ändern.
Dark/Light umschalten
| Kürzel | Wirkung |
|---|---|
| Strg + Shift + Win + O | Windows-Farbschema zwischen Dunkel und Hell umschalten |
Schaltet App- und System-Theme gleichzeitig um; Taskleiste, Explorer und die meisten Apps wechseln sofort. Ein kurzes Tooltip zeigt den neuen Zustand. Einzelne Programme mit eigener Theme-Einstellung (z. B. Browser auf „festes Theme") folgen ihrer eigenen Einstellung — stelle sie dort auf „System", wenn sie mitwechseln sollen.
Always on Top
| Kürzel | Wirkung |
|---|---|
| Strg + Alt + T | aktives Fenster immer im Vordergrund halten (an/aus) |
| Shift + Rechtsklick auf eine Titelleiste | dasselbe für das angeklickte Fenster |
Wenn etwas klemmt
Reagieren die Kürzel nicht, prüfe, ob das Skript läuft (grünes Symbol im Infobereich) und ob du wirklich alle drei Modifier hältst (erst Strg+Shift+Win gedrückt halten, dann den Buchstaben). Meldet das Skript beim Start die fehlende DLL, stimmt der Pfad in DllPath nicht. Und wenn sich Fenster beim Kacheln überlappen, sind es meist zu viele für Spalten — dann aufs Quad wechseln (J) oder Fenster auf mehrere virtuelle Desktops verteilen.
Installation
Schritt 1: AutoHotkey v2 installieren
Lade AutoHotkey v2 von der offiziellen Seite autohotkey.com herunter und installiere es. Achte darauf, dass es wirklich v2 ist — v1-Skripte und v2-Skripte sind nicht kompatibel, und dieses Skript läuft ausschließlich unter v2 (die erste Zeile #Requires AutoHotkey v2.0 sorgt dafür, dass es sich bei einer falschen Version gar nicht erst startet). Wähle bei der Installation die Standardoption „UX / v2".
Schritt 2: VirtualDesktopAccessor.dll besorgen
Die Desktop-Leiste steuert die virtuellen Windows-Desktops über eine Hilfsbibliothek namens VirtualDesktopAccessor.dll. Diese ist nicht Teil von AutoHotkey und muss separat geladen werden. Sie stammt aus dem quelloffenen Projekt VirtualDesktopAccessor von Ciantic auf GitHub.
Wichtig dabei: Diese DLL spricht interne Windows-Schnittstellen an, die sich zwischen Windows-Versionen unterscheiden. Lade daher die Version, die zu deinem Windows passt (Windows 10 bzw. Windows 11, ggf. für deinen konkreten Build). Nimmst du die falsche, startet das Skript zwar, aber das Umschalten der Desktops funktioniert nicht oder stürzt ab.
Da die DLL Fenster über Windows-interne APIs verschiebt, gilt hier dieselbe Vorsicht wie bei jedem Fremd-Download: Lade sie nur aus der offiziellen GitHub-Quelle des Projekts, nicht von Drittseiten.
Schritt 3: DLL an den richtigen Ort legen
Das Skript erwartet die DLL standardmäßig unter:
C:\Tools\VirtualDesktopAccessor\VirtualDesktopAccessor.dll
Lege den Ordner C:\Tools\VirtualDesktopAccessor\ an und kopiere die DLL hinein. Wenn du sie lieber woanders ablegen willst, öffne das Skript im Editor und passe ganz oben die Zeile
DllPath := "C:\Tools\VirtualDesktopAccessor\VirtualDesktopAccessor.dll"
auf deinen tatsächlichen Pfad an.
Schritt 4: Skript speichern
Speichere den Skript-Text als Datei mit der Endung .ahk, zum Beispiel desktop-suite.ahk. Achte beim Speichern im Editor darauf, als Dateityp „Alle Dateien" zu wählen, damit nicht versehentlich desktop-suite.ahk.txt daraus wird. Als Speicherort eignet sich ein fester Ordner wie C:\Tools\ oder dein Benutzerordner — von dort läuft es dauerhaft.
Bevor du es zum ersten Mal startest: Öffne die Datei einmal per Rechtsklick → Öffnen mit → Editor und lies sie durch. Ein Doppelklick würde das Skript sofort ausführen — das ist bei .ahk-Dateien immer so, sobald AutoHotkey installiert ist, und der Grund, warum man fremde Skripte grundsätzlich erst liest.
Schritt 5: Erster Start
Doppelklick auf desktop-suite.ahk. Zwei Dinge sollten passieren: Im Infobereich (rechts unten bei der Uhr) erscheint das grüne AutoHotkey-Symbol, und links neben der Uhr taucht die Desktop-Leiste mit den nummerierten Knöpfen auf.
Kommt stattdessen die Meldung „VirtualDesktopAccessor.dll nicht gefunden", stimmt der Pfad aus Schritt 3 nicht. Kommt gar nichts, prüfe, ob wirklich AutoHotkey v2 (nicht v1) installiert ist.
Schritt 6: Automatisch beim Anmelden starten (optional)
Damit die Suite bei jedem Windows-Start mitläuft: Drücke Win + R, gib shell:startup ein und bestätige. Es öffnet sich der Autostart-Ordner. Lege dort eine Verknüpfung zu deiner .ahk-Datei ab (Rechtsklick auf die Datei → Verknüpfung erstellen, dann die Verknüpfung in den Autostart-Ordner ziehen). Ab dem nächsten Anmelden startet die Suite automatisch.
Aktualisieren des Skripts
Wenn du etwas am Skript änderst, wird die laufende Version dadurch nicht automatisch neu geladen. Rechtsklick auf das grüne AutoHotkey-Symbol im Infobereich → Reload Script, dann läuft die neue Fassung.
Deinstallieren
Zum Beenden: Rechtsklick auf das AutoHotkey-Symbol → Exit. Soll es nicht mehr automatisch starten, lösche die Verknüpfung aus dem shell:startup-Ordner. Das Skript selbst und die DLL kannst du einfach löschen; es hinterlässt keine Systemänderungen — mit einer Ausnahme: Der Dark/Light-Umschalter verändert deine Windows-Theme-Einstellung, aber das ist dieselbe Einstellung, die du auch normal über die Windows-Einstellungen jederzeit zurücksetzen kannst.
#Requires AutoHotkey v2.0
#SingleInstance Force
CoordMode "Mouse", "Screen"
; ╔══════════════════════════════════════════════════════════════╗
; ║ Desktop-Suite: ║
; ║ 1) Virtuelle-Desktop-Leiste in der Taskleiste (vdesk) ║
; ║ 2) Autotile — Spalten / Quad ║
; ║ 3) Dark/Light-Umschalter ║
; ╚══════════════════════════════════════════════════════════════╝
;
; ── Virtuelle Desktops ──────────────────────────────────────
; Win+1..9 Desktop wechseln
; Win+Shift+1..9 aktives Fenster dorthin schicken (du bleibst)
; Win+Alt+1..9 Fenster verschieben UND mitspringen
; Win+0 Desktop-Leiste ein/aus
;
; ── Autotile (Strg+Shift+Win + Buchstabe) ───────────────────
; Strg+Shift+Win+Z Autotile AN, Layout Spalten
; Strg+Shift+Win+J Autotile AN, Layout Quad (links halb, Rest rechts)
; Strg+Shift+Win+Y Autotile AUS
; Strg+Shift+Win+N aktives Fenster eine Position weiter
; Strg+Shift+Win+B aktives Fenster eine Position zurueck
;
; ── Sonstiges ───────────────────────────────────────────────
; Strg+Shift+Win+O Windows Dark/Light umschalten
; Strg+Alt+T Always on Top fuer aktives Fenster
; Shift+Rechtsklick auf Titelleiste -> Always on Top
; ══════════════════════════════════════════════════════════════
; ══ TEIL 1: VIRTUELLE-DESKTOP-LEISTE ══
; ══════════════════════════════════════════════════════════════
; ══ Einstellungen (Leiste) ═══════════════════════════════════
DllPath := "C:\Tools\VirtualDesktopAccessor.dll"
BTN_W := 28 ; Breite pro Button
BTN_H := 26 ; Hoehe
GAP := 2 ; Abstand zwischen Buttons
PAD := 14 ; Abstand zum Infobereich (Uhr/Icons)
COL_BG := 0x1F1F1F ; Panel-Hintergrund (Taskleistenton)
COL_ON := 0x0A84FF ; aktiver Desktop
COL_OFF := 0x3A3A3A ; inaktive Desktops
AUTOHIDE := true ; Panel bei Vollbild automatisch ausblenden
; ═════════════════════════════════════════════════════════════
hDll := DllCall("LoadLibrary", "Str", DllPath, "Ptr")
if !hDll {
MsgBox "VirtualDesktopAccessor.dll nicht gefunden:`n" DllPath
ExitApp
}
pGoTo := DllCall("GetProcAddress", "Ptr", hDll, "AStr", "GoToDesktopNumber", "Ptr")
pMove := DllCall("GetProcAddress", "Ptr", hDll, "AStr", "MoveWindowToDesktopNumber", "Ptr")
pCurrent := DllCall("GetProcAddress", "Ptr", hDll, "AStr", "GetCurrentDesktopNumber", "Ptr")
pCount := DllCall("GetProcAddress", "Ptr", hDll, "AStr", "GetDesktopCount", "Ptr")
pPin := DllCall("GetProcAddress", "Ptr", hDll, "AStr", "PinWindow", "Ptr")
DeskCount() => DllCall(pCount, "Int")
DeskCurrent() => DllCall(pCurrent, "Int") + 1
; ══ Kernfunktionen ═══════════════════════════════════════════
VDGo(n) {
global pGoTo
if (n > DeskCount()) {
Flash("Desktop " n " existiert nicht")
return
}
DllCall(pGoTo, "Int", n - 1)
}
MoveWin(n, follow := false) {
global pMove, pGoTo, LastWin
if (n > DeskCount()) {
Flash("Desktop " n " existiert nicht")
return
}
hwnd := WinExist("A")
if (hwnd = PanelHwnd())
hwnd := LastWin
if !hwnd
return
DllCall(pMove, "Ptr", hwnd, "Int", n - 1)
if follow
DllCall(pGoTo, "Int", n - 1)
else
Flash("-> Desktop " n)
}
; kleines Overlay (gemeinsam fuer alle Teile)
Flash(txt) {
ToolTip txt
SetTimer () => ToolTip(), -900
}
; ══ Panel ════════════════════════════════════════════════════
global Panel := "", Tabs := [], LastCount := 0, LastCur := 0
global LastWin := 0, LastPos := "", Visible := true, Manual := true
PanelHwnd() => Panel ? Panel.Hwnd : 0
PanelWidth(cnt) => cnt * BTN_W + (cnt - 1) * GAP + 2 * GAP
TabClick(n, *) {
VDGo(n)
}
BuildPanel(cnt) {
global Panel, Tabs, BTN_W, BTN_H, GAP, COL_BG, COL_OFF, pPin
global LastCur, LastPos, Visible
if Panel
Panel.Destroy()
Tabs := []
Panel := Gui("-Caption +ToolWindow +AlwaysOnTop +E0x08000000 -DPIScale")
Panel.BackColor := COL_BG
Panel.MarginX := 0, Panel.MarginY := 0
Loop cnt {
Panel.SetFont("s10 Bold cFFFFFF", "Segoe UI")
c := Panel.Add("Text", Format("x{} y{} w{} h{} Center Background{:06X}"
, GAP + (A_Index-1)*(BTN_W+GAP), GAP
, BTN_W, BTN_H - 2*GAP, COL_OFF), A_Index)
c.OnEvent("Click", TabClick.Bind(A_Index))
Tabs.Push(c)
}
Panel.Show(Format("NoActivate Hide w{} h{}", PanelWidth(cnt), BTN_H))
DllCall(pPin, "Ptr", Panel.Hwnd) ; auf allen Desktops sichtbar
LastPos := ""
Place(cnt)
Visible := false
LastCur := 0
Refresh(true)
}
Place(cnt) {
global Panel, BTN_H, PAD, LastPos
hTray := WinExist("ahk_class Shell_TrayWnd")
if !hTray
return
WinGetPos &tx, &ty, &tw, &th, "ahk_id " hTray
w := PanelWidth(cnt)
x := tx + tw - 300 - w ; Fallback
hNotify := DllCall("FindWindowEx", "Ptr", hTray, "Ptr", 0
, "Str", "TrayNotifyWnd", "Ptr", 0, "Ptr")
if hNotify {
r := Buffer(16, 0)
DllCall("GetWindowRect", "Ptr", hNotify, "Ptr", r)
x := NumGet(r, 0, "Int") - w - PAD ; links vom Infobereich
}
y := ty + (th - BTN_H) // 2
pos := x "," y
if (pos = LastPos)
return
LastPos := pos
; SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE
DllCall("SetWindowPos", "Ptr", Panel.Hwnd, "Ptr", 0
, "Int", x, "Int", y, "Int", 0, "Int", 0, "UInt", 0x0015)
}
Raise() {
global Panel
; SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE, HWND_TOPMOST
DllCall("SetWindowPos", "Ptr", Panel.Hwnd, "Ptr", -1
, "Int", 0, "Int", 0, "Int", 0, "Int", 0, "UInt", 0x0013)
}
; ── Vollbild-Erkennung ───────────────────────────────────────
; Wie die Shell: aktives Fenster deckt den ganzen Monitor ab
; und ist weder Desktop noch Taskleiste.
IsFullscreen() {
hwnd := WinExist("A")
if !hwnd || (hwnd = PanelHwnd())
return false
cls := WinGetClass(hwnd)
if (cls = "WorkerW" || cls = "Progman" || cls = "Shell_TrayWnd")
return false
r := Buffer(16, 0)
if !DllCall("GetWindowRect", "Ptr", hwnd, "Ptr", r)
return false
wx := NumGet(r, 0, "Int"), wy := NumGet(r, 4, "Int")
wr := NumGet(r, 8, "Int"), wb := NumGet(r, 12, "Int")
; Monitor unter diesem Fenster (MONITOR_DEFAULTTONEAREST)
hMon := DllCall("MonitorFromWindow", "Ptr", hwnd, "UInt", 2, "Ptr")
mi := Buffer(40, 0)
NumPut("UInt", 40, mi, 0)
if !DllCall("GetMonitorInfo", "Ptr", hMon, "Ptr", mi)
return false
mx := NumGet(mi, 4, "Int"), my := NumGet(mi, 8, "Int")
mr := NumGet(mi, 12, "Int"), mb := NumGet(mi, 16, "Int")
return (wx <= mx) && (wy <= my) && (wr >= mr) && (wb >= mb)
}
SetVisible(show) {
global Panel, Visible
if (show = Visible)
return
Visible := show
if show {
Panel.Show("NoActivate")
Raise()
} else
Panel.Hide()
}
Paint(idx, active) {
global Tabs, COL_ON, COL_OFF
c := Tabs[idx]
c.SetFont(active ? "s10 Bold cFFFFFF" : "s10 Norm cA0A0A0", "Segoe UI")
c.Opt("+Background" Format("{:06X}", active ? COL_ON : COL_OFF))
c.Redraw()
}
Refresh(force := false) {
global Tabs, LastCur, LastCount, LastWin, Panel
global AUTOHIDE, Manual, Visible
if (h := WinExist("A")) && (h != PanelHwnd())
LastWin := h
cnt := DeskCount()
if (cnt != LastCount) {
LastCount := cnt
BuildPanel(cnt)
return
}
; sichtbar = manuell an UND (kein Autohide ODER kein Vollbild)
SetVisible(Manual && (!AUTOHIDE || !IsFullscreen()))
if !Visible
return
Place(cnt)
Raise()
cur := DeskCurrent()
if (cur = LastCur && !force)
return
LastCur := cur
Loop Tabs.Length
Paint(A_Index, A_Index = cur)
; RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN | RDW_UPDATENOW
DllCall("RedrawWindow", "Ptr", Panel.Hwnd, "Ptr", 0, "Ptr", 0, "UInt", 0x0185)
}
LastCount := DeskCount()
BuildPanel(LastCount)
SetTimer Refresh, 250
; ══════════════════════════════════════════════════════════════
; ══ TEIL 2: AUTOTILE (Spalten / Quad) ══
; ══════════════════════════════════════════════════════════════
; ══ Einstellungen (Autotile) ═════════════════════════════════
global GAP_INNER := 8 ; Abstand zwischen Fenstern (px)
global GAP_OUTER := 8 ; Abstand zum Bildschirmrand (px)
global Layout := "columns" ; Startlayout: "columns" oder "grid"
; ═════════════════════════════════════════════════════════════
global AutoTile := false, LastSig := "", Tiling := false
global TileOrder := []
; ══ Fenster-Filter: was darf gekachelt werden? ═══════════════
IsManageable(hwnd) {
try {
if !hwnd
return false
if !DllCall("IsWindowVisible", "Ptr", hwnd)
return false
; auf anderem virtuellen Desktop / versteckt (UWP) -> ueberspringen
cloaked := 0
DllCall("dwmapi\DwmGetWindowAttribute", "Ptr", hwnd
, "UInt", 14, "Int*", &cloaked, "UInt", 4) ; DWMWA_CLOAKED
if cloaked
return false
if (WinGetTitle(hwnd) = "")
return false
if (WinGetMinMax(hwnd) = -1) ; minimiert
return false
style := WinGetStyle(hwnd)
ex := WinGetExStyle(hwnd)
if (ex & 0x80) ; WS_EX_TOOLWINDOW
return false
; Fenster mit Owner (Dialoge/Popups) raus, ausser echte AppWindows
owner := DllCall("GetWindow", "Ptr", hwnd, "UInt", 4, "Ptr") ; GW_OWNER
if (owner && !(ex & 0x40000)) ; WS_EX_APPWINDOW
return false
; nur groessenveraenderbare Fenster kacheln (fixe Dialoge floaten lassen)
if !(style & 0x40000) ; WS_THICKFRAME
return false
static skip := Map("Progman",1, "WorkerW",1, "Shell_TrayWnd",1
, "Shell_SecondaryTrayWnd",1, "Windows.UI.Core.CoreWindow",1)
if skip.Has(WinGetClass(hwnd))
return false
return true
}
return false
}
MonOf(hwnd) => DllCall("MonitorFromWindow", "Ptr", hwnd, "UInt", 2, "Ptr") ; NEAREST
; Arbeitsbereich eines Monitors (ohne Taskleiste)
WorkArea(hMon) {
mi := Buffer(40, 0), NumPut("UInt", 40, mi, 0)
DllCall("GetMonitorInfo", "Ptr", hMon, "Ptr", mi)
return {l: NumGet(mi,20,"Int"), t: NumGet(mi,24,"Int")
, r: NumGet(mi,28,"Int"), b: NumGet(mi,32,"Int")}
}
; DWM-Rahmenversatz: echter Fensterrahmen vs. sichtbare Kante
FrameOffsets(hwnd) {
ow := Buffer(16,0), fw := Buffer(16,0)
DllCall("GetWindowRect", "Ptr", hwnd, "Ptr", ow)
if DllCall("dwmapi\DwmGetWindowAttribute", "Ptr", hwnd
, "UInt", 9, "Ptr", fw, "UInt", 16) ; EXTENDED_FRAME_BOUNDS
return {l:0, t:0, r:0, b:0}
return { l: NumGet(fw,0,"Int") - NumGet(ow,0,"Int")
, t: NumGet(fw,4,"Int") - NumGet(ow,4,"Int")
, r: NumGet(ow,8,"Int") - NumGet(fw,8,"Int")
, b: NumGet(ow,12,"Int") - NumGet(fw,12,"Int") }
}
; Fenster an sichtbarer Zielposition platzieren (Versatz rausgerechnet)
PlaceWindow(hwnd, x, y, w, h) {
try {
if (WinGetMinMax(hwnd) = 1) ; maximiert -> erst normal
WinRestore(hwnd)
o := FrameOffsets(hwnd)
DllCall("SetWindowPos", "Ptr", hwnd, "Ptr", 0
, "Int", x - o.l, "Int", y - o.t
, "Int", w + o.l + o.r, "Int", h + o.t + o.b
, "UInt", 0x14) ; NOZORDER | NOACTIVATE
}
}
; ══ Layouts ══════════════════════════════════════════════════
LayColumns(n, a) {
global GAP_INNER, GAP_OUTER
rects := []
L := a.l+GAP_OUTER, T := a.t+GAP_OUTER, R := a.r-GAP_OUTER, B := a.b-GAP_OUTER
colW := (R - L - (n-1)*GAP_INNER) / n
Loop n {
cx := L + (A_Index-1)*(colW+GAP_INNER)
rects.Push({x:Round(cx), y:T, w:Round(colW), h:B-T})
}
return rects
}
; "Quad": erstes Fenster = linke Haelfte (Master),
; die restlichen gestapelt in der rechten Haelfte.
; 2 Fenster -> zwei Haelften
; 3 Fenster -> links halb, rechts zwei Viertel
; 4 Fenster -> links halb, rechts drei Zeilen
LayGrid(n, a) {
global GAP_INNER, GAP_OUTER
rects := []
if (n = 0)
return rects
L := a.l+GAP_OUTER, T := a.t+GAP_OUTER, R := a.r-GAP_OUTER, B := a.b-GAP_OUTER
totalW := R - L, totalH := B - T
if (n = 1) {
rects.Push({x:L, y:T, w:totalW, h:totalH})
return rects
}
masterW := (totalW - GAP_INNER) / 2
rects.Push({x:L, y:T, w:Round(masterW), h:totalH}) ; Master links
stackN := n - 1
stackX := L + masterW + GAP_INNER
stackW := totalW - masterW - GAP_INNER
rowH := (totalH - (stackN-1)*GAP_INNER) / stackN
Loop stackN {
ry := T + (A_Index-1)*(rowH+GAP_INNER)
rects.Push({x:Round(stackX), y:Round(ry), w:Round(stackW), h:Round(rowH)})
}
return rects
}
; ══ Reihenfolge stabil halten ════════════════════════════════
; Bestehende Fenster behalten ihren Platz, neue kommen ans Ende,
; geschlossene fallen raus.
RebuildOrder() {
global TileOrder
managed := Map()
for h in WinGetList()
if IsManageable(h)
managed[h] := true
kept := []
for h in TileOrder
if managed.Has(h) {
kept.Push(h), managed.Delete(h)
}
for h in WinGetList() ; neue anhaengen
if managed.Has(h) {
kept.Push(h), managed.Delete(h)
}
TileOrder := kept
}
; Set-Signatur (sortiert) -> nur bei Fensterwechsel neu kacheln,
; nicht bei jedem Fokuswechsel
ManagedSig() {
s := ""
for h in WinGetList()
if IsManageable(h)
s .= h "`n"
return Sort(RTrim(s, "`n"), "N")
}
; ══ Kernaktion ═══════════════════════════════════════════════
Tile() {
global TileOrder, Tiling, Layout
if Tiling
return
Tiling := true
try {
RebuildOrder()
groups := Map() ; nach Monitor gruppieren
for h in TileOrder {
m := MonOf(h)
if !groups.Has(m)
groups[m] := []
groups[m].Push(h)
}
for m, list in groups {
wa := WorkArea(m)
n := list.Length
rects := (Layout = "grid") ? LayGrid(n, wa) : LayColumns(n, wa)
Loop n
PlaceWindow(list[A_Index], rects[A_Index].x, rects[A_Index].y
, rects[A_Index].w, rects[A_Index].h)
}
}
Tiling := false
}
; ══ Fenster tauschen (monitor-lokal, mit Umlauf) ═════════════
SwapFocused(dir) {
global TileOrder
hwnd := WinExist("A")
if !hwnd || !IsManageable(hwnd)
return
RebuildOrder()
hMon := MonOf(hwnd)
subIdx := [] ; Indizes dieses Monitors
for i, h in TileOrder
if (MonOf(h) = hMon)
subIdx.Push(i)
if (subIdx.Length < 2)
return
p := 0
for k, i in subIdx
if (TileOrder[i] = hwnd) {
p := k
break
}
if !p
return
q := Mod(p - 1 + dir + subIdx.Length, subIdx.Length) + 1 ; mit Umlauf
a := subIdx[p], b := subIdx[q]
tmp := TileOrder[a], TileOrder[a] := TileOrder[b], TileOrder[b] := tmp
Tile()
WinActivate(hwnd)
}
; ══ Auto-Retile-Schleife ═════════════════════════════════════
TileTick() {
global AutoTile, LastSig, Tiling
if !AutoTile || Tiling
return
if ((s := ManagedSig()) != LastSig) {
LastSig := s
Tile()
}
}
; ══ Steuerung ════════════════════════════════════════════════
StartTiling(mode) {
global Layout, AutoTile, LastSig
Layout := mode
AutoTile := true
LastSig := ManagedSig()
Tile()
SetTimer TileTick, 350 ; ab jetzt automatisch nachkacheln
Flash("Autotile AN — " (mode = "grid" ? "Quad" : "Spalten"))
}
StopTiling() {
global AutoTile
AutoTile := false
SetTimer TileTick, 0
Flash("Autotile AUS")
}
; ══════════════════════════════════════════════════════════════
; ══ TEIL 3: DARK/LIGHT-UMSCHALTER ══
; ══════════════════════════════════════════════════════════════
; Schaltet App- UND System-Theme um und benachrichtigt alle
; laufenden Programme (kein Neustart/Abmelden noetig).
ToggleTheme() {
key := "HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
cur := 1
try cur := RegRead(key, "AppsUseLightTheme")
val := cur ? 0 : 1 ; 0 = Dark, 1 = Light
try {
RegWrite val, "REG_DWORD", key, "AppsUseLightTheme"
RegWrite val, "REG_DWORD", key, "SystemUsesLightTheme"
} catch {
Flash("Theme-Umschaltung fehlgeschlagen (Registry)")
return
}
; WM_SETTINGCHANGE an alle Fenster -> Apps uebernehmen sofort
DllCall("SendMessageTimeout", "Ptr", 0xFFFF, "UInt", 0x1A
, "Ptr", 0, "Str", "ImmersiveColorSet"
, "UInt", 2, "UInt", 500, "Ptr*", 0) ; SMTO_ABORTIFHUNG
Flash(val ? "Theme: LIGHT" : "Theme: DARK")
}
; ══════════════════════════════════════════════════════════════
; ══ TEIL 4: ALWAYS ON TOP ══
; ══════════════════════════════════════════════════════════════
ToggleAOT(hwnd := 0) {
if !hwnd
hwnd := WinExist("A")
if !hwnd
return
isTop := WinGetExStyle(hwnd) & 0x8 ; WS_EX_TOPMOST
WinSetAlwaysOnTop -1, hwnd
Flash(isTop ? "Always on Top: AUS" : "Always on Top: AN")
}
OverTitleBar() {
MouseGetPos &mx, &my, &win
if !win
return false
lp := ((my & 0xFFFF) << 16) | (mx & 0xFFFF)
return SendMessage(0x0084, 0, lp, , "ahk_id " win) = 2 ; WM_NCHITTEST -> HTCAPTION
}
; ══════════════════════════════════════════════════════════════
; ══ TASTEN ══
; ══════════════════════════════════════════════════════════════
; ── Desktop-Leiste ein/aus (Notausgang) ──────────────────────
#0:: {
global Manual
Manual := !Manual
Flash("Desktop-Leiste: " (Manual ? "AN" : "AUS"))
Refresh(true)
}
; ── Desktop wechseln ─────────────────────────────────────────
#1::VDGo(1)
#2::VDGo(2)
#3::VDGo(3)
#4::VDGo(4)
#5::VDGo(5)
#6::VDGo(6)
#7::VDGo(7)
#8::VDGo(8)
#9::VDGo(9)
; ── Fenster verschieben (du bleibst) ─────────────────────────
#+1::MoveWin(1)
#+2::MoveWin(2)
#+3::MoveWin(3)
#+4::MoveWin(4)
#+5::MoveWin(5)
#+6::MoveWin(6)
#+7::MoveWin(7)
#+8::MoveWin(8)
#+9::MoveWin(9)
; ── Fenster verschieben UND mitspringen ──────────────────────
#!1::MoveWin(1, true)
#!2::MoveWin(2, true)
#!3::MoveWin(3, true)
#!4::MoveWin(4, true)
#!5::MoveWin(5, true)
#!6::MoveWin(6, true)
#!7::MoveWin(7, true)
#!8::MoveWin(8, true)
#!9::MoveWin(9, true)
; ── Autotile (Strg+Shift+Win + Buchstabe) ────────────────────
^+#z::StartTiling("columns") ; Spalten – Autotile an
^+#j::StartTiling("grid") ; Quad – Autotile an
^+#y::StopTiling() ; alles aus
^+#n::SwapFocused(1) ; aktives Fenster eine Position weiter
^+#b::SwapFocused(-1) ; ... und zurueck
; ── Dark/Light ───────────────────────────────────────────────
^+#o::ToggleTheme() ; Strg+Shift+Win+O
; ── Always on Top ────────────────────────────────────────────
^!t::ToggleAOT() ; Strg+Alt+T
#HotIf OverTitleBar()
+RButton:: {
MouseGetPos , , &win
ToggleAOT(win)
}
#HotIf
Script und Anleitung erstellt mit Claude Opus 4.8 und Claude Fabel 5. Die einzelnen Funktionen "Vritual Desktop Managemant" und "TilingWM" waren zuvor einzelnen Scripts welche mit Opus 4.8 ausgearbeitet wurden, und am Ende wurden diese mit Fabel 5 zusammengeführt und um die Dark/Light-Mode umschaltung erweitert.