├── .gitignore ├── README.md ├── VirtualDesktopAccessor.dll ├── i3wm.ahk └── user_config.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | windows-switcher-original.ahk 2 | swin-original.ahk 3 | *.lnk 4 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i3-windows 2 | 3 | Forked from [pmb6tz/windows-desktop-switcher](https://github.com/pmb6tz/windows-desktop-switcher) and [Depau/swin](https://github.com/Depau/swin)[jnoynaert/window-mover](https://github.com/jnoynaert/window-mover), to minimally reproduce some desirable i3/sway-like behavior on Windows. 4 | 5 | - Switch to virtual desktops 1-9 using `⊞-#` and backfill # of desktops if needed 6 | - Move active windows to virtual desktops 1-9 using `⊞-shift-#` and backfill desktops if needed 7 | - Close windows with `⊞-shift-q` 8 | - Open WSL with `⊞-enter` (need to create and edit link in user_config.ahk) 9 | - Open PowerShell with `⊞-shift-enter` 10 | 11 | # Dependencies 12 | 13 | - Windows 10 14 | - [AutoHotkey](https://autohotkey.com/download/) v1.1+ 15 | - [VirtualDesktopAccessor.dll](https://github.com/Ciantic/VirtualDesktopAccessor) 16 | 17 | 18 | ## Running on boot 19 | 20 | You can make the script run on every boot with either of these methods. 21 | 22 | ### Simple (Non-administrator method) 23 | 24 | 1. Press `Win + R`, enter `shell:startup`, press `OK` 25 | 2. Create a shortcut to the `desktop_switcher.ahk` file here 26 | 27 | ### Advanced (Administrator method) 28 | 29 | Windows prevents hotkeys from working in windows that were launched with higher elevation than the AutoHotKey script (such as CMD or Powershell terminals that were launched as Administrator). As a result, Windows Desktop Switcher hotkeys will only work within these windows if the script itself is `Run as Administrator`, due to the way Windows is designed. 30 | 31 | You can do this by creating a scheduled task to invoke the script at logon. You may use 'Task Scheduler', or create the task in powershell as demonstrated. 32 | ``` 33 | # Run the following commands in an Administrator powershell prompt. 34 | # Be sure to specify the correct path to your desktop_switcher.ahk file. 35 | 36 | $A = New-ScheduledTaskAction -Execute "PATH\TO\desktop_switcher.ahk" 37 | $T = New-ScheduledTaskTrigger -AtLogon 38 | $P = New-ScheduledTaskPrincipal -GroupId "BUILTIN\Administrators" -RunLevel Highest 39 | $S = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit 0 40 | $D = New-ScheduledTask -Action $A -Principal $P -Trigger $T -Settings $S 41 | Register-ScheduledTask WindowsDesktopSwitcher -InputObject $D 42 | ``` 43 | 44 | The task is now registered and will run on the next logon, and can be viewed or modified in 'Task Scheduler'. 45 | 46 | ## Original credits 47 | 48 | - Thanks to [Ciantic/VirtualDesktopAccessor](https://github.com/Ciantic/VirtualDesktopAccessor) (for the DLL) and [sdias/win-10-virtual-desktop-enhancer](https://github.com/sdias/win-10-virtual-desktop-enhancer) (for the DLL usage samples) our code can move windows between desktops. 49 | -------------------------------------------------------------------------------- /VirtualDesktopAccessor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yalibian/i3-windows/fb4100de7b1f52241372827f8ad805d61e7169a5/VirtualDesktopAccessor.dll -------------------------------------------------------------------------------- /i3wm.ahk: -------------------------------------------------------------------------------- 1 | ;see https://autohotkey.com/board/topic/3248-stuck-ctrl-key/ if issues with keys sticking 2 | #SingleInstance Force ; The script will Reload if launched while already running 3 | #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases 4 | #KeyHistory 0 ; Ensures user privacy when debugging is not needed 5 | SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory 6 | SendMode Input ; Recommended for new scripts due to its superior speed and reliability 7 | 8 | ; Globals 9 | DesktopCount := 2 ; Windows starts with 2 desktops at boot 10 | CurrentDesktop := 1 ; Desktop count is 1-indexed (Microsoft numbers them this way) 11 | PreviousDesktop := 0 ; starts as a falsy value, gets set when switching desktops 12 | 13 | ; DLL 14 | hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", A_ScriptDir . "\VirtualDesktopAccessor.dll", "Ptr") 15 | global IsWindowOnDesktopNumberProc := DllCall("GetProcAddress", Ptr, hVirtualDesktopAccessor, AStr, "IsWindowOnDesktopNumber", "Ptr") 16 | global MoveWindowToDesktopNumberProc := DllCall("GetProcAddress", Ptr, hVirtualDesktopAccessor, AStr, "MoveWindowToDesktopNumber", "Ptr") 17 | global GoToDesktopNumberProc := DllCall("GetProcAddress", Ptr, hVirtualDesktopAccessor, AStr, "GoToDesktopNumber", "Ptr") 18 | 19 | ; Main 20 | SetKeyDelay, 75 21 | mapDesktopsFromRegistry() 22 | OutputDebug, [loading] desktops: %DesktopCount% current: %CurrentDesktop% 23 | 24 | #Include %A_ScriptDir%\user_config.ahk 25 | return 26 | 27 | ; 28 | ; This function examines the registry to build an accurate list of the current virtual desktops and which one we're currently on. 29 | ; Current desktop UUID appears to be in HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\1\VirtualDesktops 30 | ; List of desktops appears to be in HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops 31 | ; 32 | mapDesktopsFromRegistry() 33 | { 34 | global CurrentDesktop, DesktopCount 35 | 36 | ; Get the current desktop UUID. Length should be 32 always, but there's no guarantee this couldn't change in a later Windows release so we check. 37 | IdLength := 32 38 | SessionId := getSessionId() 39 | if (SessionId) { 40 | RegRead, CurrentDesktopId, HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\%SessionId%\VirtualDesktops, CurrentVirtualDesktop 41 | if (CurrentDesktopId) { 42 | IdLength := StrLen(CurrentDesktopId) 43 | } 44 | } 45 | 46 | ; Get a list of the UUIDs for all virtual desktops on the system 47 | RegRead, DesktopList, HKEY_CURRENT_USER, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops, VirtualDesktopIDs 48 | if (DesktopList) { 49 | DesktopListLength := StrLen(DesktopList) 50 | ; Figure out how many virtual desktops there are 51 | DesktopCount := floor(DesktopListLength / IdLength) 52 | } 53 | else { 54 | DesktopCount := 1 55 | } 56 | 57 | ; Parse the REG_DATA string that stores the array of UUID's for virtual desktops in the registry. 58 | i := 0 59 | while (CurrentDesktopId and i < DesktopCount) { 60 | StartPos := (i * IdLength) + 1 61 | DesktopIter := SubStr(DesktopList, StartPos, IdLength) 62 | ;OutputDebug, The iterator is pointing at %DesktopIter% and count is %i%. 63 | 64 | ; Break out if we find a match in the list. If we didn't find anything, keep the 65 | ; old guess and pray we're still correct :-D. 66 | if (DesktopIter = CurrentDesktopId) { 67 | CurrentDesktop := i + 1 68 | ;OutputDebug, Current desktop number is %CurrentDesktop% with an ID of %DesktopIter%. 69 | break 70 | } 71 | i++ 72 | } 73 | } 74 | 75 | ; 76 | ; This functions finds out ID of current session. 77 | ; 78 | getSessionId() 79 | { 80 | ProcessId := DllCall("GetCurrentProcessId", "UInt") 81 | if ErrorLevel { 82 | OutputDebug, Error getting current process id: %ErrorLevel% 83 | return 84 | } 85 | OutputDebug, Current Process Id: %ProcessId% 86 | 87 | DllCall("ProcessIdToSessionId", "UInt", ProcessId, "UInt*", SessionId) 88 | if ErrorLevel { 89 | OutputDebug, Error getting session id: %ErrorLevel% 90 | return 91 | } 92 | OutputDebug, Current Session Id: %SessionId% 93 | return SessionId 94 | } 95 | 96 | 97 | ; 98 | ; This function creates a new virtual desktop and switches to it 99 | ; 100 | createVirtualDesktop() 101 | { 102 | global CurrentDesktop, DesktopCount 103 | Send, #^d 104 | DesktopCount++ 105 | CurrentDesktop := DesktopCount 106 | OutputDebug, [create] desktops: %DesktopCount% current: %CurrentDesktop% 107 | } 108 | 109 | 110 | _createEnoughDesktops(targetDesktop) { 111 | global DesktopCount 112 | 113 | ; Create virtual desktop if it does not exist 114 | while (targetDesktop > DesktopCount) { 115 | createVirtualDesktop() 116 | } 117 | return 118 | } 119 | 120 | _switchDesktopToTarget(targetDesktop) 121 | { 122 | ; Globals variables should have been updated via updateGlobalVariables() prior to entering this function 123 | global CurrentDesktop, DesktopCount, PreviousDesktop, ToggleDesktop 124 | 125 | ; Don't attempt to switch to an invalid desktop 126 | if (targetDesktop < 1) { 127 | OutputDebug, [invalid] target: %targetDesktop% current: %CurrentDesktop% 128 | return 129 | } 130 | 131 | if (targetDesktop == CurrentDesktop) { 132 | if (ToggleDesktop && PreviousDesktop) { 133 | switchDesktopByNumber(PreviousDesktop) 134 | return 135 | } 136 | return 137 | } 138 | 139 | PreviousDesktop := CurrentDesktop 140 | 141 | _createEnoughDesktops(targetDesktop) 142 | 143 | ; Fixes the issue of active windows in intermediate desktops capturing the switch shortcut and therefore delaying or stopping the switching sequence. This also fixes the flashing window button after switching in the taskbar. More info: https://github.com/pmb6tz/windows-desktop-switcher/pull/19 144 | WinActivate, ahk_class Shell_TrayWnd 145 | 146 | DllCall(GoToDesktopNumberProc, UInt, targetDesktop - 1) 147 | 148 | ; Makes the WinActivate fix less intrusive 149 | Sleep, 50 150 | focusTheForemostWindow(targetDesktop) 151 | } 152 | 153 | updateGlobalVariables() 154 | { 155 | ; Re-generate the list of desktops and where we fit in that. We do this because 156 | ; the user may have switched desktops via some other means than the script. 157 | mapDesktopsFromRegistry() 158 | } 159 | 160 | switchDesktopByNumber(targetDesktop) 161 | { 162 | global CurrentDesktop, DesktopCount 163 | updateGlobalVariables() 164 | _switchDesktopToTarget(targetDesktop) 165 | } 166 | 167 | focusTheForemostWindow(targetDesktop) 168 | { 169 | foremostWindowId := getForemostWindowIdOnDesktop(targetDesktop) 170 | WinActivate, ahk_id %foremostWindowId% 171 | } 172 | 173 | getForemostWindowIdOnDesktop(n) 174 | { 175 | n := n - 1 ; Desktops start at 0, while in script it's 1 176 | 177 | ; winIDList contains a list of windows IDs ordered from the top to the bottom for each desktop. 178 | WinGet winIDList, list 179 | Loop % winIDList { 180 | windowID := % winIDList%A_Index% 181 | windowIsOnDesktop := DllCall(IsWindowOnDesktopNumberProc, UInt, windowID, UInt, n) 182 | ; Select the first (and foremost) window which is in the specified desktop. 183 | if (windowIsOnDesktop == 1) { 184 | return windowID 185 | } 186 | } 187 | } 188 | 189 | MoveCurrentWindowToDesktop(desktopNumber) { 190 | global CurrentDesktop, DesktopCount 191 | WinGet, activeHwnd, ID, A 192 | 193 | ;updateGlobalVariables() 194 | 195 | _createEnoughDesktops(desktopNumber) 196 | DllCall(MoveWindowToDesktopNumberProc, UInt, activeHwnd, UInt, desktopNumber - 1) 197 | 198 | ;OutputDebug, Moving current window %activeHwnd% to %desktopNumber% 199 | 200 | ;output := DllCall(MoveWindowToDesktopNumberProc, UInt, activeHwnd, UInt, desktopNumber - 1) 201 | ;if output { 202 | ; OutputDebug, success 203 | ;} else { 204 | ; OutputDebug, failed 205 | ;} 206 | 207 | ;switchDesktopByNumber(desktopNumber) 208 | ;WinActivate, ahk_id activeHwnd 209 | 210 | ;focusTheForemostWindow(CurrentDesktop) 211 | switchDesktopByNumber(desktopNumber) 212 | } 213 | 214 | closeWindow(){ 215 | global CurrentDesktop 216 | 217 | WinClose, A 218 | focusTheForemostWindow(CurrentDesktop) 219 | } 220 | 221 | quitWindow(){ 222 | ;quite current window 223 | WinClose, A 224 | } 225 | 226 | 227 | toggleMaximize(){ 228 | WinGet, maximized, MinMax, A 229 | 230 | if maximized { 231 | WinRestore A 232 | } else { 233 | WinMaximize A 234 | } 235 | } -------------------------------------------------------------------------------- /user_config.ahk: -------------------------------------------------------------------------------- 1 | #Include i3wm.ahk 2 | ; ==================== 3 | ; === INSTRUCTIONS === 4 | ; ==================== 5 | ; 1. Any lines starting with ; are ignored 6 | ; 2. After changing this config file run script file "desktop_switcher.ahk" 7 | ; 3. Every line is in the format HOTKEY::ACTION 8 | 9 | ; === SYMBOLS === 10 | ; ! <- Alt 11 | ; + <- Shift 12 | ; ^ <- Ctrl 13 | ; # <- Win 14 | ; For more, visit https://autohotkey.com/docs/Hotkeys.htm 15 | 16 | ; === EXAMPLES === 17 | ; !n::switchDesktopToRight() <- + will switch to the next desktop (to the right of the current one) 18 | ; #!space::switchDesktopToRight() <- + + will switch to next desktop 19 | ; CapsLock & n::switchDesktopToRight() <- + will switch to the next desktop (& is necessary when using non-modifier key such as CapsLock) 20 | 21 | ; =========================== 22 | ; === END OF INSTRUCTIONS === 23 | ; =========================== 24 | 25 | ; `toggleDesktop` causes switchDesktopByNumber to switch back to the previous 26 | ; desktop if called with the currently active desktop. 27 | ToggleDesktop := false 28 | 29 | #1::switchDesktopByNumber(1) 30 | #2::switchDesktopByNumber(2) 31 | #3::switchDesktopByNumber(3) 32 | #4::switchDesktopByNumber(4) 33 | #5::switchDesktopByNumber(5) 34 | #6::switchDesktopByNumber(6) 35 | #7::switchDesktopByNumber(7) 36 | #8::switchDesktopByNumber(8) 37 | #9::switchDesktopByNumber(9) 38 | 39 | #+1::MoveCurrentWindowToDesktop(1) 40 | #+2::MoveCurrentWindowToDesktop(2) 41 | #+3::MoveCurrentWindowToDesktop(3) 42 | #+4::MoveCurrentWindowToDesktop(4) 43 | #+5::MoveCurrentWindowToDesktop(5) 44 | #+6::MoveCurrentWindowToDesktop(6) 45 | #+7::MoveCurrentWindowToDesktop(7) 46 | #+8::MoveCurrentWindowToDesktop(8) 47 | #+9::MoveCurrentWindowToDesktop(9) 48 | 49 | #q::quitWindow() 50 | 51 | ;#f::toggleMaximize() 52 | 53 | ; WSL -- create shortcut by dragging from start menu (can't be from a search result) 54 | #Enter::Run, C:\window-mover.git\Debian GNU-Linux 55 | 56 | ; PowerShell 57 | #+Enter::Run, powershell 58 | --------------------------------------------------------------------------------