Jump to content

How to get a list of recently activated windows (Alt-tab order)?


Recommended Posts

I'm creating an alternative to Windows' alt-tab menu, and I'm encountering issues with WinList as it DOES NOT provide the correct order of recently opened or activated windows. While WinList normally respects the z-order, this is not the case when topmost windows are present (mentioned here).

Quote

The system maintains the z-order in a single list. It adds windows to the z-order based on whether they are topmost windows, top-level windows, or child windows. A topmost window overlaps all other non-topmost windows, regardless of whether it is the active or foreground window. A topmost window has the WS_EX_TOPMOST style. All topmost windows appear in the z-order before any non-topmost windows. A child window is grouped with its parent in z-order.

 

Here is an example showing what I mean:

#include <WinAPI.au3>
#include <WindowsConstants.au3>

Run('notepad.exe')
Run('calc.exe')

Local $sNotepadTitle = 'Untitled - Notepad'
Local $sCalculatorTitle = 'Calculator'

WinWait($sNotepadTitle)
WinWait($sCalculatorTitle)


WinSetOnTop($sCalculatorTitle, '', $WINDOWS_ONTOP)


WinActivate($sNotepadTitle)

ConsoleWrite('Active window: ' & WinGetTitle('[ACTIVE]') & @CRLF)
ConsoleWrite('First visible window from WinList: ' & getTopWindow() & @CRLF)


Func getTopWindow() 
    Local $aList = WinList()

    For $i = 1 To $aList[0][0]
        $iExStyle = _WinAPI_GetWindowLong($aList[$i][1], $GWL_EXSTYLE)     ; get the extended style of each Window

        If Not BitAND($iExStyle, $WS_EX_TOOLWINDOW) And _      ; Search for windows without $WS_EX_TOOLWINDOW extended style
                BitAND(WinGetState($aList[$i][1]), $WIN_STATE_VISIBLE) Then             ; And only visible windows
            Return $aList[$i][0]
        EndIf
    Next
EndFunc

Explain: Activating Notepad should precede Calculator in WinList. However, due to $WS_EX_TOPMOST, Calculator takes precedence over any non-$WS_EX_TOPMOST windows in the WinList array.
I've tried _WinAPI_EnumWindows_WinAPI_EnumWindowsTop, both yield the same result as WinList.

 

Are there any other APIs or methods to retrieve a list of recently activated windows, regardless of topmost windows (similar to Windows' alt-tab menu)?

Edited by sylremo
clarify
Link to comment
Share on other sites

I got a script named "378 - List of Alt-tabbable windows.au3" based on code found in the commented links at the end of the script.

#include <ComboConstants.au3>
#include <GuiConstants.au3>
#include <WinAPISysWin.au3>

Opt('MustDeclareVars', 1)

GUICreate("List of Alt-tabbable windows", 390, 250)
Local $idCombo = GUICtrlCreateCombo("", 20, 60, 350, 100, $CBS_DROPDOWNLIST)
Local $idButton = GUICtrlCreateButton("Create list", 150, 150, 85, 20)
GUISetState()

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $idButton
            Local $aWin = WinList()
            Local $bVisible, $bToolWindow, $sComboStrings = ''
            GUICtrlSetData($idCombo, '')
            For $i = 1 To $aWin[0][0]
                $bVisible = BitAND(WinGetState($aWin[$i][1]), 2) ; $WIN_STATE_VISIBLE (2) = Window is visible
                $bToolWindow = BitAND(_WinAPI_GetWindowLong($aWin[$i][1], $GWL_EXSTYLE), $WS_EX_TOOLWINDOW)

                If $bVisible And Not $bToolWindow Then
                    $sComboStrings &= $aWin[$i][0] & '|'
                EndIf
            Next
            GUICtrlSetData($idCombo, $sComboStrings) ; or StringTrimRight($sComboStrings, 1)) to remove last "|"  (same visual effect)
            GUICtrlSendMsg($idCombo, $CB_SHOWDROPDOWN, True, 0) ; wparam : True to show, False to hide
            GUICtrlSendMsg($idCombo, $CB_SETCURSEL, 0, 0)       ; wparam : zero-based index of the string to select
    EndSwitch
WEnd

; https://www.autoitscript.com/forum/topic/90149-solved-get-all-windows/?do=findComment&comment=649609
; https://www.autoitscript.com/forum/topic/90149-solved-get-all-windows/?do=findComment&comment=650710

; Personal (reworked a bit the variable names, get rid of Func GetWindowLong etc...)

; https://www.autoitscript.com/forum/topic/90149-solved-get-all-windows/?do=findComment&comment=648698
; Ascend4nt : "Definitely a neat technique to getting the 'alt-tabbable' windows! Kudos"

Hope it helps :)
Link to comment
Share on other sites

@pixelsearch, thank you for your time.

The thing that I'm trying to achieve is the last activation order when topmost window(s) exist.

The code that you provided is actually the same as mine getTopWindow() without returning only the first one. 

As you can see, if I clicked on "Create list" button on the GUI, the autoit window should be the current activated window, but in this case, Calculator, which is topmost window, still on top of the list instead of the autoit window.


image.png.9c1305cda24700d0294b8f00749da9da.png

 

I still need to get the recently activated window list.

Edited by sylremo
Link to comment
Share on other sites

I do not believe that there is an API that can give you what you want "on the spot".  My guess is that Windows keeps track of a list of last active windows dynamically.  But you could mimic Windows with _WinAPI_RegisterShellHookWindow.  On activation you put the window on top of your list, on closure you remove the window from the list.  I quickly tested it with your script and it seems to work fine.  But you would only get a list of windows after you have started your script.

Of course you could start it at logon and create an IPC to provide list to other programs.  Some work has to be done, but all depends how important it is for you.

Link to comment
Share on other sites

The script below should do it.
It will ignore the Scite Window and the Gui window (just to display a clearer list)
Now the 1st line displayed in the combo box will be the last active window, even if it's not a Topmost one.
#include <ComboConstants.au3>
#include <GuiConstants.au3>
#include <WinAPISysWin.au3>

Opt('MustDeclareVars', 1)

Local $hWin, $hWin_old, $hWin_temp, $hScite = WinGetHandle(@ScriptFullPath)
; ConsoleWrite($hScite & @crlf)

Local $hGUI = GUICreate("List of Alt-tabbable windows (with active 1st)", 390, 250)
Local $idCombo = GUICtrlCreateCombo("", 20, 60, 350, 100, $CBS_DROPDOWNLIST)
Local $idButton = GUICtrlCreateButton("Create list", 150, 150, 85, 20)
GUISetState()

While 1
    $hWin_temp = WinGetHandle("[active]")
    If $hWin_temp <> $hGUI And $hWin_temp <> $hScite Then
        $hWin = $hWin_temp
        If $hWin <> $hWin_old Then
            ConsoleWrite($hWin & "  " & _WinAPI_GetWindowText($hWin) & @crlf)
            $hWin_old = $hWin
        EndIf
    EndIf

    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $idButton
            Local $aWin = WinList()
            Local $bVisible, $bToolWindow, $sComboStrings = ''
            GUICtrlSetData($idCombo, '')
            For $i = 1 To $aWin[0][0]
                $bVisible = BitAND(WinGetState($aWin[$i][1]), 2) ; $WIN_STATE_VISIBLE (2) = Window is visible
                $bToolWindow = BitAND(_WinAPI_GetWindowLong($aWin[$i][1], $GWL_EXSTYLE), $WS_EX_TOOLWINDOW)
                If $bVisible And Not $bToolWindow Then
                    If $aWin[$i][1] <> $hGUI And $aWin[$i][1] <> $hScite Then
                        If $aWin[$i][1] <> $hWin Then
                            $sComboStrings &= $aWin[$i][0] & '|'
                        Else ; Active window at 1st place (even if not topmost)
                            $sComboStrings = $aWin[$i][0] & '|' & $sComboStrings
                        EndIf
                    EndIf
                EndIf
            Next
            GUICtrlSetData($idCombo, $sComboStrings) ; or StringTrimRight($sComboStrings, 1)) to remove last "|"  (same visual effect)
            GUICtrlSendMsg($idCombo, $CB_SHOWDROPDOWN, True, 0) ; wparam : True to show, False to hide
            GUICtrlSendMsg($idCombo, $CB_SETCURSEL, 0, 0)       ; wparam : zero-based index of the string to select
    EndSwitch
WEnd
Link to comment
Share on other sites

No you dont understand -- WinList is useless.  OP does not want z-order as other APIs do.  OP wants the last activated windows, which no API will give.  Plz take a moment to run the script he gave us and then do Alt-tab, you will see it is different than the z-order of WinList. 

Link to comment
Share on other sites

  • sylremo changed the title to How to get a list of recently activated windows (Alt-tab order)?
6 hours ago, Nine said:

But you could mimic Windows with _WinAPI_RegisterShellHookWindow

Hi @Nine,

Thank you for sharing your thoughts on how to keep track of active windows dynamically in Windows. Your suggestion to use _WinAPI_RegisterShellHookWindow seems like a viable option.

Would you be able to provide some more details on how to implement it? It would be greatly appreciated.

Thank you!

Link to comment
Share on other sites

As per MSDN :

Quote
HSHELL_WINDOWDESTROYED A handle to the top-level window being destroyed.

See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registershellhookwindow

Here my simple way to detect windows :

#include <GUIConstants.au3>
#include <APISysConstants.au3>
#include <WinAPISysWin.au3>
#include <Constants.au3>

Global $hForm = GUICreate('')
GUIRegisterMsg(_WinAPI_RegisterWindowMessage('SHELLHOOK'), WM_SHELLHOOK)
_WinAPI_RegisterShellHookWindow($hForm)
OnAutoItExitRegister(OnAutoItExit)

Run('notepad.exe')
Run('calc.exe')

Local $sNotepadTitle = '[CLASS:Notepad]'
Local $sCalculatorTitle = 'Calculatrice'

WinWait($sNotepadTitle)
WinWait($sCalculatorTitle)
WinSetOnTop($sCalculatorTitle, '', $WINDOWS_ONTOP)

WinActivate($sNotepadTitle)
Sleep(1000)

Func WM_SHELLHOOK($hWnd, $iMsg, $wParam, $lParam)
  Switch $wParam
    Case $HSHELL_WINDOWACTIVATED, $HSHELL_WINDOWCREATED, $HSHELL_RUDEAPPACTIVATED
      ConsoleWrite($lParam & "/" & WinGetTitle($lParam) & " has been activated" & @CRLF)
    Case $HSHELL_WINDOWDESTROYED
      ConsoleWrite($lParam & "/" & WinGetTitle($lParam) & " has been closed" & @CRLF)
  EndSwitch
EndFunc   ;==>WM_SHELLHOOK

Func OnAutoItExit()
    _WinAPI_DeregisterShellHookWindow($hForm)
EndFunc   ;==>OnAutoItExit

 

Edited by Nine
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...