55

I am looking for a way to switch between two open windows of the same app with a key shortcut, I have found this https://neosmart.net/EasySwitch/ but it is not open source and I don't trust it even works. Does anyone has an alternative ? This is OOTB in Unity and Mac OS it is absurd that Windows doesn't have it.

Some time ago a Microsoft employee made this tool : https://switcher.en.softonic.com/ not sure what is the official website, it is GREAT but works only on Windows 7 and doesnt work on 10, also not open source.

The closest thing I have found that is open source is this one https://github.com/JochenBaier/fastwindowswitcher but is less then ideal.

is there an alternative ?

JOKe
  • 651

10 Answers10

57

There are three ways for doing this:

  1. You can hold Ctrl + Click on the icon of the app in the taskbar
  2. Use Win + position of app on taskbar. For example, if Google Chrome is on third position, do Win+3 to switch between windows of Chrome. Note that this cycles through the different windows, so for opening 4th window you'll have to press 3 four times.
  3. The fastest of all, Using Win + ctrl + n does exactly what you want. (where n is position of the app on taskbar)

Note that in 2nd and 3rd options if n is specified such that no window of that program is already open, windows will open a new window. This effect can also be acheived by pressing Win + shift + n, regardless of if windows of that app were already open or not.

50

You may implement the Alt+` shortcut yourself using the free AutoHotkey.

The following script will cycle through all the windows of the active process:

!`::
WinGetClass, OldClass, A
WinGet, ActiveProcessName, ProcessName, A
WinGet, WinClassCount, Count, ahk_exe %ActiveProcessName%
IF WinClassCount = 1
    Return
loop, 2 {
  WinSet, Bottom,, A
  WinActivate, ahk_exe %ActiveProcessName%
  WinGetClass, NewClass, A
  if (OldClass <> "CabinetWClass" or NewClass = "CabinetWClass")
    break
}

After installing AutoHotKey, put the above text in a .ahk file and double-click it to test. You may stop the script by right-click on the green H icon in the traybar and choosing Exit. To have it run on login, place it in the Startup group at
C:\Users\USER-NAME\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup.

Useful AutoHotkey documentation:

harrymc
  • 498,455
24

I've updated the script of harrymc to be executable in AutoHotkey v2:

#Requires AutoHotkey v2.0

!`::{ OldClass := WinGetClass("A") ActiveProcessName := WinGetProcessName("A") WinClassCount := WinGetCount("ahk_exe" ActiveProcessName) IF WinClassCount = 1 Return loop 2 { WinMoveBottom("A") WinActivate("ahk_exe" ActiveProcessName) NewClass := WinGetClass("A") if (OldClass != "CabinetWClass" or NewClass = "CabinetWClass") break } }

From this it should also be easy to convert the script of EugeneK.

I tried to implement EugeneK's script as well in v2. I'm not sure if it has all the intended features, but it works fine and will be what I'll be using

#Requires AutoHotkey v2.0

!`:: { win_id := WinActive("A") win_class := WinGetClass("A") active_process_name := WinGetProcessName("A") ; We have to be extra careful about explorer.exe since that process is responsible for more than file explorer if (active_process_name = "explorer.exe") win_list := WinGetList("ahk_exe" active_process_name " ahk_class" win_class) else win_list := WinGetList("ahk_exe" active_process_name)

; Calculate index of next window. Since activating a window puts it at the top of the list, we have to take from the bottom.
next_window_i := win_list.Length
next_window_id := win_list[next_window_i]

; Activate the next window and send it to the top.
WinMoveTop(&quot;ahk_id&quot; next_window_id)
WinActivate(&quot;ahk_id&quot; next_window_id)

}

In addition: I like having this functionality tied to the key Alt + |. So, in case you also like this better, change !':: to !|::.

9cco
  • 341
8

I've modified ahk script in order to have an Ubuntu style switching. Pressing ` while holding an alt key will cycle through all application windows putting the next one on top rather than sending top window to the bottom. And single alt + ` combinations will cycle between last two windows.

!`::
WinGet, ActiveProcessName, ProcessName, A
WinGet, WinClassCount, List, ahk_exe %ActiveProcessName%

if (WinClassCount = 1) return

if (NextWindow = "") NextWindow := 2

element := % WinClassCount%NextWindow%

WinSet, Top,, ahk_id %element% WinActivate, ahk_id %element%

NextWindow += 1

if (NextWindow > WinClassCount || !getKeyState("Alt")) NextWindow := 2

return

EugeneK
  • 81
  • 1
  • 1
5

I couldn't get any of these answers to do quite what I wanted so I took inspiration from jenda's solution and wrote my own AHK V2 script. I really like the way Mac handles this functionality so I tried to mimic that behaviour as closely as possible.

alt + ` to cycle forwards through windows

alt + shift + ` to cycle backwards through windows

I found that Windows Explorer creates extra processes and windows when pressing alt so I've added CatchWindowsExplorerErrors = true to filter out these.

#ErrorStdOut
!`::
{
    ;OutputDebug 'cmd+`` `n'
    changeActiveWindow("forward")
}

<+!:: { ;OutputDebug 'cmd+shift+``n' changeActiveWindow("back") }

changeActiveWindow(dir) { static windowOrder := [] static expectedOrder := [] currentOrder := [] CatchWindowsExplorerErrors := true ; Windows Explorer creates a number of invisible windows which breaks behavior when using the alt menu. Set this to false to let these errors through. debugging := false

; Get all windows the same as the active window
OldClass := WinGetClass(&quot;A&quot;)
ActiveProcessName := WinGetProcessName(&quot;A&quot;)
WinClassCount := WinGetCount(&quot;ahk_exe &quot; ActiveProcessName)
ActiveId := WinGetID(&quot;A&quot;)
OutputDebug 'Current Window:    ' ActiveId '/' OldClass '/' ActiveProcessName '/' WinGetTitle(&quot;ahk_id&quot; ActiveId) '`n'

; If there's only one window, do nothing
if (WinClassCount = 1)
    Return

; Get all windows of the same process
ids := WinGetList(&quot;ahk_exe &quot; ActiveProcessName)
for SiblingID in ids {
    if (WinGetTitle(SiblingID) != &quot;&quot;){
        if (CatchWindowsExplorerErrors){
            if ( WinGetClass(SiblingID) != &quot;KbxLabelClass&quot; &amp;&amp; WinGetTitle(SiblingID) != &quot;Program Manager&quot;){
                currentOrder.Push(SiblingID)
            }
        } else {
            currentOrder.Push(SiblingID)
        }
    }
}

; Check first run and populate
if windowOrder.Length = 0 {
    resetWindows()
}
printDebugging()

; Check if current order and length match expected order and length
; If they don't, expected order has changed or a window has been removed or inserted
if (currentOrder.Length = expectedOrder.Length) {
    Loop currentOrder.Length {
        if (currentOrder[A_Index] != expectedOrder[A_Index]){
            resetWindows()
            break
        }
    }
} else {
    resetWindows()
}

windowOrder := moveToNextIndex(windowOrder, dir)
changeActiveWindow()
expectedOrder := updateExpectedOrder(expectedOrder, windowOrder)
printDebugging()

OutputDebug '`n'


; Functions
; Reset the windows to the current state - used if the order doesn't match the expected order e.g. a new window is introduced or the user has clicked and changed order
resetWindows(){
    OutputDebug 'Restting memory`n'
    windowOrder := currentOrder.Clone()
    expectedOrder := currentOrder.Clone()
    return
}

; Used to update the active window tracked by windowOrder
changeActiveWindow(){
    WinActivate(&quot;ahk_id&quot; windowOrder[1])
    try {
        OutputDebug 'Switching to:    ' WinGetTitle(&quot;ahk_id&quot; windowOrder[1]) ' -- ' windowOrder[1] '`n'
    } catch Error as e {
        OutputDebug &quot;An error was thrown!`nSpecifically: &quot; e.Message
        Exit
    }
}

; Print debugging information
printDebugging(){
    if (debugging){
        OutputDebug '---------------------- currentOrder: ----------------------`n'
        for e in currentOrder {
            OutputDebug WinGetTitle(&quot;ahk_id&quot; e) ' -- ' e '`n'
        }
        OutputDebug '---------------------- windowOrder: ----------------------`n'
        for e in windowOrder {
            OutputDebug WinGetTitle(&quot;ahk_id&quot; e) ' -- ' e '`n'
        }
        OutputDebug '---------------------- expectedOrder: ----------------------`n'
        for e in expectedOrder {
            OutputDebug WinGetTitle(&quot;ahk_id&quot; e) ' -- ' e '`n'
        }
    }
}

}

getArrayValueIndex(arr, val){ Loop arr.Length { if (arr[A_Index] == val) return A_Index } }

moveToNextIndex(arr, dir){ if (dir == "forward"){ e := arr[1] arr.RemoveAt(1) arr.Push(e) return arr } else if (dir == "back"){ e := arr[arr.Length] arr.RemoveAt(arr.Length) arr.InsertAt(1, e) return arr } }

moveLastIndexToFirst(arr){ e := arr[1] arr.RemoveAt(1) arr.Push(e) return arr }

updateExpectedOrder(eo, wo){ activeWindowIndex := getArrayValueIndex(eo, wo[1]) activeWindow := eo[activeWindowIndex] eo.RemoveAt(activeWindowIndex) eo.InsertAt(1, activeWindow) return eo }

Happy to accept PRs if you can spot any optimisations!

https://github.com/BdSteel/AHK-Scripts/blob/main/Window%20Switcher/Window%20Switcher.ahk

5

Windows' native Alt+Tab switcher but with only windows from one application

I wanted a nice Alt+` window switching experience, and I thought of a way to do it using the actual Alt+Tab UI, no mimicry required.

This script listens for Alt+` and converts it to an Alt+Tab after filtering the windows seen by the task switcher, by setting an attribute on the windows. When you release Alt, it unfilters the windows by removing the attribute.

You can use ` or Tab while Alt is held to cycle the list, and you can hold Shift to cycle backwards, including when opening the switcher.

Limitations:

  • Windows are hidden from the task bar as well, which can be distracting, especially with taskbar button labels enabled, as it animates the taskbar buttons collapsing and expanding. Windows has a setting to disable taskbar animation, but it doesn't seem to work in Windows 11.
  • Some windows are not hidden from the task switcher, such as the Task Manager, due to permission errors.
    • Running as administrator fixes this.
  • UWP windows, such as Windows's Settings app, are not filtered out either. They don't play well with any of the methods I've tried.
  • There may be side effects on the windows that get hidden, since it temporarily changes their window type, essentially. If something goes wrong, windows may be left excluded from the task bar and task switcher, and without minimize or maximize buttons. I've only seen this happen once, and it may have been due to a work-in-progress version of the script.

I don't use UWP apps much, so this is good enough for me, but I would love it if someone found a solution for hiding UWP windows.

Usage:

  • Install AutoHotkey v2
  • Save script as window-switcher.ahk or similar
  • Double click the new file to run it once.
  • To run at startup with administrator privileges:
    • Open Task Scheduler
    • Action > Create Task...
    • Check "Run with highest privileges" in "Security options" in General tab
    • In Triggers tab, click "New..." and set the type to "At log on"
    • For the Action, you can browse for the script. (You may want to move it somewhere where you'd like to keep it long term first, since it will use the file path to locate the script.)
; Requires AutoHotkey v2

;-------------------------------------------------------- ; Alt+` to switch between windows of the same application ;--------------------------------------------------------

; This script piggybacks on the built-in Alt+Tab window switcher, ; filtering it to show only windows from the same process as the active window. ; It listens for Alt+and Alt+Shift+ and converts them to Alt+Tab and Alt+Shift+Tab, respectively, ; after hiding windows from the task switcher by setting their WS_EX_TOOLWINDOW style, ; and then unhiding them after the switcher is closed. ; Pressing again while holding Alt will tab through the windows of the same application, ; and Shift+ will tab through them in reverse. ; Tab or Shift+Tab also works (automatically, since that's what the switcher normally uses.)

; Limitations: ; - Windows are hidden from the task bar as well, which can be distracting, ; especially with taskbar button labels enabled, as it animates the taskbar buttons collapsing and expanding. ; - Some windows are not hidden from the task switcher, such as the Task Manager, due to permission errors. ; - Running as administrator fixes this. ; - UWP windows, such as Windows's Settings app, are not filtered out either. ; - They don't play well with any of the methods I've tried. ; - There may be side effects on the windows that get hidden, since it changes their window type, essentially, temporarily. ; - I now have seen it leave File Explorer with no minimize or maximize buttons, stuck on a tool window style, ; and permanently excluded from the task bar and task switcher. ; (This may have only been due to a work-in-progress version of this script, or it may be a real issue.) ; I didn't see this before adding removal of WS_EX_APPWINDOW, so that may be the cause (if it's not a fluke.) ; Actually it might not be the removal of WS_EX_APPWINDOW, but the code supporting that, ; which allows for changes to other styles while hidden. If I change that, it might be fine. ; I've changed it to restore all styles for now, but I don't know if that's even related.

; TODO: remove windows from task switcher only, and not the task bar. ; Adding WS_EX_TOOLWINDOW is much faster than WinHide/WinShow (it makes the actual interaction instantaneous!), ; but it still causes distracting animation in the taskbar, particularly when taskbar button labels are enabled. ; Is there a less obtrusive way to remove windows from the task switcher?

#MaxThreadsPerHotkey 2

WS_EX_APPWINDOW := 0x00040000 WS_EX_TOOLWINDOW := 0x00000080 WS_CHILD := 0x40000000

TempHiddenWindows := [] OriginalExStyles := Map()

!+:: { FilteredWindowSwitcher() } !:: { FilteredWindowSwitcher() } FilteredWindowSwitcher() { if TempHiddenWindows.Length { ; Needs #MaxThreadsPerHotkey 2 to handle Alt+++... to tab through windows with, while waiting for Alt to be released ; Needs {Blind} to handle Alt+Shift+to go in reverse Send &quot;{Blind}{Tab}&quot; return } try { ActiveProcessName := WinGetProcessName(&quot;A&quot;) } catch TargetError { MakeSplash(&quot;Window Switcher&quot;, &quot;Active window not found.&quot;, 1000) return } WinClassCount := WinGetCount(&quot;ahk_exe &quot; ActiveProcessName) if WinClassCount = 1 { return } WindowsOfApp := WinGetList(&quot;ahk_exe &quot; ActiveProcessName) AllWindows := WinGetList() Messages := [] for Window in AllWindows { SameApp := false for WindowOfApp in WindowsOfApp { if Window = WindowOfApp { SameApp := true break } } if !SameApp { if Switchable(Window) { try { ; MsgBox(&quot;Would hide:n`n" DescribeWindow(Window), "Window Switcher") ExStyle := WinGetExStyle(Window) ; redundantly accessed in Switchable... OriginalExStyles[Window] := ExStyle if WinGetClass(Window) = "ApplicationFrameWindow" { ; This is a Windows UWP app. It doesn't work to add WS_EX_TOOLWINDOW (though it doesn't generate an error). ; In fact, not even replacing all styles works: ; WinSetExStyle(WS_EX_TOOLWINDOW, Window) ; WinSetStyle(WS_CHILD, Window) ; WinHide doesn't work either, for UWP apps. ; It hides the window itself, but it doesn't hide it from the task switcher or the task bar. ; TODO: Find a way to hide UWP apps from the task switcher. This is pretty annoying! ; My only real idea is to move the window to a different virtual desktop, ; which would only work well with "Show all open windows when I press Alt+Tab" set to "Only on the desktop I'm using", ; and ideally with "On the taskbar, show all open windows" set to "On all desktops", ; which theoretically could avoid the taskbar animation, which could be nice for other windows as well. ; (These settings are in Multitasking in Settings.) ; No idea if it would be performant enough. There's a library for this though: https://github.com/FuPeiJiang/VD.ahk ; Perhaps that just speaks to the complexity of the solution though. ; It might be better to reimplement a task switcher from scratch at that point, though it would never look quite the same. ; WinHide(Window) ; Um, MakeSplash is no good here, since it blocks execution. But it's useful for debugging. ; MakeSplash("Window Switcher", "Hiding UWP app window: " WinGetTitle(Window), 1000) ; MakeSplash("Window Switcher", "Can't hide UWP app window from task switcher: " WinGetTitle(Window), 1000) } else { ; I have not seen any benefit to removing WS_EX_APPWINDOW, but I don't know if I've seen any windows with it. ; It may help in some cases, if I've done it right, but I don't know. WinSetExStyle(ExStyle | WS_EX_TOOLWINDOW & ~WS_EX_APPWINDOW, Window) } TempHiddenWindows.Push(Window) } catch Error as e { ; It can get permission errors for certain windows, such as the Task Manager. ; But it's better to leave some extraneous windows in the list than to throw an error message up ; (especially while some windows are hidden, though I've made an array to delay the messages now.)

      ; Messages.Push(&quot;Error hiding window from the task switcher.`n`n&quot; DescribeWindow(Window) &quot;`n`n&quot; e.Message)
    }
  }
}

} Send "{LAlt Down}" Send "{Blind}{Tab}" ; Tab or Shift+Tab to go in reverse KeyWait "LAlt" ; MakeSplash("Window Switcher", "Alt (physical key) released", 1000) for Window in TempHiddenWindows { ; If WinShow is ever used for a fallback, it should not be called for all windows, and it should be called at the end, so it doesn't slow things down for every window. ; WinShow(Window)

; Don't need to remember WS_EX_TOOLWINDOW state, since we're not matching windows with WS_EX_TOOLWINDOW.
; Restore WS_EX_APPWINDOW, if it was set.
; My first instinct was to allow other styles to change while hidden, as this may avoid problems with some apps,
; but styles may be forced to change as a result of WS_EX_TOOLWINDOW / removing WS_EX_APPWINDOW, I'm not sure.
try {
  ; WinSetExStyle(WinGetExStyle(Window) &amp; ~WS_EX_TOOLWINDOW | (OriginalExStyles[Window] &amp; WS_EX_APPWINDOW), Window)
  WinSetExStyle(OriginalExStyles[Window], Window)
  ; MsgBox(&quot;Would show:`n`n&quot; DescribeWindow(Window), &quot;Window Switcher&quot;)
} catch Error as e {
  ; Delay error messages until after the switcher is closed and all windows are unhidden that can be.
  Messages.Push(&quot;Failed to unhide window from the task switcher.`n`n&quot; DescribeWindow(Window) &quot;`n`n&quot; e.Message)
}

} TempHiddenWindows.Length := 0 ; MakeSplash("Window Switcher", "Closing switcher (triggering logical release of Alt)", 1000) Send "{LAlt Up}" ; This could be earlier, couldn't it? ; MakeSplash("Window Switcher", "Switcher closed.", 1000)

for message in Messages { MsgBox(message, "Window Switcher", 0x10) } }

Switchable(Window) { ; Heuristics determine if a window is in the taskbar ; https://stackoverflow.com/a/2262791 ExStyle := WinGetExStyle(Window) if ExStyle & WS_EX_TOOLWINDOW { return false } if ExStyle & WS_EX_APPWINDOW { return true } Style := WinGetStyle(Window) return !(Style & WS_CHILD)

; Not sure of the specific rules, or how much the priority of the cases matters. ; AI-autocompleted logic is slightly different: ; Style := WinGetStyle(Window) ; ExStyle := WinGetExStyle(Window) ; if Style & WS_CHILD { ; return false ; } ; if ExStyle & WS_EX_APPWINDOW { ; return true ; } ; if ExStyle & WS_EX_TOOLWINDOW { ; return false ; } ; return true }

DescribeWindow(Window) { return "Window Title: " WinGetTitle(Window) "nWindow Class: &quot; WinGetClass(Window) &quot;nProcess Name: " WinGetProcessName(Window) }

;-------------------------------------------------------- ; Win+Tab to switch between windows (TODO: between apps) ;--------------------------------------------------------

; #Tab:: ; { ; Send "{LAlt Down}{Tab}" ; KeyWait "LWin" ; Wait to release left Win key ; Send "{LAlt Up}" ; Close switcher on hotkey release ; } ; return

;-------------------------------------------------------- ; AUTO RELOAD THIS SCRIPT ;-------------------------------------------------------- ~^s:: { if WinActive(A_ScriptName) { MakeSplash("AHK Auto-Reload", "n Reloading &quot; A_ScriptName &quot;n", 500) Reload } } MakeSplash(Title, Text, Duration := 0) { SplashGui := Gui(, Title) SplashGui.Opt("+AlwaysOnTop +Disabled -SysMenu +Owner") ; +Owner avoids a taskbar button. SplashGui.Add("Text", , Text) SplashGui.Show("NoActivate") ; NoActivate avoids deactivating the currently active window. if Duration { Sleep(Duration) SplashGui.Destroy() } return SplashGui }

1j01
  • 202
3

Now, we can use Window Switcher to fully simulate the behavior of macOS switching windows.
By default, use Alt+`(Backtick) to switch between windows of the same app.

ipcjs
  • 310
2

I've implemented a script using AutoHotkey v2 which does what Ubuntu can do out of the box: switch between windows of the current app by pressing Alt+; (which is what the key in the top left corner types by default on my keyboard layout; you will probably want to adjust for yourself).
It counts with multiple presses so works even in cases there are multiple windows of the app, not just two. Feel free to use, PRs for improving welcome :-D

https://github.com/jendakol/autohotkey/blob/main/window-switch-multiple.ahk

Current version:

!;::
{
    static LastRun := 0
    static QuickPress := 0
Now := A_TickCount

if (Now - LastRun &lt; 400)
    QuickPress++
else
    QuickPress := 0

OutputDebug '`n`nNow: ' Now ' LastRun: ' LastRun ' QuickPress: ' QuickPress '`n'



OldClass := WinGetClass(&quot;A&quot;)
ActiveProcessName := WinGetProcessName(&quot;A&quot;)
WinClassCount := WinGetCount(&quot;ahk_exe &quot; ActiveProcessName)
ActiveId := WinGetID(&quot;A&quot;)
OutputDebug 'Current:    ' ActiveId '/' OldClass '/' ActiveProcessName '/' WinGetTitle(&quot;ahk_id&quot; ActiveId) &quot;`n&quot;

if (WinClassCount = 1)
    Return

ToSkip := QuickPress
OutputDebug 'Will skip ' ToSkip ' of ' WinClassCount '`n'

ids := WinGetList(&quot;ahk_exe &quot; ActiveProcessName)
for SiblingId in ids {
    if (WinGetClass(&quot;ahk_id&quot; SiblingId) != OldClass)
        continue

    OutputDebug 'Found:      ' SiblingId '/' WinGetClass(&quot;ahk_id&quot; SiblingId) '/' WinGetProcessName(&quot;ahk_id&quot; SiblingId) '/' WinGetTitle(&quot;ahk_id&quot; SiblingId) '`n'

    if (SiblingId != ActiveId &amp;&amp; ToSkip-- &lt;=0) {
        OutputDebug 'Switch to:  ' SiblingId '/' WinGetClass(&quot;ahk_id&quot; SiblingId) '/' WinGetProcessName(&quot;ahk_id&quot; SiblingId) '/' WinGetTitle(&quot;ahk_id&quot; SiblingId) '`n'
        WinActivate(&quot;ahk_id&quot; SiblingId)
        break
    }
}


LastRun := A_TickCount

}

jenda
  • 121
0

This works perfectly (Autohotkey v2)

; Shift+Capslock toggles between windows of active app (eg. different firefox windows)
+Capslock::
{
    ActiveApp := WinGetProcessName("A")
    WinActivateBottom("ahk_exe " . ActiveApp)
}
Raveren
  • 716
0

For Windows 7/8/10 users:

7+ Taskbar Tweaker is a great tool that enables you to cycle between windows of an app, plus many other great features.

7-Taskbar-Tweaker.png


For Windows 11+ users:

Unfortunately "7+ Taskbar Tweaker" is not working for Windows 11+. But Thanks to Khaled Elsayed Mohamed's answer, you can achieve this ability manually just by creating a DWORD (32-bit) regisrty value with the name LastActiveClick and the value 1 in this path:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced

ABS
  • 11