diff --git a/bugn.exe b/bugn.exe deleted file mode 100644 index 5b7abc7..0000000 Binary files a/bugn.exe and /dev/null differ diff --git a/src/Bar.ahk b/src/Bar.ahk index c24cfbc..40bdc50 100644 --- a/src/Bar.ahk +++ b/src/Bar.ahk @@ -61,7 +61,7 @@ Bar_init(m) { } ; layout i := Config_viewCount + 1 - text := " ??? " + text := " 1x9|=- " w := Bar_getTextWidth(text) Gui, Add, Text, x%x1% y%y1% w%w% h%h1% BackgroundTrans vBar_#%m%_#%i%_layout gBar_GuiClick, Gui, Add, Progress, x%x1% y%y1% w%w% h%h1% Background%Config_normBgColor2% @@ -214,8 +214,10 @@ Bar_initCmdGui() { TV_Add("rotate master axis", itemId20) TV_Add("rotate stack axis", itemId20) TV_Add("mirror tile layout", itemId20) - TV_Add("increase master split", itemId20) - TV_Add("decrease master split", itemId20) + TV_Add("increase master X", itemId20) + TV_Add("decrease master X", itemId20) + TV_Add("increase master Y", itemId20) + TV_Add("decrease master Y", itemId20) TV_Add("increase master factor", itemId20) TV_Add("decrease master factor", itemId20) itemId30 := TV_Add("View") @@ -229,6 +231,12 @@ Bar_initCmdGui() { TV_Add("toggle task bar", itemId40) TV_Add("activate next", itemId40) TV_Add("activate prev", itemId40) + ;itemId50 := TV_add("Log") + ; TV_Add("increment debug level", itemId50) + ; TV_Add("decrement debug level", itemId50) + ; TV_Add("log help info", itemId50) + ; TV_Add("log view window info", itemId50) + ; TV_Add("log managed window info", itemId50) TV_Add("Reload") TV_Add("Quit") GuiControl, +Redraw, Bar_#0_#0 @@ -320,10 +328,14 @@ Bar_evaluateCommand() { View_rotateLayoutAxis(3, +1) Else If (Bar_command_#1 = "mirror tile layout") View_rotateLayoutAxis(1, +2) - Else If (Bar_command_#1 = "increase master split") - View_setMSplit(+1) - Else If (Bar_command_#1 = "decrease master split") - View_setMSplit(-1) + Else If (Bar_command_#1 = "increase master X") + View_setMX(+1) + Else If (Bar_command_#1 = "decrease master X") + View_setMX(-1) + Else If (Bar_command_#1 = "increase master Y") + View_setMY(+1) + Else If (Bar_command_#1 = "decrease master Y") + View_setMY(-1) Else If (Bar_command_#1 = "increase master factor") View_setMFactor(+0.05) Else If (Bar_command_#1 = "decrease master factor") @@ -349,6 +361,17 @@ Bar_evaluateCommand() { Manager_activateMonitor(+1) Else If (Bar_command_#1 = "activate prev") Manager_activateMonitor(-1) + } Else If (Bar_command_#2 = "Log") { + If (Bar_command_#1 = "increment debug level") + Log_incDebugLevel() + If (Bar_command_#1 = "decrement debug level") + Log_decDebugLevel() + If (Bar_command_#1 = "log help info") + Manager_logHelp() + If (Bar_command_#1 = "log view window info") + Manager_logViewWindowList() + If (Bar_command_#1 = "log managed window info") + Manager_logManagedWindowList() } Else If (Bar_command_#1 = "Reload") Main_reload() Else If (Bar_command_#1 = "Quit") @@ -697,28 +720,34 @@ Bar_updateTitle(debugMsg = "") { Bar_aWndId := aWndId } +; Update the view portion of the status bar. Bar_updateView(m, v) { - Local managedWndId0, wndId0, wndIds + Local IdsLen, ViewIdsLen - StringTrimRight, wndIds, Manager_managedWndIds, 1 - StringSplit, managedWndId, wndIds, `; GuiN := (m - 1) + 1 Gui, %GuiN%: Default + + IdsLen := StrLen(Manager_managedWndIds) + + If (v = Monitor_#%m%_aView_#1) { + ; Set foreground/background colors if the view is the current view. + GuiControl, +Background%Config_selBgColor1% +c%Config_selFgColor2%, Bar_#%m%_#%v%_tagged + GuiControl, +c%Config_selFgColor1%, Bar_#%m%_#%v% + } Else If StrLen(View_#%m%_#%v%_wndIds) > 0 { + ; Set foreground/background colors if the view contains windows. + GuiControl, +Background%Config_normBgColor5% +c%Config_normFgColor8%, Bar_#%m%_#%v%_tagged + GuiControl, +c%Config_normFgColor7%, Bar_#%m%_#%v% + } Else { + ; Set foreground/background colors if the view is empty. + GuiControl, +Background%Config_normBgColor1% +c%Config_normFgColor8%, Bar_#%m%_#%v%_tagged + GuiControl, +c%Config_normFgColor1%, Bar_#%m%_#%v% + } + Loop, %Config_viewCount% { - StringTrimRight, wndIds, View_#%m%_#%A_Index%_wndIds, 1 - StringSplit, wndId, wndIds, `; - If (A_Index = v) - If (v = Monitor_#%m%_aView_#1) { - GuiControl, +Background%Config_selBgColor1% +c%Config_selFgColor2%, Bar_#%m%_#%v%_tagged - GuiControl, +c%Config_selFgColor1%, Bar_#%m%_#%v% - } Else If wndId0 { - GuiControl, +Background%Config_normBgColor5% +c%Config_normFgColor8%, Bar_#%m%_#%v%_tagged - GuiControl, +c%Config_normFgColor7%, Bar_#%m%_#%v% - } Else { - GuiControl, +Background%Config_normBgColor1% +c%Config_normFgColor8%, Bar_#%m%_#%v%_tagged - GuiControl, +c%Config_normFgColor1%, Bar_#%m%_#%v% - } - GuiControl, , Bar_#%m%_#%A_Index%_tagged, % wndId0 / managedWndId0 * 100 + ViewIdsLen := StrLen( View_#%m%_#%A_Index%_wndIds ) + ; Update the percentage fill for the view. + GuiControl, , Bar_#%m%_#%A_Index%_tagged, % ViewIdsLen / IdsLen * 100 + ; Refresh the number on the bar. GuiControl, , Bar_#%m%_#%A_Index%, %A_Index% } } diff --git a/src/Config.ahk b/src/Config.ahk index 6c4c147..9f06201 100644 --- a/src/Config.ahk +++ b/src/Config.ahk @@ -23,11 +23,11 @@ Config_init() { ; status bar Config_showBar := True ; If false, the bar is hidden. It can be made visible or hidden by hotkey (see below). - Config_horizontalBarPos := "left" ; The horizontal position of the bar: "center", "left" or "right" side of the monitor or an offset in pixel (px) from the left (>= 0) or right (< 0). - Config_verticalBarPos := "top" ; The vertical position of the bar: "top" or "bottom" of the monitor, "tray" = sub-window of the task bar. - Config_barWidth := "100%" ; The width of the bar in pixel (px) or with a per cent sign (%) as a percentage. - Config_singleRowBar := True ; If false, the bar will have to rows, one for the window title and one for all other GUI controls. - Config_spaciousBar := False ; If true, the height of the bar will be set to a value equal to the height of an edit control, else it will be set to the text height. + Config_horizontalBarPos := "left" ; The horizontal position of the bar: "center", "left" or "right" side of the monitor or an offset in pixel (px) from the left (>= 0) or right (< 0). + Config_verticalBarPos := "top" ; The vertical position of the bar: "top" or "bottom" of the monitor, "tray" = sub-window of the task bar. + Config_barWidth := "100%" ; The width of the bar in pixel (px) or with a per cent sign (%) as a percentage. + Config_singleRowBar := True ; If false, the bar will have to rows, one for the window title and one for all other GUI controls. + Config_spaciousBar := False ; If true, the height of the bar will be set to a value equal to the height of an edit control, else it will be set to the text height. Config_fontName := "Lucida Console" ; A monospace font is preferable for bug.n to calculate the correct width of the bar and its elements (sub-windows). Config_fontSize := Config_normBgColor := @@ -37,7 +37,7 @@ Config_init() { Config_readinBat := False ; If true, the system battery status is read in and displayed in the status bar. This only makes sense, if you have a system battery (notebook). Config_readinCpu := False ; If true, the current CPU load is read in and displayed in the status bar. Config_readinDate := True ; If true, the current date is read in (format: "WW, DD. MMM. YYYY") and displayed in the status bar. - Config_readinDiskLoad := False ; If true, the current disk load (read and write) is read in and displayed in the status bar. + Config_readinDiskLoad := False ; If true, the current disk load (read and write) is read in and displayed in the status bar. Config_readinMemoryUsage := False ; If true, the system memory usage is read in and displayed in the status bar. Config_readinNetworkLoad := False ; If true, the current network load (up and down) is read in and displayed in the status bar. Config_readinTime := True ; If true, the current time is read in (format: "HH:MM") and displayed in the status bar. @@ -60,13 +60,13 @@ Config_init() { Config_layoutAxis_#1 := 1 ; The layout axis: 1 = x, 2 = y; negative values mirror the layout, setting the master area to the right / bottom instead of left / top. Config_layoutAxis_#2 := 2 ; The master axis: 1 = x (from left to right), 2 = y (from top to bottom), 3 = z (monocle). Config_layoutAxis_#3 := 2 ; The stack axis: 1 = x (from left to right), 2 = y (from top to bottom), 3 = z (monocle). - Config_layoutGapWidth := 0 ; The default gap width in px (only even numbers) of the "tile" layout, i. e. the space between windows and around the layout. + Config_layoutGapWidth := 0 ; The default gap width in px (only even numbers) of the "tile" layout, i. e. the space between windows and around the layout. Config_layoutMFactor := 0.6 ; The factor for the size of the master area, which is multiplied by the monitor size. Config_mouseFollowsFocus := True ; If true, the mouse pointer is set over the focused window, if a window is activated by bug.n. Config_onActiveHiddenWnds := "view" ; The action, which will be taken, if a window e. g. should be activated, but is not visible; "view": show the view accordng to the first tag of the window in question, "tag": add the window in question to the current visible view, "hide": hide the window again ignoring the activation. - Config_newWndPosition := "top" ; The position of a new window in a view; "top": at the beginning of the window list and the master area (default), "masterBottom": at the end of the master area, "stackTop": on top of the stack area, "bottom": at the end of the window list and the stack area. + Config_newWndPosition := "top" ; The position of a new window in a view; "top": at the beginning of the window list and the master area (default), "masterBottom": at the end of the master area, "stackTop": on top of the stack area, "bottom": at the end of the window list and the stack area. Config_shellMsgDelay := 350 ; The time bug.n waits after a shell message (a window is opened, closed or the focus has been changed); if there are any problems recognizing, when windows are opened or closed, try to increase this number. - Config_syncMonitorViews := 0 ; The number of monitors (2 or more), for which views should be activated, when using the accordant hotkey. If set to 1, the views are actiated for all monitors. If set to 0, views are activated independently (only on the active monitor). + Config_syncMonitorViews := 0 ; The number of monitors (2 or more), for which views should be activated, when using the accordant hotkey. If set to 1, the views are activated for all monitors. If set to 0, views are activated independently (only on the active monitor). Config_viewFollowsTagged := False ; If true and a window is tagged with a single tag, the view is correspondingly set to the tag. ; Config_rule_#i := ";;<window style (hexadecimal number or blank)>; @@ -363,8 +363,10 @@ Config_saveSession() { text .= "View_#" m "_#" A_Index "_layoutGapWidth=" View_#%m%_#%A_Index%_layoutGapWidth "`n" If Not (View_#%m%_#%A_Index%_layoutMFact = Config_layoutMFactor) text .= "View_#" m "_#" A_Index "_layoutMFact=" View_#%m%_#%A_Index%_layoutMFact "`n" - If Not (View_#%m%_#%A_Index%_layoutMSplit = 1) - text .= "View_#" m "_#" A_Index "_layoutMSplit=" View_#%m%_#%A_Index%_layoutMSplit "`n" + If Not (View_#%m%_#%A_Index%_layoutMX = 1) + text .= "View_#" m "_#" A_Index "_layoutMX=" View_#%m%_#%A_Index%_layoutMX "`n" + If Not (View_#%m%_#%A_Index%_layoutMY = 1) + text .= "View_#" m "_#" A_Index "_layoutMY=" View_#%m%_#%A_Index%_layoutMY "`n" } } @@ -390,6 +392,11 @@ Config_saveSession() { #+x::Manager_maximizeWindow() ; Move and resize the active window to the size of the work area (only floating windows). #i::Manager_getWindowInfo() ; Get information for the active window (id, title, class, process name, style, geometry, tags and floating state). #+i::Manager_getWindowList() ; Get a window list for the active view (id, title and class). +#^i::Manager_logViewWindowList() ; Dump window information for the active view. +#+^i::Manager_logManagedWindowList() ; Dump window information for every managed window. +#^h::Manager_logHelp() ; Dump to the log an explanation of some of the other more cryptic log messages. +#^[::Log_decDebugLevel() ; Decrement the log debug level. +#^]::Log_incDebugLevel() ; Increment the log debug level. #Tab::View_setLayout(-1) ; Set the previously set layout. You may also use View_setLayout(">") for setting the next layout in the layout array. #f::View_setLayout(3) ; Set the 3rd defined layout (i. e. floating layout in the default configuration). @@ -401,10 +408,13 @@ Config_saveSession() { #^Enter::View_rotateLayoutAxis(1, +2) ; Mirror the layout axis (i. e. -1 -> 1 / 1 -> -1 = master on the left / right side, -2 -> 2 / 2 -> -2 = master at top / bottom, only for the "tile" layout). #^Tab::View_rotateLayoutAxis(2, +1) ; Rotate the master axis (i. e. 3 -> 1 = x-axis = horizontal stack, 1 -> 2 = y-axis = vertical stack, 2 -> 3 = z-axis = monocle, only for the "tile" layout). #^+Tab::View_rotateLayoutAxis(3, +1) ; Rotate the stack axis (i. e. 3 -> 1 = x-axis = horizontal stack, 1 -> 2 = y-axis = vertical stack, 2 -> 3 = z-axis = monocle, only for the "tile" layout). -#^Left::View_setMSplit(+1) ; Move the master splitter, i. e. decrease the number of windows in the master area (only for the "tile" layout). -#^Right::View_setMSplit(-1) ; Move the master splitter, i. e. increase the number of windows in the master area (only for the "tile" layout). -#<::View_setGapWidth(-2) ; Decrease the gap width by 2 px (only for the "tile" layout and even numbers; see the variable "Config_layoutGapWidth"). -#+<::View_setGapWidth(+2) ; Increase the gap width by 2 px (only for the "tile" layout and even numbers; see the variable "Config_layoutGapWidth"). +#^Up::View_setMY(+1) ; Increase the master Y dimension [1,9] (only for the "tile" layout). +#^Down::View_setMY(-1) ; Decrease the master Y dimension [1,9] (only for the "tile" layout). +#^Right::View_setMX(+1) ; Increase the master X dimension [1,9] (only for the "tile" layout). +#^Left::View_setMX(-1) ; Decrease the master X dimension [1,9] (only for the "tile" layout). +#+Left::View_setGapWidth(-2) ; Decrease the gap width by 2 px (only for the "tile" layout and even numbers; see the variable "Config_layoutGapWidth"). +#+Right::View_setGapWidth(+2) ; Increase the gap width by 2 px (only for the "tile" layout and even numbers; see the variable "Config_layoutGapWidth"). + #BackSpace::Monitor_activateView(-1) ; Activate the previously activated view. You may also use Monitor_activateView("<") or Monitor_activateView(">") for activating the previous or next adjacent view. #+0::Monitor_setWindowTag(0) ; Tag the active window with all tags (1 ... Config_viewCount). You may also use Monitor_setWindowTag("<") or Monitor_setWindowTag(">") for setting the tag of the previous or next adjacent to the current view. diff --git a/src/List.ahk b/src/List.ahk new file mode 100644 index 0000000..30afbba --- /dev/null +++ b/src/List.ahk @@ -0,0 +1,111 @@ +/** + * AHK List implementation + * Copyright (c) 2012 Joshua Fuhs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This is an admittedly poor list implementation, but I was new to AHK, + * and I had seen several examples of this already used. + * The lists being operated on should not be larger than a few hundred, + * and preferably no larger than one hundred. + */ + + +List_new() { + Global + Return "" +} + +List_prepend( ByRef l, e ) { + l := e . "`;" . l + Return +} + +List_append(ByRef l, e) { + l := l . e . "`;" + Return +} + +; Insert at position immediately preceding index p +List_insert(ByRef l, e, p) { + Local arr, search, replace + If ( p = 0) + return List_prepend(l, e) + StringSplit arr, l, `; + if ( p >= arr0 - 1 ) + return List_append(l, e) + p += 1 + search := arr%p% . ";" + replace := e . ";" . search + StringReplace, l, l, %search%, %replace% +} + +List_remove(ByRef l, e) { + Local search + search := "" . e . ";" + StringReplace, l, l, %search%, + return +} + + +List_removeAt(ByRef l, p) { + Local arr, search + StringSplit arr, l, `; + if( p >= arr0 - 1) + Return + p += 1 + search := arr%p% . ";" + StringReplace, l, l, %search%, +} + +List_find(ByRef l, e) { + Local arr, arr0 + StringSplit arr, l, `; + Loop, % (arr0 - 1) { + If arr%A_Index% = %e% + Return (A_Index - 1) + } + Return -1 +} + +List_dump(l) { + Local result + StringReplace, result, l, `;, %A_Space%, All + Return result +} + +List_get(l, p) { + Local arr + StringSplit arr, l, `; + If( p >= arr0 ) + Return "" + p += 1 + Return arr%p% +} + +List_size(l) { + Local arr, arr0 + StringSplit arr, l, `; + Return (arr0 - 1) +} + +List_toArray(l, arrName) { + Local trimmedList + StringTrimRight, trimmedList, l, 1 + StringSplit %arrName%, trimmedList, `; + Return (%arrName%0) +} diff --git a/src/List.test.ahk b/src/List.test.ahk new file mode 100644 index 0000000..b6fb0bc --- /dev/null +++ b/src/List.test.ahk @@ -0,0 +1,84 @@ + +Test_check(Condition, msg) { + Global + If Condition + Return + Log_bare(msg) + Exit, 1 +} + + +Log_init("List.test.txt", True) + +testlist := List_new() +Log_bare("new list: " . List_dump(testlist)) +Test_check(List_dump(testlist) = "", "Test 1 failure") +If Not (List_dump(testlist) = "" ) + Log_bare("Test 1 failure") +List_append(testlist, "abc") +Log_bare("appended 'abc': " . List_dump(testlist)) +If Not (List_dump(testlist) = "abc " ) + Log_bare("Test 2 failure") +List_append(testlist, "def") +Log_bare("appended 'def': " . List_dump(testlist)) +If Not (List_dump(testlist) = "abc def " ) + Log_bare("Test 3 failure") +List_append(testlist, "ghi") +List_remove(testlist, "abc") +Log_bare("removed 'abc': " . List_dump(testlist)) +If Not (List_dump(testlist) = "def ghi " ) + Log_bare("Test 4 failure") +List_append(testlist, "jkl") +List_remove(testlist, "ghi") +Log_bare("add 'jkl', remove 'ghi': " . List_dump(testlist)) +If Not (List_dump(testlist) = "def jkl " ) + Log_bare("Test 5 failure") +List_append(testlist, "mno") +List_remove(testlist, "mno") +Log_bare("add and remove 'mno': " . List_dump(testlist)) +If Not (List_dump(testlist) = "def jkl " ) + Log_bare("Test 6 failure") +List_prepend(testlist, "12345") +Log_bare("prepend '12345': " . List_dump(testlist)) +If Not (List_dump(testlist) = "12345 def jkl ") + Log_bare("Test 7 failure") +List_insert(testlist, "xyz", 0) +List_insert(testlist, "Happy", 1) +List_insert(testlist, "Blah", 5) +List_insert(testlist, "10", 10) +Log_bare("Attempt multiple inserts by index: " . List_dump(testlist)) +If Not (List_dump(testlist) = "xyz Happy 12345 def jkl Blah 10 ") + Log_bare("Test 8 failure") +List_removeAt(testlist, 0) +List_removeAt(testlist, 1) +List_removeAt(testlist, 2) +List_removeAt(testlist, 3) +List_removeAt(testlist, 10) +Log_bare("Attempt multiple removals by index: " . List_dump(testlist)) +If Not (List_dump(testlist) = "Happy def Blah ") + Log_bare("Test 9 failure") +If Not (List_find(testlist, "Happy") = 0) + Log_bare("Test 10 failure") +If Not (List_find(testlist, "def") = 1) + Log_bare("Test 11 failure") +If Not (List_find(testlist, "Blah") = 2) + Log_bare("Test 12 failure") +If Not (List_find(testlist, "nonexistent") = -1) + Log_bare("Test 13 failure") +If Not (List_size(testlist) = 3) + Log_bare("Test 14 failure") +If Not (List_get(testlist, 0) = "Happy") + Log_bare("Test 15 failure") +If Not (List_get(testlist, 1) = "def") + Log_bare("Test 16 failure") +If Not (List_get(testlist, 2) = "Blah") + Log_bare("Test 17 failure") +If Not (List_get(testlist, 3) = "") + Log_bare("Test 18 failure") + + + +Return + +#Include Log.ahk +#Include List.ahk \ No newline at end of file diff --git a/src/Log.ahk b/src/Log.ahk new file mode 100755 index 0000000..47f2435 --- /dev/null +++ b/src/Log.ahk @@ -0,0 +1,82 @@ +/** + * AHK Debug log implementation + * Copyright (c) 2012 Joshua Fuhs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @version 8.3.0 + */ + +Log_debug_level := 0 + +Log_init(name, truncate) { + Global + If truncate + IfExist, %name% + FileDelete, %name% + Log_name := name +} + +Log_msg( message ) { + Local CurrentTime + If Not Log_name + Return + FormatTime, CurrentTime, , yyyyMMddHHmmss + FileAppend, %CurrentTime% %message%`r`n, %Log_name% +} + +Log_bare( message ) { + Local padded_message + If Not Log_name + Return + padded_message := " " . message . "`r`n" + FileAppend, %padded_message% , %Log_name% +} + +Log_incDebugLevel() { + Global + If Not Log_name + Return + If ( Log_debug_level < 9 ) + { + Log_debug_level += 1 + Log_msg("Debug logging level incremented to " . Log_debug_level ) + } +} + +Log_decDebugLevel() { + Global + If Not Log_name + Return + If ( Log_debug_level > 0 ) { + Log_debug_level -= 1 + If ( Log_debug_level = 0 ) + Log_msg("Debug logging is disabled") + Else + Log_msg("Debug logging level decremented to " . Log_debug_level) + } +} + + +Log_dbg_msg( level, message ) { + Global + If (level > 0 And Log_debug_level >= level) + Log_msg( "DBG " . level . ": " . message ) +} + +Log_dbg_bare( level, message ) { + Global + If (level > 0 And Log_debug_level >= level) + Log_bare( "DBG " . level . ": " . message ) +} \ No newline at end of file diff --git a/src/Main.ahk b/src/Main.ahk index 96db310..96dffd4 100644 --- a/src/Main.ahk +++ b/src/Main.ahk @@ -32,6 +32,23 @@ SetWinDelay, 10 #WinActivateForce ; pseudo main function + + + EnvGet, appDir, APPDATA + bugnDir := appDir . "\bug.n" + IfNotExist, %bugnDir% + FileCreateDir, %bugnDir% + FileGetAttrib, attrib, %bugnDir% + IfNotInString, attrib, D + { + MsgBox, The file path '%appDir%' already exists and is not a directory. Aborting. + Return + } + logFile := bugnDir . "\bugn_log.txt" + Log_init(logFile, False) + + + Log_msg("====== Initializing ======") If 0 = 1 Config_filePath = %1% Config_init() @@ -52,6 +69,7 @@ Return ; end of the auto-execute section * function & label definitions */ Main_cleanup: ; The labels with "ExitApp" or "Return" at the end and hotkeys have to be after the auto-execute section. + Log_msg("====== Cleaning up ======") If Config_autoSaveSession Config_saveSession() Manager_cleanup() @@ -129,6 +147,8 @@ Main_toggleBar: Monitor_toggleBar() Return +#Include Log.ahk +#Include List.ahk #Include Bar.ahk #Include Config.ahk #Include Manager.ahk diff --git a/src/Manager.ahk b/src/Manager.ahk index 78f50ad..8af7559 100644 --- a/src/Manager.ahk +++ b/src/Manager.ahk @@ -57,7 +57,7 @@ Manager_init() { Bar_hideTitleWndIds := "" Manager_allWndIds := "" Manager_managedWndIds := "" - Manager_sync() + Manager_initial_sync() Bar_updateStatus() Bar_updateTitle() @@ -125,12 +125,6 @@ Manager_applyRules(wndId, ByRef isManaged, ByRef m, ByRef tags, ByRef isFloating hideTitle := rule9 } } - If (m = 0) - m := Manager_aMonitor - If (m > Manager_monitorCount) ; If the specified monitor is out of scope, set it to the max. monitor. - m := Manager_monitorCount - If (tags = 0) - tags := 1 << Monitor_#%m%_aView_#1 - 1 } Else { isManaged := False If wndTitle @@ -162,10 +156,10 @@ Manager_cleanup() { Manager_hideShow := True Loop, PARSE, wndIds, `; { - WinShow, ahk_id %A_LoopField% + Manager_winShow(A_LoopField) If Not Config_showBorder - WinSet, Style, +0x40000, ahk_id %A_LoopField% - WinSet, Style, +0xC00000, ahk_id %A_LoopField% + Manager_winSet("Style", "+0x40000", A_LoopField) + Manager_winSet("Style", "+0xC00000", A_LoopField) } ; Show the task bar. @@ -182,8 +176,8 @@ Manager_cleanup() { Loop, % Config_viewCount View_arrange(m, A_Index) } - WinSet, AlwaysOnTop, On, ahk_id %aWndId% - WinSet, AlwaysOnTop, Off, ahk_id %aWndId% + Manager_winSet("AlwaysOnTop", "On", aWndId) + Manager_winSet("AlwaysOnTop", "Off", aWndId) DllCall("Shell32.dll\SHAppBarMessage", "UInt", (ABM_REMOVE := 0x1), "UInt", &Bar_appBarData) ; SKAN: Crazy Scripting : Quick Launcher for Portable Apps (http://www.autohotkey.com/forum/topic22398.html) @@ -194,7 +188,7 @@ Manager_closeWindow() { WinGetClass, aWndClass, ahk_id %aWndId% WinGetTitle, aWndTitle, ahk_id %aWndId% If Not (aWndClass = "AutoHotkeyGUI" And RegExMatch(aWndTitle, "bug.n_BAR_[0-9]+")) - WinClose, ahk_id %aWndId% + Manager_winClose(aWndId) } Manager_getWindowInfo() { @@ -238,6 +232,112 @@ Manager_getWindowList() { Clipboard := text } +Manager_logViewLayout() { + +} + +Manager_logWindowInfo( w ) { + Local v, wndId, isWinFocus, isBugnActive, isFloating, isHidden, isDecorated, isResponsive, isGhost, wndTitle, wndProc, wndClass, wndStyle, wndX, wndY, wndW, wndH, detect_state + + detect_state := A_DetectHiddenWindows + DetectHiddenWindows, On + WinGet, wndId, ID, A + If wndId = %w% + isWinFocus := "*" + Else + isWinFocus := " " + v := Monitor_#%Manager_aMonitor%_aView_#1 + If View_#%Manager_aMonitor%_#%v%_aWndId = %w% + isBugnActive := "*" + Else + isBugnActive := " " + WinGetTitle, wndTitle, ahk_id %w% + WinGetClass, wndClass, ahk_id %w% + WinGet, wndProc, ProcessName, ahk_id %w% + If InStr(Bar_hiddenWndIds, w) + isHidden := "*" + Else + isHidden := " " + If Manager_#%w%_isFloating + isFloating := "*" + Else + isFloating := " " + If Manager_#%w%_isDecorated + isDecorated := "*" + Else + isDecorated := " " + WinGet, wndStyle, Style, ahk_id %w% + WinGetPos, wndX, wndY, wndW, wndH, ahk_id %w% + + If Manager_isGhost(w) + isGhost := "*" + Else + isGhost := " " + + DetectHiddenWindows, %detect_state% + + ; Intentionally don't detect hidden windows here to see what Manager_hungTest does + If Manager_isHung(w) + isResponsive := " " + Else + isResponsive := "*" + + + Log_bare(w . "`t" . isHidden . " " isWinFocus . " " . isBugnActive . " " . isFloating . " " . isDecorated . " " . isResponsive . " " . isGhost . " " . Manager_#%w%_monitor . "`t" . Manager_#%w%_tags . "`t" . wndX . "`t" . wndY . "`t" . wndW . "`t" . wndH . "`t" . wndStyle . "`t" . wndProc . " / " . wndClass . " / " . wndTitle) +} + +Manager_logHeader() { + Log_bare( "ID`t`tH W A F D R G M`tTags`tX`tY`tW`tH`tStyle`t`tProc / Class / Title") +} + +Manager_logViewWindowList() { + Local text, v, aWndId, wndIds, aWndTitle + + v := Monitor_#%Manager_aMonitor%_aView_#1 + Log_msg( "Window dump for active view (" . Manager_aMonitor . ", " . v . ")" ) + Manager_logHeader() + + StringTrimRight, wndIds, View_#%Manager_aMonitor%_#%v%_wndIds, 1 + Loop, PARSE, wndIds, `; + { + Manager_logWindowInfo( A_LoopField ) + } +} + +Manager_logManagedWindowList() { + Local wndIds + + Log_msg( "Window dump for manager" ) + Manager_logHeader() + + StringTrimRight, wndIds, Manager_managedWndIds, 1 + Loop, PARSE, wndIds, `; + { + Manager_logWindowInfo( A_LoopField) + } +} + +Manager_logHelp() { + Log_msg("Help Display") + Log_bare("Window list columns") + Log_bare(" ID - Windows ID. Unique, OS-assigned ID") + Log_bare(" H - Hidden. Whether bug.n thinks this window is hidden.") + Log_bare(" W - Windows active. This window is active according to Windows.") + Log_bare(" A - View active. This window is active according to bug.n.") + Log_bare(" F - Floating. This window should not be positioned and resized by the layout.") + Log_bare(" D - Decorated. Does the window have a title bar?") + Log_bare(" R - Responsive. Is responding to messages?") + Log_bare(" G - Ghost. Is this window a ghost of another hung window?") + Log_bare(" M - Monitor number.") + Log_bare(" Tags - Bit-mask of the views in which the window is active.") + Log_bare(" X - Windows X position.") + Log_bare(" Y - Windows Y position.") + Log_bare(" W - Windows width.") + Log_bare(" H - Windows height.") + Log_bare(" Style - Windows style.") + Log_bare(" Proc / Class / Title - Process/Class/Title of the window.") +} + Manager_lockWorkStation() { Global Config_shellMsgDelay @@ -261,13 +361,50 @@ Manager_loop(index, increment, lowerBound, upperBound) { Return, index } -Manager_manage(wndId) { - Local a, c0, hideTitle, i, isDecorated, isFloating, isManaged, l, m, n, replace, search, tags +; Accept a window to be added to the system for management. +; Provide a monitor, view preference, but don't override the config. +; pm - Preferred monitor +; pv - Preferred view +; wndId - Window to add to the manager. +Manager_manage(pm, pv, wndId) { + Local a, c0, hideTitle, i, isDecorated, isFloating, isManaged, l, m, n, replace, search, tags, body Local wndControlList0, wndId0, wndIds, wndX, wndY, wndWidth, wndHeight, wndProcessName If Not InStr(Manager_allWndIds, wndId ";") Manager_allWndIds .= wndId ";" - Manager_applyRules(wndId, isManaged, m, tags, isFloating, isDecorated, hideTitle) + + body := 0 + If Manager_isGhost( wndId ) { + Log_dbg_msg(2, "A window has given up the ghost (Ghost wndId: " . wndId . ")") + ; Ghosts need special attention. + ; Say a quick prayer and try to reattach it to its body. + body := Manager_findHung( wndId ) + If body { + isManaged := InStr(Manager_managedWndIds, body ";") + m := Manager_#%body%_monitor + tags := Manager_#%body%_tags + isDecorated := Manager_#%body%_isDecorated + isFloating := Manager_#%body%_isFloating + hideTitle := InStr(Bar_hideTitleWndIds, body ";") + } + Else { + Log_dbg_msg(1, "No body could be found for ghost wndId: " . wndId) + } + } + + ; Apply rules if the window is either a normal window or a ghost without a body. + If ( body = 0 ) { + Manager_applyRules(wndId, isManaged, m, tags, isFloating, isDecorated, hideTitle) + + If (m = 0) + m := pm + If (m < 0) + m := 1 + If (m > Manager_monitorCount) ; If the specified monitor is out of scope, set it to the max. monitor. + m := Manager_monitorCount + If (tags = 0) + tags := 1 << (pv - 1) + } WinGet, wndProcessName, ProcessName, ahk_id %wndId% If (wndProcessName = "chrome.exe") { @@ -281,38 +418,26 @@ Manager_manage(wndId) { Monitor_moveWindow(m, wndId) Manager_managedWndIds .= wndId ";" + Manager_#%wndId%_monitor := m Manager_#%wndId%_tags := tags Manager_#%wndId%_isDecorated := isDecorated Manager_#%wndId%_isFloating := isFloating Loop, % Config_viewCount If (Manager_#%wndId%_tags & 1 << A_Index - 1) { - l := View_#%m%_#%A_Index%_layout_#1 - If (Config_layoutFunction_#%l% = "tile") And ((Config_newWndPosition = "masterBottom") Or (Config_newWndPosition = "stackTop")) { - n := View_getTiledWndIds(m, A_Index, wndIds) - If (n > 1) { - StringSplit, wndId, wndIds, `; - If (wndId0 < View_#%m%_#%A_Index%_layoutMSplit) - View_#%m%_#%A_Index%_layoutMSplit := wndId0 - i := View_#%m%_#%A_Index%_layoutMSplit - search := wndId%i% ";" - replace := search wndId ";" - StringReplace, View_#%m%_#%A_Index%_wndIds, View_#%m%_#%A_Index%_wndIds, %search%, %replace% - } Else - View_#%m%_#%A_Index%_wndIds .= wndId ";" - If (Config_newWndPosition = "masterBottom") And (n > 0) - View_#%m%_#%A_Index%_layoutMSplit += 1 - } Else If (Config_newWndPosition = "bottom") - View_#%m%_#%A_Index%_wndIds .= wndId ";" - Else - View_#%m%_#%A_Index%_wndIds := wndId ";" View_#%m%_#%A_Index%_wndIds - Bar_updateView(m, A_Index) + If (body) { + ; Try to position near the body. + View_ghostWnd(m, A_Index, body, wndId) + } + Else { + View_addWnd(m, A_Index, wndId) + } } If Not Config_showBorder - WinSet, Style, -0x40000, ahk_id %wndId% + Manager_winSet("Style", "-0x40000", wndId) If Not Manager_#%wndId%_isDecorated - WinSet, Style, -0xC00000, ahk_id %wndId% + Manager_winSet("Style", "-0xC00000", wndId) a := Manager_#%wndId%_tags & 1 << Monitor_#%m%_aView_#1 - 1 If a { @@ -320,7 +445,7 @@ Manager_manage(wndId) { Manager_winActivate(wndId) } Else { Manager_hideShow := True - WinHide, ahk_id %wndId% + Manager_winHide(wndId) Manager_hideShow := False } } @@ -341,7 +466,7 @@ Manager_maximizeWindow() { l := View_#%Manager_aMonitor%_#%v%_layout_#1 If Not Manager_#%aWndId%_isFloating And Not (Config_layoutFunction_#%l% = "") View_toggleFloating() - WinSet, Top,, ahk_id %aWndId% + Manager_winSet("Top", "", aWndId) Manager_winMove(aWndId, Monitor_#%Manager_aMonitor%_x, Monitor_#%Manager_aMonitor%_y, Monitor_#%Manager_aMonitor%_width, Monitor_#%Manager_aMonitor%_height) } @@ -354,19 +479,61 @@ Manager_moveWindow() { l := View_#%Manager_aMonitor%_#%v%_layout_#1 If Not Manager_#%aWndId%_isFloating And Not (Config_layoutFunction_#%l% = "") View_toggleFloating() - WinSet, Top,, ahk_id %aWndId% + Manager_winSet("Top", "", aWndId) WM_SYSCOMMAND = 0x112 SC_MOVE = 0xF010 SendMessage, WM_SYSCOMMAND, SC_MOVE, , , ahk_id %aWndId% } +HSHELL_WINDOWCREATED := 1 +; Seems to get sent sometimes when windows are deactivated. +HSHELL_WINDOWDESTROYED := 2 +HSHELL_WINDOWACTIVATED := 4 +; At least title change. +HSHELL_REDRAW := 6 +; The following two are seen when a hung window recovers. +; lParam notes the ghost process +HSHELL_WINDOWREPLACED := 13 +; lParam notes the recovered process +;14 +; Full-screen app activated? Root-privileged window activated? +HSHELL_RUDEAPPACTIVATED := 32772 +; When a window is signalling an application update. +WINDOW_NOTICE := 32774 + +; +; Reliable messages and their meanings (note that any message may be missed if bug.n is hung): +; 1 - Window shown (shown ID) +; 2 - Window destroyed or hidden, same message for both (destroyed or hidden ID) +; 4 - Window activated via mouse, alt+tab, or hotkey (sometimes 32772, but always one of them) +; 6 - Window title change (ID of redrawn window) +; 13 - Hung window recovers and replaces ghost window (ghost window ID is provided) +; 14 - Hung window recovered (ID of previously hung window) +; 32772 - Window activated via mouse, alt+tab, or hotkey (sometimes 4, but always one of them) +; 32774 - Window is flashing due to some event, one message for each flash +; +; Indications of: +; New windows - cmd/shell may be starting a new window on message 6 +; Win+e indicates a new window with message 6 as long as the button +; presses are below a certain frequency. +; Message 1 may indicate a new window started from Windows Explorer +; There doesn't seem to be a reliable way to get all application starts. +; Closed windows - 13 always indicates closed ghost window +; 2 always indicates closed standard window +; Focus change - 4 or 32772 always catch this +; Window event - 6 indicates when title changes which can be used +; in the case of some applications, 32774 works for others +; Manager_onShellMessage(wParam, lParam) { - Local a, aWndClass, aWndHeight, aWndId, aWndTitle, aWndWidth, aWndX, aWndY, flag, m, t, wndClass, wndId, wndIds, wndPName, wndTitle, x, y + Local a, isChanged, aWndClass, aWndHeight, aWndId, aWndTitle, aWndWidth, aWndX, aWndY, m, t, wndClass, wndId, wndIds, wndPName, wndTitle, x, y SetFormat, Integer, hex lParam := lParam+0 SetFormat, Integer, d + + Log_dbg_msg(2, "Manager_onShellMessage( wParam: " . wParam . ", lParam: " . lParam . " )") + WinGetClass, wndClass, ahk_id %lParam% WinGetTitle, wndTitle, ahk_id %lParam% WinGet, wndPName, ProcessName, ahk_id %lParam% @@ -382,6 +549,15 @@ Manager_onShellMessage(wParam, lParam) { Bar_updateTitle() } + If ( wParam = HSHELL_WINDOWREPLACED ) { + ; This shouldn't need a redraw because the window was supposedly replaced. + Manager_unmanage(lParam) + } + + If ( wParam = 14 ) { + ; Window recovered from being hung. Maybe force a redraw. + } + If (wParam = 1 Or wParam = 2 Or wParam = 4 Or wParam = 6 Or wParam = 32772) And lParam And Not Manager_hideShow And Not Manager_focus { If Not (wParam = 4 Or wParam = 32772) If Not wndClass And Not (wParam = 2) { @@ -393,42 +569,41 @@ Manager_onShellMessage(wParam, lParam) { Sleep, %Config_shellMsgDelay% } - If (wParam = 1 Or wParam = 6) And Not InStr(Manager_allWndIds, lParam ";") And Not InStr(Manager_managedWndIds, lParam ";") - a := Manager_manage(lParam) - Else { - flag := True - a := Manager_sync(wndIds) - If wndIds - a := False - } - If a { + isChanged := Manager_sync(wndIds) + If wndIds + isChanged := False + + If a Or isChanged { View_arrange(Manager_aMonitor, Monitor_#%Manager_aMonitor%_aView_#1) Bar_updateView(Manager_aMonitor, Monitor_#%Manager_aMonitor%_aView_#1) } - If flag - If (Manager_monitorCount > 1) { - WinGetPos, aWndX, aWndY, aWndWidth, aWndHeight, ahk_id %aWndId% - m := Monitor_get(aWndX + aWndWidth / 2, aWndY + aWndHeight / 2) - If m - Manager_aMonitor := m - } + If (Manager_monitorCount > 1) { + WinGetPos, aWndX, aWndY, aWndWidth, aWndHeight, ahk_id %aWndId% + m := Monitor_get(aWndX + aWndWidth / 2, aWndY + aWndHeight / 2) + If m + Manager_aMonitor := m + } If wndIds { If (Config_onActiveHiddenWnds = "view") { wndId := SubStr(wndIds, 1, InStr(wndIds, ";") - 1) Loop, % Config_viewCount If (Manager_#%wndId%_tags & 1 << A_Index - 1) { + Log_dbg_msg(3, "Switching views because " . wndId . " is considered hidden and active") + Manager_aMonitor := Manager_#%wndId%_monitor Monitor_activateView(A_Index) Break } } Else { StringTrimRight, wndIds, wndIds, 1 StringSplit, wndId, wndIds, `; + ; Otherwise re-hide them. If (Config_onActiveHiddenWnds = "hide") { Loop, % wndId0 - WinHide, % "ahk_id " wndId%A_Index% + Manager_winHide(wndId%A_Index%) } Else If (Config_onActiveHiddenWnds = "tag") { + ; Or tag all of them for the current view. t := Monitor_#%Manager_aMonitor%_aView_#1 Loop, % wndId0 { wndId := wndId%A_Index% @@ -521,7 +696,7 @@ Manager_sizeWindow() { l := View_#%Manager_aMonitor%_#%v%_layout_#1 If Not Manager_#%aWndId%_isFloating And Not (Config_layoutFunction_#%l% = "") View_toggleFloating() - WinSet, Top,, ahk_id %aWndId% + Manager_winSet("Top", "", aWndId) WM_SYSCOMMAND = 0x112 SC_SIZE = 0xF000 @@ -540,11 +715,14 @@ Manager_sync(ByRef wndIds = "") { Loop, % wndId { If Not InStr(shownWndIds, wndId%A_Index% ";") { If Not InStr(Manager_managedWndIds, wndId%A_Index% ";") { - flag := Manager_manage(wndId%A_Index%) + flag := Manager_manage(Manager_aMonitor, Monitor_#%Manager_aMonitor%_aView_#1, wndId%A_Index%) If flag a := flag - } Else + } Else If Not Manager_isHung(wndId%A_Index%) { + ; This is a window that is already managed but was brought into focus by something. Maybe it + ; would be useful to do something with it. wndIds .= wndId%A_Index% ";" + } } visibleWndIds := visibleWndIds wndId%A_Index% ";" } @@ -563,15 +741,55 @@ Manager_sync(ByRef wndIds = "") { Return, a } +; No windows are known to the system yet. +; Try to do something smart with the initial layout. +Manager_initial_sync() { + Local wndId0, wnd, wndX, wndY, wndW, wndH, x, y, m, len + + ; Initialize lists + ; Note that these variables make this function non-reentrant. + Loop, % Manager_monitorCount + Manager_initial_sync_m#%A_Index%_wndList := List_new() + + ; check all visible windows against the known windows + WinGet, wndId, List, , , + Loop, % wndId { + ; Based on some analysis here, determine which monitors and layouts would best + ; serve existing windows. Do not override configuration settings. + + ; Which monitor is it on? + + wnd := wndId%A_Index% + WinGetPos, wndX, wndY, wndW, wndH, ahk_id %wnd% + + x := wndX + wndW/2 + y := wndY + wndH/2 + + m := Monitor_get(x, y) + If m > 0 + List_append(Manager_initial_sync_m#%m%_wndList, wndId%A_index%) + + ; @todo: What percentage of the monitor area is it occupying? (Suggest layout) + ; @todo: What part of the monitor is it on? (Ordering of windows) + } + + Loop, % Manager_monitorCount { + m := A_Index + len := List_toArray(Manager_initial_sync_m#%m%_wndList, "Manager_initial_sync_tmpArray") + Loop, % len + Manager_manage(m, 1, Manager_initial_sync_tmpArray%A_Index%) + } +} + Manager_toggleDecor() { Local aWndId WinGet, aWndId, ID, A Manager_#%aWndId%_isDecorated := Not Manager_#%aWndId%_isDecorated If Manager_#%aWndId%_isDecorated - WinSet, Style, +0xC00000, ahk_id %aWndId% + Manager_winSet("Style", "+0xC00000", aWndId) Else - WinSet, Style, -0xC00000, ahk_id %aWndId% + Manager_winSet("Style", "-0xC00000", aWndId) } Manager_unmanage(wndId) { @@ -580,9 +798,10 @@ Manager_unmanage(wndId) { a := Manager_#%wndId%_tags & 1 << Monitor_#%Manager_aMonitor%_aView_#1 - 1 Loop, % Config_viewCount If (Manager_#%wndId%_tags & 1 << A_Index - 1) { - StringReplace, View_#%Manager_aMonitor%_#%A_Index%_wndIds, View_#%Manager_aMonitor%_#%A_Index%_wndIds, %wndId%`;, + View_delWnd( Manager_aMonitor, A_Index, wndId ) Bar_updateView(Manager_aMonitor, A_Index) } + Manager_#%wndId%_monitor := Manager_#%wndId%_tags := Manager_#%wndId%_isDecorated := Manager_#%wndId%_isFloating := @@ -594,7 +813,7 @@ Manager_unmanage(wndId) { } Manager_winActivate(wndId) { - Local wndHeight, wndWidth, wndX, wndY + Local wndHeight, wndWidth, wndX, wndY, newWnd If Config_mouseFollowsFocus { If wndId { @@ -603,15 +822,135 @@ Manager_winActivate(wndId) { } Else DllCall("SetCursorPos", "Int", Round(Monitor_#%Manager_aMonitor%_x + Monitor_#%Manager_aMonitor%_width / 2), "Int", Round(Monitor_#%Manager_aMonitor%_y + Monitor_#%Manager_aMonitor%_height / 2)) } - WinActivate, ahk_id %wndId% + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winActivate: Potentially hung window " . wndId) + Return 1 + } + Else { + WinActivate, ahk_id %wndId% + WinGet, newWin, ID, A + If (wndId != newWin) + Return 1 + } Bar_updateTitle() + Return 0 } Manager_winMove(wndId, x, y, width, height) { - WinRestore, ahk_id %wndId% + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winMove: Potentially hung window " . wndId) + Return 1 + } + Else + WinRestore, ahk_id %wndId% WM_ENTERSIZEMOVE = 0x0231 WM_EXITSIZEMOVE = 0x0232 SendMessage, WM_ENTERSIZEMOVE, , , , ahk_id %wndId% - WinMove, ahk_id %wndId%, , %x%, %y%, %width%, %height% - SendMessage, WM_EXITSIZEMOVE, , , , ahk_id %wndId% + If ErrorLevel { + Log_dbg_msg(2, "Manager_winMove: Potentially hung window " . wndId) + Return 1 + } + Else { + WinMove, ahk_id %wndId%, , %x%, %y%, %width%, %height% + SendMessage, WM_EXITSIZEMOVE, , , , ahk_id %wndId% + } } + +Manager_winHide(wndId) { + + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winHide: Potentially hung window " . wndId) + Return 1 + } + Else { + WinHide, ahk_id %wndId% + Return 0 + } +} + +Manager_winShow(wndId) { + + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winShow: Potentially hung window " . wndId) + Return 1 + } + Else { + WinShow, ahk_id %wndId% + Return 0 + } +} + +Manager_winClose(wndId) { + + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winClose: Potentially hung window " . wndId) + Return 1 + } + Else { + WinClose, ahk_id %wndId% + Return 0 + } +} + +Manager_winSet(type, value, wndId) { + + If Manager_isHung(wndId) { + Log_dbg_msg(2, "Manager_winSet: Potentially hung window " . wndId) + Return 1 + } + Else { + WinSet, %type%, %value%, ahk_id %wndId% + Return 0 + } +} + +; 0 - Not hung +; 1 - Hung +Manager_isHung(wndId) { + Local result, detect_setting, WM_NULL + WM_NULL := 0 + detect_setting := A_DetectHiddenWindows + DetectHiddenWindows, On + SendMessage, WM_NULL, , , , ahk_id %wndId% + result := ErrorLevel + DetectHiddenWindows, %detect_setting% + + If result + Return 1 + Else + Return 0 +} + +; Given a ghost window, try to find its body. +; This is only known to work on Windows 7 +Manager_findHung( ghostWnd ) { + Local expectedTitle, expectedX, expectedY, expectedW, expectedH, wndTitle, wndX, wndY, wndW, wndH, wndIds + ;Log_dbg_msg(3, "Manager_findHung(" . ghostWnd . ")") + WinGetTitle, expectedTitle, ahk_id %ghostWnd% + StringReplace, expectedTitle, expectedTitle, " (Not Responding)", "" + WinGetPos, expectedX, expectedY, expectedW, expectedH, ahk_id %ghostWnd% + + SetTitleMatchMode, 2 + WinGet, wndIds, List, %expectedTitle% + Loop, % wndIds { + If (A_Index = ghostWnd) + Continue + WinGetPos, wndX, wndY, wndW, wndH, % "ahk_id" wndIDs%A_Index% + + If (wndX = expectedX) And (wndY = expectedY) And (wndW = expectedW) And (wndH = expectedH) + Return wndIds%A_Index% + } + Return 0 +} + +Manager_isGhost(wndId) { + Local wndClass, wndProc + + WinGet, wndProc, ProcessName, ahk_id %wndId% + WinGetClass, wndClass, ahk_id %wndId% + + If (wndProc = "dwm.exe") And (wndClass = "Ghost") + Return 1 + Else + Return 0 +} \ No newline at end of file diff --git a/src/Monitor.ahk b/src/Monitor.ahk index c98187d..4447f22 100644 --- a/src/Monitor.ahk +++ b/src/Monitor.ahk @@ -40,56 +40,71 @@ Monitor_activateView(v) { v := Manager_loop(Monitor_#%Manager_aMonitor%_aView_#1, +1, 1, Config_viewCount) Else If (v = "<") v := Manager_loop(Monitor_#%Manager_aMonitor%_aView_#1, -1, 1, Config_viewCount) - If (v > 0) And (v <= Config_viewCount) And Not Manager_hideShow And Not (v = Monitor_#%Manager_aMonitor%_aView_#1) { - aView := Monitor_#%Manager_aMonitor%_aView_#1 - WinGet, aWndId, ID, A - If WinExist("ahk_id" aWndId) And InStr(View_#%Manager_aMonitor%_#%aView%_wndIds, aWndId ";") { - WinGetClass, aWndClass, ahk_id %aWndId% - WinGetTitle, aWndTitle, ahk_id %aWndId% - If Not (aWndClass = "Progman") And Not (aWndClass = "AutoHotkeyGui" And SubStr(aWndTitle, 1, 10) = "bug.n_BAR_") And Not (aWndClass = "DesktopBackgroundClass") - View_#%Manager_aMonitor%_#%aView%_aWndId := aWndId - } - - n := Config_syncMonitorViews - If (n = 1) - n := Manager_monitorCount - Else If (n < 1) - n := 1 - Loop, % n { - If (n = 1) - m := Manager_aMonitor - Else - m := A_Index - - Monitor_#%m%_aView_#2 := aView - Monitor_#%m%_aView_#1 := v - - Manager_hideShow := True - StringTrimRight, wndIds, View_#%m%_#%aView%_wndIds, 1 - Loop, PARSE, wndIds, `; - If Not (Manager_#%A_LoopField%_tags & (1 << v - 1)) - WinHide, ahk_id %A_LoopField% - StringTrimRight, wndIds, View_#%m%_#%v%_wndIds, 1 - Loop, PARSE, wndIds, `; - WinShow, ahk_id %A_LoopField% - Manager_hideShow := False - - Bar_updateView(m, aView) - Bar_updateView(m, v) - - View_arrange(m, v) - } - - wndId := View_#%Manager_aMonitor%_#%v%_aWndId - If Not (wndId And WinExist("ahk_id" wndId)) { - If View_#%Manager_aMonitor%_#%v%_wndIds { - wndId := SubStr(View_#%Manager_aMonitor%_#%v%_wndIds, 1, InStr(View_#%Manager_aMonitor%_#%v%_wndIds, ";")-1) - View_#%Manager_aMonitor%_#%v%_aWndId := wndId - } Else - wndId := 0 - } - Manager_winActivate(wndId) + + Log_dbg_msg(1, "Monitor_activateView(" . v . ") Manager_aMonitor: " . Manager_aMonitor . "; wndIds: " . View_#%m%_#%aView%_wndIds) + + If (v <= 0) Or (v > Config_viewCount) Or Manager_hideShow + Return + ; Re-arrange the windows on the view. + If (v = Monitor_#%Manager_aMonitor%_aView_#1) { + View_arrange(Manager_aMonitor, v) + Return } + + aView := Monitor_#%Manager_aMonitor%_aView_#1 + WinGet, aWndId, ID, A + If WinExist("ahk_id" aWndId) And InStr(View_#%Manager_aMonitor%_#%aView%_wndIds, aWndId ";") { + WinGetClass, aWndClass, ahk_id %aWndId% + WinGetTitle, aWndTitle, ahk_id %aWndId% + If Not (aWndClass = "Progman") And Not (aWndClass = "AutoHotkeyGui" And SubStr(aWndTitle, 1, 10) = "bug.n_BAR_") And Not (aWndClass = "DesktopBackgroundClass") + View_#%Manager_aMonitor%_#%aView%_aWndId := aWndId + } + + n := 1 + If (Config_syncMonitorViews > 0) + n := Manager_monitorCount + Loop, % n { + If (n = 1) + m := Manager_aMonitor + Else + m := A_Index + + Monitor_#%m%_aView_#2 := aView + Monitor_#%m%_aView_#1 := v + + Manager_hideShow := True + ; Most of the operations here are dispersed to multiple _different_ windows. + ; Delays in this part of the code are extremely noticeable and the users + ; do a lot of view switching. + SetWinDelay, 0 + StringTrimRight, wndIds, View_#%m%_#%aView%_wndIds, 1 + Loop, PARSE, wndIds, `; + If Not (Manager_#%A_LoopField%_tags & (1 << v - 1)) + Manager_winHide(A_LoopField) + SetWinDelay, 10 + DetectHiddenWindows, On + View_arrange(m, v) + DetectHiddenWindows, Off + StringTrimRight, wndIds, View_#%m%_#%v%_wndIds, 1 + SetWinDelay, 0 + Loop, PARSE, wndIds, `; + Manager_winShow(A_LoopField) + SetWinDelay, 10 + Manager_hideShow := False + + Bar_updateView(m, aView) + Bar_updateView(m, v) + } + + wndId := View_#%Manager_aMonitor%_#%v%_aWndId + If Not (wndId And WinExist("ahk_id" wndId)) { + If View_#%Manager_aMonitor%_#%v%_wndIds { + wndId := SubStr(View_#%Manager_aMonitor%_#%v%_wndIds, 1, InStr(View_#%Manager_aMonitor%_#%v%_wndIds, ";")-1) + View_#%Manager_aMonitor%_#%v%_aWndId := wndId + } Else + wndId := 0 + } + Manager_winActivate(wndId) } Monitor_get(x, y) { @@ -164,19 +179,9 @@ Monitor_getWorkArea(m) { } Monitor_moveWindow(m, wndId) { - Local fX, fY, monitor, wndHeight, wndWidth, wndX, wndY + Global - WinGetPos, wndX, wndY, wndWidth, wndHeight, ahk_id %wndId% - monitor := Monitor_get(wndX+wndWidth/2, wndY+wndHeight/2) - If Not (m = monitor) { - ; move the window to the target monitor and scale it, if it does not fit on the monitor - fX := Monitor_#%m%_width / Monitor_#%monitor%_width - fY := Monitor_#%m%_height / Monitor_#%monitor%_height - If (wndX-Monitor_#%monitor%_x+wndWidth > Monitor_#%m%_width) Or (wndY-Monitor_#%monitor%_y+wndHeight > Monitor_#%m%_height) - Manager_winMove(wndId, Monitor_#%m%_x+fX*(wndX-Monitor_#%monitor%_x), Monitor_#%m%_y+fY*(wndY-Monitor_#%monitor%_y), fX*wndWidth, fY*wndHeight) - Else - Manager_winMove(wndId, Monitor_#%m%_x+(wndX-Monitor_#%monitor%_x), Monitor_#%m%_y+(wndY-Monitor_#%monitor%_y), wndWidth, wndHeight) - } + Manager_#%wndId%_monitor = m } Monitor_setWindowTag(t) { @@ -220,7 +225,7 @@ Monitor_setWindowTag(t) { Monitor_activateView(t) Else { Manager_hideShow := True - WinHide, ahk_id %aWndId% + Manager_winHide(aWndId) Manager_hideShow := False View_arrange(Manager_aMonitor, aView) Bar_updateView(Manager_aMonitor, t) @@ -273,7 +278,7 @@ Monitor_toggleWindowTag(t) { Bar_updateView(Manager_aMonitor, t) If (t = Monitor_#%Manager_aMonitor%_aView_#1) { Manager_hideShow := True - WinHide, ahk_id %aWndId% + Manager_winHide(aWndId) Manager_hideShow := False wndId := SubStr(View_#%Manager_aMonitor%_#%t%_wndIds, 1, InStr(View_#%Manager_aMonitor%_#%t%_wndIds, ";")-1) Manager_winActivate(wndId) diff --git a/src/View.ahk b/src/View.ahk index 4bd9258..68b50f6 100644 --- a/src/View.ahk +++ b/src/View.ahk @@ -29,44 +29,122 @@ View_init(m, v) { View_#%m%_#%v%_layoutAxis_#3 := Config_layoutAxis_#3 View_#%m%_#%v%_layoutGapWidth := Config_layoutGapWidth View_#%m%_#%v%_layoutMFact := Config_layoutMFactor - View_#%m%_#%v%_layoutMSplit := 1 + View_#%m%_#%v%_layoutMX := 1 + View_#%m%_#%v%_layoutMY := 1 View_#%m%_#%v%_layoutSymbol := Config_layoutSymbol_#1 View_#%m%_#%v%_wndIds := "" } View_activateWindow(d) { - Local aWndId, i, j, v, wndId, wndId0, wndIds + Local aWndId, i, j, v, wndId, wndId0, wndIds, failure, direction + + Log_dbg_msg(1, "View_activateWindow(" . d . ")") + + If (d = 0) + Return WinGet, aWndId, ID, A + Log_dbg_bare(2, "Active Windows ID: " . aWndId) v := Monitor_#%Manager_aMonitor%_aView_#1 + Log_dbg_bare(2, "View (" . v . ") wndIds: " . View_#%Manager_aMonitor%_#%v%_wndIds) StringTrimRight, wndIds, View_#%Manager_aMonitor%_#%v%_wndIds, 1 StringSplit, wndId, wndIds, `; + Log_dbg_bare(2, "wndId count: " . wndId0) If (wndId0 > 1) { + If Manager_#%aWndId%_isFloating + Manager_winSet("Bottom", "", aWndId) Loop, % wndId0 If (wndId%A_Index% = aWndId) { i := A_Index Break } + If (d > 0) + direction = 1 + Else + direction = -1 + Log_dbg_bare(2, "Current wndId index: " . i) j := Manager_loop(i, d, 1, wndId0) - wndId := wndId%j% - WinSet, AlwaysOnTop, On, ahk_id %wndId% - WinSet, AlwaysOnTop, Off, ahk_id %wndId% - If Manager_#%aWndId%_isFloating - WinSet, Bottom, , ahk_id %aWndId% - Manager_winActivate(wndId) + Loop, % wndId0 { + Log_dbg_bare(2, "Next wndId index: " . j) + wndId := wndId%j% + Manager_winSet("AlwaysOnTop", "On", wndId) + Manager_winSet("AlwaysOnTop", "Off", wndId) + ; This is a lot of extra work in case there are hung windows on the screen. + ; We still want to be able to cycle through them. + failure := Manager_winActivate(wndId) + If Not failure { + Break + } + + j := Manager_loop(j, direction, 1, wndId0) + } + } } +View_updateLayout(m, v) { + Local fn, l, wndIds + l := View_#%m%_#%v%_layout_#1 + fn := Config_layoutFunction_#%l% + View_updateLayout_%fn%(m, v) +} + +; Add a window to the view in question. +View_addWnd(m, v, wndId) { + Local l, msplit, i, wndIds, n + + l := View_#%m%_#%v%_layout_#1 + If (Config_layoutFunction_#%l% = "tile") And ((Config_newWndPosition = "masterBottom") Or (Config_newWndPosition = "stackTop")) { + n := View_getTiledWndIds(m, v, wndIds) + msplit := View_#%m%_#%v%_layoutMX * View_#%m%_#%v%_layoutMY + If ( msplit = 1 And Config_newWndPosition="masterBottom" ) { + View_#%m%_#%v%_wndIds := wndId ";" . View_#%m%_#%v%_wndIds + } + Else If ( (Config_newWndPosition="masterBottom" And n < msplit) Or (Config_newWndPosition="stackTop" And n <= msplit) ) { + View_#%m%_#%v%_wndIds .= wndId ";" + } + Else { + If (Config_newWndPosition="masterBottom") + i := msplit - 1 + Else + i := msplit + StringSplit, wndId, wndIds, `; + search := wndId%i% ";" + replace := search wndId ";" + StringReplace, View_#%m%_#%v%_wndIds, View_#%m%_#%v%_wndIds, %search%, %replace% + } + } + Else If (Config_newWndPosition = "bottom") + View_#%m%_#%v%_wndIds .= wndId ";" + Else + View_#%m%_#%v%_wndIds := wndId ";" View_#%m%_#%v%_wndIds +} + +View_ghostWnd(m, v, bodyWndId, ghostWndId) { + Local search, replace + + search := bodyWndId ";" + replace := search ghostWndId ";" + StringReplace, View_#%m%_#%v%_wndIds, View_#%m%_#%v%_wndIds, %search%, %replace% +} + +; Remove a window from the view in question. +View_delWnd(m, v, wndId) { + StringReplace, View_#%m%_#%v%_wndIds, View_#%m%_#%v%_wndIds, %wndId%`;, +} + View_arrange(m, v) { Local fn, l, wndIds - + Log_dbg_msg(1, "View_arrange(" . m . ", " . v . ")") + ; All window actions are performed on independent windows. A delay won't help. + SetWinDelay, 0 l := View_#%m%_#%v%_layout_#1 fn := Config_layoutFunction_#%l% - If fn And (View_getTiledWndIds(m, v, wndIds) Or fn = "tile") - View_%fn%(m, v, wndIds) - Else - View_#%m%_#%v%_layoutSymbol := Config_layoutSymbol_#%l% + View_getTiledWndIds(m, v, wndIds) + View_arrange_%fn%(m, v, wndIds) + View_updateLayout(m, v) Bar_updateLayout(m) + SetWinDelay, 10 } View_getTiledWndIds(m, v, ByRef tiledWndIds) { @@ -75,7 +153,7 @@ View_getTiledWndIds(m, v, ByRef tiledWndIds) { StringTrimRight, wndIds, View_#%m%_#%v%_wndIds, 1 Loop, PARSE, wndIds, `; { - If Not Manager_#%A_LoopField%_isFloating And WinExist("ahk_id " A_LoopField) { + If Not Manager_#%A_LoopField%_isFloating And WinExist("ahk_id " A_LoopField) and Not Manager_isHung(A_LoopField) { n += 1 tiledWndIds .= A_LoopField ";" } @@ -84,18 +162,36 @@ View_getTiledWndIds(m, v, ByRef tiledWndIds) { Return, n } -View_monocle(m, v, wndIds) { - Local wndId0 - - StringTrimRight, wndIds, wndIds, 1 +View_updateLayout_(m, v) +{ + View_#%m%_#%v%_layoutSymbol := "><>" +} + +View_arrange_(m, v) +{ + ; Place-holder +} + +View_updateLayout_monocle(m, v) +{ + Local wndIds, wndId, wndId0 + StringTrimRight, wndIds, View_#%m%_#%v%_wndIds, 1 StringSplit, wndId, wndIds, `; - Loop, % wndId0 - Manager_winMove(wndId%A_Index%, Monitor_#%m%_x, Monitor_#%m%_y, Monitor_#%m%_width, Monitor_#%m%_height) View_#%m%_#%v%_layoutSymbol := "[" wndId0 "]" } +View_arrange_monocle(m, v, wndIds) { + Local gw + + gw := View_#%m%_#%v%_layoutGapWidth + + StringTrimRight, wndIds, wndIds, 1 + StringSplit, View_arrange_monocle_wndId, wndIds, `; + View_draw_stack("View_arrange_monocle_wndId", 1, View_arrange_monocle_wndId0, 0, Monitor_#%m%_x, Monitor_#%m%_y, Monitor_#%m%_width, Monitor_#%m%_height, gw/2) +} + View_rotateLayoutAxis(i, d) { - Local f, l, v + Local f, l, v, n, tmp v := Monitor_#%Manager_aMonitor%_aView_#1 l := View_#%Manager_aMonitor%_#%v%_layout_#1 @@ -107,8 +203,16 @@ View_rotateLayoutAxis(i, d) { f := View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i% / Abs(View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i%) View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i% := f * Manager_loop(Abs(View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i%), d, 1, 2) } - } Else - View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i% := Manager_loop(View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i%, d, 1, 3) + } Else { + n := Manager_loop(View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i%, d, 1, 3) + ; When we rotate the axis, we may need to swap the X and Y dimensions. + If Not (n = View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i%) And (n = 1) Or (View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i% = 1) { + tmp := View_#%Manager_aMonitor%_#%v%_layoutMX + View_#%Manager_aMonitor%_#%v%_layoutMX := View_#%Manager_aMonitor%_#%v%_layoutMY + View_#%Manager_aMonitor%_#%v%_layoutMY := tmp + } + View_#%Manager_aMonitor%_#%v%_layoutAxis_#%i% := n + } View_arrange(Manager_aMonitor, v) } } @@ -124,7 +228,7 @@ View_setGapWidth(d) { Else d := Ceil(d / 2) * 2 w := View_#%Manager_aMonitor%_#%v%_layoutGapWidth + d - If (w >= 0 And w < Monitor_#%Manager_aMonitor%_height And w < Monitor_#%Manager_aMonitor%_width) { + If (w < Monitor_#%Manager_aMonitor%_height And w < Monitor_#%Manager_aMonitor%_width) { View_#%Manager_aMonitor%_#%v%_layoutGapWidth := w View_arrange(Manager_aMonitor, v) } @@ -154,11 +258,7 @@ View_setMFactor(d) { v := Monitor_#%Manager_aMonitor%_aView_#1 l := View_#%Manager_aMonitor%_#%v%_layout_#1 If (Config_layoutFunction_#%l% = "tile") { - mfact := 0 - If (d >= 1.05) - mfact := d - Else - mfact := View_#%Manager_aMonitor%_#%v%_layoutMFact + d + mfact := View_#%Manager_aMonitor%_#%v%_layoutMFact + d If (mfact >= 0.05 And mfact <= 0.95) { View_#%Manager_aMonitor%_#%v%_layoutMFact := mfact View_arrange(Manager_aMonitor, v) @@ -166,15 +266,35 @@ View_setMFactor(d) { } } -View_setMSplit(d) { - Local l, n, v, wndIds +View_setMX(d) { + Local l, n, m, v - v := Monitor_#%Manager_aMonitor%_aView_#1 - l := View_#%Manager_aMonitor%_#%v%_layout_#1 - If (Config_layoutFunction_#%l% = "tile") { - n := View_getTiledWndIds(Manager_aMonitor, v, wndIds) - View_#%Manager_aMonitor%_#%v%_layoutMSplit := Manager_loop(View_#%Manager_aMonitor%_#%v%_layoutMSplit, d, 1, n) - View_arrange(Manager_aMonitor, v) + m := Manager_aMonitor + v := Monitor_#%m%_aView_#1 + l := View_#%m%_#%v%_layout_#1 + If Not (Config_layoutFunction_#%l% = "tile") + Return + + n := View_#%m%_#%v%_layoutMX + d + If (n >= 1) And (n <= 9) { + View_#%m%_#%v%_layoutMX := n + View_arrange(m, v) + } +} + +View_setMY(d) { + Local l, n, m, v + + m := Manager_aMonitor + v := Monitor_#%m%_aView_#1 + l := View_#%m%_#%v%_layout_#1 + If Not (Config_layoutFunction_#%l% = "tile") + Return + + n := View_#%m%_#%v%_layoutMY + d + If (n >= 1) And (n <= 9) { + View_#%m%_#%v%_layoutMY := n + View_arrange(m, v) } } @@ -219,117 +339,241 @@ View_shuffleWindow(d) { } } -View_tile(m, v, wndIds) { - Local axis1, axis2, axis3, gapW, h1, h2, i, mfact, msplit, n1, n2, sym1, sym3, w1, w2, wndId0, x1, x2, y1, y2 +View_updateLayout_tile(m, v) { + Local axis1, axis2, axis3, mp, ms, sym1, sym3, master_div, master_dim, master_sym, stack_sym + ; Main axis + ; 1 - vertical divider, master left + ; 2 - horizontal divider, master top + ; -1 - vertical divider, master right + ; -2 - horizontal divider, master bottom axis1 := View_#%m%_#%v%_layoutAxis_#1 + ; Master axis + ; 1 - vertical divider + ; 2 - horizontal divider + ; 3 - monocle axis2 := View_#%m%_#%v%_layoutAxis_#2 + ; Stack axis + ; 1 - vertical divider + ; 2 - horizontal divider + ; 3 - monocle axis3 := View_#%m%_#%v%_layoutAxis_#3 - gapW := View_#%m%_#%v%_layoutGapWidth - mfact := View_#%m%_#%v%_layoutMFact - msplit := View_#%m%_#%v%_layoutMSplit + mx := View_#%m%_#%v%_layoutMX + my := View_#%m%_#%v%_layoutMY + + If ( Abs(axis1) = 1 ) + master_div := "|" + Else + master_div := "=" + + If ( axis2 = 1 ) { + master_sym := "|" + master_dim := mx . "x" . my + } + Else If ( axis2 = 2 ) { + master_sym := "-" + master_dim := mx . "x" . my + } + Else + master_sym := "[" . (mx * my) . "]" + + If ( axis3 = 1 ) + stack_sym := "|" + Else If ( axis3 = 2 ) + stack_sym := "-" + Else + stack_sym := "o" + + If ( axis1 > 0 ) + View_#%m%_#%v%_layoutSymbol := master_dim . master_sym . master_div . stack_sym + Else + View_#%m%_#%v%_layoutSymbol := stack_sym . master_div . master_sym . master_dim +} + +; Stack a bunch of windows on top of each other. +; +; arrName - Name of a globally stored array of windows: +; %arrName%1, %arrName%2, ... +; off - Offset into the array from which to start drawing. +; len - Number of windows from the array to draw. +; dir - Determines the direction through which we traverse arrName +; x - View x-position +; y - View y-position +; w - View width +; h - View height +; margin - Number of pixels to put between the windows. +View_draw_stack( arrName, off, len, dir, x, y, w, h, margin ) { + Local base, inc + If (dir = 0) { + base := off + inc := 1 + } + Else { + base := off + len - 1 + inc := -1 + } + x += margin + y += margin + w -= 2 * margin + h -= 2 * margin + + Loop, % len { + Manager_winMove(%arrName%%base%, x, y, w, h) + base += inc + } +} + +; Draw a row of windows. +; +; arrName - Name of a globally stored array of windows: +; %arrName%1, %arrName%2, ... +; off - Offset into the array from which to start drawing. +; len - Number of windows from the array to draw. +; dir - Determines the direction through which we traverse arrName +; axis - X/Y <=> 0/1 +; x - View x-position +; y - View y-position +; w - View width +; h - View height +; margin - Number of pixels to put between the windows. +View_draw_row( arrName, off, len, dir, axis, x, y, w, h, margin ) { + Local base, inc, x_inc, y_inc, wHeight, wWidth + ;Log_bare("View_draw_row(" . arrName . ", " . off . ", " . len . ", " . dir . ", " . axis . ", " . x . ", " . y . ", " . w . ", " . h . ", " . margin . ")") + If (dir = 0) { + ; Left-to-right and top-to-bottom, depending on axis + base := off + inc := 1 + } + Else { + ; Right-to-left and bottom-to-top, depending on axis + base := off + len - 1 + inc := -1 + } + If (axis = 0) { + ; Create row along X + x_inc := w / len + y_inc := 0 + wWidth := x_inc - 2 * margin + wHeight := h - 2 * margin + } + Else { + ; Create row along Y + x_inc := 0 + y_inc := h / len + wWidth := w - 2 * margin + wHeight := y_inc - 2 * margin + } + + ; Set original positions with respect to the margins. + x += margin + y += margin + + Loop, % len { + Manager_winMove(%arrName%%base%, x, y, wWidth, wHeight) + x += x_inc + y += y_inc + base += inc + } +} + +View_arrange_tile_action(arrName, off, len, bugn_axis, x, y, w, h, m) { + ; 161 is a magic number determined somewhere. Maybe make this configurable. + ; Same with 2*Bar_height. + If (bugn_axis = 3 Or (bugn_axis = 1 And w/len < 161) Or (bugn_axis = 2 And h/len < (2*Bar_height))) + View_draw_stack(arrName, off, len, 0, x, y, w, h, m) + Else + View_draw_row(arrName, off, len, 0, bugn_axis - 1, x, y, w, h, m) +} + +View_split_region(axis, split_point, x, y, w, h, ByRef x1, ByRef y1, ByRef w1, ByRef h1, ByRef x2, ByRef y2, ByRef w2, ByRef h2) { + x1 := x + y1 := y + If(axis = 0) { + w1 := w * split_point + w2 := w - w1 + h1 := h + h2 := h + x2 := x + w1 + y2 := y + } + Else + { + w1 := w + w2 := w + h1 := h * split_point + h2 := h - h1 + x2 := x + y2 := y + h1 + } +} + +View_arrange_tile(m, v, wndIds) { + Local axis1, axis2, axis3, gapW_2, h1, h2, i, mfact, mp, ms, mx2, my2, mw2, mh2, msplit, n1, n2, w1, w2, x1, x2, y1, y2, flipped, stack_len, secondary_areas, areas_remaining, draw_windows StringTrimRight, wndIds, wndIds, 1 - StringSplit, wndId, wndIds, `; - If (msplit > wndId0) { - If (wndId0 < 1) - View_#%m%_#%v%_layoutMSplit := 1 - Else - View_#%m%_#%v%_layoutMSplit := wndId0 - msplit := View_#%m%_#%v%_layoutMSplit + StringSplit, View_arrange_tile_wndId, wndIds, `; + Log_dbg_msg(1, "View_arrange_tile: (" . View_arrange_tile_wndId0 . ") " . wndIds) + If (View_arrange_tile_wndId0 = 0) + Return + + axis1 := Abs(View_#%m%_#%v%_layoutAxis_#1) + axis2 := View_#%m%_#%v%_layoutAxis_#2 + axis3 := View_#%m%_#%v%_layoutAxis_#3 + flipped := View_#%m%_#%v%_layoutAxis_#1 < 0 + gapW_2 := View_#%m%_#%v%_layoutGapWidth/2 + mfact := View_#%m%_#%v%_layoutMFact + dimAligned := (axis2 = 1) ? View_#%m%_#%v%_layoutMX : View_#%m%_#%v%_layoutMY + dimOrtho := (axis2 = 1) ? View_#%m%_#%v%_layoutMY : View_#%m%_#%v%_layoutMX + msplit := dimAligned * dimOrtho + + If (msplit > View_arrange_tile_wndId0) { + msplit := View_arrange_tile_wndId0 } - ; layout symbol - sym1 := "=" - If (axis2 = Abs(axis1)) - sym1 := "|" - If (axis2 = 3) - If (wndId0 = 0) - sym1 := 0 + ; master and stack area + If( View_arrange_tile_wndId0 > msplit) { + If( flipped = 0) + View_split_region( axis1 - 1, mfact, Monitor_#%m%_x, Monitor_#%m%_y, Monitor_#%m%_width, Monitor_#%m%_height, x1, y1, w1, h1, x2, y2, w2, h2) Else - sym1 := msplit - sym3 := "=" - If (axis3 = Abs(axis1)) - sym3 := "|" - If (axis3 = 3) - If (wndId0 = 0) - sym3 := 0 - Else - sym3 := wndId0 - msplit - If (axis1 < 0) - If (msplit = 1) - View_#%m%_#%v%_layoutSymbol := sym3 "[]" - Else - View_#%m%_#%v%_layoutSymbol := sym3 "[" sym1 + View_split_region( axis1 - 1, 1 - mfact, Monitor_#%m%_x, Monitor_#%m%_y, Monitor_#%m%_width, Monitor_#%m%_height, x2, y2, w2, h2, x1, y1, w1, h1) + } + Else { + x1 := Monitor_#%m%_x + y1 := Monitor_#%m%_y + w1 := Monitor_#%m%_width + h1 := Monitor_#%m%_height + } + + ; master + ; Number + If( axis2 = 3 ) + { + View_draw_stack("View_arrange_tile_wndId", 1, msplit, 0, x1, y1, w1, h1, gapW_2) + } Else - If (msplit = 1) - View_#%m%_#%v%_layoutSymbol := "[]" sym3 - Else - View_#%m%_#%v%_layoutSymbol := sym1 "]" sym3 - - If (wndId0 > 0) { - ; master and stack area - h1 := Monitor_#%m%_height - gapW - h2 := Monitor_#%m%_height - gapW - w1 := Monitor_#%m%_width - gapW - w2 := Monitor_#%m%_width - gapW - x1 := Monitor_#%m%_x + gapW / 2 - x2 := Monitor_#%m%_x + gapW / 2 - y1 := Monitor_#%m%_y + gapW / 2 - y2 := Monitor_#%m%_y + gapW / 2 - If (Abs(axis1) = 1 And wndId0 > msplit) { - w1 *= mfact - w2 -= w1 - If (axis1 < 0) - x1 += w2 - Else - x2 += w1 - } Else If (Abs(axis1) = 2 And wndId0 > msplit) { - h1 *= mfact - h2 -= h1 - If (axis1 < 0) - y1 += h2 - Else - y2 += h1 - } - - ; master - If (axis2 != 1 Or w1 / msplit < 161) - n1 := 1 - Else - n1 := msplit - If (axis2 != 2 Or h1 / msplit < Bar_height) - n2 := 1 - Else - n2 := msplit - Loop, % msplit { - Manager_winMove(wndId%A_Index%, x1 + gapW / 2, y1 + gapW / 2, w1 / n1 - gapW, h1 / n2 - gapW) - If (n1 > 1) - x1 += w1 / n1 - If (n2 > 1) - y1 += h1 / n2 - } - - ; stack - If (wndId0 > msplit) { - If (axis3 != 1 Or w2 / (wndId0 - msplit) < 161) - n1 := 1 - Else - n1 := wndId0 - msplit - If (axis3 != 2 Or h2 / (wndId0 - msplit) < Bar_height) - n2 := 1 - Else - n2 := wndId0 - msplit - Loop, % wndId0 - msplit { - i := msplit + A_Index - Manager_winMove(wndId%i%, x2 + gapW / 2, y2 + gapW / 2, w2 / n1 - gapW, h2 / n2 - gapW) - If (n1 > 1) - x2 += w2 / n1 - If (n2 > 1) - y2 += h2 / n2 + { + secondary_areas := Ceil(msplit / dimAligned) + areas_remaining := secondary_areas + windows_remaining := msplit + ;Log_bare("msplit: " . msplit . "; layoutMX/Y: " . dimAligned . "; secondary_areas: " . secondary_areas . "; areas_remaining: " . areas_remaining . "; windows_remaining: " . windows_remaining) + Loop, % secondary_areas { + View_split_region(Not (axis2 - 1), (1/areas_remaining), x1, y1, w1, h1, mx1, my1, mw1, mh1, x1, y1, w1, h1) + draw_windows := dimAligned + If (windows_remaining < dimAligned) { + draw_windows := windows_remaining } + View_draw_row("View_arrange_tile_wndId", msplit - windows_remaining + 1, draw_windows, 0, axis2 - 1, mx1, my1, mw1, mh1, gapW_2) + windows_remaining -= draw_windows + areas_remaining -= 1 } } + + ; stack + If (View_arrange_tile_wndId0 <= msplit) + Return + + stack_len := View_arrange_tile_wndId0 - msplit + View_arrange_tile_action("View_arrange_tile_wndId", msplit + 1, stack_len, axis3, x2, y2, w2, h2, gapW_2) } View_toggleFloating() { diff --git a/src/docs/changes.t2t b/src/docs/changes.t2t index 407b153..457ab6e 100644 --- a/src/docs/changes.t2t +++ b/src/docs/changes.t2t @@ -10,6 +10,23 @@ (~) changed (+) added +=8.2.2(?)= + +(+) Multi-dimensional tiling of the master area. The user may now specify + X and Y dimensions independently up to 9 x 9. +(+) Initially assign windows to the monitor on which they appear if + nothing is specified in the configuration. +(~) Improved view arranging runtime. +(+) Created bug.n log to record major and debugging events. +(~) Fixed bug #18641: Freezing problem. In most cases, bug.n will no + longer hang when one of its managed windows hangs. +(~) Fixed bug: Sometimes cycling through the windows in a view would + get stuck on a particular window. +(~) Fixed bug: Activating a window on a non-active view on + a non-active monitor could cause both monitors to change views. +(~) Gap widths are now treated identically on both "tile" and "monocle" + layouts. + =8.2.1= diff --git a/src/docs/help.t2t b/src/docs/help.t2t index 0f85922..fd0e24e 100644 --- a/src/docs/help.t2t +++ b/src/docs/help.t2t @@ -71,7 +71,7 @@ There are three layouts. A master area for the main window(s) and a stacking area for the rest, all windows are shown at any time. This layout can be further changed in the following respects: - - the number of windows in the master area (1 ... all) + - the dimensions of the master area (1x1 ... 9x9) - the stacking direction of the master and stacking area (from left to right, from top to bottom or monocle) - the position of the master area (left, top, right or bottom) diff --git a/src/docs/hotkeys.t2t b/src/docs/hotkeys.t2t index 7d7db4b..f96fde6 100644 --- a/src/docs/hotkeys.t2t +++ b/src/docs/hotkeys.t2t @@ -134,13 +134,21 @@ pressing the left Windows key and the shift key and the q key 1 -> 2 = y-axis = vertical stack, 2 -> 3 = z-axis = monocle, only for the "tile" layout). -: #^Left::**View_setMSplit(+1)** - Move the master splitter, i. e. decrease the number of windows in the - master area (only for the "tile" layout). +: #^Left::**View_setMX(-1)** + Decrease the master X dimension by 1, i. e. decrease the number of + windows in the master area by Y. Minimum of 1. -: #^Right::**View_setMSplit(-1)** - Move the master splitter, i. e. increase the number of windows in the - master area (only for the "tile" layout). +: #^Right::**View_setMX(+1) + Increase the master X dimension by 1, i. e. increase the number of + windows in the master area by Y. Maximum of 9. + +: #^Down::**View_setMY(-1)** + Decrease the master Y dimension by 1, i.e. decrease the number of + windows in the master area by X. Minimum of 1. + +: #^Up::**View_setMY(+1)** + Increase the master Y dimension by 1, i.e. increase the number of + windows in the master area by X. Maximum of 9. === Tag / View related hotkeys === @@ -197,6 +205,25 @@ pressing the left Windows key and the shift key and the q key Hide / Show the task bar. +=== Logging/Debugging related hotkeys === + +: #^i::**Manager_logViewWindowList()** + Dump the contents of the current view to the bug.n log. + +: #+^i::**Manager_logManagedWindowList()** + Dump the contents of the managed window list to the bug.n log. + +: #^h::**Manager_logHelp()** + Print to the log a description of the formatting used in the + previous two log messages. + +: #^[::**Log_decDebugLevel()** + Decrement the debug log level. Show fewer debug messages. + +: #^]::**Log_incDebugLevel()** + Increment the debug log level. Show more debug messages. + + === Application related hotkeys === : #y::**Bar_toggleCommandGui()** diff --git a/test/hang.cpp b/test/hang.cpp new file mode 100644 index 0000000..c297715 --- /dev/null +++ b/test/hang.cpp @@ -0,0 +1,87 @@ +/** + * Intended to be built with MinGW. + * + */ + +#include <windows.h> + + +char *AppTitle="Win1"; +LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + +int WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int nCmdShow) +{ + WNDCLASS wc; + HWND hwnd; + MSG msg; + + wc.style=CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc=WindowProc; + wc.cbClsExtra=0; + wc.cbWndExtra=0; + wc.hInstance=hInst; + wc.hIcon=LoadIcon(NULL,IDI_WINLOGO); + wc.hCursor=LoadCursor(NULL,IDC_ARROW); + wc.hbrBackground=(HBRUSH)COLOR_WINDOWFRAME; + wc.lpszMenuName=NULL; + wc.lpszClassName=AppTitle; + + if (!RegisterClass(&wc)) + return 0; + + hwnd = CreateWindow(AppTitle,AppTitle, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT,CW_USEDEFAULT,100,100, + NULL,NULL,hInst,NULL); + + if (!hwnd) + return 0; + + ShowWindow(hwnd,nCmdShow); + UpdateWindow(hwnd); + + while (GetMessage(&msg,NULL,0,0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + + +int maintain_bomb = 60; +int activate_bomb = 5; + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC dc; + RECT r; + GetClientRect(hwnd,&r); + dc=BeginPaint(hwnd,&ps); + DrawText(dc,"This window intentionally hangs",-1,&r,DT_SINGLELINE|DT_CENTER|DT_VCENTER); + EndPaint(hwnd,&ps); + break; + } + + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_SHOWWINDOW: + case WM_ACTIVATE: + if(activate_bomb == 0) + while(maintain_bomb) { + Sleep(1000); + maintain_bomb--; + } + activate_bomb--; + + default: + return DefWindowProc(hwnd, msg, wparam, lparam); + } + return 0; +}