Desktop-suite.ahk für Windows 10 IoT 2021 LTSC

Version vom 16. Juli 2026, 21:46 Uhr von Admin (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „#Requires AutoHotkey v2.0 #SingleInstance Force CoordMode "Mouse", "Screen" ; ╔══════════════════════════════════════════════════════════════╗ ; ║ Desktop-Suite: ║ ; ║ 1) Virtuelle-Desktop-Leiste in der Taskleiste (vdesk) ║ ; ║ 2) Autotile — Spalten / Quad…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
  1. Requires AutoHotkey v2.0
  2. 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) ──────────────────────
  1. 0:: {
   global Manual
   Manual := !Manual
   Flash("Desktop-Leiste: " (Manual ? "AN" : "AUS"))
   Refresh(true)

}

── Desktop wechseln ─────────────────────────────────────────
  1. 1::VDGo(1)
  2. 2::VDGo(2)
  3. 3::VDGo(3)
  4. 4::VDGo(4)
  5. 5::VDGo(5)
  6. 6::VDGo(6)
  7. 7::VDGo(7)
  8. 8::VDGo(8)
  9. 9::VDGo(9)
── Fenster verschieben (du bleibst) ─────────────────────────
  1. +1::MoveWin(1)
  2. +2::MoveWin(2)
  3. +3::MoveWin(3)
  4. +4::MoveWin(4)
  5. +5::MoveWin(5)
  6. +6::MoveWin(6)
  7. +7::MoveWin(7)
  8. +8::MoveWin(8)
  9. +9::MoveWin(9)
── Fenster verschieben UND mitspringen ──────────────────────
  1. !1::MoveWin(1, true)
  2. !2::MoveWin(2, true)
  3. !3::MoveWin(3, true)
  4. !4::MoveWin(4, true)
  5. !5::MoveWin(5, true)
  6. !6::MoveWin(6, true)
  7. !7::MoveWin(7, true)
  8. !8::MoveWin(8, true)
  9. !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

  1. HotIf OverTitleBar()

+RButton:: {

   MouseGetPos , , &win
   ToggleAOT(win)

}

  1. HotIf