├── gdiplus.dll ├── graphics ├── logo.png └── logo.svg ├── i10.code-workspace ├── config ├── Options.json ├── Windows.json └── Hotkeys.json ├── includes ├── GUI │ ├── GuiFrame │ │ ├── MonitorFrame.ahk │ │ ├── GuiFrame.ahk │ │ ├── TextFrame.ahk │ │ └── GDIPFrame.ahk │ └── Gui.ahk ├── Defs.ahk ├── Hotkeys.ahk ├── Config │ ├── Config.ahk │ ├── OptionsConfig.ahk │ ├── HotkeyConfig.ahk │ └── WindowConfig.ahk ├── Main.ahk ├── Util.ahk ├── Init.ahk ├── Debug │ └── Logging.ahk ├── AppBar.ahk ├── Tree │ ├── SplitContainer.ahk │ ├── MonitorContainer.ahk │ ├── WorkspaceContainer.ahk │ ├── Tree.ahk │ ├── RootContainer.ahk │ ├── Container.ahk │ └── WindowContainer.ahk ├── WindowStatics.ahk └── Navigation.ahk ├── i10.ahk ├── README.md └── TODO.txt /gdiplus.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfty-misc/i10/HEAD/gdiplus.dll -------------------------------------------------------------------------------- /graphics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shfty-misc/i10/HEAD/graphics/logo.png -------------------------------------------------------------------------------- /i10.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "C:\\Users\\Josh\\Documents\\AutoHotkey\\Lib" 8 | } 9 | ], 10 | "settings": {} 11 | } -------------------------------------------------------------------------------- /config/Options.json: -------------------------------------------------------------------------------- 1 | { 2 | "DebugMode": 1, 3 | "DefaultInnerGap": 15, 4 | "DefaultOrientation": "None", 5 | "DefaultOuterGap": 0, 6 | "FocusFollowsMouse": 1, 7 | "MouseWarping": "Window", 8 | "WorkspaceLayout": "None" 9 | } -------------------------------------------------------------------------------- /includes/GUI/GuiFrame/MonitorFrame.ahk: -------------------------------------------------------------------------------- 1 | class MonitorFrame extends GDIPFrame 2 | { 3 | __New() 4 | { 5 | base.__New() 6 | 7 | WinGetPos,,,,taskbarHeight, ahk_class Shell_TrayWnd 8 | this.height := taskbarHeight 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /includes/Defs.ahk: -------------------------------------------------------------------------------- 1 | ; Definitions 2 | 3 | Layout_None := "None" 4 | Layout_Split := "Split" 5 | Layout_Tabbed := "Tabbed" 6 | 7 | Orientation_None := "None" 8 | Orientation_H := "Horizontal" 9 | Orientation_V := "Vertical" 10 | 11 | MouseWarp_None := "None" 12 | MouseWarp_Workspace := "Workspace" 13 | MouseWarp_Window := "Window" -------------------------------------------------------------------------------- /includes/Hotkeys.ahk: -------------------------------------------------------------------------------- 1 | ; Hotkeys 2 | 3 | ; Use keyboard hook for more reliable hotkeys 4 | #UseHook 5 | 6 | ; Disable windows key 7 | LWin & vk07::return 8 | LWin::vk07 9 | 10 | ; Disable game bar shortcut 11 | #g::return 12 | 13 | ; Mouse input passthrough handlers 14 | ~LButton::treeRoot.HandleMouseDown() 15 | ~WheelUp::treeRoot.HandleMouseWheel(-1) 16 | ~WheelDown::treeRoot.HandleMouseWheel(1) 17 | -------------------------------------------------------------------------------- /i10.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include includes/Init.ahk 4 | #include includes/AppBar.ahk 5 | #include includes/Defs.ahk 6 | #include includes/WindowStatics.ahk 7 | #include includes/Tree/Tree.ahk 8 | #include includes/Navigation.ahk 9 | #include includes/Util.ahk 10 | #include includes/Config/Config.ahk 11 | #include includes/Debug/Logging.ahk 12 | #include includes/GUI/GUI.ahk 13 | #include includes/Main.ahk 14 | 15 | AppBar_Init() 16 | GUI_Init() 17 | i10_Init() 18 | 19 | SetTimer i10_Update, 0 20 | SetTimer GUI_Update, 50 21 | 22 | #include includes/Hotkeys.ahk -------------------------------------------------------------------------------- /includes/Config/Config.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include includes/Config/OptionsConfig.ahk 4 | #include includes/Config/WindowConfig.ahk 5 | #include includes/Config/HotkeyConfig.ahk 6 | 7 | LoadConfig(ByRef outputVar, filename, debugMessage = false) 8 | { 9 | FileRead, fileString, % filename 10 | 11 | if(debugMessage) 12 | { 13 | MsgBox, % fileString 14 | } 15 | 16 | jsonObject := JSON.Load(fileString) 17 | 18 | if(jsonObject) 19 | { 20 | outputVar := jsonObject 21 | return true 22 | } 23 | 24 | return false 25 | } 26 | 27 | SaveConfig(config, filename) 28 | { 29 | FileDelete, % filename 30 | FileAppend, % JSON.Dump(config,, "`t"), % filename 31 | } 32 | 33 | -------------------------------------------------------------------------------- /includes/Config/OptionsConfig.ahk: -------------------------------------------------------------------------------- 1 | defaultOptions := {} 2 | 3 | defaultOptions["FocusFollowsMouse"] := false 4 | defaultOptions["MouseWarping"] := MouseWarp_Window 5 | 6 | defaultOptions["WorkspaceLayout"] := Layout_None 7 | defaultOptions["DefaultOrientation"] := Orientation_None 8 | 9 | defaultOptions["DefaultOuterGap"] := 0 10 | defaultOptions["DefaultInnerGap"] := 15 11 | 12 | defaultOptions["DebugMode"] := false 13 | 14 | optionsConfig := {} 15 | 16 | if(!LoadConfig(optionsConfig, "config/Options.json")) 17 | { 18 | optionsConfig := defaultOptions 19 | SaveConfig(defaultOptions, "config/Options.json") 20 | } 21 | 22 | GetOption(key) 23 | { 24 | global optionsConfig 25 | return optionsConfig[key] 26 | } 27 | 28 | SetOption(key, value) 29 | { 30 | global optionsConfig 31 | optionsConfig[key] := value 32 | } 33 | -------------------------------------------------------------------------------- /includes/Main.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | isPaused := false 4 | treeRoot := new RootContainer("") 5 | 6 | i10_Init() 7 | { 8 | global treeRoot 9 | treeRoot.Init() 10 | } 11 | 12 | ; Main Loop 13 | i10_Update() 14 | { 15 | global isPaused 16 | if(!isPaused) 17 | { 18 | global treeRoot 19 | treeRoot.Update() 20 | } 21 | } 22 | 23 | i10_Run(target) 24 | { 25 | Run, % target 26 | } 27 | 28 | i10_Pause() 29 | { 30 | global isPaused 31 | isPaused := !isPaused 32 | } 33 | 34 | i10_Restart() 35 | { 36 | Reload 37 | } 38 | 39 | i10_Exit() 40 | { 41 | ExitApp 42 | } 43 | 44 | i10_Shutdown() 45 | { 46 | global treeRoot 47 | treeRoot.Destroy() 48 | } 49 | 50 | i10_SaveState() 51 | { 52 | global treeRoot 53 | JSON.Dump(treeRoot,, "`t") 54 | } 55 | 56 | OnExit("i10_Shutdown") 57 | -------------------------------------------------------------------------------- /includes/Config/HotkeyConfig.ahk: -------------------------------------------------------------------------------- 1 | hotkeyConfig := {} 2 | LoadConfig(hotkeyConfig, "config/Hotkeys.json") 3 | 4 | class FunctionObject { 5 | __Call(method, args*) { 6 | if (method = "") 7 | return this.Call(args*) 8 | if (IsObject(method)) 9 | return this.Call(method, args*) 10 | } 11 | } 12 | 13 | class HotkeyFunction extends FunctionObject { 14 | method := -1 15 | params := [] 16 | 17 | __New(method, params) 18 | { 19 | this.method := Func(method) 20 | this.params := params 21 | } 22 | 23 | Call() { 24 | this.method.Call(this.params*) 25 | } 26 | } 27 | 28 | for hotkeyObjIndex, hotkeyObj in hotkeyConfig.Hotkeys 29 | { 30 | for hotkeyIndex, hotkey in hotkeyObj.Hotkeys 31 | { 32 | hotkeyFunc := new HotkeyFunction(hotkeyObj.Function, hotkeyObj.Params) 33 | Hotkey, % hotkey, % hotkeyFunc 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /includes/Util.ahk: -------------------------------------------------------------------------------- 1 | ; Utility functions 2 | IndexOf(object, ByRef array) 3 | { 4 | for arrayIndex, arrayElement in array 5 | { 6 | if(arrayElement == object) 7 | { 8 | return arrayIndex 9 | } 10 | } 11 | 12 | return -1 13 | } 14 | 15 | GetSystemVolume() 16 | { 17 | SoundGet, systemVolume 18 | return systemVolume 19 | } 20 | 21 | GetNetworkIsConnected(flag = 0x40) 22 | { 23 | return DllCall("Wininet.dll\InternetGetConnectedState", "Str", flag,"Int",0) 24 | } 25 | 26 | SetTaskbarVisibile(visible) 27 | { 28 | if(visible) 29 | { 30 | WinShow ahk_class Shell_TrayWnd 31 | } 32 | else 33 | { 34 | WinHide ahk_class Shell_TrayWnd 35 | } 36 | } 37 | 38 | TV_Update(treeEntry, treeParent, entryString, optionString) 39 | { 40 | if(!treeEntry) 41 | { 42 | treeEntry := TV_Add(entryString, treeParent, optionString) 43 | } 44 | else 45 | { 46 | treeEntry := TV_Modify(treeEntry, optionString, entryString) 47 | } 48 | 49 | return treeEntry 50 | } 51 | -------------------------------------------------------------------------------- /includes/Init.ahk: -------------------------------------------------------------------------------- 1 | ; Initialization 2 | 3 | ; Reload if the user attempts to run the script while an instance already exists 4 | #SingleInstance force 5 | 6 | ; Skip the gentle method of window activation 7 | #WinActivateForce 8 | 9 | ; Use stricter empty variable checking 10 | #NoEnv 11 | 12 | ; Enable warnings for debugging 13 | #Warn 14 | 15 | ; Enable RegEx title matching for more precise window filtering 16 | SetTitleMatchMode, RegEx 17 | 18 | ; Prevent the script from automatically sleeping 19 | SetBatchLines, -1 20 | SetWinDelay, -1 21 | SetControlDelay, -1 22 | SetKeyDelay, -1 23 | SetMouseDelay, -1 24 | 25 | ; Convenient mouse coordinate space 26 | CoordMode, Mouse, Screen 27 | 28 | ; Prevent threads from interrupting eachother to avoid data corruption 29 | Critical 30 | 31 | ; Only three threads should ever be running - main, GUI, last pressed hotkey 32 | #MaxThreads 3 33 | 34 | ; Only allow one active thread per hotkey 35 | ; Multiple presses will be buffered by virtue of running in Critical mode 36 | #MaxThreadsPerHotkey 1 37 | 38 | ; Prevent free-scrolling mice from triggering hotkey warning 39 | #MaxHotkeysPerInterval, 1000 -------------------------------------------------------------------------------- /includes/Debug/Logging.ahk: -------------------------------------------------------------------------------- 1 | logLines := [] 2 | logLineCount := 100 3 | 4 | Log_Message := "Message" 5 | Log_Warning := "Warning" 6 | Log_Error := "Error" 7 | 8 | Log(level, string) 9 | { 10 | if(GetOption("DebugMode")) 11 | { 12 | global mainGui 13 | global guiLog 14 | Gui, % mainGui . ":Default" 15 | Gui, % mainGui . ":ListView", guiLog 16 | 17 | LV_Add("", level, Exception("", -3).What . "()", string) 18 | LV_ModifyCol(1) 19 | LV_ModifyCol(2) 20 | LV_ModifyCol(3, "AutoHdr") 21 | LV_Modify(LV_GetCount(), "Vis") 22 | 23 | global logLineCount 24 | if(LV_GetCount() > logLineCount) 25 | { 26 | LV_Delete(1) 27 | } 28 | } 29 | } 30 | 31 | LogMessage(string) 32 | { 33 | if(GetOption("DebugMode")) 34 | { 35 | global Log_Message 36 | Log(Log_Message, string) 37 | } 38 | } 39 | 40 | LogWarning(string) 41 | { 42 | if(GetOption("DebugMode")) 43 | { 44 | global Log_Warning 45 | Log(Log_Warning, string) 46 | } 47 | } 48 | 49 | LogError(string) 50 | { 51 | if(GetOption("DebugMode")) 52 | { 53 | global Log_Error 54 | Log(Log_Error, string) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /includes/GUI/GuiFrame/GuiFrame.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class GuiFrame 4 | { 5 | gui := 0 6 | hwnd := 0 7 | height := 22 8 | border := 2 9 | backgroundColor := "" 10 | 11 | bounds := { x: 0, y:0, w:0, h:0 } 12 | 13 | __New() 14 | { 15 | this.gui := GuiFactory.CreateGUI("-Caption +ToolWindow", this.__Class) 16 | 17 | Gui, % this.gui . ":+HwndframeHwnd" 18 | this.hwnd := frameHwnd 19 | 20 | this.Init() 21 | } 22 | 23 | Init() 24 | { 25 | Gui, % this.gui . ":Show", x0 y0 26 | } 27 | 28 | Update() 29 | { 30 | 31 | } 32 | 33 | Destroy() 34 | { 35 | WinClose, % "ahk_id " . this.hwnd 36 | this.hwnd := 0 37 | 38 | Gui, % this.gui . ":Destroy" 39 | this.gui := 0 40 | } 41 | 42 | SetBackgroundColor(newColor) 43 | { 44 | if(this.backgroundColor != newColor) 45 | { 46 | this.backgroundColor := newColor 47 | Gui, % this.gui . ":Color", % this.backgroundColor 48 | } 49 | } 50 | 51 | SetPosition(x, y, w, h) 52 | { 53 | this.bounds := { x: x, y: y, w: w, h: h } 54 | SetWindowPosition(this.hwnd, x, y, w, h) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Recommended Windows Configuration Changes 2 | ========================================= 3 | 4 | Free up built-in windows hotkeys 5 | -------------------------------- 6 | Open gpedit.msc 7 | Set Configuration/Administrative Templates/Windows Components/File Explorer/Turn Off Windows + X Hotkeys to Enabled 8 | Set Configuration/Administrative Templates/System/Ctrl+Alt+Del Options/Remove Lock Computer to Enabled 9 | 10 | Disable edge snapping 11 | --------------------- 12 | Settings > System > Multitasking > Disable 'Arrange windows automatically by dragging' 13 | 14 | Disable game bar & DVR 15 | ---------------------- 16 | Settings > Gaming > Game Bar > Disable 17 | Settings > Gaming > Game DVR > Disable 18 | 19 | Prevent windows store, photos and settings applications auto-opening 20 | -------------------------------------------------------------------- 21 | -Disable SuperFetch via cmd 22 | -sc config sysmain start=disabled 23 | -sc stop sysmain 24 | 25 | Hide desktop icons 26 | ------------------ 27 | -Open Settings > Personalization > Themes > Desktop icon settings. 28 | -Deselect all checkboxes 29 | -Click Apply 30 | 31 | Disable animations 32 | ------------------ 33 | -Open Settings > Display 34 | -Set Show animations in Windows to Off 35 | 36 | Compatibility 37 | ============= 38 | 39 | Games 40 | ----- 41 | Certain exclusive fullscreen games may misbehave and try to remain focused at all times 42 | This can be fixed by setting the game in question to use borderless window mode 43 | -------------------------------------------------------------------------------- /includes/GUI/GuiFrame/TextFrame.ahk: -------------------------------------------------------------------------------- 1 | class TextFrame extends GuiFrame 2 | { 3 | textElements := {} 4 | textColors := {} 5 | 6 | Init() 7 | { 8 | this.PopulateText() 9 | base.Init() 10 | } 11 | 12 | PopulateText() 13 | { 14 | this.AddTextElement(this.gui, "Title", "w1000") 15 | } 16 | 17 | AddTextElement(gui, name, options = "") 18 | { 19 | global 20 | Gui, % gui . ":Add", Text, % "v" . gui . name . " " . options 21 | } 22 | 23 | SetTextElement(newText, elementName) 24 | { 25 | if(this.textElements[elementName] != newText) 26 | { 27 | this.textElements[elementName] := newText 28 | this.SetTextElement_Internal(this.gui, elementName, this.textElements[elementName]) 29 | } 30 | } 31 | 32 | SetTextElement_Internal(gui, elementName, newText) 33 | { 34 | global 35 | GuiControl, % gui . ":Text", % gui . elementName, % newText 36 | } 37 | 38 | GetTextElementColor(elementName) 39 | { 40 | return this.textColors[elementName] 41 | } 42 | 43 | SetTextElementColor(newTextColor, elementName) 44 | { 45 | if(this.textColors[elementName] != newTextColor) 46 | { 47 | this.textColors[elementName] := newTextColor 48 | this.SetTextElementColor_Internal(this.gui, elementName, this.textColors[elementName]) 49 | } 50 | } 51 | 52 | SetTextElementColor_Internal(gui, elementName, newTextColor) 53 | { 54 | global 55 | Gui, % gui . ":Font", % "c" . newTextColor, Verdana 56 | GuiControl, % gui . ":Font", % gui . elementName 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /config/Windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExcludedWindows": [ 3 | "^$", 4 | ".* ahk_class Shell_TrayWnd", 5 | ".* ahk_class Shell_SecondaryTrayWnd", 6 | ".* ahk_class Windows.UI.Core.CoreWindow", 7 | 8 | "Wox", 9 | "NVIDIA GeForce Overlay", 10 | "^$ ahk_class UnrealWindow", 11 | "ahk_class TApplication", 12 | "^((?!Deluge).)*$ ahk_class gdkWindowToplevel", 13 | 14 | "ahk_class _XRAY_1.5", 15 | "ahk_class MT FRAMEWORK", 16 | "MainWindow" 17 | ], 18 | 19 | "ExcludedPopups": [ 20 | "^$", 21 | "ahk_class Windows.UI.Core.CoreWindow", 22 | "ahk_class Xaml_WindowedPopupClass", 23 | "ahk_class Qt5QWindowToolTipSaveBits", 24 | "Program Manager ahk_class Progman", 25 | "ahk_class TaskListOverlayWnd", 26 | "ahk_class TaskListThumbnailWnd", 27 | "ahk_class MultitaskingViewFrame", 28 | "ahk_class SysDragImage", 29 | "ahk_class AutoHotkeyGUI", 30 | "ahk_class tooltips_class32", 31 | "ahk_class Qt5QWindowPopupDropShadowSaveBits", 32 | "ahk_class Qt5QWindowToolTipDropShadowSaveBits", 33 | "ahk_class gdkWindowTemp", 34 | "ahk_class SysShadow", 35 | "VaHoverButton ahk_class Static", 36 | 37 | "ahk_class MozillaWindowClass", 38 | "^$ ahk_class UnrealWindow", 39 | "Perforce Password Required ahk_class Qt5QWindowIcon", 40 | "Get Revision Progress ahk_class Qt5QWindowIcon", 41 | "^Helix P4V ahk_class Qt5QWindowIcon", 42 | "Backup and Sync ahk_class wxWindowNR", 43 | "hover ahk_class SDL_app" 44 | ], 45 | 46 | "ExcludedChildren": [ 47 | "^$" 48 | ], 49 | 50 | "FloatedWindows": [ 51 | "ahk_class OperationStatusWindow", 52 | "ahk_class #32770", 53 | 54 | "SteamVR Status ahk_class Qt5QWindowIcon", 55 | "vrmonitor ahk_class Qt5QWindowToolSaveBits", 56 | "RemotePC ahk_class HwndWrapper.*", 57 | 58 | "ahk_class NIOH", 59 | 60 | "NCC Touchscreen" 61 | ], 62 | 63 | "SleepWindows": { 64 | "Skype ahk_class ApplicationFrameWindow": 500 65 | } 66 | } -------------------------------------------------------------------------------- /includes/AppBar.ahk: -------------------------------------------------------------------------------- 1 | appBarData := "" 2 | 3 | AppBar_Init() 4 | { 5 | WinGetPos, appX,appY,appW,appH, ahk_id %A_ScriptHwnd% 6 | 7 | appBarMessage := DllCall( "RegisterWindowMessage", Str,"AppBarMsg" ) 8 | 9 | global appBarData 10 | VarSetCapacity(appBarData, 36, 0) 11 | Off := NumPut(36, appBarData) 12 | Off := NumPut(A_ScriptHwnd, Off+0 ) 13 | Off := NumPut(appBarMessage, Off+0 ) 14 | Off := NumPut(1, Off+0 ) 15 | Off := NumPut(appX, Off+0 ) 16 | Off := NumPut(appY, Off+0 ) 17 | Off := NumPut(appW, Off+0 ) 18 | Off := NumPut(appH, Off+0 ) 19 | Off := NumPut(1, Off+0 ) 20 | 21 | DllCall("Shell32.dll\SHAppBarMessage", UInt,(ABM_NEW:=0x0), UInt, &appBarData) 22 | OnMessage(appBarMessage, "AppBar_Callback") 23 | } 24 | 25 | AppBar_Callback(wParam, lParam, msg, hwnd) { 26 | if(wParam == 2) 27 | { 28 | global treeRoot 29 | 30 | if(lParam == 1) 31 | { 32 | LogMessage("Fullscreen window activated") 33 | 34 | WinGet, id, list 35 | Loop, %id% 36 | { 37 | hwnd := id%A_Index% 38 | window := GetWindowWithHwnd(treeRoot, hwnd) 39 | if(window != "") 40 | { 41 | if(GetWindowIsFullscreen(hwnd)) 42 | { 43 | window.GetParentMonitor().fullscreenWindow := window 44 | } 45 | } 46 | } 47 | } 48 | else 49 | { 50 | LogMessage("Fullscreen window deactivated") 51 | for index, element in treeRoot.GetActiveChild().children 52 | { 53 | fullscreenWindow := element.fullscreenWindow 54 | if(fullscreenWindow != "") 55 | { 56 | if(!GetWindowIsFullscreen(hwnd)) 57 | { 58 | element.fullscreenWindow := "" 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | AppBar_Shutdown() 67 | { 68 | global appBarData 69 | DllCall("Shell32.dll\SHAppBarMessage", UInt, (ABM_REMOVE := 0x1), UInt, &appBarData) 70 | } 71 | 72 | OnExit("AppBar_Shutdown") -------------------------------------------------------------------------------- /includes/Config/WindowConfig.ahk: -------------------------------------------------------------------------------- 1 | windowConfig := {} 2 | LoadConfig(windowConfig, "config/Windows.json") 3 | 4 | ; Window Filtering Functions 5 | QueryIncludesWindow(winTitle, hwnd) 6 | { 7 | WinGet, id, list, % winTitle 8 | Loop, %id% 9 | { 10 | candidateHwnd := id%A_Index% 11 | if(hwnd == candidateHwnd) 12 | { 13 | return true 14 | } 15 | } 16 | 17 | return false 18 | } 19 | 20 | ShouldExcludeWindow(hwnd) 21 | { 22 | if(GetWindowIsPopup(hwnd)) 23 | { 24 | return ShouldExcludePopup(hwnd) 25 | } 26 | 27 | if(GetWindowIsChild(hwnd)) 28 | { 29 | return ShouldExcludeChild(hwnd) 30 | } 31 | 32 | shouldFloat := false 33 | 34 | global windowConfig 35 | for index, element in windowConfig.ExcludedWindows 36 | { 37 | if(QueryIncludesWindow(element, hwnd)) 38 | { 39 | return true 40 | } 41 | } 42 | 43 | return false 44 | } 45 | 46 | ShouldExcludePopup(hwnd) 47 | { 48 | shouldFloat := false 49 | 50 | global windowConfig 51 | for index, element in windowConfig.ExcludedPopups 52 | { 53 | if(QueryIncludesWindow(element, hwnd)) 54 | { 55 | return true 56 | } 57 | } 58 | 59 | return false 60 | } 61 | 62 | ShouldExcludeChild(hwnd) 63 | { 64 | shouldFloat := false 65 | 66 | global windowConfig 67 | for index, element in windowConfig.ExcludedChildren 68 | { 69 | if(QueryIncludesWindow(element, hwnd)) 70 | { 71 | return true 72 | } 73 | } 74 | 75 | return false 76 | } 77 | 78 | ShouldFloatWindow(hwnd) 79 | { 80 | shouldFloat := false 81 | 82 | global windowConfig 83 | for index, element in windowConfig.FloatedWindows 84 | { 85 | if(QueryIncludesWindow(element, hwnd)) 86 | { 87 | return true 88 | } 89 | } 90 | 91 | return false 92 | } 93 | 94 | GetSleepForWindow(hwnd) 95 | { 96 | global windowConfig 97 | for index, element in windowConfig.SleepWindows 98 | { 99 | if(QueryIncludesWindow(index, hwnd)) 100 | { 101 | return element 102 | } 103 | } 104 | 105 | return -1 106 | } -------------------------------------------------------------------------------- /includes/Tree/SplitContainer.ahk: -------------------------------------------------------------------------------- 1 | ; SplitContainer node - contains WindowContainer nodes and represents their focus and layout 2 | class SplitContainer extends Container 3 | { 4 | layout := Layout_None 5 | orientation := Orientation_None 6 | guiWorkArea := 0 7 | 8 | __New(ByRef parent, orientation, layout) 9 | { 10 | base.__New(parent) 11 | 12 | this.layout := layout 13 | this.orientation := orientation 14 | } 15 | 16 | Update() 17 | { 18 | if(this.parent.__Class != "RootContainer") 19 | { 20 | if(this.parent.__Class == "WorkspaceContainer") 21 | { 22 | if(this.GetParentMonitor().children.Length() != 1 && this.GetParentWorkspace() != this.GetParentMonitor().GetActiveChild()) 23 | { 24 | if(this.GetChildCount() == 0) 25 | { 26 | this.Destroy() 27 | return 28 | } 29 | } 30 | } 31 | else 32 | { 33 | if(this.GetChildCount() == 0) 34 | { 35 | this.Destroy() 36 | return 37 | } 38 | } 39 | } 40 | 41 | base.Update() 42 | } 43 | 44 | GetLayout() 45 | { 46 | return this.layout 47 | } 48 | 49 | GetOrientation() 50 | { 51 | return this.orientation 52 | } 53 | 54 | CreateFrame() 55 | { 56 | if(this.parent.__Class != "RootContainer") 57 | { 58 | base.CreateFrame() 59 | this.frame.border.top := 0 60 | } 61 | } 62 | 63 | UpdateFrame() 64 | { 65 | if(this.parent.__Class != "RootContainer") 66 | { 67 | base.UpdateFrame() 68 | this.frame.text := this.ToString() 69 | } 70 | } 71 | 72 | ToString() 73 | { 74 | NodeString := this.GetOrientation() . " " . this.GetLayout() 75 | 76 | return NodeString 77 | } 78 | 79 | IsStringBold() 80 | { 81 | return this == this.parent.GetActiveChild() 82 | } 83 | 84 | PopulateGUI(guiParent) 85 | { 86 | base.PopulateGUI(guiParent) 87 | 88 | workArea := this.GetWorkArea() 89 | this.guiWorkArea := TV_Update(this.guiWorkArea, this.guiTreeEntry, "Work Area: " . workArea.left . ", " . workArea.top . ", " . workArea.right . ", " . workArea.bottom, "+First") 90 | } 91 | 92 | MarkGUIDirty() 93 | { 94 | TV_Delete(this.guiWorkArea) 95 | this.guiWorkArea := 0 96 | 97 | base.MarkGUIDirty() 98 | } 99 | } -------------------------------------------------------------------------------- /includes/GUI/GuiFrame/GDIPFrame.ahk: -------------------------------------------------------------------------------- 1 | class GDIPFrame extends GuiFrame 2 | { 3 | canvasHwnd := "" 4 | canvasImage := "" 5 | canvasGraphics := "" 6 | 7 | borderColor := 0xFF2C4FDB 8 | backgroundColor := 0xFF4A6EFF 9 | textColor := "FFFFFFFF" 10 | 11 | border := { left: 1, top: 1, right: 1, bottom: 1 } 12 | text := "" 13 | 14 | _borderBrush := "" 15 | _backgroundBrush := "" 16 | 17 | Init() 18 | { 19 | this.CreateCanvas(this.gui, this.height) 20 | GuiControlGet, canvasHwnd, hwnd, % this.gui . "Canvas" 21 | this.canvasHwnd := canvasHwnd 22 | 23 | base.Init() 24 | } 25 | 26 | CreateCanvas(gui, height) 27 | { 28 | global 29 | Gui, % gui . ":Add", Picture, % "x0 y0 w400 h" . height . " 0xE v" . gui . "Canvas" 30 | } 31 | 32 | Update() 33 | { 34 | base.Update() 35 | 36 | this.DrawGDIP() 37 | this.UpdateImage() 38 | } 39 | 40 | DrawGDIP() 41 | { 42 | 43 | innerX := this.border.left 44 | innerY := this.border.top 45 | innerW := this.bounds.w - this.border.left - this.border.right 46 | innerH := this.bounds.h - this.border.top - this.border.bottom 47 | 48 | borderBrush := Gdip_BrushCreateSolid(this.borderColor) 49 | backgroundBrush := Gdip_BrushCreateSolid(this.backgroundColor) 50 | 51 | Gdip_FillRectangle(this.canvasGraphics, borderBrush, 0, 0, this.bounds.w, this.bounds.h) 52 | Gdip_FillRectangle(this.canvasGraphics, backgroundBrush, innerX, innerY, innerW, innerH) 53 | Gdip_TextToGraphics(this.canvasGraphics, this.text, "x" . innerX + 4 . " y" innerY + 2 . " w" . innerW . " h" . innerH . " c" . this.textColor . " r0 s11 Left vCenter", "Verdana") 54 | 55 | Gdip_DeleteBrush(borderBrush) 56 | Gdip_DeleteBrush(backgroundBrush) 57 | } 58 | 59 | UpdateImage() 60 | { 61 | hBitmap := Gdip_CreateHBITMAPFromBitmap(this.canvasImage) 62 | SetImage(this.canvasHwnd, hBitmap) 63 | DeleteObject(hBitmap) 64 | } 65 | 66 | Destroy() 67 | { 68 | this.DeleteGDIPResources() 69 | 70 | base.Destroy() 71 | } 72 | 73 | SetPosition(x, y, w, h) 74 | { 75 | base.SetPosition(x, y, w, h) 76 | this.RefreshGDIPResources() 77 | } 78 | 79 | RefreshGDIPResources() 80 | { 81 | this.DeleteGDIPResources() 82 | this.CreateGDIPResources() 83 | } 84 | 85 | CreateGDIPResources() 86 | { 87 | this.canvasImage := Gdip_CreateBitmap(this.bounds.w, this.bounds.h) 88 | this.canvasGraphics := Gdip_GraphicsFromImage(this.canvasImage) 89 | } 90 | 91 | DeleteGDIPResources() 92 | { 93 | Gdip_DeleteGraphics(this.canvasGraphics) 94 | Gdip_DisposeImage(this.canvasImage) 95 | } 96 | 97 | SetBorderColor(newColor) 98 | { 99 | this.borderColor := newColor 100 | } 101 | 102 | SetTextColor(newColor) 103 | { 104 | this.textColor := newColor 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /includes/GUI/Gui.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include includes/GUI/GuiFrame/GuiFrame.ahk 4 | #include includes/GUI/GuiFrame/TextFrame.ahk 5 | #include includes/GUI/GuiFrame/MonitorFrame.ahk 6 | #include includes/GUI/GuiFrame/GDIPFrame.ahk 7 | 8 | ; GUI 9 | mainGui := "" 10 | guiTree := "" 11 | guiUnmanaged := "" 12 | guiIncludes := "" 13 | guiFloats := "" 14 | guiExcludes := "" 15 | guiOptions := "" 16 | guiLog := "" 17 | 18 | ; Functions 19 | GUI_Init() 20 | { 21 | ; Set Icon 22 | Menu, Tray, Icon, graphics/logo.png 23 | Menu, Tray, Tip, i10 24 | if(!GetOption("DebugMode")) 25 | { 26 | Menu, Tray, NoStandard 27 | } 28 | Menu, Tray, Add, Open, SpawnGui 29 | Menu, Tray, Default, Open 30 | Menu, Tray, Add, Window Spy, OpenWindowSpy 31 | Menu, Tray, Add, Exit, Exit 32 | 33 | ; Setup GUI 34 | global mainGui 35 | mainGui := GuiFactory.CreateGUI("+Delimiter`n +AlwaysOnTop", "main") 36 | 37 | ; Create tabs 38 | tabs := "State`nOptions`n" 39 | if(GetOption("DebugMode")) 40 | { 41 | tabs .= "Log" 42 | } 43 | Gui, % mainGui . ":Add", Tab, w620 h780, %tabs% 44 | 45 | ; Tree 46 | global guiTree 47 | Gui, % mainGui . ":Add", Text, Section, Tree 48 | Gui, % mainGui . ":Add", TreeView, w590 h360 vguiTree 49 | 50 | Gui, % mainGui . ":Add", Button,, Toggle Floating 51 | Gui, % mainGui . ":Add", Button, x+5, Exclude 52 | 53 | Gui, % mainGui . ":Add", Text, Section xs0, Unmanaged Windows 54 | global guiUnmanaged 55 | Gui, % mainGui . ":Add", ListView, w590 h260 vguiUnmanaged +NoSort +NoSortHdr, HWND`nTitle`nClass 56 | 57 | Gui, % mainGui . ":Add", Button,, Include 58 | Gui, % mainGui . ":Add", Button,x+5, Float 59 | 60 | ; Options 61 | Gui, % mainGui . ":Tab", 2 62 | Gui, % mainGui . ":Add", Text,, Options 63 | global guiOptions 64 | Gui, % mainGui . ":Add", ListView, w590 h160 vguiOptions +NoSort +NoSortHdr, Key`nValue 65 | 66 | global optionsConfig 67 | for key, value in optionsConfig 68 | { 69 | LV_Add("", key, value) 70 | } 71 | 72 | LV_ModifyCol(1) 73 | LV_ModifyCol(2, "AutoHdr") 74 | 75 | global guiIncludes 76 | Gui, % mainGui . ":Add", Text, Section, Included Windows 77 | Gui, % mainGui . ":Add", ListView, w590 h160 vguiIncludes +NoSort +NoSortHdr, WinTitle 78 | 79 | global guiFloats 80 | Gui, % mainGui . ":Add", Text, Section, Floated Windows 81 | Gui, % mainGui . ":Add", ListView, w590 h160 vguiFloats +NoSort +NoSortHdr, WinTitle 82 | 83 | global guiExcludes 84 | Gui, % mainGui . ":Add", Text, Section, Excluded Windows 85 | Gui, % mainGui . ":Add", ListView, w590 h160 vguiExcludes +NoSort +NoSortHdr, WinTitle 86 | 87 | ; Log 88 | if(GetOption("DebugMode")) 89 | { 90 | Gui, % mainGui . ":Tab", 3 91 | global guiLog 92 | Gui, % mainGui . ":Add", ListView, w590 h740 vguiLog +NoSort +NoSortHdr, Level`nSource`nLog 93 | } 94 | } 95 | 96 | GUI_Update() 97 | { 98 | global treeRoot 99 | 100 | global guiTree 101 | GuiControl, -Redraw, guiTree 102 | treeRoot.PopulateGUI(0) 103 | GuiControl, +Redraw, guiTree 104 | } 105 | 106 | SpawnGui() 107 | { 108 | global mainGui 109 | Gui, % mainGui . ":Show", w640 h800, i10 110 | } 111 | 112 | OpenWindowSpy() 113 | { 114 | Run, C:\OtherPrograms\AutoHotkey\WindowSpy.ahk 115 | } 116 | 117 | Exit() 118 | { 119 | ExitApp 120 | } 121 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | -Activate containers when clicking their frame 4 | -Needs a proper click handling system for frame elements 5 | 6 | -Proper monitor frame functionality 7 | -Correct text sizing and alignment using parent width 8 | -Taskbar icon popup 9 | 10 | -GUI-driven include / exclude configuration 11 | 12 | -Theme options for GUI colours, fonts 13 | 14 | -Wrap focus around monitors when navigating 15 | 16 | -Built-in dmenu-like functionality 17 | 18 | -Github-driven auto update using URlDownloadToFile and https://github.com/:owner/:repo/zipball/:branch 19 | 20 | -Placeholder GUI windows 21 | -Take WinTitle as a member variable, wait for it and convert into window container using hwnd 22 | 23 | -Layout save/restore 24 | 25 | -Check for existing window of same process and insert adjacent when adding new window containers to the tree 26 | -To fix UE4 opening the editor on the currently active workspace rather than the one the splash appeared on 27 | -The splash closes before the editor is opened, so this will need a window history setup. Complex. 28 | 29 | -Floating window move/resize hotkeys 30 | 31 | -Vim-like marks 32 | 33 | -Window position/size interpolation option 34 | -Lerp window into work area 35 | 36 | -Window throw gesture 37 | -Derive velocity from mouse position over time and add offset to window position on drag release 38 | 39 | Fixes 40 | ===== 41 | -Unify container / layout implementation 42 | -Workspaces can be achieved with a horizontal tab container 43 | -Set workspace hotkeys to find the inner/outermost (depending on shift modifier) tabbed layout parent and switch its active child 44 | -Single container class? Maybe not, but layout (workspace, split) can be simplified to a single implementation 45 | 46 | -Proper sorting for workspaces within monitor 47 | 48 | -Fix window frame top border being 0 for vertical split children with index > 1 49 | 50 | -Fix custom workspace names becoming associated with different monitors 51 | -How to fix if the OS' internal monitor names change without warning? 52 | -Tie to monitor position relative to primary? 53 | -Could tie in to a monitor navigation refactor, grid system? 54 | 55 | -Fix taskbar becoming managed under some circumstances 56 | -WIP, rewrote window filtering 57 | -If it still doesn't work, it means windows are losing their title or class temporarily 58 | -Fix by checking each window container for exclusion and destroying if needed? 59 | 60 | -More robust monitor config change detection 61 | -Check monitor index against name 62 | -Preserve hierarchy by moving existing monitors, add / remove others as needed 63 | 64 | -More robust monitor-to-monitor navigation 65 | -Need a solution that doesn't assume monitors are laid out ie=mcn a linear fashion 66 | -Remove root container split, offload navigation logic to root 67 | 68 | -Check window for min/max size in WindowContainer, center in work area rather than aligning top-left 69 | -Should allow for nicer management of fixed-size windows like the UE4 splash 70 | -Won't work with pre-sized windows that don't enforce size, add as window config option? 71 | 72 | -Revisit undocking / implicit split behaviour (ex. 2V containing a 2H and 1W, try to move child of 2H upward, doesn't turn 2V into a 3V) 73 | 74 | -Fix floating windows not responding to moving via keyboard 75 | 76 | -Fix certain windows causing windows key to fall through and activate the start menu 77 | -Reproducible with RemotePC client window 78 | -Seems to be down to the way RemotePC hooks the windows key 79 | 80 | Other 81 | ===== 82 | -Create logo with semitransparent background for exe icon 83 | 84 | -Fix README.md formatting -------------------------------------------------------------------------------- /includes/WindowStatics.ahk: -------------------------------------------------------------------------------- 1 | ; OS window manipulation functions 2 | 3 | GetWindowPosition(hwnd) 4 | { 5 | WinGetPos, x, y, w, h, ahk_id %hwnd% 6 | return { x: x, y: y, w: w, h: h } 7 | } 8 | 9 | GetWindowCenter(hwnd) 10 | { 11 | windowPosition := GetWindowPosition(hwnd) 12 | return { x: windowPosition.x + (windowPosition.w / 2), y: windowPosition.y + (windowPosition.h / 2) } 13 | } 14 | 15 | GetWindowTitle(hwnd) 16 | { 17 | WinGetTitle, winTitle, ahk_id %hwnd% 18 | return winTitle 19 | } 20 | 21 | GetWindowClass(hwnd) 22 | { 23 | WinGetClass, winClass, ahk_id %hwnd% 24 | return winClass 25 | } 26 | 27 | GetWindowIsActive(hwnd) 28 | { 29 | return hwnd == WinExist("A") 30 | } 31 | 32 | GetWindowIsMaximized(hwnd) 33 | { 34 | WinGet, winMinMax, MinMax, ahk_id %hwnd% 35 | return winMinMax == 1 36 | } 37 | 38 | GetWindowIsMinimized(hwnd) 39 | { 40 | WinGet, winMinMax, MinMax, ahk_id %hwnd% 41 | return winMinMax == -1 42 | } 43 | 44 | GetWindowIsPopup(hwnd) 45 | { 46 | WinGet, winStyle, Style, ahk_id %hwnd% 47 | return (winStyle & 0x80000000) != 0 48 | } 49 | 50 | GetWindowIsChild(hwnd) 51 | { 52 | WinGet, winStyle, Style, ahk_id %hwnd% 53 | return winStyle & 0x40000000 54 | } 55 | 56 | GetWindowAlwaysOnTop(hwnd) 57 | { 58 | WinGet, winExStyle, ExStyle, ahk_id %hwnd% 59 | return winExStyle & 0x00000008 60 | } 61 | 62 | GetWindowIsFullscreen(hwnd) 63 | { 64 | WinGet, style, Style, ahk_id %hwnd% 65 | if((style & 0x20800000) ? 0 : 1) 66 | { 67 | winPos := GetWindowPosition(hwnd) 68 | winRight := winPos.x + winPos.w 69 | winBottom := winPos.y + winPos.h 70 | 71 | global treeRoot 72 | for index, element in treeRoot.GetActiveChild().children 73 | { 74 | displayArea := element.GetDisplayArea() 75 | isFullscreen := true 76 | isFullscreen &= winPos.x == displayArea.left 77 | isFullscreen &= winRight == displayArea.right 78 | isFullscreen &= winPos.y == displayArea.top 79 | isFullscreen &= winBottom == displayArea.bottom 80 | if(isFullscreen) 81 | { 82 | return true 83 | } 84 | } 85 | } 86 | 87 | return false 88 | } 89 | 90 | GetWindowIsHidden(hwnd) 91 | { 92 | WinGet, style, Style, % "ahk_id " . hwnd 93 | return style & 0x10000000 ? 0 : 1 94 | } 95 | 96 | SetWindowPosition(hwnd, x, y, w, h) 97 | { 98 | WinMove, % "ahk_id " . hwnd,, x, y, w, h 99 | } 100 | 101 | SetWindowActive(hwnd) 102 | { 103 | if(!GetWindowIsActive(hwnd)) 104 | { 105 | WinActivate, ahk_id %hwnd% 106 | } 107 | } 108 | 109 | SetWindowInactive() 110 | { 111 | if(WinExist("A") != WinExist("ahk_class Progman")) 112 | { 113 | WinActivate, ahk_class Progman 114 | } 115 | } 116 | 117 | SetWindowMaximized(hwnd) 118 | { 119 | if(!GetWindowIsMaximized(hwnd)) 120 | { 121 | WinMaximize, ahk_id %hwnd% 122 | } 123 | } 124 | 125 | SetWindowRestored(hwnd) 126 | { 127 | if(GetWindowIsMaximized(hwnd) || GetWindowIsMinimized(hwnd)) 128 | { 129 | WinRestore, ahk_id %hwnd% 130 | } 131 | } 132 | 133 | SetWindowMinimized(hwnd) 134 | { 135 | if(!GetWindowIsMinimized(hwnd)) 136 | { 137 | WinMinimize, ahk_id %hwnd% 138 | } 139 | } 140 | 141 | SetWindowHidden(hwnd) 142 | { 143 | WinHide, % "ahk_id " . hwnd 144 | } 145 | 146 | SetWindowShown(hwnd) 147 | { 148 | WinShow, % "ahk_id " . hwnd 149 | } 150 | 151 | SetWindowAlwaysOnTop(hwnd) 152 | { 153 | if(!GetWindowAlwaysOnTop(hwnd)) 154 | { 155 | WinSet, AlwaysOnTop, On, ahk_id %hwnd% 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /includes/Tree/MonitorContainer.ahk: -------------------------------------------------------------------------------- 1 | ; MonitorContainer - Represents a workspace, contains SplitContainers 2 | class MonitorContainer extends Container 3 | { 4 | monitor := "" 5 | outerGap := GetOption("DefaultOuterGap") 6 | innerGap := GetOption("DefaultInnerGap") 7 | guiWorkArea := 0 8 | fullscreenWindow := "" 9 | 10 | __New(ByRef parent, ByRef monitor) 11 | { 12 | base.__New(parent) 13 | 14 | this.monitor := monitor 15 | 16 | newWorkspace := new WorkspaceContainer(this, 1) 17 | this.AddChild(newWorkspace) 18 | this.SetActiveChild(newWorkspace) 19 | } 20 | 21 | Update() 22 | { 23 | if(this.fullscreenWindow == "") 24 | { 25 | base.Update() 26 | } 27 | } 28 | 29 | CreateFrame() 30 | { 31 | this.frame := new MonitorFrame() 32 | this.frame.border.left := 0 33 | this.frame.border.top := 0 34 | this.frame.border.right := 0 35 | } 36 | 37 | RemoveChildAt(childIndex, updateActive = false) 38 | { 39 | ; Override updateActive to prevent auto-switching when an empty unfocused workspace destroys itself 40 | base.RemoveChildAt(childIndex, false) 41 | } 42 | 43 | UpdateFrame() 44 | { 45 | base.UpdateFrame() 46 | 47 | this.frame.text := "" 48 | this.frame.text .= GetNetworkIsConnected() ? "Connected" : "No Connection" 49 | this.frame.text .= " " 50 | 51 | this.frame.text .= Floor(GetSystemVolume()) . "%", "Volume" 52 | this.frame.text .= " " 53 | 54 | FormatTime, formattedTime, A_Now, h:mm tt 55 | this.frame.text .= formattedTime 56 | this.frame.text .= " " 57 | } 58 | 59 | GetDisplayArea() 60 | { 61 | monitor := this.monitor 62 | SysGet displayArea, Monitor, %monitor% 63 | return {left: displayAreaLeft, top: displayAreaTop, right: displayAreaRight, bottom: displayAreaBottom} 64 | } 65 | 66 | GetWorkArea() 67 | { 68 | monitor := this.monitor 69 | SysGet monWorkArea, MonitorWorkArea, %monitor% 70 | return {left: monWorkAreaLeft + this.outerGap, top: monWorkAreaTop + this.outerGap, right: monWorkAreaRight - this.outerGap, bottom: monWorkAreaBottom - this.outerGap} 71 | } 72 | 73 | GetFrameArea() 74 | { 75 | frameArea := this.GetWorkArea() 76 | frameArea.bottom := frameArea.top + this.frame.height 77 | return frameArea 78 | } 79 | 80 | ToString() 81 | { 82 | NodeString := GetMonitorName(this.monitor) 83 | if(GetMonitorIsPrimary(this.monitor)) 84 | { 85 | NodeString .= " (Primary)" 86 | } 87 | 88 | return NodeString 89 | } 90 | 91 | IsStringBold() 92 | { 93 | return this == GetActiveMonitorContainer() 94 | } 95 | 96 | PopulateGUI(guiParent) 97 | { 98 | base.PopulateGUI(guiParent) 99 | 100 | workArea := this.GetWorkArea() 101 | this.guiWorkArea := TV_Update(this.guiWorkArea, this.guiTreeEntry, "Work Area: " . workArea.left . ", " . workArea.top . ", " . workArea.right . ", " . workArea.bottom, "+First") 102 | } 103 | 104 | MarkGUIDirty() 105 | { 106 | TV_Delete(this.guiWorkArea) 107 | this.guiWorkArea := 0 108 | 109 | base.MarkGUIDirty() 110 | } 111 | } 112 | 113 | ; Functions 114 | GetMonitorName(monitor) 115 | { 116 | monName := "" 117 | SysGet, monName, MonitorName, % monitor 118 | return monName 119 | } 120 | 121 | GetMonitorIsPrimary(monitor) 122 | { 123 | monPrimary := "" 124 | SysGet, monPrimary, MonitorPrimary 125 | return monitor == monPrimary 126 | } -------------------------------------------------------------------------------- /graphics/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 48 | 53 | 54 | 56 | 57 | 59 | image/svg+xml 60 | 62 | 63 | 64 | 65 | 66 | 72 | 79 | 80 | 86 | 89 | 96 | 103 | 110 | 117 | 126 | 133 | 138 | 143 | 149 | 155 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /config/Hotkeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hotkeys": [ 3 | { 4 | "hotkeys": [ 5 | "#+e" 6 | ], 7 | "function": "i10_Exit" 8 | }, 9 | { 10 | "hotkeys": [ 11 | "#+r" 12 | ], 13 | "function": "i10_Restart" 14 | }, 15 | 16 | { 17 | "hotkeys": [ 18 | "pause", 19 | "#+p" 20 | ], 21 | "function": "i10_Pause" 22 | }, 23 | 24 | { 25 | "hotkeys": [ 26 | "#h" 27 | ], 28 | "function": "SplitActiveContainer", 29 | "params": [ 30 | "Horizontal" 31 | ] 32 | }, 33 | { 34 | "hotkeys": [ 35 | "#v" 36 | ], 37 | "function": "SplitActiveContainer", 38 | "params": [ 39 | "Vertical" 40 | ] 41 | }, 42 | { 43 | "hotkeys": [ 44 | "#+h" 45 | ], 46 | "function": "SplitActiveContainerWithPlaceholder", 47 | "params": [ 48 | "Horizontal" 49 | ] 50 | }, 51 | { 52 | "hotkeys": [ 53 | "#+v" 54 | ], 55 | "function": "SplitActiveContainerWithPlaceholder", 56 | "params": [ 57 | "Vertical" 58 | ] 59 | }, 60 | 61 | { 62 | "hotkeys": [ 63 | "#e" 64 | ], 65 | "function": "ToggleSplitLayout" 66 | }, 67 | { 68 | "hotkeys": [ 69 | "#w" 70 | ], 71 | "function": "SetTabbedLayout" 72 | }, 73 | { 74 | "hotkeys": [ 75 | "#s" 76 | ], 77 | "function": "SetStackingLayout" 78 | }, 79 | 80 | { 81 | "hotkeys": [ 82 | "#Enter" 83 | ], 84 | "function": "i10_Run", 85 | "params": [ 86 | "C:\\Shortcuts\\ConEmu.lnk" 87 | ] 88 | }, 89 | 90 | { 91 | "hotkeys": [ 92 | "#f" 93 | ], 94 | "function": "ToggleActiveWindowFullscreen" 95 | }, 96 | { 97 | "hotkeys": [ 98 | "#c" 99 | ], 100 | "function": "CloseActiveWindow" 101 | }, 102 | 103 | { 104 | "hotkeys": [ 105 | "#j", 106 | "#Left" 107 | ], 108 | "function": "Navigate", 109 | "params": [ 110 | -1, 111 | "Horizontal" 112 | ] 113 | }, 114 | { 115 | "hotkeys": [ 116 | "#k", 117 | "#Down" 118 | ], 119 | "function": "Navigate", 120 | "params": [ 121 | 1, 122 | "Vertical" 123 | ] 124 | }, 125 | { 126 | "hotkeys": [ 127 | "#l", 128 | "#Up" 129 | ], 130 | "function": "Navigate", 131 | "params": [ 132 | -1, 133 | "Vertical" 134 | ] 135 | }, 136 | { 137 | "hotkeys": [ 138 | "#;", 139 | "#Right" 140 | ], 141 | "function": "Navigate", 142 | "params": [ 143 | 1, 144 | "Horizontal" 145 | ] 146 | }, 147 | { 148 | "hotkeys": [ 149 | "#a" 150 | ], 151 | "function": "FocusParentContainer" 152 | }, 153 | { 154 | "hotkeys": [ 155 | "#d" 156 | ], 157 | "function": "FocusChildContainer" 158 | }, 159 | 160 | { 161 | "hotkeys": [ 162 | "#+j", 163 | "#+Left" 164 | ], 165 | "function": "MoveActiveWindow", 166 | "params": [ 167 | -1, 168 | "Horizontal" 169 | ] 170 | }, 171 | { 172 | "hotkeys": [ 173 | "#+k", 174 | "#+Down" 175 | ], 176 | "function": "MoveActiveWindow", 177 | "params": [ 178 | 1, 179 | "Vertical" 180 | ] 181 | }, 182 | { 183 | "hotkeys": [ 184 | "#+l", 185 | "#+Up" 186 | ], 187 | "function": "MoveActiveWindow", 188 | "params": [ 189 | -1, 190 | "Vertical" 191 | ] 192 | }, 193 | { 194 | "hotkeys": [ 195 | "#+;", 196 | "#+Right" 197 | ], 198 | "function": "MoveActiveWindow", 199 | "params": [ 200 | 1, 201 | "Horizontal" 202 | ] 203 | }, 204 | 205 | { 206 | "hotkeys": [ 207 | "#1" 208 | ], 209 | "function": "SetActiveWorkspace", 210 | "params": [ 211 | 1 212 | ] 213 | }, 214 | { 215 | "hotkeys": [ 216 | "#2" 217 | ], 218 | "function": "SetActiveWorkspace", 219 | "params": [ 220 | 2 221 | ] 222 | }, 223 | { 224 | "hotkeys": [ 225 | "#3" 226 | ], 227 | "function": "SetActiveWorkspace", 228 | "params": [ 229 | 3 230 | ] 231 | }, 232 | { 233 | "hotkeys": [ 234 | "#4" 235 | ], 236 | "function": "SetActiveWorkspace", 237 | "params": [ 238 | 4 239 | ] 240 | }, 241 | { 242 | "hotkeys": [ 243 | "#5" 244 | ], 245 | "function": "SetActiveWorkspace", 246 | "params": [ 247 | 5 248 | ] 249 | }, 250 | { 251 | "hotkeys": [ 252 | "#6" 253 | ], 254 | "function": "SetActiveWorkspace", 255 | "params": [ 256 | 6 257 | ] 258 | }, 259 | { 260 | "hotkeys": [ 261 | "#7" 262 | ], 263 | "function": "SetActiveWorkspace", 264 | "params": [ 265 | 7 266 | ] 267 | }, 268 | { 269 | "hotkeys": [ 270 | "#8" 271 | ], 272 | "function": "SetActiveWorkspace", 273 | "params": [ 274 | 8 275 | ] 276 | }, 277 | { 278 | "hotkeys": [ 279 | "#9" 280 | ], 281 | "function": "SetActiveWorkspace", 282 | "params": [ 283 | 9 284 | ] 285 | }, 286 | 287 | { 288 | "hotkeys": [ 289 | "#+1" 290 | ], 291 | "function": "MoveActiveWindowToWorkspace", 292 | "params": [ 293 | 1 294 | ] 295 | }, 296 | { 297 | "hotkeys": [ 298 | "#+2" 299 | ], 300 | "function": "MoveActiveWindowToWorkspace", 301 | "params": [ 302 | 2 303 | ] 304 | }, 305 | { 306 | "hotkeys": [ 307 | "#+3" 308 | ], 309 | "function": "MoveActiveWindowToWorkspace", 310 | "params": [ 311 | 3 312 | ] 313 | }, 314 | { 315 | "hotkeys": [ 316 | "#+4" 317 | ], 318 | "function": "MoveActiveWindowToWorkspace", 319 | "params": [ 320 | 4 321 | ] 322 | }, 323 | { 324 | "hotkeys": [ 325 | "#+5" 326 | ], 327 | "function": "MoveActiveWindowToWorkspace", 328 | "params": [ 329 | 5 330 | ] 331 | }, 332 | { 333 | "hotkeys": [ 334 | "#+6" 335 | ], 336 | "function": "MoveActiveWindowToWorkspace", 337 | "params": [ 338 | 6 339 | ] 340 | }, 341 | { 342 | "hotkeys": [ 343 | "#+7" 344 | ], 345 | "function": "MoveActiveWindowToWorkspace", 346 | "params": [ 347 | 7 348 | ] 349 | }, 350 | { 351 | "hotkeys": [ 352 | "#+8" 353 | ], 354 | "function": "MoveActiveWindowToWorkspace", 355 | "params": [ 356 | 8 357 | ] 358 | }, 359 | { 360 | "hotkeys": [ 361 | "#+9" 362 | ], 363 | "function": "MoveActiveWindowToWorkspace", 364 | "params": [ 365 | 9 366 | ] 367 | }, 368 | 369 | { 370 | "hotkeys": [ 371 | "#n" 372 | ], 373 | "function": "ModifyActiveMonitorInnerGap", 374 | "params": [ 375 | 5 376 | ] 377 | }, 378 | { 379 | "hotkeys": [ 380 | "#+n" 381 | ], 382 | "function": "ModifyActiveMonitorInnerGap", 383 | "params": [ 384 | -5 385 | ] 386 | }, 387 | { 388 | "hotkeys": [ 389 | "#m" 390 | ], 391 | "function": "ModifyActiveMonitorOuterGap", 392 | "params": [ 393 | 5 394 | ] 395 | }, 396 | { 397 | "hotkeys": [ 398 | "#+m" 399 | ], 400 | "function": "ModifyActiveMonitorOuterGap", 401 | "params": [ 402 | -5 403 | ] 404 | }, 405 | 406 | { 407 | "hotkeys": [ 408 | "#z" 409 | ], 410 | "function": "i10_SaveState" 411 | } 412 | ] 413 | } -------------------------------------------------------------------------------- /includes/Tree/WorkspaceContainer.ahk: -------------------------------------------------------------------------------- 1 | ; WorkspaceContainer - Represents a workspace, contains MonitorContainers 2 | class WorkspaceContainer extends Container 3 | { 4 | workspaceIndex := -1 5 | maximizedWindow := "" 6 | 7 | __New(ByRef parent, index) 8 | { 9 | global Layout_Split 10 | global Layout_None 11 | global Orientation_H 12 | global Orientation_V 13 | global Orientation_None 14 | 15 | base.__New(parent) 16 | 17 | this.workspaceIndex := index 18 | 19 | workArea := this.GetWorkArea() 20 | workAreaWidth := workArea.right - workArea.left 21 | workAreaHeight := workArea.bottom - workArea.top 22 | 23 | ; Apply default orientation and layout 24 | layout := GetOption("WorkspaceLayout") 25 | if(layout == Layout_None) 26 | { 27 | layout := Layout_Split 28 | } 29 | 30 | orientation := GetOption("DefaultOrientation") 31 | if(orientation == Orientation_None) 32 | { 33 | if(workAreaWidth > workAreaHeight) 34 | { 35 | orientation := Orientation_H 36 | } 37 | else 38 | { 39 | orientation := Orientation_V 40 | } 41 | } 42 | 43 | newSplit := new SplitContainer(this, orientation, layout) 44 | this.AddChild(newSplit) 45 | this.SetActiveChild(newSplit) 46 | } 47 | 48 | Update() 49 | { 50 | if(this.GetChildCount() == 0 && this.parent.children.Length() > 1) 51 | { 52 | this.Destroy() 53 | return 54 | } 55 | 56 | base.Update() 57 | 58 | if(this.maximizedWindow != "") 59 | { 60 | SetWindowHidden(this.frame.hwnd) 61 | SetWindowHidden(this.GetRootSplitContainer().frame.hwnd) 62 | } 63 | else if(this.parent.GetActiveChild() != this) 64 | { 65 | SetWindowShown(this.frame.hwnd) 66 | SetWindowHidden(this.GetRootSplitContainer().frame.hwnd) 67 | } 68 | else 69 | { 70 | SetWindowShown(this.frame.hwnd) 71 | SetWindowShown(this.GetRootSplitContainer().frame.hwnd) 72 | } 73 | } 74 | 75 | CreateFrame() 76 | { 77 | base.CreateFrame() 78 | this.frame.height += 2 79 | } 80 | 81 | UpdateFrame() 82 | { 83 | base.UpdateFrame() 84 | this.frame.text := this.ToString() 85 | } 86 | 87 | GetFrameArea() 88 | { 89 | frameArea := this.GetWorkArea() 90 | frameArea.bottom := frameArea.top + this.frame.height 91 | 92 | tabWidth := frameArea.right - frameArea.left 93 | frameWidth := Floor(tabWidth / this.parent.children.Length()) 94 | 95 | frameArea.left := frameArea.left + frameWidth * (this.GetIndex() - 1) 96 | frameArea.right := frameArea.left + frameWidth 97 | 98 | return frameArea 99 | } 100 | 101 | GetRootSplitContainer() 102 | { 103 | for index, element in this.children 104 | { 105 | if(element.__Class == "SplitContainer") 106 | { 107 | return element 108 | } 109 | } 110 | 111 | return "" 112 | } 113 | 114 | GetWorkArea() 115 | { 116 | workArea := base.GetWorkArea() 117 | 118 | innerGap := this.GetParentMonitor().innerGap 119 | 120 | if(this.maximizedWindow == "") 121 | { 122 | workArea.left += innerGap 123 | workArea.top += innerGap 124 | workArea.right -= innerGap 125 | workArea.bottom -= innerGap 126 | } 127 | 128 | return workArea 129 | } 130 | 131 | ToString() 132 | { 133 | NodeString := "" 134 | 135 | parentMonitorName := GetMonitorName(this.GetParentMonitor()) 136 | workspaceNames := GetOption("WorkspaceNames")[parentMonitorName] 137 | workspaceName := workspaceNames[this.workspaceIndex] 138 | 139 | if(workspaceName) 140 | { 141 | NodeString .= workspaceNames[this.workspaceIndex] 142 | } 143 | else 144 | { 145 | NodeString .= this.workspaceIndex 146 | } 147 | 148 | return NodeString 149 | } 150 | 151 | IsStringBold() 152 | { 153 | return this == this.parent.GetActiveChild() 154 | } 155 | 156 | PopulateGUI(guiParent) 157 | { 158 | base.PopulateGUI(guiParent) 159 | 160 | workArea := this.GetWorkArea() 161 | this.guiWorkArea := TV_Update(this.guiWorkArea, this.guiTreeEntry, "Work Area: " . workArea.left . ", " . workArea.top . ", " . workArea.right . ", " . workArea.bottom, "+First") 162 | } 163 | 164 | MarkGUIDirty() 165 | { 166 | TV_Delete(this.guiWorkArea) 167 | this.guiWorkArea := 0 168 | 169 | base.MarkGUIDirty() 170 | } 171 | } 172 | 173 | GetWorkspaceContainer(ByRef monitorContainer, workspaceIndex) 174 | { 175 | if(monitorContainer != "") 176 | { 177 | foundWorkspace := "" 178 | for index, element in monitorContainer.children 179 | { 180 | if(element.workspaceIndex == workspaceIndex) 181 | { 182 | return element 183 | } 184 | } 185 | 186 | if(foundWorkspace == "") 187 | { 188 | LogMessage("Workspace not found, creating...") 189 | newWorkspace := new WorkspaceContainer(monitorContainer, workspaceIndex) 190 | monitorContainer.AddChild(newWorkspace) 191 | return newWorkspace 192 | } 193 | 194 | return foundWorkspace 195 | } 196 | } 197 | 198 | SetActiveWorkspace(workspaceIndex) 199 | { 200 | LogMessage("Setting active workspace to " . workspaceIndex) 201 | activeMonitor := GetActiveMonitorContainer() 202 | if(activeMonitor != "") 203 | { 204 | workspace := GetWorkspaceContainer(activeMonitor, workspaceIndex) 205 | workspace.SetActiveContainer(false) 206 | } 207 | } 208 | 209 | MoveActiveWindowToWorkspace(workspaceIndex) 210 | { 211 | LogMessage("Moving active window to workspace " . workspaceIndex) 212 | activeContainer := GetActiveContainer() 213 | if(activeContainer.__Class == "WindowContainer") 214 | { 215 | activeMonitor := activeContainer.GetParentMonitor() 216 | if(activeMonitor != "") 217 | { 218 | activeWorkspace := activeContainer.GetParentWorkspace() 219 | if(activeWorkspace.maximizedWindow == activeContainer) 220 | { 221 | activeWorkspace.maximizedWindow := "" 222 | } 223 | 224 | LogMessage("Removing " . activeContainer.hwnd . " from parent split " . activeContainer.parent.ToString()) 225 | activeContainer.parent.RemoveChild(activeContainer) 226 | 227 | targetWorkspace := GetWorkspaceContainer(activeMonitor, workspaceIndex) 228 | 229 | if(activeContainer.IsFloating()) 230 | { 231 | targetWorkspace.AddChildAt(0, activeContainer) 232 | targetWorkspace.SetActiveChild(activeContainer) 233 | } 234 | else 235 | { 236 | targetSplit := GetLeafSplitContainer(GetWorkspaceRootSplitContainer(targetWorkspace)) 237 | targetSplit.AddChild(activeContainer) 238 | targetSplit.SetActiveChild(activeContainer) 239 | } 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /includes/Tree/Tree.ahk: -------------------------------------------------------------------------------- 1 | ; MonitorContainer Structure 2 | 3 | #include includes/Tree/Container.ahk 4 | #include includes/Tree/RootContainer.ahk 5 | #include includes/Tree/WorkspaceContainer.ahk 6 | #include includes/Tree/MonitorContainer.ahk 7 | #include includes/Tree/SplitContainer.ahk 8 | #include includes/Tree/WindowContainer.ahk 9 | 10 | ; Functions 11 | GetWindowWithHwnd(container, hwnd) 12 | { 13 | for index, element in container.children 14 | { 15 | if(element.__Class == "WindowContainer" && element.hwnd == hwnd) 16 | { 17 | return element 18 | } 19 | else 20 | { 21 | candidateWindow := GetWindowWithHwnd(element, hwnd) 22 | if(candidateWindow != "") 23 | { 24 | return candidateWindow 25 | } 26 | } 27 | } 28 | 29 | return "" 30 | } 31 | 32 | GetContainerRootIndex(parentContainer, targetContainer) 33 | { 34 | for index, element in parentContainer.children 35 | { 36 | if(element == targetContainer) 37 | { 38 | return index 39 | } 40 | else 41 | { 42 | containerIndex := GetContainerRootIndex(element, targetContainer) 43 | if(containerIndex != -1) 44 | { 45 | return index 46 | } 47 | } 48 | } 49 | 50 | return -1 51 | } 52 | 53 | GetActiveMonitorContainer() 54 | { 55 | global treeRoot 56 | return treeRoot.GetActiveChild().GetActiveChild() 57 | } 58 | 59 | GetActiveWorkspaceContainer() 60 | { 61 | return GetActiveMonitorContainer().GetActiveChild() 62 | } 63 | 64 | GetWorkspaceRootSplitContainer(workspaceContainer) 65 | { 66 | for index, element in workspaceContainer.children 67 | { 68 | if(element.__Class == "SplitContainer") 69 | { 70 | return element 71 | } 72 | } 73 | 74 | return "" 75 | } 76 | 77 | GetActiveRootSplitContainer() 78 | { 79 | return GetWorkspaceRootSplitContainer(GetActiveWorkspaceContainer()) 80 | } 81 | 82 | GetActiveLeafSplitContainer() 83 | { 84 | rootSplit := GetActiveRootSplitContainer() 85 | if(rootSplit != "") 86 | { 87 | split := rootSplit 88 | activeChild := rootSplit.GetActiveChild() 89 | while(activeChild != "" && activeChild.__Class != "WindowContainer") 90 | { 91 | split := activeChild 92 | activeChild := split.GetActiveChild() 93 | } 94 | return split 95 | } 96 | 97 | return "" 98 | } 99 | 100 | GetActiveContainer() 101 | { 102 | split := GetActiveMonitorContainer() 103 | activeChild := split.GetActiveChild() 104 | while(activeChild != "") 105 | { 106 | split := activeChild 107 | activeChild := split.GetActiveChild() 108 | } 109 | return split 110 | } 111 | 112 | GetLeafSplitContainer(parentContainer) 113 | { 114 | split := parentContainer 115 | 116 | activeChild := split.GetActiveChild() 117 | while(activeChild != "" && activeChild.__Class != "WindowContainer") 118 | { 119 | split := activeChild 120 | activeChild := split.GetActiveChild() 121 | } 122 | return split 123 | } 124 | 125 | GetContainerUnderPoint(parentContainer, x, y) 126 | { 127 | for ci, child in parentContainer.children 128 | { 129 | if(child.WorkAreaContainsPoint(x, y)) 130 | { 131 | if(child.children.Length() > 0) 132 | { 133 | return GetContainerUnderMouse(child) 134 | } 135 | else 136 | { 137 | return child 138 | } 139 | } 140 | } 141 | 142 | return "" 143 | } 144 | 145 | GetContainerUnderMouse(ByRef parentContainer) 146 | { 147 | MouseGetPos, x, y 148 | return GetContainerUnderPoint(parentContainer, x, y) 149 | } 150 | 151 | GetMonitorUnderMouse() 152 | { 153 | global treeRoot 154 | 155 | MouseGetPos, x, y 156 | 157 | for mi, monitor in treeRoot.GetActiveChild().children 158 | { 159 | if(monitor.workAreaContainsPoint(x, y)) 160 | { 161 | return monitor 162 | } 163 | } 164 | 165 | return "" 166 | } 167 | 168 | SplitActiveContainer(newSplitAxis) 169 | { 170 | activeContainer := GetActiveContainer() 171 | if(activeContainer.__Class == "WindowContainer") 172 | { 173 | activeWorkspace := GetActiveMonitorContainer().GetActiveChild() 174 | leafSplit := GetLeafSplitContainer(GetWorkspaceRootSplitContainer(activeWorkspace)) 175 | activeWindow := leafSplit.GetActiveChild() 176 | 177 | ; Create new split container and add to parent split container 178 | global Layout_Split 179 | newSplit := new SplitContainer(leafSplit, newSplitAxis, Layout_Split) 180 | leafSplit.ReplaceChild(activeWindow, newSplit) 181 | newSplit.AddChild(activeWindow) 182 | activeWindow.SetActiveContainer() 183 | } 184 | } 185 | 186 | SplitActiveContainerWithPlaceholder(newSplitAxis) 187 | { 188 | SplitActiveContainer(newSplitAxis) 189 | Run, C:\Shortcuts\ConEmu.lnk 190 | } 191 | 192 | SetLayout(layout) 193 | { 194 | GetActiveLeafSplitContainer().layout := layout 195 | } 196 | 197 | SetOrientation(orientation) 198 | { 199 | GetActiveLeafSplitContainer().orientation := orientation 200 | } 201 | 202 | SetTabbedLayout() 203 | { 204 | global Orientation_H 205 | global Layout_Tabbed 206 | 207 | SetLayout(Layout_Tabbed) 208 | SetOrientation(Orientation_H) 209 | } 210 | 211 | SetStackingLayout() 212 | { 213 | global Orientation_V 214 | global Layout_Tabbed 215 | 216 | SetLayout(Layout_Tabbed) 217 | SetOrientation(Orientation_V) 218 | } 219 | 220 | ToggleSplitLayout() 221 | { 222 | global Orientation_H 223 | global Orientation_V 224 | global Layout_Split 225 | global Layout_Tabbed 226 | 227 | activeSplitContainer := GetActiveLeafSplitContainer() 228 | if(activeSplitContainer.layout == Layout_Split) 229 | { 230 | if(activeSplitContainer.orientation == Orientation_H) 231 | { 232 | activeSplitContainer.orientation := Orientation_V 233 | } 234 | else if(activeSplitContainer.orientation == Orientation_V) 235 | { 236 | activeSplitContainer.orientation := Orientation_H 237 | } 238 | } 239 | else 240 | { 241 | activeSplitContainer.layout := Layout_Split 242 | } 243 | } 244 | 245 | TryWarpMouse(ByRef targetContainer) 246 | { 247 | global MouseWarp_None 248 | global MouseWarp_Workspace 249 | global MouseWarp_Window 250 | 251 | mouseWarpSetting := GetOption("MouseWarping") 252 | if(mouseWarpSetting == MouseWarp_None) 253 | { 254 | } 255 | else if(mouseWarpSetting == MouseWarp_Workspace) 256 | { 257 | if(targetContainer.GetParentMonitor() != sourceContainer.GetParentMonitor()) 258 | { 259 | WarpMouse(targetContainer) 260 | } 261 | } 262 | else if(mouseWarpSetting == MouseWarp_Window) 263 | { 264 | WarpMouse(targetContainer) 265 | } 266 | } 267 | 268 | WarpMouse(ByRef targetContainer) 269 | { 270 | workArea := targetContainer.GetWorkArea() 271 | workAreaWidth := workArea.right - workArea.left 272 | workAreaHeight := workArea.bottom - workArea.top 273 | x := workArea.left + workAreaWidth / 2 274 | y := workArea.top + workAreaHeight / 2 275 | 276 | global treeRoot 277 | treeRoot.prevMousePosition.x := x 278 | treeRoot.prevMousePosition.y := y 279 | 280 | MouseMove, % x , % y, 0 281 | } 282 | 283 | FocusParentContainer() 284 | { 285 | activeContainer := GetActiveContainer() 286 | 287 | if(activeContainer.__Class == "WindowContainer") 288 | { 289 | SetWindowInactive() 290 | } 291 | 292 | activeParent := activeContainer.parent 293 | if(activeParent.__Class != "WorkspaceContainer") 294 | { 295 | activeParent.SetActiveChild("") 296 | } 297 | } 298 | 299 | FocusChildContainer() 300 | { 301 | activeContainer := GetActiveContainer() 302 | lastActiveChild := activeContainer.GetLastActiveChild() 303 | 304 | activeContainer.SetActiveChild(lastActiveChild) 305 | } 306 | 307 | CloseActiveWindow() 308 | { 309 | activeContainer := GetActiveContainer() 310 | if(activeContainer.__Class == "WindowContainer") 311 | { 312 | activeContainer.Close() 313 | } 314 | } 315 | 316 | SetWindowContainerFullscreen(windowContainer) 317 | { 318 | if(windowContainer.__Class == "WindowContainer") 319 | { 320 | parentWorkspace := windowContainer.GetParentWorkspace() 321 | if(parentWorkspace.maximizedWindow == "") 322 | { 323 | parentWorkspace.maximizedWindow := windowContainer 324 | } 325 | else 326 | { 327 | parentWorkspace.maximizedWindow := "" 328 | } 329 | } 330 | } 331 | 332 | ToggleActiveWindowFullscreen() 333 | { 334 | SetWindowContainerFullscreen(GetActiveContainer()) 335 | } -------------------------------------------------------------------------------- /includes/Navigation.ahk: -------------------------------------------------------------------------------- 1 | ; Navigation functions 2 | GetAdjacentContainer(ByRef container, delta, axis) 3 | { 4 | activeSplit := container.GetParentSplit() 5 | if(activeSplit.orientation == axis) 6 | { 7 | activeIndex := activeSplit.GetActiveChildIndex() 8 | activeIndex += delta 9 | if(activeIndex < 1 || activeIndex > activeSplit.GetChildCount()) 10 | { 11 | return "" 12 | } 13 | 14 | return activeSplit.children[activeIndex] 15 | } 16 | 17 | return "" 18 | } 19 | 20 | Navigate(delta, axis, move = false) 21 | { 22 | LogMessage("Navigation delta " . delta . " , axis " . axis . " move? " . move) 23 | global treeRoot 24 | global Layout_Split 25 | 26 | ; Choose either the active window or the active leaf split 27 | sourceContainer := GetActiveContainer() 28 | 29 | if(sourceContainer == "") 30 | { 31 | MsgBox Navigation Error: No active source container 32 | return 33 | } 34 | 35 | parentWorkspace := sourceContainer.GetParentWorkspace() 36 | if(sourceContainer == parentWorkspace.maximizedWindow) 37 | { 38 | ; If we're navigating from a maximized window, start from the parent workspace's root split 39 | sourceContainer := parentWorkspace.GetRootSplitContainer() 40 | } 41 | 42 | ; Walk up the tree split-by-split until we find one with an adjacent container 43 | targetContainer := sourceContainer 44 | adjacentContainer := GetAdjacentContainer(targetContainer, delta, axis) 45 | while(adjacentContainer == "") 46 | { 47 | parentSplit := targetContainer.GetParentSplit() 48 | if(parentSplit == "") 49 | { 50 | break 51 | } 52 | 53 | targetContainer := parentSplit 54 | adjacentContainer := GetAdjacentContainer(targetContainer, delta, axis) 55 | } 56 | 57 | ; If there is no adjacent container 58 | if(adjacentContainer == "") 59 | { 60 | if(move) 61 | { 62 | if(sourceContainer.__Class == "WindowContainer") 63 | { 64 | ; Set source parent orientation to navigation axis 65 | sourceParent := sourceContainer.parent 66 | prevOrientation := sourceParent.orientation 67 | sourceParent.orientation := axis 68 | 69 | ; If there are more than two children, store them in a new split using the original orientation 70 | if(sourceParent.children.Length() > 2) 71 | { 72 | newSplit := new SplitContainer(sourceParent, prevOrientation, Layout_Split) 73 | 74 | tempChildren := [] 75 | 76 | for index, element in sourceParent.children 77 | { 78 | if(element != sourceContainer) 79 | { 80 | tempChildren.Push(element) 81 | } 82 | } 83 | 84 | sourceParent.ClearChildren() 85 | 86 | if(delta < 0) 87 | { 88 | sourceParent.AddChild(sourceContainer) 89 | sourceParent.AddChild(newSplit) 90 | } 91 | else if(delta > 0) 92 | { 93 | sourceParent.AddChild(newSplit) 94 | sourceParent.AddChild(sourceContainer) 95 | } 96 | 97 | for index, element in tempChildren 98 | { 99 | newSplit.AddChild(element) 100 | } 101 | } 102 | 103 | sourceContainer.SetActiveContainer() 104 | } 105 | } 106 | 107 | return 108 | } 109 | 110 | ; Walk down the tree from the adjacent container to find its innermost split container 111 | targetSplit := "" 112 | targetWindow := "" 113 | 114 | if(adjacentContainer.__Class == "WindowContainer") 115 | { 116 | targetWindow := adjacentContainer 117 | targetSplit := adjacentContainer.GetParentSplit() 118 | } 119 | else 120 | { 121 | if(adjacentContainer.__Class == "MonitorContainer") 122 | { 123 | targetSplit := adjacentContainer.GetActiveChild().GetRootSplitContainer() 124 | } 125 | else if(adjacentContainer.__Class == "SplitContainer") 126 | { 127 | targetSplit := adjacentContainer 128 | } 129 | 130 | targetWindow := targetSplit.GetActiveChild() 131 | while(targetWindow != "" && targetWindow.__Class != "WindowContainer") 132 | { 133 | targetSplit := targetWindow 134 | targetWindow := targetSplit.GetActiveChild() 135 | } 136 | } 137 | 138 | ; Move or activate as appropriate 139 | if(move) 140 | { 141 | ; Only move if this is a window container 142 | if(sourceContainer.__Class == "WindowContainer") 143 | { 144 | ; If we're moving to another monitor 145 | if(sourceContainer.GetParentMonitor() != targetSplit.GetParentMonitor()) 146 | { 147 | ; Check to see if the source container is parented to the workspace's root split 148 | parentRootSplit := sourceContainer.GetParentWorkspace().GetRootSplitContainer() 149 | if(sourceContainer.GetParentSplit() != parentRootSplit) 150 | { 151 | targetSplit := parentRootSplit 152 | targetWindow := "" 153 | } 154 | 155 | targetRootSplit := targetSplit.GetParentWorkspace().GetRootSplitContainer() 156 | if(targetSplit != targetRootSplit) 157 | { 158 | targetSplit := targetRootSplit 159 | targetWindow := "" 160 | } 161 | } 162 | 163 | 164 | MoveWindow(sourceContainer, targetSplit, targetWindow, delta) 165 | 166 | sourceContainer.SetActiveContainer() 167 | } 168 | } 169 | else 170 | { 171 | activeContainer := "" 172 | if(targetWindow != "") 173 | { 174 | activeContainer := targetWindow 175 | } 176 | else 177 | { 178 | activeContainer := targetSplit 179 | SetWindowInactive() 180 | } 181 | 182 | activeContainer.SetActiveContainer() 183 | } 184 | } 185 | 186 | MoveWindow(ByRef sourceWindow, ByRef targetSplit, ByRef targetWindow, delta) 187 | { 188 | global treeRoot 189 | 190 | if(sourceWindow.IsFloating()) 191 | { 192 | sourceWorkArea := sourceWindow.GetWorkArea() 193 | sourceWidth := sourceWorkArea.right - sourceWorkArea.left 194 | sourceHeight := sourceWorkArea.bottom - sourceWorkArea.top 195 | 196 | targetWorkArea := targetSplit.GetWorkArea() 197 | targetWidth := targetWorkArea.right - targetWorkArea.left 198 | targetHeight := targetWorkArea.bottom - targetWorkArea.top 199 | 200 | SetWindowPosition(sourceWindow.hwnd, targetWorkArea.left + (targetWidth / 2) - (sourceWidth / 2), targetWorkArea.top + (targetHeight / 2) - (sourceHeight / 2), sourceWidth, sourceHeight) 201 | } 202 | else 203 | { 204 | sourceParent := sourceWindow.parent 205 | 206 | if(sourceParent == targetSplit) 207 | { 208 | ; If moving within the same parent, swap position 209 | sourceParent.RemoveChild(sourceWindow) 210 | if(delta < 0) 211 | { 212 | sourceParent.AddChildAt(targetWindow.GetIndex(), sourceWindow) 213 | } 214 | else if(delta > 0) 215 | { 216 | sourceParent.AddChildAt(targetWindow.GetIndex() + 1, sourceWindow) 217 | } 218 | } 219 | else 220 | { 221 | ; If moving a window out of a child and into its parent, insert adjacent to child 222 | containerIndex := GetContainerRootIndex(targetSplit, sourceParent) 223 | if(containerIndex != -1) 224 | { 225 | sourceParent.RemoveChild(sourceWindow) 226 | if(delta < 0) 227 | { 228 | targetSplit.AddChildAt(containerIndex, sourceWindow) 229 | } 230 | else if(delta > 0) 231 | { 232 | targetSplit.AddChildAt(containerIndex + 1, sourceWindow) 233 | } 234 | } 235 | else 236 | { 237 | containerIndex := GetContainerRootIndex(sourceParent, targetSplit) 238 | if(containerIndex != -1) 239 | { 240 | ; If moving a window out of a parent and into a child 241 | sourceParent.RemoveChild(sourceWindow) 242 | if(delta < 0) 243 | { 244 | targetSplit.AddChild(sourceWindow) 245 | } 246 | else if(delta > 0) 247 | { 248 | targetSplit.AddChildAt(1, sourceWindow) 249 | } 250 | 251 | if(sourceParent.children.Length() == 1) 252 | { 253 | sourceParent.RemoveChild(targetSplit) 254 | sourceParent.parent.ReplaceChild(sourceParent, targetSplit) 255 | } 256 | } 257 | else 258 | { 259 | ; If moving into a different container tree, append to either the start or end 260 | sourceParent.RemoveChild(sourceWindow) 261 | if(delta < 0) 262 | { 263 | targetSplit.AddChild(sourceWindow) 264 | } 265 | else if(delta > 0) 266 | { 267 | targetSplit.AddChildAt(1, sourceWindow) 268 | } 269 | } 270 | } 271 | } 272 | 273 | sourceWindow.SetActiveContainer() 274 | } 275 | 276 | ; Invoke source container's update function to update its position in the tree 277 | sourceWindow.Update() 278 | } 279 | 280 | MoveActiveWindow(delta, axis) 281 | { 282 | Navigate(delta, axis, true) 283 | } 284 | 285 | ; Gap manipulation functions 286 | ModifyActiveMonitorInnerGap(offset) 287 | { 288 | activeMonitor := GetActiveMonitorContainer() 289 | if(activeMonitor != "") 290 | { 291 | activeMonitor.innerGap += offset 292 | } 293 | } 294 | 295 | ModifyActiveMonitorOuterGap(offset) 296 | { 297 | activeMonitor := GetActiveMonitorContainer() 298 | if(activeMonitor != "") 299 | { 300 | activeMonitor.outerGap += offset 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /includes/Tree/RootContainer.ahk: -------------------------------------------------------------------------------- 1 | ; RootContainer - Represents the tree root, contains WorkspaceContainers 2 | class RootContainer extends Container 3 | { 4 | unmanagedWindows := [] 5 | 6 | prevMousePosition := { x: 0, y: 0 } 7 | prevHoveredContainer := "" 8 | 9 | Init() 10 | { 11 | global Orientation_H 12 | global Layout_Split 13 | newSplit := new SplitContainer(this, Orientation_H, Layout_Split) 14 | 15 | this.AddChild(newSplit) 16 | this.SetActiveChild(newSplit) 17 | 18 | base.Init() 19 | } 20 | 21 | Update() 22 | { 23 | ; Enumerate monitors and create corresponding MonitorContainers 24 | SysGet, monCount, MonitorCount 25 | rootSplit := this.GetActiveChild() 26 | 27 | ; If the monitor count has changed 28 | if(monCount != rootSplit.GetChildCount()) 29 | { 30 | ; Clear root split children 31 | rootSplit.ClearChildren() 32 | 33 | ; Create a monitor container for each physical monitor 34 | monitors := [] 35 | Loop, % monCount 36 | { 37 | newMonitor := new MonitorContainer(this, A_Index) 38 | monitors.Push(newMonitor) 39 | } 40 | 41 | ; Loop through the created monitors, sort them left-to-right and add them to the root split in order 42 | furthestLeft := 0 43 | furthestLeftIndex := -1 44 | while(monitors.Length() > 0) 45 | { 46 | for index, element in monitors 47 | { 48 | candidateLeft := element.getWorkArea().left 49 | if(index == 1 || candidateLeft < furthestLeft) 50 | { 51 | furthestLeft := candidateLeft 52 | furthestLeftIndex := index 53 | } 54 | } 55 | 56 | furthestLeftMonitor := monitors[furthestLeftIndex] 57 | monitors.RemoveAt(furthestLeftIndex) 58 | rootSplit.AddChild(furthestLeftMonitor) 59 | 60 | if(GetMonitorIsPrimary(element.monitor)) 61 | { 62 | rootSplit.SetActiveChild(element) 63 | } 64 | } 65 | } 66 | 67 | ; Enumerate windows and create correspdonding WindowContainers 68 | WinGet, id, list 69 | Loop, %id% 70 | { 71 | hwnd := id%A_Index% 72 | 73 | ; Skip windows with no title or class 74 | windowTitle := GetWindowTitle(hwnd) 75 | windowClass := GetWindowClass(hwnd) 76 | if(windowTitle == "" && windowClass == "") 77 | { 78 | continue 79 | } 80 | 81 | if(ShouldExcludeWindow(hwnd)) 82 | { 83 | global mainGui 84 | global guiUnmanaged 85 | 86 | if(IndexOf(hwnd, this.unmanagedWindows) == -1) 87 | { 88 | LogMessage("Excluding window " . hwnd . ", Title: " . GetWindowTitle(hwnd) . ", Class: " . GetWindowClass(hwnd)) 89 | 90 | this.unmanagedWindows.Push(hwnd) 91 | 92 | Gui, % mainGui . ":Default" 93 | Gui, % mainGui . ":ListView", guiUnmanaged 94 | 95 | LV_Add(, hwnd, windowTitle, windowClass) 96 | LV_ModifyCol(1) 97 | LV_ModifyCol(2) 98 | LV_ModifyCol(3, "AutoHdr") 99 | } 100 | } 101 | else 102 | { 103 | if(GetWindowWithHwnd(this, hwnd) == "") 104 | { 105 | activeMonitor := rootSplit.GetActiveChild() 106 | 107 | newWindow := new WindowContainer("", hwnd) 108 | LogMessage("Creating window container for " . hwnd) 109 | LogMessage("Title: " . windowTitle . ", Class: " . windowClass) 110 | 111 | if(GetWindowClass(hwnd) == "Shell_TrayWnd") 112 | { 113 | MsgBox, Error: Creating window container for Shell_TrayWnd 114 | } 115 | 116 | if(GetWindowClass(hwnd) == "Windows.UI.Core.CoreWindow") 117 | { 118 | MsgBox, Error: Creating window container for Windows.UI.Core.CoreWindow 119 | } 120 | 121 | if(ShouldFloatWindow(hwnd)) 122 | { 123 | LogMessage("Setting up window " . hwnd . " as floating") 124 | activeWorkspace := activeMonitor.GetActiveChild() 125 | 126 | SetWindowAlwaysOnTop(hwnd) 127 | 128 | ; Iterate through monitors 129 | for mi, monitor in this.GetActiveChild().children 130 | { 131 | if(monitor.DisplayAreaContainsWindow(hwnd, true)) 132 | { 133 | activeWorkspace := monitor.GetActiveChild() 134 | activeWorkspace.AddChildAt(0, newWindow) 135 | activeWorkspace.SetActiveChild(newWindow) 136 | } 137 | } 138 | } 139 | else 140 | { 141 | LogMessage("Setting up window " . hwnd . " as managed") 142 | 143 | sleepMs := GetSleepForWindow(hwnd) 144 | if(sleepMs > 0) 145 | { 146 | LogMessage("Sleeping for " . sleepMs . " to allow window " . hwnd . " to initialize") 147 | Sleep, % sleepMs 148 | } 149 | 150 | ; Position window over active monitor so it will auto-arrange correctly 151 | workArea := activeMonitor.GetWorkArea() 152 | workAreaWidth := workArea.right - workArea.left 153 | workAreaHeight := workArea.bottom - workArea.top 154 | 155 | SetWindowRestored(hwnd) 156 | SetWindowPosition(hwnd, workArea.left + workAreaWidth / 3, workArea.top + workAreaHeight / 3, workAreaWidth / 3, workAreaHeight / 3) 157 | 158 | ; Iterate through monitors 159 | for mi, monitor in this.GetActiveChild().children 160 | { 161 | if(monitor.DisplayAreaContainsWindow(hwnd)) 162 | { 163 | activeWorkspace := monitor.GetActiveChild() 164 | leafSplit := GetLeafSplitContainer(GetWorkspaceRootSplitContainer(activeWorkspace)) 165 | leafSplit.AddChild(newWindow) 166 | 167 | if(activeWorkspace.maximizedWindow == "") 168 | { 169 | newWindow.SetActiveContainer() 170 | } 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | ; Clean up unmanaged window references when they close 179 | hwndsToRemove := [] 180 | for index, element in this.unmanagedWindows 181 | { 182 | if(!WinExist("ahk_id " . element)) 183 | { 184 | Gui, % mainGui . ":Default" 185 | Gui, % mainGui . ":ListView", guiUnmanaged 186 | 187 | ; Cache element for removal after loop 188 | hwndsToRemove.Push(element) 189 | 190 | ; Clean up GUI hwnd 191 | Loop, % LV_GetCount() 192 | { 193 | LV_GetText(guiHwnd, A_Index, 1) 194 | if(guiHwnd == element) 195 | { 196 | LV_Delete(A_Index) 197 | } 198 | } 199 | } 200 | } 201 | 202 | ; Remove cached unmanaged window hwnds 203 | for index, element in hwndsToRemove 204 | { 205 | this.unmanagedWindows.Remove(IndexOf(element, this.unmanagedWindows)) 206 | } 207 | 208 | ; Update mouse focus 209 | MouseGetPos, x, y 210 | 211 | hoveredMonitor := GetMonitorUnderMouse() 212 | activeWorkspace := hoveredMonitor.GetActiveChild() 213 | hoveredContainer := GetContainerUnderMouse(activeWorkspace) 214 | 215 | if(GetOption("FocusFollowsMouse")) 216 | { 217 | if(!GetKeyState("LButton")) 218 | { 219 | if(hoveredMonitor.fullscreenWindow == "") 220 | { 221 | hasMovedMouse := x != this.prevMousePosition.x 222 | hasMovedMouse |= y != this.prevMousePosition.y 223 | if(hasMovedMouse) 224 | { 225 | if(this.prevHoveredContainer != hoveredContainer) 226 | { 227 | this.UpdateFocus() 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | this.prevMousePosition.x := x 235 | this.prevMousePosition.y := y 236 | this.prevHoveredContainer := hoveredContainer 237 | 238 | base.Update() 239 | } 240 | 241 | UpdateFocus() 242 | { 243 | LogMessage("Updating mouse focus") 244 | hoveredMonitor := GetMonitorUnderMouse() 245 | hoveredContainer := GetContainerUnderMouse(hoveredMonitor.GetActiveChild()) 246 | 247 | if(hoveredContainer != "") 248 | { 249 | LogMessage("Hovered container: " . hoveredContainer.ToString()) 250 | if(hoveredContainer != GetActiveContainer()) 251 | { 252 | LogMessage("Hovered container inactive, setting active") 253 | hoveredContainer.SetActiveContainer(false) 254 | } 255 | } 256 | } 257 | 258 | CreateFrame() 259 | { 260 | } 261 | 262 | UpdateFrame() 263 | { 264 | } 265 | 266 | HandleMouseDown() 267 | { 268 | this.UpdateFocus() 269 | } 270 | 271 | HandleMouseWheel(delta) 272 | { 273 | hoveredMonitor := GetMonitorUnderMouse() 274 | hoveredContainer := GetContainerUnderMouse(hoveredMonitor.GetActiveChild()) 275 | MouseGetPos,,, hoveredHwnd 276 | 277 | if(!GetWindowWithHwnd(this, hoveredHwnd)) 278 | { 279 | leafSplit := GetActiveLeafSplitContainer() 280 | if(leafSplit) 281 | { 282 | leafSplit.SetActiveChildIndex(leafSplit.GetActiveChildIndex() + delta) 283 | } 284 | } 285 | } 286 | 287 | GetWorkArea() 288 | { 289 | return { left: 0, top: 0, right: 0, bottom: 0 } 290 | } 291 | 292 | ToString() 293 | { 294 | NodeString := "Root" 295 | 296 | return NodeString 297 | } 298 | 299 | IsStringBold() 300 | { 301 | return true 302 | } 303 | } -------------------------------------------------------------------------------- /includes/Tree/Container.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ; Base Container class 4 | class Container 5 | { 6 | parent := "" 7 | children := [] 8 | activeChild := "" 9 | lastActiveChild := "" 10 | guiTreeEntry := 0 11 | 12 | frame := 0 13 | 14 | __New(ByRef parent) 15 | { 16 | this.parent := parent 17 | } 18 | 19 | Init() 20 | { 21 | for index, element in this.children 22 | { 23 | element.Init() 24 | } 25 | } 26 | 27 | Update() 28 | { 29 | if(!this.frame) 30 | { 31 | this.CreateFrame() 32 | } 33 | 34 | for index, element in this.children 35 | { 36 | element.Update() 37 | } 38 | 39 | if(this.frame) 40 | { 41 | this.UpdateFrame() 42 | } 43 | } 44 | 45 | Destroy() 46 | { 47 | while(this.children.Length() > 0) 48 | { 49 | this.children[1].Destroy() 50 | } 51 | 52 | if(this.frame) 53 | { 54 | this.frame.Destroy() 55 | this.frame := 0 56 | } 57 | 58 | this.activeChild := 0 59 | this.lastActiveChild := 0 60 | 61 | this.parent.RemoveChild(this) 62 | } 63 | 64 | CreateFrame() 65 | { 66 | this.frame := new GDIPFrame() 67 | } 68 | 69 | GetFrameArea() 70 | { 71 | frameArea := this.GetWorkArea() 72 | frameArea := this.GetWorkArea() 73 | frameArea.bottom := frameArea.top + this.frame.height 74 | return frameArea 75 | } 76 | 77 | UpdateFrame() 78 | { 79 | ; Update frame position 80 | frameArea := this.GetFrameArea() 81 | this.frame.SetPosition(frameArea.left, frameArea.top, frameArea.right - frameArea.left, frameArea.bottom - frameArea.top) 82 | 83 | ; Change background and text color based on whether this is active 84 | this.frame.SetBorderColor(this.IsActive() ? 0xFF2C4FDB : 0xFF909090) 85 | this.frame.SetBackgroundColor(this.IsActive() ? 0xFF4A6EFF : 0xFFE0E0E0) 86 | this.frame.SetTextColor(this.IsActive() ? "FFFFFFFF" : "FF000000") 87 | 88 | ; Show / hide frame based on whether the parent workspace is active 89 | parentWorkspace := this.GetParentWorkspace() 90 | if(parentWorkspace) 91 | { 92 | if(parentWorkspace.IsActive()) 93 | { 94 | SetWindowShown(this.frame.hwnd) 95 | } 96 | else 97 | { 98 | SetWindowHidden(this.frame.hwnd) 99 | } 100 | } 101 | 102 | this.frame.Update() 103 | } 104 | 105 | IsActive() 106 | { 107 | return this.parent.GetActiveChild() == this 108 | } 109 | 110 | AddChild(ByRef child) 111 | { 112 | LogMessage("Calling AddChildAt() for new child " . child.ToString()) 113 | return this.AddChildAt(this.children.Length() + 1, child, setActive) 114 | } 115 | 116 | AddChildAt(index, ByRef child) 117 | { 118 | global treeRoot 119 | 120 | child.parent := this 121 | this.children.InsertAt(index, child) 122 | LogMessage("Added child " . child.ToString() . " at index " . index) 123 | treeRoot.MarkGUIDirty() 124 | return child 125 | } 126 | 127 | RemoveChild(ByRef child) 128 | { 129 | childIndex := this.GetChildIndex(child) 130 | LogMessage("Calling RemoveChildAt() on child " . child.ToString() . " with index " . childIndex) 131 | this.RemoveChildAt(this.GetChildIndex(child)) 132 | } 133 | 134 | RemoveChildAt(childIndex, updateActive = true) 135 | { 136 | global treeRoot 137 | 138 | if(childIndex != -1) 139 | { 140 | childToRemove := this.children[childIndex] 141 | childToRemove.parent := "" 142 | LogMessage("Removing child at index " . childIndex . ": " . childToRemove.ToString()) 143 | this.children.RemoveAt(childIndex) 144 | 145 | if(updateActive) 146 | { 147 | newActiveChild := "" 148 | if(IndexOf(this.GetLastActiveChild(), this.children) != -1) 149 | { 150 | newActiveChild := this.GetLastActiveChild() 151 | } 152 | else if(this.children[childIndex] != "") 153 | { 154 | newActiveChild := this.children[childIndex] 155 | } 156 | else if(this.children[childIndex - 1] != "") 157 | { 158 | newActiveChild := this.children[childIndex - 1] 159 | } 160 | 161 | if(newActiveChild != "") 162 | { 163 | newActiveChild.SetActiveContainer(true) 164 | } 165 | else 166 | { 167 | this.SetActiveChild("") 168 | } 169 | } 170 | 171 | treeRoot.MarkGUIDirty() 172 | } 173 | else 174 | { 175 | LogWarning("RemoveChild() failed") 176 | } 177 | } 178 | 179 | ReplaceChild(ByRef child, ByRef newChild) 180 | { 181 | global treeRoot 182 | 183 | childIndex := IndexOf(child, this.children) 184 | if(childIndex != -1) 185 | { 186 | LogMessage("Replacing child " . child.ToString() . " with new child " . newChild.ToString()) 187 | this.children[childIndex].parent = "" 188 | newChild.parent := this 189 | this.children[childIndex] := newChild 190 | 191 | treeRoot.MarkGUIDirty() 192 | 193 | return this.children[childIndex] 194 | } 195 | 196 | return "" 197 | } 198 | 199 | ClearChildren() 200 | { 201 | while(this.children.Length() > 0) 202 | { 203 | childCount := this.GetChildCount() 204 | this.children[childCount].ClearChildren() 205 | this.RemoveChild(this.children[childCount]) 206 | } 207 | 208 | this.SetActiveChild("") 209 | } 210 | 211 | GetChildCount() 212 | { 213 | return this.children.Length() 214 | } 215 | 216 | GetChildIndex(child) 217 | { 218 | return IndexOf(child, this.children) 219 | } 220 | 221 | GetActiveChild() 222 | { 223 | return this.activeChild 224 | } 225 | 226 | GetActiveChildIndex() 227 | { 228 | return IndexOf(this.activeChild, this.children) 229 | } 230 | 231 | GetLastActiveChild() 232 | { 233 | return this.lastActiveChild 234 | } 235 | 236 | GetLastActiveChildIndex() 237 | { 238 | return IndexOf(this.lastActiveChild, this.children) 239 | } 240 | 241 | SetActiveChild(child) 242 | { 243 | LogMessage("Setting active child to " . child.ToString()) 244 | this.lastActiveChild := this.activeChild 245 | this.activeChild := child 246 | } 247 | 248 | SetActiveChildIndex(index) 249 | { 250 | if(index < 1) 251 | { 252 | index := this.children.Length() 253 | } 254 | 255 | if(index > this.children.Length()) 256 | { 257 | index := 1 258 | } 259 | 260 | this.SetActiveChild(this.children[index]) 261 | } 262 | 263 | SetActiveContainer(tryWarpMouse = true) 264 | { 265 | LogMessage("Setting parent active container to " . this.ToString()) 266 | this.parent.SetActiveChild(this) 267 | this.parent.SetActiveContainer(false) 268 | 269 | WinSet, Bottom,, % "ahk_id " . this.frame.hwnd 270 | 271 | if(tryWarpMouse) 272 | { 273 | TryWarpMouse(this) 274 | } 275 | } 276 | 277 | GetParentOfClass(class) 278 | { 279 | activeContainer := this.parent 280 | while(activeContainer.__Class != class) 281 | { 282 | if(activeContainer.parent == "") 283 | { 284 | return "" 285 | } 286 | 287 | activeContainer := activeContainer.parent 288 | } 289 | 290 | return activeContainer 291 | } 292 | 293 | GetParentMonitor() 294 | { 295 | return this.GetParentOfClass("MonitorContainer") 296 | } 297 | 298 | GetParentWorkspace() 299 | { 300 | return this.GetParentOfClass("WorkspaceContainer") 301 | } 302 | 303 | GetParentSplit() 304 | { 305 | return this.GetParentOfClass("SplitContainer") 306 | } 307 | 308 | GetIndex() 309 | { 310 | return IndexOf(this, this.parent.children) 311 | } 312 | 313 | GetLayout() 314 | { 315 | return this.parent.GetLayout() 316 | } 317 | 318 | GetWorkArea() 319 | { 320 | global Orientation_H 321 | global Orientation_V 322 | global Layout_Split 323 | 324 | parentWorkArea := this.parent.GetWorkArea() 325 | 326 | parentOrientation := this.parent.GetOrientation() 327 | parentLayout := this.parent.GetLayout() 328 | 329 | parentWorkArea.top += this.parent.frame.height 330 | 331 | workArea := parentWorkArea 332 | 333 | if(parentLayout == Layout_Split) 334 | { 335 | innerGap := this.GetParentMonitor().innerGap 336 | 337 | if(parentOrientation == Orientation_H) 338 | { 339 | pw := parentWorkArea.right - parentWorkArea.left 340 | lw := Floor(pw / this.parent.GetChildCount()) 341 | lx := parentWorkArea.left + (lw * (this.GetIndex() - 1)) 342 | workArea := { left: Floor(lx), top: parentWorkArea.top, right: Ceil(lx + lw), bottom: parentWorkArea.bottom } 343 | 344 | if(this.GetIndex() > 1) 345 | { 346 | workArea.left += Ceil(innerGap / 2) 347 | } 348 | 349 | if(this.GetIndex() < this.parent.GetChildCount()) 350 | { 351 | workArea.right -= Floor(innerGap / 2) 352 | } 353 | } 354 | else if(parentOrientation == Orientation_V) 355 | { 356 | ph := parentWorkArea.bottom - parentWorkArea.top 357 | lh := Floor(ph / this.parent.GetChildCount()) 358 | ly := parentWorkArea.top + (lh * (this.GetIndex() - 1)) 359 | workArea := { left: parentWorkArea.left, top: Floor(ly), right: parentWorkArea.right, bottom: Ceil(ly + lh) } 360 | 361 | if(this.GetIndex() > 1) 362 | { 363 | workArea.top += Ceil(innerGap / 2) 364 | } 365 | 366 | if(this.GetIndex() < this.parent.GetChildCount()) 367 | { 368 | workArea.bottom -= Floor(innerGap / 2) 369 | } 370 | } 371 | } 372 | 373 | return workArea 374 | } 375 | 376 | WorkAreaContainsPoint(x, y) 377 | { 378 | workArea := this.GetWorkArea() 379 | if(x > workArea.left && x < workArea.right && y > workArea.top && y < workArea.bottom) 380 | { 381 | return true 382 | } 383 | 384 | return false 385 | } 386 | 387 | DisplayAreaContainsPoint(x,y) 388 | { 389 | displayArea := this.GetDisplayArea() 390 | if(x > displayArea.left && x < displayArea.right && y > displayArea.top && y < displayArea.bottom) 391 | { 392 | return true 393 | } 394 | 395 | return false 396 | } 397 | 398 | WorkAreaContainsWindow(hwnd, useCenter = true) 399 | { 400 | winPos := "" 401 | if(useCenter) 402 | { 403 | winPos := GetWindowCenter(hwnd) 404 | } 405 | else 406 | { 407 | winPos := GetWindowPosition(hwnd) 408 | offset := WindowGaps.GetWindowGapOffset(hwnd) 409 | winPos.x += offset.left 410 | winPos.y += offset.right 411 | } 412 | 413 | return this.WorkAreaContainsPoint(winPos.x, winPos.y) 414 | } 415 | 416 | DisplayAreaContainsWindow(hwnd, useCenter = true) 417 | { 418 | winPos := "" 419 | if(useCenter) 420 | { 421 | winPos := GetWindowCenter(hwnd) 422 | } 423 | else 424 | { 425 | winPos := GetWindowPosition(hwnd) 426 | offset := WindowGaps.GetWindowGapOffset(hwnd) 427 | winPos.x += offset.left 428 | winPos.y += offset.right 429 | } 430 | 431 | return this.DisplayAreaContainsPoint(winPos.x, winPos.y) 432 | } 433 | 434 | ToString() 435 | { 436 | return "Container" 437 | } 438 | 439 | IsStringBold() 440 | { 441 | return false 442 | } 443 | 444 | PopulateGUI(guiParent) 445 | { 446 | entryString := this.ToString() 447 | 448 | optionString := "" 449 | if(this.guiTreeEntry == 0) 450 | { 451 | optionString .= " +Expand" 452 | } 453 | 454 | if(this.IsStringBold()) 455 | { 456 | optionString .= " +Bold" 457 | } 458 | else 459 | { 460 | optionString .= " -Bold" 461 | } 462 | 463 | global mainGui 464 | global guiTree 465 | Gui, % mainGui . ":Default" 466 | Gui, % mainGui . ":TreeView", guiTree 467 | this.guiTreeEntry := TV_Update(this.guiTreeEntry, guiParent, entryString, optionString) 468 | 469 | for index, element in this.children 470 | { 471 | element.PopulateGUI(this.guiTreeEntry) 472 | } 473 | } 474 | 475 | MarkGUIDirty() 476 | { 477 | for index, element in this.children 478 | { 479 | element.MarkGUIDirty() 480 | } 481 | 482 | TV_Delete(this.guiTreeEntry) 483 | this.guiTreeEntry := 0 484 | } 485 | } -------------------------------------------------------------------------------- /includes/Tree/WindowContainer.ahk: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ; WindowContainer node - Contains a window 4 | class WindowContainer extends Container 5 | { 6 | hwnd := "" 7 | hidden := false 8 | 9 | guiTreeTitle := 0 10 | guiTreeClass := 0 11 | guiTreeWorkArea := 0 12 | 13 | __New(ByRef parent, hwnd) 14 | { 15 | base.__New(parent) 16 | 17 | this.hwnd := hwnd 18 | } 19 | 20 | Update() 21 | { 22 | global treeRoot 23 | global Layout_Split 24 | 25 | ; Destroy self if the associated window no longer exists 26 | DetectHiddenWindows, On 27 | if(!WinExist("ahk_id " . this.hwnd)) 28 | { 29 | this.Destroy() 30 | DetectHiddenWindows, Off 31 | return 32 | } 33 | else if(!this.hidden && GetWindowIsHidden(this.hwnd)) 34 | { 35 | this.Destroy(false) 36 | DetectHiddenWindows, Off 37 | return 38 | } 39 | else 40 | { 41 | DetectHiddenWindows, Off 42 | } 43 | 44 | ; Destroy self and exclude associated window if it goes fullscreen 45 | if(this.GetParentMonitor().fullscreenWindow != "") 46 | { 47 | return 48 | } 49 | 50 | if(this.IsFloating()) 51 | { 52 | this.Update_Floating() 53 | } 54 | else 55 | { 56 | maximizedWindow := this.GetParentWorkspace().maximizedWindow 57 | 58 | if(this.IsBeingDragged()) 59 | { 60 | if(maximizedWindow == "") 61 | { 62 | this.UpdateDragged() 63 | } 64 | } 65 | else 66 | { 67 | this.UpdatePosition() 68 | } 69 | } 70 | 71 | this.UpdateVisibility() 72 | this.UpdateFocus() 73 | 74 | ; Call base update function 75 | base.Update() 76 | } 77 | 78 | Destroy(showWindow = true) 79 | { 80 | if(showWindow) 81 | { 82 | ; Unhide window 83 | this.SetShown() 84 | } 85 | 86 | ; If this is the maximized window on its parent workspace, null out the reference 87 | parentWorkspace := this.GetParentWorkspace() 88 | if(parentWorkspace.maximizedWindow == this) 89 | { 90 | parentWorkspace.maximizedWindow := "" 91 | } 92 | 93 | ; Remove self from parent 94 | oldParent := this.parent 95 | base.Destroy() 96 | oldParent.GetActiveChild().SetActiveContainer() 97 | } 98 | 99 | CreateFrame() 100 | { 101 | if(!this.IsFloating()) 102 | { 103 | this.frame := new GDIPFrame() 104 | this.frame.border.top := 0 105 | this.frame.border.bottom := 0 106 | } 107 | } 108 | 109 | UpdateFrame() 110 | { 111 | base.UpdateFrame() 112 | 113 | windowTitle := GetWindowTitle(this.hwnd) 114 | if(windowTitle != this.frame.titleText) 115 | { 116 | titleText := windowTitle 117 | 118 | if(GetWindowIsPopup(this.hwnd)) 119 | { 120 | titleText .= " (Popup)" 121 | } 122 | 123 | if(GetWindowIsChild(this.hwnd)) 124 | { 125 | titleText .= " (Child)" 126 | } 127 | 128 | this.frame.text := titleText 129 | } 130 | } 131 | 132 | GetFrameArea() 133 | { 134 | global Layout_Tabbed 135 | global Orientation_H 136 | global Orientation_V 137 | 138 | frameArea := this.GetWorkArea() 139 | frameArea.bottom := frameArea.top + this.frame.height 140 | 141 | if(this.parent.layout == Layout_Tabbed) 142 | { 143 | if(this.parent.orientation == Orientation_H) 144 | { 145 | tabWidth := frameArea.right - frameArea.left 146 | frameWidth := Floor(tabWidth / this.parent.children.Length()) 147 | 148 | frameArea.left := frameArea.left + frameWidth * (this.GetIndex() - 1) 149 | frameArea.right := frameArea.left + frameWidth 150 | } 151 | else if(this.parent.orientation == Orientation_V) 152 | { 153 | frameArea.top += this.frame.height * (this.GetIndex() - 1) 154 | frameArea.bottom += this.frame.height * (this.GetIndex() - 1) 155 | } 156 | } 157 | 158 | return frameArea 159 | } 160 | 161 | Close() 162 | { 163 | WinClose, % "ahk_id " . this.hwnd 164 | } 165 | 166 | SetMinimized() 167 | { 168 | SetWindowMinimized(this.hwnd) 169 | } 170 | 171 | SetMaximized() 172 | { 173 | SetWindowMaximized(this.hwnd) 174 | } 175 | 176 | SetRestored() 177 | { 178 | SetWindowRestored(this.hwnd) 179 | } 180 | 181 | SetHidden() 182 | { 183 | this.hidden := true 184 | SetWindowHidden(this.hwnd) 185 | } 186 | 187 | SetShown() 188 | { 189 | this.hidden := false 190 | SetWindowShown(this.hwnd) 191 | } 192 | 193 | UpdateVisibility() 194 | { 195 | ; Show/Hide based on active workspace 196 | global Layout_Tabbed 197 | isTab := this.parent.layout == Layout_Tabbed 198 | isActiveTab := isTab && this.IsActive() 199 | 200 | parentWorkspace := this.GetParentWorkspace() 201 | parentMonitor := this.GetParentMonitor() 202 | 203 | if(parentWorkspace != parentMonitor.GetActiveChild()) 204 | { 205 | if(!this.hidden) 206 | { 207 | this.SetHidden() 208 | } 209 | } 210 | else 211 | { 212 | if(this.hidden) 213 | { 214 | if(parentWorkspace.maximizedWindow == "") 215 | { 216 | if(!isTab || (isTab && isActiveTab)) 217 | { 218 | LogMessage("Showing active workspace window " . this.hwnd) 219 | this.SetShown() 220 | } 221 | } 222 | else if(parentWorkspace.maximizedWindow == this) 223 | { 224 | this.SetShown() 225 | } 226 | } 227 | else 228 | { 229 | if(parentWorkspace.maximizedWindow != "" && parentWorkspace.maximizedWindow != this) 230 | { 231 | this.SetHidden() 232 | } 233 | 234 | if(isTab && !isActiveTab) 235 | { 236 | this.SetHidden() 237 | SetWindowShown(this.frame.hwnd) 238 | } 239 | } 240 | } 241 | } 242 | 243 | UpdateFocus() 244 | { 245 | if(GetActiveContainer() == this && !GetKeyState("LButton")) 246 | { 247 | activeHwnd := WinExist("A") 248 | 249 | if(activeHwnd == this.frame.hwnd) 250 | { 251 | SetWindowActive(this.hwnd) 252 | } 253 | else 254 | { 255 | progmanHwnd := WinExist("ahk_class Progman") 256 | desktopHwnd := WinExist("ahk_class WorkerW") 257 | global treeRoot 258 | if(activeHwnd == "" || activeHwnd == progmanHwnd || activeHwnd == desktopHwnd || GetWindowWithHwnd(treeRoot, activeHwnd)) 259 | { 260 | if(activeHwnd != this.hwnd) 261 | { 262 | if(GetWindowTitle(activeHwnd) != "" || GetWindowTitle(activeHwnd) != "") 263 | { 264 | LogMessage("Active window is " . GetWindowTitle(activeHwnd) . ", Class: " . GetWindowClass(activeHwnd)) 265 | LogMessage("Setting window " . this.hwnd . " active") 266 | SetWindowActive(this.hwnd) 267 | } 268 | } 269 | } 270 | } 271 | } 272 | 273 | if(GetWindowIsMinimized(this.hwnd) || GetWindowIsMaximized(this.hwnd)) 274 | { 275 | this.SetRestored() 276 | } 277 | } 278 | 279 | Update_Floating() 280 | { 281 | global treeRoot 282 | 283 | ; Iterate through monitors 284 | for mi, monitor in treeRoot.GetActiveChild().children 285 | { 286 | if(monitor != this.GetParentMonitor()) 287 | { 288 | if(monitor.DisplayAreaContainsWindow(this.hwnd, true)) 289 | { 290 | activeWorkspace := monitor.GetActiveChild() 291 | this.parent.RemoveChild(this) 292 | activeWorkspace.AddChildAt(0, this) 293 | this.SetActiveContainer(false) 294 | return 295 | } 296 | } 297 | } 298 | } 299 | 300 | UpdateDragged() 301 | { 302 | this.SetActiveContainer(false) 303 | 304 | sourceParent := this.parent 305 | 306 | activeMonitor := GetMonitorUnderMouse() 307 | if(activeMonitor != "") 308 | { 309 | activeContainer := GetContainerUnderMouse(activeMonitor) 310 | if(activeContainer != "" && activeContainer != this) 311 | { 312 | if(activeContainer.__Class == "SplitContainer") 313 | { 314 | sourceParent.RemoveChild(this) 315 | activeContainer.AddChild(this) 316 | this.SetActiveContainer(false) 317 | } 318 | else if(activeContainer.__Class == "WindowContainer") 319 | { 320 | if(!activeContainer.IsFloating()) 321 | { 322 | activeSplit := activeContainer.GetParentSplit() 323 | if(sourceParent == activeSplit) 324 | { 325 | if(activeContainer.GetIndex() > this.GetIndex()) 326 | { 327 | activeSplit.ReplaceChild(activeContainer, this) 328 | activeSplit.SetActiveChild(this) 329 | sourceParent.ReplaceChild(this, activeContainer) 330 | sourceParent.SetActiveChild(activeContainer) 331 | } 332 | else if(activeContainer.GetIndex() < this.GetIndex()) 333 | { 334 | sourceParent.ReplaceChild(this, activeContainer) 335 | sourceParent.SetActiveChild(activeContainer) 336 | activeSplit.ReplaceChild(activeContainer, this) 337 | activeSplit.SetActiveChild(this) 338 | } 339 | } 340 | else 341 | { 342 | activeSplit.ReplaceChild(activeContainer, this) 343 | activeSplit.SetActiveChild(this) 344 | sourceParent.ReplaceChild(this, activeContainer) 345 | sourceParent.SetActiveChild(activeContainer) 346 | } 347 | } 348 | } 349 | } 350 | } 351 | } 352 | 353 | UpdatePosition() 354 | { 355 | windowArea := this.GetWindowArea() 356 | windowAreaWidth := windowArea.right - windowArea.left 357 | windowAreaHeight := windowArea.bottom - windowArea.top 358 | 359 | winPos := GetWindowPosition(this.hwnd) 360 | if(windowArea.left != winPos.x || windowArea.top != winPos.y || windowAreaWidth != winPos.w || windowAreaHeight != winPos.h) 361 | { 362 | SetWindowPosition(this.hwnd, windowArea.left, windowArea.top, windowAreaWidth, windowAreaHeight) 363 | } 364 | } 365 | 366 | IsFloating() 367 | { 368 | return this.parent.__Class == "WorkspaceContainer" 369 | } 370 | 371 | IsBeingDragged() 372 | { 373 | if(GetWindowIsActive(this.hwnd)) 374 | { 375 | if(GetKeyState("LButton")) 376 | { 377 | windowArea := this.GetWindowArea() 378 | 379 | WinGetPos,x,y 380 | if(x != windowArea.left || y != windowArea.top) 381 | { 382 | return true 383 | } 384 | } 385 | } 386 | 387 | return false 388 | } 389 | 390 | GetWorkArea() 391 | { 392 | global Layout_Split 393 | global Layout_Tabbed 394 | global Orientation_H 395 | global Orientation_V 396 | 397 | if(this.IsFloating()) 398 | { 399 | winPos := GetWindowPosition(this.hwnd) 400 | return { left: winPos.x, top: winPos.y, right: winPos.x + winPos.w, bottom: winPos.y + winPos.h } 401 | } 402 | 403 | if(this == this.GetParentWorkspace().maximizedWindow) 404 | { 405 | return this.GetParentWorkspace().GetWorkArea() 406 | } 407 | 408 | workArea := base.GetWorkArea() 409 | 410 | return workArea 411 | } 412 | 413 | GetWindowArea() 414 | { 415 | offset := WindowGaps.GetWindowGapOffset(this.hwnd) 416 | 417 | workArea := this.GetWorkArea() 418 | workAreaWidth := workArea.right - workArea.left 419 | workAreaHeight := workArea.bottom - workArea.top 420 | 421 | tx := workArea.left - offset.left 422 | ty := workArea.top - offset.top 423 | tw := workAreaWidth + offset.left + offset.right 424 | th := workAreaHeight + offset.top + offset.bottom 425 | 426 | if(this.GetParentWorkspace().maximizedWindow != this) 427 | { 428 | height := 0 429 | 430 | global Layout_Tabbed 431 | global Orientation_V 432 | if(this.parent.layout == Layout_Tabbed && this.parent.orientation == Orientation_V) 433 | { 434 | height := this.frame.height * this.parent.children.Length() 435 | } 436 | else 437 | { 438 | height := this.frame.height 439 | } 440 | 441 | ty += height 442 | th -= height 443 | } 444 | 445 | return { left: tx, top: ty, right: tx + tw, bottom: ty + th } 446 | } 447 | 448 | WorkAreaContainsPoint(x,y) 449 | { 450 | if(this.hidden) 451 | { 452 | return false 453 | } 454 | 455 | return base.WorkAreaContainsPoint(x,y) 456 | } 457 | 458 | ToString() 459 | { 460 | NodeString := "Window " this.hwnd 461 | 462 | if(this == this.GetParentWorkspace().maximizedWindow) 463 | { 464 | NodeString .= " (Maximized)" 465 | } 466 | 467 | return NodeString 468 | } 469 | 470 | IsStringBold() 471 | { 472 | return this == GetActiveContainer() 473 | } 474 | 475 | PopulateGUI(guiParent) 476 | { 477 | base.PopulateGUI(guiParent) 478 | 479 | this.guiTreeTitle := TV_Update(this.guiTreeTitle, this.guiTreeEntry, "Title: " . GetWindowTitle(this.hwnd), "") 480 | this.guiTreeClass := TV_Update(this.guiTreeClass, this.guiTreeEntry, "Class: " . GetWindowClass(this.hwnd), "") 481 | 482 | workArea := this.GetWorkArea() 483 | this.guiTreeWorkArea := TV_Update(this.guiTreeWorkArea, this.guiTreeEntry, "Work Area: " . workArea.left . ", " . workArea.top . ", " . workArea.right . ", " . workArea.bottom, "") 484 | } 485 | 486 | MarkGUIDirty() 487 | { 488 | TV_Delete(this.guiTreeTitle) 489 | this.guiTreeTitle := 0 490 | 491 | TV_Delete(this.guiTreeClass) 492 | this.guiTreeClass := 0 493 | 494 | TV_Delete(this.guiTreeWorkArea) 495 | this.guiTreeWorkArea := 0 496 | 497 | base.MarkGUIDirty() 498 | } 499 | } 500 | --------------------------------------------------------------------------------