; Minimize Window to Tray Menu ; https://www.autohotkey.com ; This script assigns a hotkey of your choice to hide any window so that ; it becomes an entry at the bottom of the script's tray menu. Hidden ; windows can then be unhidden individually or all at once by selecting ; the corresponding item on the menu. If the script exits for any reason, ; all the windows that it hid will be unhidden automatically. ; CONFIGURATION SECTION: Change the below values as desired. ; This is the maximum number of windows to allow to be hidden (having a ; limit helps performance): g_MaxWindows := 50 ; This is the hotkey used to hide the active window: g_Hotkey := "#h" ; Win+H ; This is the hotkey used to unhide the last hidden window: g_UnHotkey := "#u" ; Win+U ; If you prefer to have the tray menu empty of all the standard items, ; such as Help and Pause, use False. Otherwise, use True: g_StandardMenu := false ; These next few performance settings help to keep the action within the ; A_HotkeyModifierTimeout period, and thus avoid the need to release and ; press down the hotkey's modifier if you want to hide more than one ; window in a row. These settings are not needed if you choose to have ; the script use the keyboard hook via InstallKeybdHook or other means: A_HotkeyModifierTimeout := 100 SetWinDelay 10 SetKeyDelay 0 #SingleInstance ; Allow only one instance of this script to be running. Persistent ; END OF CONFIGURATION SECTION (do not make changes below this point ; unless you want to change the basic functionality of the script). g_WindowIDs := [] g_WindowTitles := [] Hotkey g_Hotkey, Minimize Hotkey g_UnHotkey, UnMinimize ; If the user terminates the script by any means, unhide all the ; windows first: OnExit RestoreAllThenExit if g_StandardMenu = true A_TrayMenu.Add() else { A_TrayMenu.Delete() A_TrayMenu.Add("E&xit and Unhide All", RestoreAllThenExit) } A_TrayMenu.Add("&Unhide All Hidden Windows", RestoreAll) A_TrayMenu.Add() ; Another separator line to make the above more special. g_MaxLength := 260 ; Reduce this to restrict the width of the menu. Minimize(*) { if g_WindowIDs.Length >= g_MaxWindows { MsgBox "No more than " g_MaxWindows " may be hidden simultaneously." return } ; Set the "last found window" to simplify and help performance. ; Since in certain cases it is possible for there to be no active window, ; a timeout has been added: if !WinWait("A",, 2) ; It timed out, so do nothing. return ; Otherwise, the "last found window" has been set and can now be used: ActiveID := WinGetID() ActiveTitle := WinGetTitle() ActiveClass := WinGetClass() if ActiveClass ~= "Shell_TrayWnd|Progman" { MsgBox "The desktop and taskbar cannot be hidden." return } ; Because hiding the window won't deactivate it, activate the window ; beneath this one (if any). I tried other ways, but they wound up ; activating the task bar. This way sends the active window (which is ; about to be hidden) to the back of the stack, which seems best: Send "!{esc}" ; Hide it only now that WinGetTitle/WinGetClass above have been run (since ; by default, those functions cannot detect hidden windows): WinHide ; If the title is blank, use the class instead. This serves two purposes: ; 1) A more meaningful name is used as the menu name. ; 2) Allows the menu item to be created (otherwise, blank items wouldn't ; be handled correctly by the various routines below). if ActiveTitle = "" ActiveTitle := "ahk_class " ActiveClass ; Ensure the title is short enough to fit. ActiveTitle also serves to ; uniquely identify this particular menu item. ActiveTitle := SubStr(ActiveTitle, 1, g_MaxLength) ; In addition to the tray menu requiring that each menu item name be ; unique, it must also be unique so that we can reliably look it up in ; the array when the window is later unhidden. So make it unique if it ; isn't already: for WindowTitle in g_WindowTitles { if WindowTitle = ActiveTitle { ; Match found, so it's not unique. ActiveIDShort := Format("{:X}" ,ActiveID) ActiveIDShortLength := StrLen(ActiveIDShort) ActiveTitleLength := StrLen(ActiveTitle) ActiveTitleLength += ActiveIDShortLength ActiveTitleLength += 1 ; +1 the 1 space between title & ID. if ActiveTitleLength > g_MaxLength { ; Since menu item names are limted in length, trim the title ; down to allow just enough room for the Window's Short ID at ; the end of its name: TrimCount := ActiveTitleLength TrimCount -= g_MaxLength ActiveTitle := SubStr(ActiveTitle, 1, -TrimCount) } ; Build unique title: ActiveTitle .= " " ActiveIDShort break } } ; First, ensure that this ID doesn't already exist in the list, which can ; happen if a particular window was externally unhidden (or its app unhid ; it) and now it's about to be re-hidden: AlreadyExists := false for WindowID in g_WindowIDs { if WindowID = ActiveID { AlreadyExists := true break } } ; Add the item to the array and to the menu: if AlreadyExists = false { A_TrayMenu.Add(ActiveTitle, RestoreFromTrayMenu) g_WindowIDs.Push(ActiveID) g_WindowTitles.Push(ActiveTitle) } } RestoreFromTrayMenu(ThisMenuItem, *) { A_TrayMenu.Delete(ThisMenuItem) ; Find window based on its unique title stored as the menu item name: for WindowTitle in g_WindowTitles { if WindowTitle = ThisMenuItem ; Match found. { IDToRestore := g_WindowIDs[A_Index] WinShow IDToRestore WinActivate IDToRestore ; Sometimes needed. g_WindowIDs.RemoveAt(A_Index) ; Remove it to free up a slot. g_WindowTitles.RemoveAt(A_Index) break } } } ; This will pop the last minimized window off the stack and unhide it. UnMinimize(*) { ; Make sure there's something to unhide. if g_WindowIDs.Length > 0 { ; Get the id of the last window minimized and unhide it IDToRestore := g_WindowIDs.Pop() WinShow IDToRestore WinActivate IDToRestore ; Get the menu name of the last window minimized and remove it MenuToRemove := g_WindowTitles.Pop() A_TrayMenu.Delete(MenuToRemove) } } RestoreAllThenExit(*) { RestoreAll() ExitApp ; Do a true exit. } RestoreAll(*) { for WindowID in g_WindowIDs { IDToRestore := WindowID WinShow IDToRestore WinActivate IDToRestore ; Sometimes needed. ; Do it this way vs. DeleteAll so that the sep. line and first ; item are retained: MenuToRemove := g_WindowTitles[A_Index] A_TrayMenu.Delete(MenuToRemove) } ; Free up all slots: global g_WindowIDs := [] global g_WindowTitles := [] }