Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:




[code]
<pre>


#Requires AutoHotkey v2.0
#Requires AutoHotkey v2.0
Zeile 622: Zeile 622:
#HotIf
#HotIf


[/code]
</pre>

Version vom 16. Juli 2026, 21:48 Uhr



#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