4 options to change the current folder in Windows Explorer
Re: 4 options to change the current folder in Windows Explorer
Thanks for the references, jeeswg. I used Navigate2 with an integer parameter (for example 17 = My Computer) but I did not know about object parameters. Interesting.
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explor
Just registered to say THANK YOU!! for all this fundamental research you all have done.
Very valuable, as I''m writing my first AHK (to navigate to folders using Everything, a very reliable and fast file/folder search utility). This thread helps enormously with that
BTW:
Very valuable, as I''m writing my first AHK (to navigate to folders using Everything, a very reliable and fast file/folder search utility). This thread helps enormously with that
BTW:
No (longer?) such problems with AHK 1.1.27.07 @ Win10 1709 (both x64)JnLlnd wrote:Unfortunately, there does not seem to be a solution to this:
This will fail if myPath includes a hash (# as in C:\C#Projects).Code: Select all
For pExp in ComObjCreate("Shell.Application").Windows if (pExp.hwnd = strWinId) try pExp.Navigate(myPath)
Re: 4 options to change the current folder in Windows Explorer
Hi Eureka,
Please read this post and the following:
https://autohotkey.com/boards/viewtopic ... 199#p25199
For me, it still does not work if there is a slash after the hash. My message and example should have been: "This will fail if myPath includes a hash and has a slash after it (as in C:\C#Projects\)."
Please read this post and the following:
https://autohotkey.com/boards/viewtopic ... 199#p25199
For me, it still does not work if there is a slash after the hash. My message and example should have been: "This will fail if myPath includes a hash and has a slash after it (as in C:\C#Projects\)."
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explorer
Ah, I see. Same here ...JnLlnd wrote:Hi Eureka,
Please read this post and the following:
https://autohotkey.com/boards/viewtopic ... 199#p25199
For me, it still does not work if there is a slash after the hash. My message and example should have been: "This will fail if myPath includes a hash and has a slash after it (as in C:\C#Projects\)."
That's probably because this method uses the URL syntax. And a hash/pound/number sign has special meaning in that case.
I tried to replace it with it's URL escape character: %23 (*), but in that case the % was replaced by it's URL escape character (%25).
It should be possible to feed a literal % by escaping it in AHK, but I'm not (yet) experienced enough in AHK to get that working.
(*) Drag the foldername to the address bar of your browser to see the converted filename
Re: 4 options to change the current folder in Windows Explorer
Thanks a lot @jeeswg for your efforts and solutions you get to This really helps a lotjeeswg wrote: ↑13 Jun 2017, 15:54I have what looks to be a working solution for this, which I've also posted here:
windows - Navigate Shell command not working when the path includes an hash - Stack Overflow
https://stackoverflow.com/questions/22868546/navigate-shell-command-not-working-when-the-path-includes-an-hash
Code: Select all
;links: ;Explorer Windows Manipulations - Page 5 - Scripts and Functions - AutoHotkey Community ;https://autohotkey.com/board/topic/19039-explorer-windows-manipulations/page-5#entry297581 ;Navigate2 Method (IWebBrowser2) ;https://msdn.microsoft.com/en-us/library/aa752134(v=vs.85).aspx ;4 options to change the current folder in Windows Explorer - AutoHotkey Community ;https://autohotkey.com/boards/viewtopic.php?f=5&t=526 ;windows - Navigate Shell command not working when the path includes an hash - Stack Overflow ;https://stackoverflow.com/questions/22868546/navigate-shell-command-not-working-when-the-path-includes-an-hash ;an AutoHotkey v1.1 script ;note: will create folder: %A_Desktop%\abc#def\abc#def q:: ;explorer - navigate to folder (tested on Windows 7) WinGet, hWnd, ID, A WinGetClass, vWinClass, % "ahk_id " hWnd if !(vWinClass = "CabinetWClass") && !(vWinClass = "ExploreWClass") return vDir = %A_Desktop%\abc#def\abc#def ;vDir = %A_Desktop%\abc def\abc def if !FileExist(vDir) FileCreateDir, % vDir DllCall("shell32\SHParseDisplayName", WStr,vDir, Ptr,0, PtrP,vPIDL, UInt,0, Ptr,0) for oWin in ComObjCreate("Shell.Application").Windows if (oWin.HWND = hWnd) { if !InStr(vDir, "#") oWin.Navigate(vDir) else { VarSetCapacity(SAFEARRAY, A_PtrSize=8?32:24, 0) NumPut(1, SAFEARRAY, 0, "UShort") ;cDims NumPut(1, SAFEARRAY, 4, "UInt") ;cbElements NumPut(vPIDL, SAFEARRAY, A_PtrSize=8?16:12, "Ptr") ;pvData NumPut(DllCall("shell32\ILGetSize", Ptr,vPIDL, UInt), SAFEARRAY, A_PtrSize=8?24:16, "Int") ;rgsabound[1] oWin.Navigate2(ComObject(0x2011,&SAFEARRAY)) DllCall("shell32\ILFree", Ptr,vPIDL) } break } return
It's working efficiently with paths containing "#" .. Tested on Windows 10 build 18363
I've just packed in a simple function and all is working effectively:
Code: Select all
Explorer_Navigate(vDir, hWnd="") {
; WinGet, hWnd, ID, A
hWnd := (hWnd="") ? WinExist("A") : hWnd
WinGetClass, vWinClass, % "ahk_id " hWnd
if !(vWinClass = "CabinetWClass") && !(vWinClass = "ExploreWClass")
return
; vDir = %A_Desktop%\abc#def\abc#def
;vDir = %A_Desktop%\abc def\abc def
; if !FileExist(vDir)
; FileCreateDir, % vDir
DllCall("shell32\SHParseDisplayName", WStr,vDir, Ptr,0, PtrP,vPIDL, UInt,0, Ptr,0)
for oWin in ComObjCreate("Shell.Application").Windows
if (oWin.HWND = hWnd)
{
if !InStr(vDir, "#")
oWin.Navigate(vDir)
else
{
VarSetCapacity(SAFEARRAY, A_PtrSize=8?32:24, 0)
NumPut(1, SAFEARRAY, 0, "UShort")
NumPut(1, SAFEARRAY, 4, "UShort")
NumPut(vPIDL, SAFEARRAY, A_PtrSize=8?16:12, "Ptr")
NumPut(DllCall("shell32\ILGetSize", Ptr,vPIDL, UInt), SAFEARRAY, A_PtrSize=8?24:16, "Int")
oWin.Navigate2(ComObject(0x2011,&SAFEARRAY))
DllCall("shell32\ILFree", Ptr,vPIDL)
}
break
}
return
}
Re: 4 options to change the current folder in Windows Explorer
Windows 11 version 22H2, codenamed "Moment 1", released on October 18, 2022 (build 22621.675) has this new feature: "New tabbed browsing feature and refreshed layout of the left navigation pane in the File Explorer".
These new tabs will certainly be much appreciated. But they cause an issue with the code of the "Method 4" in the original post of this thread: it always change the folder in the first tab even when another tab is active.
The other methods sending keystrokes with Send (method 2) or using ControlSetText (method 3) are OK. Tey send their command to the active tab. But I think method 4 is largely used and lots of scripts will have to be adjusted. I'll follow up in the next post with some diag info from Win 11 Explorer with tabs.
These new tabs will certainly be much appreciated. But they cause an issue with the code of the "Method 4" in the original post of this thread: it always change the folder in the first tab even when another tab is active.
Code: Select all
Explorer_Navigate(FullPath, hwnd="") { ; by Learning one
hwnd := (hwnd="") ? WinExist("A") : hwnd ; if omitted, use active window
WinGet, ProcessName, ProcessName, % "ahk_id " hwnd
if (ProcessName != "explorer.exe") ; not Windows explorer
return
For pExp in ComObjCreate("Shell.Application").Windows
{
if (pExp.hwnd = hwnd) { ; matching window found
pExp.Navigate("file:///" FullPath)
return
}
}
}
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explorer
I used the following code to retrieve info from the instances of Windows File Explorer.
Here is the result with one instance of Explorer containing three tabs:
Explorer #1
blnIsSpecialFolder = 0
intMinMax = 0
strLocationName = file:///C:/Windows
strLocationURL = file:///C:/Windows
strPosition = 206|23|1200|900
strWindowId = 591696
Explorer #2
blnIsSpecialFolder = 0
intMinMax = 0
strLocationName = file:///C:/Dropbox/AutoHotkey/SandBox
strLocationURL = file:///C:/Dropbox/AutoHotkey/SandBox
strPosition = 206|23|1200|900
strWindowId = 591696
Explorer #3
blnIsSpecialFolder = 1
intMinMax = 0
strLocationName = Ce PC
strLocationURL = ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
strPosition = 206|23|1200|900
strWindowId = 591696
As you can see, ComObjCreate("Shell.Application").Windows return three objects, which is good. But the pExplorer.HWND is the same for the three tabs, which is not good... This cause at least these two issues:
- changing the location of the active tab when it is not the first tab
- retrieving the location of the active tab (this issue is also discussed in this thread on the v2 forum).
But I'm sure a solution exists :-)
Code: Select all
#requires AutoHotkey v1.1
#SingleInstance,Force
#NoEnv
saExplorers := Object()
For pExplorer in ComObjCreate("Shell.Application").Windows
{
strType := ""
try strType := pExplorer.Type ; Gets the type name of the contained document object. "Document HTML" for IE windows. Should be empty for file Explorer windows.
strWindowID := ""
try strWindowID := pExplorer.HWND ; Try to get the handle of the window. Some ghost Explorer in the ComObjCreate may return an empty handle
if !StrLen(strType) ; must be empty
and StrLen(strWindowID) ; must not be empty
{
aaExplorer := Object()
aaExplorer.strPosition := pExplorer.Left . "|" . pExplorer.Top . "|" . pExplorer.Width . "|" . pExplorer.Height
aaExplorer.blnIsSpecialFolder := !StrLen(pExplorer.LocationURL) ; empty for special folders like Recycle bin
if (aaExplorer.blnIsSpecialFolder)
{
aaExplorer.strLocationURL := pExplorer.Document.Folder.Self.Path
aaExplorer.strLocationName := pExplorer.LocationName ; see http://msdn.microsoft.com/en-us/library/aa752084#properties
}
else
{
aaExplorer.strLocationURL := pExplorer.LocationURL
aaExplorer.strLocationName := pExplorer.LocationURL
}
aaExplorer.strWindowId := strWindowID ; not used for Explorer windows, but keep it
WinGet, intMinMax, MinMax, % "ahk_id " . pExplorer.HWND
aaExplorer.intMinMax := intMinMax
saExplorers.Push(aaExplorer) ; I was checking if StrLen(pExplorer.HWND) - any reason?
}
}
for intIndex, oExplorer in saExplorers
str .= "Explorer #" . A_Index . BuildStringObj(oExplorer) . "`n`n"
MsgBox, %str%
ExitApp
BuildStringObj(obj)
{
str := "`n`n"
for strKey, strValue in obj
str .= strKey . " = " . strValue . "`n"
return str
}
Explorer #1
blnIsSpecialFolder = 0
intMinMax = 0
strLocationName = file:///C:/Windows
strLocationURL = file:///C:/Windows
strPosition = 206|23|1200|900
strWindowId = 591696
Explorer #2
blnIsSpecialFolder = 0
intMinMax = 0
strLocationName = file:///C:/Dropbox/AutoHotkey/SandBox
strLocationURL = file:///C:/Dropbox/AutoHotkey/SandBox
strPosition = 206|23|1200|900
strWindowId = 591696
Explorer #3
blnIsSpecialFolder = 1
intMinMax = 0
strLocationName = Ce PC
strLocationURL = ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
strPosition = 206|23|1200|900
strWindowId = 591696
As you can see, ComObjCreate("Shell.Application").Windows return three objects, which is good. But the pExplorer.HWND is the same for the three tabs, which is not good... This cause at least these two issues:
- changing the location of the active tab when it is not the first tab
- retrieving the location of the active tab (this issue is also discussed in this thread on the v2 forum).
But I'm sure a solution exists :-)
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explorer
Lexikos posted a solution to get the active tab. I only converted it to v1:
Code: Select all
GetActiveExplorerTab(hwnd:="") {
if !hwnd
hwnd := WinExist("A")
activeTab := 0
try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd
for w in ComObjCreate("Shell.Application").Windows {
if (w.hwnd != hwnd)
continue
if activeTab {
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
shellBrowser := ComObjQuery(w, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(numGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab)
if (thisTab != activeTab)
continue
}
return w
}
}
^1::
tab := GetActiveExplorerTab()
MsgBox % tab.Document.Folder.Self.Path
Return
Re: 4 options to change the current folder in Windows Explorer
I was trying to do it. I'm glad you did it before. Tested. It works perfectly.
I'll update the script in the OP with this code (not now but soon).
Thank you @ntepa !
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explorer
@JnLlnd I made a small edit. Added ObjRelease.
lexikos wrote:Also note that ComObjQuery returns a pointer value, not a ComObj, in v1. The pointer value can be used directly with NumGet and DllCall, but you must ObjRelease it afterward.
Code: Select all
GetActiveExplorerTab(hwnd:="") {
if !hwnd
hwnd := WinExist("A")
activeTab := 0
try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd
for w in ComObjCreate("Shell.Application").Windows {
if (w.hwnd != hwnd)
continue
if activeTab {
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
shellBrowser := ComObjQuery(w, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(numGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab)
if (thisTab != activeTab)
continue
ObjRelease(shellBrowser)
}
return w
}
}
^1::
tab := GetActiveExplorerTab()
MsgBox % tab.Document.Folder.Self.Path
Return
Re: 4 options to change the current folder in Windows Explorer
I merged the GetActiveExplorerTab() code from ntepa with the existing function Explorer_Navigate() from Learning one into the new function Explorer_Navigate_Tab(). Tested on Win 10 and Win 11 build 22621.755. If there is not issue reported here, I'll add it to this thread OP.
Code: Select all
#Requires AutoHotkey v1.1
#NoEnv
#SingleInstance force
return
^1::
Explorer_Navigate_Tab("C:\Windows")
Return
Explorer_Navigate_Tab(FullPath, hwnd="") {
; originally from Learning one (https://www.autohotkey.com/boards/viewtopic.php?p=4480#p4480)
; adapted by JnLlnd for tabbed browsing new to Windows 11 version 22H2 (build 22621.675)
; with code from ntepa (https://www.autohotkey.com/boards/viewtopic.php?p=488735#p488735)
; also works with previous versions of Windows Explorer
hwnd := (hwnd="") ? WinExist("A") : hwnd ; if omitted, use active window
WinGet, ProcessName, ProcessName, % "ahk_id " hwnd
if (ProcessName != "explorer.exe") ; not Windows explorer
return
For pExp in ComObjCreate("Shell.Application").Windows
{
if (pExp.hwnd = hwnd) { ; matching window found
activeTab := 0
try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd
if activeTab {
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
shellBrowser := ComObjQuery(pExp, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(numGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab)
if (thisTab != activeTab) ; matching active tab
continue
ObjRelease(shellBrowser)
}
pExp.Navigate("file:///" FullPath)
return
}
}
}
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Re: 4 options to change the current folder in Windows Explorer
I've updated the OP with code for Win 11 Explorer with tabs (see method 5).
viewtopic.php?f=76&t=526
viewtopic.php?f=76&t=526
Author of freeware Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
Now working on Quick Clipboard Editor
The Automator's Courses on AutoHotkey
-
- Posts: 8
- Joined: 21 Sep 2023, 02:08
Re: 4 options to change the current folder in Windows Explorer
Thank you for the code, it really helped me a lot to open frequently used folders easily when using Explorer.
Can you please add a function to make it possible to switch to the target tab when it already exists instead of opening a new one.
I am sorry for making such a request, without basic knowledge of AHK, I am not able to implement what I need on this code.
Can you please add a function to make it possible to switch to the target tab when it already exists instead of opening a new one.
I am sorry for making such a request, without basic knowledge of AHK, I am not able to implement what I need on this code.
JnLlnd wrote: ↑03 Nov 2022, 12:35I merged the GetActiveExplorerTab() code from ntepa with the existing function Explorer_Navigate() from Learning one into the new function Explorer_Navigate_Tab(). Tested on Win 10 and Win 11 build 22621.755. If there is not issue reported here, I'll add it to this thread OP.
Code: Select all
#Requires AutoHotkey v1.1 #NoEnv #SingleInstance force return ^1:: Explorer_Navigate_Tab("C:\Windows") Return Explorer_Navigate_Tab(FullPath, hwnd="") { ; originally from Learning one (https://www.autohotkey.com/boards/viewtopic.php?p=4480#p4480) ; adapted by JnLlnd for tabbed browsing new to Windows 11 version 22H2 (build 22621.675) ; with code from ntepa (https://www.autohotkey.com/boards/viewtopic.php?p=488735#p488735) ; also works with previous versions of Windows Explorer hwnd := (hwnd="") ? WinExist("A") : hwnd ; if omitted, use active window WinGet, ProcessName, ProcessName, % "ahk_id " hwnd if (ProcessName != "explorer.exe") ; not Windows explorer return For pExp in ComObjCreate("Shell.Application").Windows { if (pExp.hwnd = hwnd) { ; matching window found activeTab := 0 try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd if activeTab { static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}" shellBrowser := ComObjQuery(pExp, IID_IShellBrowser, IID_IShellBrowser) DllCall(NumGet(numGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab) if (thisTab != activeTab) ; matching active tab continue ObjRelease(shellBrowser) } pExp.Navigate("file:///" FullPath) return } } }
Re: 4 options to change the current folder in Windows Explorer
I have a function to switch to a tab if it exists. It requires UIA library to be included.windfancy3 wrote: Can you please add a function to make it possible to switch to the target tab when it already exists instead of opening a new one.
The script is for v2 though. I wasn't able to convert it to v1:
Code: Select all
#Requires AutoHotkey v2.0
#Include <UIA>
^1::{
ExplorerFindTab("C:\Windows")
}
; looks for an existing tab with the path.
ExplorerFindTab(pathToFind) {
shellWindows := ComObject("Shell.Application").Windows
Found := 0
for w in shellWindows {
try ctrl := ControlGetHwnd("Windows.UI.Composition.DesktopWindowContentBridge1", w)
catch
continue
Path := w.Document.Folder.Self.Path
if (pathToFind = Path) {
TabName := w.Document.Folder.Title
topPane := UIA.ElementFromHandle(ctrl)
tabEl := topPane.FindElement({T:"TabItem", N:TabName})
WinActivate(w)
tabEl.Click()
Found := 1
break
}
}
if !found
Run("explorer.exe " pathToFind)
}
-
- Posts: 8
- Joined: 21 Sep 2023, 02:08
Re: 4 options to change the current folder in Windows Explorer
Thank you very much! But I'm getting the error when the C:\Windows tab already exists , and when it doesn't it opens from a new window. I'm not sure if this has anything to do with the way I imported UIA, but I changed the import code to #include UIA.ahk, which I downloaded from https://github.com/Descolada/UIA-v2.
The specific error message is
The script is for v2 though. I wasn't able to convert it to v1:
[/quote]
The specific error message is
Code: Select all
Error: An element matching the condition was not found
020: TabName := w.Document.
021: topPane := UIA.ElementFromHandle(ctrl)
▶ 022: tabEl := topPane.FindElement({T: "TabItem", N:TabName})[quote=ntepa post_id=540745 time=1695671345 user_id=149849]
I have a function to switch to a tab if it exists. It requires UIA library to be included.windfancy3 wrote: Can you please add a function to make it possible to switch to the target tab when it already exists instead of opening a new one.
The script is for v2 though. I wasn't able to convert it to v1:
Code: Select all
#Requires AutoHotkey v2.0
#Include <UIA>
^1::{
ExplorerFindTab("C:\Windows")
}
; looks for an existing tab with the path.
ExplorerFindTab(pathToFind) {
shellWindows := ComObject("Shell.Application").Windows
Found := 0
for w in shellWindows {
try ctrl := ControlGetHwnd("Windows.UI.Composition.DesktopWindowContentBridge1", w)
catch
continue
Path := w.Document.Folder.Self.Path
if (pathToFind = Path) {
TabName := w.Document.Folder.Title
topPane := UIA.ElementFromHandle(ctrl)
tabEl := topPane.FindElement({T:"TabItem", N:TabName})
WinActivate(w)
tabEl.Click()
Found := 1
break
}
}
if !found
Run("explorer.exe " pathToFind)
}
Re: 4 options to change the current folder in Windows Explorer
Did you copy it correctly? line 20 is cut off in your error message.
Re: 4 options to change the current folder in Windows Explorer
I wanted to leave the following code to help someone else in the future.
It is a v1 script that does not use UIA, but will:
Also I know it could use a lot of polish, but I am limited to the IDE I have at work
Thanks for the core work that others had done, and I am sorry if I forgot to credit anyone.
And the support files
SettingsClass.ahk
getSupportFilename.ahk
It is a v1 script that does not use UIA, but will:
- monitor and record open tabs
- open previously used tabs
- open saved favorites tabs
- activate an already open tab (if you try to open it)
- really only works if 1 FE window is open (gets confused with 2+ FE windows)
Also I know it could use a lot of polish, but I am limited to the IDE I have at work
Thanks for the core work that others had done, and I am sorry if I forgot to credit anyone.
Code: Select all
#Requires AutoHotkey v1.1.33
#NoEnv
#SingleInstance Force
; #Persistent
; #InstallKeybdHook ; needed for $ HotKey modifier (no trigger self) and others (e.g, Inactivity)
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%
/*
;==============================================================================
;========== What This Script Does ============================================
;==============================================================================
; Script that monitors what Tabs are open in File Explorer in Win 11
; NOTE: Only works if 1 FE window is open (not 2+ windows).
;==============================================================================
*/
; =========================================
; ===== Includes
; =========================================
;BeginRegion
#Include %A_ScriptDir%\Library\SettingsClass.ahk
#Include %A_ScriptDir%\Library\getSupportFilename.ahk
;EndRegion
;==============================================================================
;========== Pre-Main Script ==================================================
;==============================================================================
------------------------------------------------------------------------
; Files to include during a compile
;BeginRegion
imageDir := A_ScriptDir "\Script_Images"
IconName := getSupportFilename("ico") ; use the older simple filename version of getSupportFilename("ico")
FileInstall, .\Script_Images\Icons\File_Explorer_Manager.ico, %imageDir%\Icons\%IconName%
if(A_IsCompiled)
{
IconName = %imageDir%\Icons\%IconName%
}
Else
{
IconName := getSupportFilename2("ico") ; now use the updated version of getSupportFilename2("ico")
}
;EndRegion
; ------------------------------------------------------------------------
; setup Settings variable
;BeginRegion
settingsArray := []
; new SettingsProperty(propertyName, iniName="", defaultValue="", isWriteOnClose=false, isArray=false, arraySplitChar="", isBoolean=false)
settingsArray.push( new SettingsProperty("Save_Every_X_Minutes", , 2, false) )
settingsArray.push( new SettingsProperty("TabList_OnClose", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_Favourites", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_1", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_2", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_3", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_4", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_5", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_Preserved", , "", false, true, "|") )
global Settings := new SettingsClass(settingsArray)
;EndRegion
; ------------------------------------------------------------------------
; Set up SystemTray Menu
;BeginRegion
FileGetTime, ScriptLastModified, %A_ScriptFullPath%, M
FormatTime, ScriptLastModifiedX, %ScriptLastModified%
TrayTipString =
( LTrim
---- %A_ScriptName% ----
Script last modified:
%ScriptLastModifiedX%
)
Menu, Tray, Tip, %TrayTipString% ; Create system tray Mouse-over ToolTip
IfExist %IconName%
{
Menu, Tray, Icon, %IconName%
}
Menu, tray, NoStandard ; Remove all standard menu items
GoSub Menu_Create_SubMenu_Favourites
Menu, tray, add, Favourite tabs, :SubMenu_FavouriteTabsMenuItems
Menu, Tray, add ; Creates a separator line.
GoSub Menu_Create_SubMenu_OpenTabs
Menu, tray, add, Open tabs, :SubMenu_OpenTabsMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, Tray, add, Open all tabs that were open when Explorer was closed , TabList_Open_Last
Menu, tray, default, Open all tabs that were open when Explorer was closed
SubMenuNumber := 0
GoSub Menu_Create_SubMenu_PriorTabs
Menu, Tray, add, Preserved tab list, :SubMenu_PriorTabsMenuItems_0
SubMenuNumber := 1
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 1st most recent tab list, :SubMenu_PriorTabsMenuItems_1
SubMenuNumber := 2
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 2nd most recent tab list, :SubMenu_PriorTabsMenuItems_2
SubMenuNumber := 3
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 3rd most recent tab list, :SubMenu_PriorTabsMenuItems_3
SubMenuNumber := 4
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 4th most recent tab list, :SubMenu_PriorTabsMenuItems_4
SubMenuNumber := 5
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 5th most recent tab list, :SubMenu_PriorTabsMenuItems_5
Menu, tray, add, Prior tab lists, :SubMenu_PriorTabListsMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, SubMenu_StandardMenuItems, Standard
Menu, tray, add, Standard menu, :SubMenu_StandardMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, Tray, add, Open INI file, Menu_OpenINI
; GoSub Menu_Create_SubMenu_PriorTabList
;Menu, tray, add, Test, :SubMenu_PriorTabs1MenuItems
; Menu, Tray, add, About..., TabList_Open
;EndRegion
; ------------------------------------------------------------------------
; Setup the Log file
; global Log := createLogInstance()
; Log.isLogClassTurnedOff := true
; ------------------------------------------------------------------------
; Setup the ability to receive messages from other programs/scripts
;BeginRegion
RegisterShellHook() ; if FE is NOT running or for when it closes
;EndRegion
; When the script ends, perform this function
; OnExit("ExitFunction")
;EndRegion
;==============================================================================
;========== Main Script ======================================================
;==============================================================================
;BeginRegion
Settings.Update_Tab_Menu_Every_X_Minutes := 0.5
if ( getFileExplorerHWND() )
{ ; if File Explorer is already running
; update periodically
timerPeriod := Settings.Save_Every_X_Minutes * 60 * 1000
SetTimer, TabList_Update, %timerPeriod%
timerPeriod2 := Settings.Update_Tab_Menu_Every_X_Minutes * 60 * 1000
SetTimer, Menu_Create_SubMenu_OpenTabs, %timerPeriod2%
}
return
;EndRegion
; =========================================
; ===== Menu Subroutines / Labels
; =========================================
;BeginRegion
TabList_Update:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.TabList_1
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
Settings.TabList_5 := Settings.TabList_4
Settings.TabList_4 := Settings.TabList_3
Settings.TabList_3 := Settings.TabList_2
Settings.TabList_2 := Settings.TabList_1
Settings.TabList_1 := newPathArray
Settings.writeINIFile()
}
; MsgBox % arrayToString(newPathArray) . "`n`n`n" . arrayToString(oldPathArray)
}
return
TabList_Save_Preserved:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.TabList_Preserved
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
Settings.TabList_Preserved := newPathArray
Settings.writeINIFile()
; rederaw the menu
SubMenuNumber := 0
GoSub Menu_Create_SubMenu_PriorTabs
}
}
return
TabList_Open_Last:
reopenTabArray(Settings.TabList_OnClose)
return
Menu_OpenINI:
{ ; opens an INI file for editing.
IniName := Settings.iniFile
IfExist %IniName%
{
Run, open %IniName%
}
}
return
Menu_Create_SubMenu_PriorTabs:
{
; SubMenuNumber needs to be defined prior to entering the subroutine (valid values are 0-5, 0 is the Preserved tab list)
SubMenuName := "SubMenu_PriorTabsMenuItems_" . SubMenuNumber
PriorTabsArray := getTheRightTabListArray(SubMenuNumber)
Menu, %SubMenuName%, add
Menu, %SubMenuName%, DeleteAll
Menu, %SubMenuName%, add, Open All, Menu_Open_PriorTabsArray
Menu, %SubMenuName%, add, ; Separator
if (PriorTabsArray.Length() < 1)
{
Menu, %SubMenuName%, add, No Tabs in this list yet, TabList_Open_Last ; just a dummy target label
Menu, %SubMenuName%, disable, No Tabs in this list yet
}
else
{
for index, value in PriorTabsArray
{
; the Tab_Open_Favourite subroutine works just fine here
Menu, %SubMenuName%, add, %value% , Tab_Open_Favourite
}
}
if(SubMenuNumber == 0)
{
Menu, %SubMenuName%, add ; Creates a separator line.
Menu, %SubMenuName%, add, Set Current tab list as the Preserved tab list, TabList_Save_Preserved
}
}
return
Menu_Open_PriorTabsArray:
{
SubMenuNumber := SubStr(A_ThisMenu , 0, 1)
PriorTabsArray := getTheRightTabListArray(SubMenuNumber)
reopenTabArray(PriorTabsArray)
}
return
Menu_Create_SubMenu_Favourites:
{
Menu, SubMenu_FavouriteTabsMenuItems, add
Menu, SubMenu_FavouriteTabsMenuItems, DeleteAll
Menu, SubMenu_FavouriteTabsMenuItems, add, Add Active Tab as Favourite tab (Ctrl+Shift+F), Tab_Save_Favourite
Menu, SubMenu_FavouriteTabsMenuItems, add,
if (Settings.TabList_Favourites.Length() < 1)
{
Menu, SubMenu_FavouriteTabsMenuItems, add, No Favourites yet, TabList_Open_Last ; just a dummy target label
Menu, SubMenu_FavouriteTabsMenuItems, disable, No Favourites yet
}
else
{
for index, value in Settings.TabList_Favourites
{
; BoundMenuFunction := Func("Explorer_Navigate_Tab").Bind(value)
Menu, SubMenu_FavouriteTabsMenuItems, add, %value% , Tab_Open_Favourite
}
}
}
return
Tab_Save_Favourite:
{
activePath := GetActiveExplorerTab()
; msgbox, % activePath
wasAddedToArray := false
if (Settings.TabList_Favourites.Length() < 1)
{
Settings.TabList_Favourites := [activePath]
wasAddedToArray := true
; msgbox, "nothing"
}
else
{
; msgbox, "something"
isInArray := false
for index, value in Settings.TabList_Favourites
{
if(value == activePath)
{
isInArray := true
}
}
if (!isInArray)
{
; msgbox, "not in array"
wasAddedToArray := true
Settings.TabList_Favourites.push(activePath)
}
}
if (wasAddedToArray)
{
; msgbox, "writing"
Settings.writeINIFile()
GoSub Menu_Create_SubMenu_Favourites
; notify the user of a change in status
TrayTip, File Explorer Tab Manager, New Favourite folder saved.`n%activePath%,, 16
SetTimer, HideTrayTip, -4000
}
}
return
Tab_Open_Favourite:
reopenTabArray([A_ThisMenuItem])
return
Menu_Create_SubMenu_OpenTabs:
{
Menu, SubMenu_OpenTabsMenuItems, add
Menu, SubMenu_OpenTabsMenuItems, DeleteAll
Menu, SubMenu_OpenTabsMenuItems, add, Set Current tab list as the Preserved tab list, TabList_Save_Preserved
Menu, SubMenu_OpenTabsMenuItems, add ; Creates a separator line.
openTabsArray := getAllTabLocationsFromFileExplorer()
Settings.OpenTabs := openTabsArray
if (openTabsArray.Length() < 1)
{
Menu, SubMenu_OpenTabsMenuItems, add, No Open Tabs, TabList_Open_Last ; just a dummy target label
Menu, SubMenu_OpenTabsMenuItems, disable, No Open Tabs
}
else
{
for index, value in openTabsArray
{
Menu, SubMenu_OpenTabsMenuItems, add, %value% , Menu_ActivateAnOpenTab
}
}
}
return
Timer_Create_SubMenu_OpenTabs:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.OpenTabs
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
GoSub Menu_Create_SubMenu_OpenTabs
}
}
return
Menu_ActivateAnOpenTab:
activateAnOpenTab(A_ThisMenuItem)
return
;EndRegion
; =========================================
; ===== Functions
; =========================================
;BeginRegion
ShellMessage( wParam, lParam )
{
static WindowHandle
if ( wParam = (HSHELL_WINDOWCREATED:=1) )
{
; WinGet, WindowProcessName, ProcessName, ahk_id %lParam%
if ( lParam = getFileExplorerHWND() )
{
WindowHandle := lParam
OnExplorerOpen(lParam)
}
}
else if ( wParam = (HSHELL_WINDOWDESTROYED:=2) )
{
if ( lParam = WindowHandle )
{
OnExplorerExit(lParam)
}
}
return
}
RegisterShellHook()
{
Gui, +LastFound
ScriptHandle := WinExist()
DllCall( "RegisterShellHookWindow", "UInt", ScriptHandle )
ShellHookMessageIdentifier := DllCall( "RegisterWindowMessage", "Str", "SHELLHOOK" )
OnMessage( ShellHookMessageIdentifier, "ShellMessage" )
return
}
OnExplorerOpen(winHWHD)
{
; notify the user of a change in status
TrayTip, File Explorer Tab Manager, The File Explorer app has opened.`nThe helper program will start to monitor the open tabs.
SetTimer, HideTrayTip, -3000
; update periodically
timerPeriod := Settings.Save_Every_X_Minutes * 60 * 1000
SetTimer, TabList_Update, %timerPeriod%
timerPeriod2 := Settings.Update_Tab_Menu_Every_X_Minutes * 60 * 1000
SetTimer, Menu_Create_SubMenu_OpenTabs, %timerPeriod2%
return winHWHD
}
OnExplorerExit(winHWHD)
{
; save the last open tabs
if(Settings.TabList_1.Length() >= 1)
{
Settings.TabList_OnClose := Settings.TabList_1
}
else
{
Settings.TabList_OnClose := Settings.TabList_2
}
Settings.writeINIFile()
if (getFileExplorerHWND())
{ ; only if there are no more open FE windows
; turn off TabList updates
SetTimer, TabList_Update, Off
SetTimer, Menu_Create_SubMenu_OpenTabs, Off
; notify the user of a change in status
tmpStr := "The File Explorer app has exited." . "`n"
tmpStr .= "The helper program will no longer monitor the open tabs."
TrayTip, File Explorer Tab Manager, %tmpStr%
SetTimer, HideTrayTip, -3000
}
else
{ ; if another FE window is open (not that this program handles multiple FE windows well at all)
; notify the user of a change in status
tmpStr := "A File Explorer window has closed." . "`n"
tmpStr .= "But, at least one File Explorer window is still open." . "`n"
tmpStr .= "The helper program will continue to monitor the open tabs."
TrayTip, File Explorer Tab Manager, %tmpStr%
SetTimer, HideTrayTip, -3000
}
return
}
HideTrayTip()
{ ; from https://www.autohotkey.com/docs/v1/lib/TrayTip.htm
TrayTip ; Attempt to hide it the normal way.
if SubStr(A_OSVersion,1,3) = "10." {
Menu Tray, NoIcon
Sleep 200 ; It may be necessary to adjust this sleep.
Menu Tray, Icon
}
}
; =========================================
activateAnOpenTab(targetFullPath)
{
hwnd := openFileExplorer()
; hwnd := getFileExplorerHWND()
; WinActivate, ahk_id %hwnd%
if ( isPathInFileExplorer(targetFullPath, hwnd) )
{
; OK this is painful but the best I can do. Tab through the open tabs until you hit the desired one
openTabsArray := getAllTabLocationsFromFileExplorer() ; I need to know how many tabs are open
Loop, % openTabsArray.Length()
{
; move to next tab
Send, ^{Tab} ; move to newxt tab (cntl+Tab)
; Note 1: This hotkey works in English , don't know for other localizations
; Note 2: AFAIK, this hotkey does nothing in earlier versions of Windows Explorer but it would be safer to check the version before sending it
Sleep, 100 ; for safety
canidateFullPath := GetActiveExplorerTab(hwnd)
tmpStr := "A_Index := " . A_Index . "`n"
tmpStr .= "targetFullPath := " . targetFullPath . "`n"
tmpStr .= "canidateFullPath := " . canidateFullPath . "`n"
; msgbox, % tmpStr
if (canidateFullPath == targetFullPath)
{
return 0
}
}
}
; if we can't find the Tab open it anew
reopenTabArray([targetFullPath])
return 1
}
reopenTabArray(pathArray)
{
hwnd := openFileExplorer()
; hwnd := getFileExplorerHWND()
; WinActivate, ahk_id %hwnd%
for index, pathCurrent in pathArray
{
; MsgBox, % pathCurrent
if ! isPathInFileExplorer(pathCurrent)
{
WinActivate, ahk_id %hwnd%
Explorer_Navigate_New_Tab(pathCurrent)
}
}
; update the menu
GoSub Menu_Create_SubMenu_OpenTabs
return
}
getTheRightTabListArray(whichNumber)
{
switch whichNumber
{
case 0:
tmpArr := Settings.TabList_Preserved
case 1:
tmpArr := Settings.TabList_1
case 2:
tmpArr := Settings.TabList_2
case 3:
tmpArr := Settings.TabList_3
case 4:
tmpArr := Settings.TabList_4
case 5:
tmpArr := Settings.TabList_5
default:
tmpArr := Settings.TabList_1
}
return tmpArr
}
getAllTabLocationsFromFileExplorer()
{ ; selects the FileExplorer window(s) and lists all open tabs
tabPaths := []
; https://www.autohotkey.com/boards/viewtopic.php?p=28761&sid=bf16c7462b523f4f0944d69383ea9a0e#p28761
For Window in ComObjCreate("Shell.Application").Windows ; see http://msdn.microsoft.com/en-us/library/windows/desktop/aa752084(v=vs.85).aspx
{
if (Folder := Window.Document.Folder.Self) ; && Window.Visible
{
tabPaths.Push(Folder.Path)
; MsgBox, 0, Shell Windows
; , % ++Count . "`n"
; . "Window.HWND: !" . Window.HWND . "!`n"
; . "Window.Visible: !" . Window.Visible . "!`n"
; . "Folder.Path: !" . Folder.Path . "!`n" ; <--- this is what you want
; . "Folder.Name: !" . Folder.Name . "!`n"
; . "Folder.Type: !" . Folder.Type . "!"
}
}
return tabPaths
}
isPathInFileExplorer(targetFullPath, targetHWND="")
{
_targetHWND := targetHWND ; create a stable version that ignores changes we make in the loop
tabNumber := 0
For Window in ComObjCreate("Shell.Application").Windows ; see http://msdn.microsoft.com/en-us/library/windows/desktop/aa752084(v=vs.85).aspx
{
if (Folder := Window.Document.Folder.Self) ; && Window.Visible
{
tabNumber := tabNumber + 1
; MsgBox, 0, Shell Windows
; , % ++Count . "`n"
; . "Window.HWND: !" . Window.HWND . "!`n"
; . "Window.Visible: !" . Window.Visible . "!`n"
; . "Folder.Path: !" . Folder.Path . "!`n" ; <--- this is what you want
; . "Folder.Name: !" . Folder.Name . "!`n"
; . "Folder.Type: !" . Folder.Type . "!"
canidateFullPath := Folder.Path
canidateHWND := Window.HWND
if (_targetHWND == "")
targetHWND := canidateHWND
if (canidateFullPath == targetFullPath) && (canidateHWND == targetHWND)
return tabNumber
}
}
return 0
}
Explorer_Navigate_New_Tab(FullPath, hwnd="")
{ ; opens a new tab and then sets that tab's path to FullPath
; error checking / hwnd == "" try to make Active Window
hwnd := (hwnd="") ? getFileExplorerHWND("") : hwnd ; if omitted, use active window
if !(getFileExplorerHWND(hwnd))
{ ; exit if no Explorer window
ErrorLevel := 1
return 0
}
WinWaitActive, ahk_id %hwnd%
; Windows Explorer is the active window
Send, ^t ; add a new tab
; Note 1: This hotkey works in English and French, don't know for other localizations
; Note 2: AFAIK, this hotkey does nothing in earlier versions of Windows Explorer but
; it would be safer to check the version before sending it
Sleep, 100 ; for safety
; the new tab is the active tab
return Explorer_Navigate_Tab(FullPath, hwnd)
}
Explorer_Navigate_Tab(FullPath, hwnd="")
{ ; changes the active tab to FullPath (over-writing the existing value)
; see https://www.autohotkey.com/boards/viewtopic.php?p=489575#p489575
; originally from Learning one (https://www.autohotkey.com/boards/viewtopic.php?p=4480#p4480)
; adapted by JnLlnd for tabbed browsing new to Windows 11 version 22H2 (build 22621.675)
; with code from ntepa (https://www.autohotkey.com/boards/viewtopic.php?p=488735#p488735)
; also works with previous versions of Windows Explorer
; error checking / hwnd == "" try to make Active Window
hwnd := (hwnd="") ? getFileExplorerHWND("") : hwnd ; if omitted, use active window
if !(getFileExplorerHWND(hwnd))
{ ; exit if no Explorer window
ErrorLevel := 1
return 0
}
; msgbox, % "Explorer_Navigate_Tab(" . FullPath . ", " . hwnd . ")"
For pExp in ComObjCreate("Shell.Application").Windows
{
if (pExp.hwnd = hwnd)
{ ; matching window found
activeTab := 0
try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd
if activeTab {
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
shellBrowser := ComObjQuery(pExp, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(NumGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab)
tmpStr := "HWND:`t" . hwnd . "`n"
tmpStr .= "thisTab:`t" . thisTab . "`n"
tmpStr .= "activeTab:`t" . activeTab . "`n"
tmpStr .= "Loc:`t" . pExp.LocationURL . "`n"
tmpStr .= "Loc2:`t" . GetFullPathName(pExp.LocationURL) . "`n"
; msgbox, % tmpStr
if (thisTab != activeTab) ; matching active tab
continue
ObjRelease(shellBrowser)
}
pExp.Navigate("file:///" FullPath)
return 1
}
}
return 0 ; window not found
}
openFileExplorer()
{ ; if a window is already open does nothing (but activates the window)
hwnd := getFileExplorerHWND()
if( hwnd == 0)
{ ; no window open
Run, Explorer.exe /n`,/e`
Sleep, 1000
hwnd := getFileExplorerHWND()
}
WinActivate, ahk_id %hwnd%
return hwnd
}
getFileExplorerHWND(hwnd="")
{ ; get a File Explorer window OR error check that HWND is a File Explorer window
if (hwnd == "")
{ ; get a File Explorer window (preference for the Active Window)
hwnd := WinExist("A")
if ( getFileExplorerHWND(hwnd) )
{ ; see if Active Window is a file explorer window
ErrorLevel := 0
return hwnd
}
else if WinExist("ahk_Class CabinetWClass")
{ ; now grab any File Explorer window (explorer.exe is the Desktop process too. So you cannot check ahk_exe)
hwnd := WinExist("ahk_Class CabinetWClass")
ErrorLevel := 0
return hwnd
}
else
{ ; fail
ErrorLevel := 1
return 0
}
}
else
{ ; perform error checking
WinGet, ProcessName, ProcessName, % "ahk_id " hwnd
WinGetClass, ClassName, % "ahk_id " hwnd
if ((ProcessName == "explorer.exe") AND (ClassName == "CabinetWClass") )
{
ErrorLevel := 0
return hwnd
}
else
{
ErrorLevel := 1
return 0
}
}
}
GetFullPathName(path) {
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
return buf
}
GetActiveExplorerTab(hwnd:="")
{ ; https://www.autohotkey.com/boards/viewtopic.php?p=544495#p544495 added case where hwnd==""
; does both File Explorer & IE
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
if(hwnd == "")
{
hwnd := getFileExplorerHWND()
}
; msgbox, % "HWND=" . hwnd
activeTab := 0, hwnd := hwnd ? hwnd : WinExist("A")
try
ControlGet, activeTab, Hwnd,, ShellTabWindowClass1, ahk_id %hwnd% ; File Explorer (Windows 11)
catch
try
ControlGet, activeTab, Hwnd,, TabWindowClass1, ahk_id %hwnd% ; IE
for w in ComObjCreate("Shell.Application").Windows
{
; msgbox, % "looking for w=" . w.hwnd . "==" . hwnd
if (w.hwnd != hwnd)
continue
if (activeTab)
{ ; The window has tabs, so make sure this is the right one.
shellBrowser := ComObjQuery(w, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(NumGet(shellBrowser+0), 3*A_PtrSize), "UPtr",shellBrowser, "UIntP",thisTab:=0)
if (thisTab != activeTab)
continue
}
; return w ; you can just return the window object
tab := w
switch ComObjType(tab.Document, "Class")
{
case "ShellFolderView":
; msgbox, % tab.Document.Folder.Self.Path
return tab.Document.Folder.Self.Path
default: ;case "HTMLDocument":
return tab.LocationURL
}
}
}
arrayToString(array, arraySplitChar="|")
{
tmpVal := ""
For Index, Value In array
tmpVal .= arraySplitChar . Value ; Remove leading delimiters when the loop is done
tmpVal := LTrim(tmpVal, arraySplitChar) ; Remove leading delimiters (,)
return tmpVal
}
;EndRegion
; =========================================
; ===== HotKeys
; =========================================
;BeginRegion
; -------- Hotkey Definitions Reminder ----------
; # - Win
; ! - Alt
; ^ - Control
; + - Shift
; ~ - pass key command to OS
; example -- $>!v:: ; $ (no trigger self) > (right key of a pair) ---- Alt (!) + V
^+F:: ; Ctrl+Shft+F
GoSub Tab_Save_Favourite
return
;EndRegion
; =========================================
; ===== Classes
; =========================================
;BeginRegion
;EndRegion
/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;
; v0.9 2024-01-05
; * First proof of concept
;
; v1.0 2024-01-07
; * working functionality
;
; v1.1 2024-01-23
; * added a submenu to list all open tabs
; * added function to active an open tab
; * fixed the bug where OnClose was empty
; * opening a tab now starts FE if it isn't already running
;
; v1.2 2024-01-28
; * added a submenus for each prior tab list (including Preserved)
; * closing an FEno longer stops all monitoring, checks if another FE is open
;
;==============================================================================
*/
And the support files
SettingsClass.ahk
Code: Select all
; =========================================
; ===== Classes
; =========================================
;BeginRegion
class SettingsClass
{
__New(aArrayOfProperties, aIniFile="", aSectionName="")
{
; Input variables
; aArrayOfProperties -> an array of objects or associative arrays with the following keys: propertyName, iniName, defaultValue, isWriteOnClose (class SettingsProperty, at the end of this file). Properties are dynamically created from this array.
; aIniFile -> the INI file to read/write (default is A_Scriptname.INI)
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; dynamically create the properties
; this.settingsConfigutations := {}
Loop, % aArrayOfProperties.Length()
{
currentSetting := aArrayOfProperties[A_Index]
propertyName := currentSetting.propertyName
this[propertyName] := currentSetting.defaultValue
this.settingsConfigurations[propertyName] := currentSetting
; tmpStr := "propertyName=" propertyName
; msgbox, % tmpStr
}
; set the non-dynamic/universal properties
if(aIniFile="")
{
aIniFile := this.getSupportFilename("ini")
}
this.iniFile := aIniFile
if(aSectionName="")
{
this.SectionName := A_ComputerName . "-" . A_UserName
}
else
{
this.SectionName := aSectionName
}
; load the values of the settings from the INI file
this.readINIFile()
return this
}
; Properties
; ---------------------------------------------
; Properties are dynamically created from __New(aArrayOfProperties)
; the few that are universally defined in all version of the Class
; this.iniFile -> the INI file name to be read/written
; this.SectionName -> the section in the INI to read/write to
; Properties hidden from the user
; --------------------------------
; this.settingsConfigurations[propertyName] -> a version of aArrayOfProperties keyed by the propertyName. Used in the read/write methods.
; Methods (of suitable importance)
; ---------------------------------------------
readINIFile(aIniFile="")
{ ; get the INI variables from the file (various default values)
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
if(aIniFile="")
{
aIniFile := this.iniFile
}
retValF := FileExist(aIniFile) ; indicate if the INI file already existed (and hopefully the default values where NOT used)
IniRead, OutputVarSectionNames, %aIniFile%
secVar := this.SectionName
retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
retVal := (retValF AND retValS)
if (!retVal) ; write an INI file if we don't have one with the needed Section
{
this.writeDefaultINIFile(aIniFile)
}
for propertyName, propertyConfig in this.settingsConfigurations
{
; tmpStr := "propertyName = " propertyName "`n"
; tmpStr .= "propertyConfig.iniName = " propertyConfig.iniName "`n"
; tmpStr .= "propertyConfig.defaultValue = " propertyConfig.defaultValue "`n"
; tmpStr .= "propertyConfig.isArray = " propertyConfig.isArray "`n"
; tmpStr .= "propertyConfig.arraySplitChar = " propertyConfig.arraySplitChar "`n"
; tmpStr .= "propertyConfig.isBoolean = " propertyConfig.isBoolean "`n"
; msgbox, % tmpStr
iniName := propertyConfig.iniName
defaultValue := propertyConfig.defaultValue
propertyValue := this.readINIFileValue(iniName, defaultValue, aIniFile)
if (propertyConfig.isBoolean)
{
if (propertyConfig.isArray)
{
defaultValueArray := StrSplit(defaultValue, propertyConfig.arraySplitChar)
for index, element in propertyValue
{
propertyValueTmp := this.validateBoolean(element, defaultValueArray[index])
propertyValue[index] := propertyValueTmp
}
}
else
{
propertyValue := this.validateBoolean(propertyValue, defaultValue)
}
}
else if (propertyConfig.isArray)
{
propertyValue := StrSplit(propertyValue, propertyConfig.arraySplitChar)
}
else if (propertyConfig.isObject)
{
propertyValueTmp := this.jsonToObject(propertyValue)
propertyValue := propertyValueTmp
}
this[propertyName] := propertyValue
}
return retVal
}
_writeINIFile(aIniFile="", isOnClose=false, isWriteDefaultValues=false, aSection="")
{ ; write user changeable settings to the INI file
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
if(aIniFile="")
{
aIniFile := this.iniFile
}
errLvl := false
for propertyName, propertyConfig in this.settingsConfigurations
{
; tmpStr := "----- Write ----" isOnClose "-" isWriteDefaultValues "-`n"
; tmpStr .= "aIniFile = " aIniFile "`n"
; tmpStr .= "propertyName = " propertyName "`n"
; tmpStr .= "propertyValue = " this[propertyName] "`n"
; tmpStr .= "propertyConfig.iniName = " propertyConfig.iniName "`n"
; tmpStr .= "propertyConfig.defaultValue = " propertyConfig.defaultValue "`n"
; tmpStr .= "propertyConfig.isWriteOnClose = " propertyConfig.isWriteOnClose "`n"
; tmpStr .= "propertyConfig.isArray = " propertyConfig.isArray "`n"
; tmpStr .= "propertyConfig.arraySplitChar = " propertyConfig.arraySplitChar "`n"
; tmpStr .= "propertyConfig.isBoolean = " propertyConfig.isBoolean "`n"
; msgbox, % tmpStr
tmpStr := "propertyName = " propertyName "`n"
iniName := propertyConfig.iniName
if(isWriteDefaultValues)
{ ; if creating a default INI file
errLvl_new := this.writeINIFileValue(iniName, propertyConfig.defaultValue, aIniFile, aSection)
tmpStr .= "written=" errLvl_new "`n"
}
else if (!isOnClose OR (isOnClose AND propertyConfig.isWriteOnClose))
{ ; if a normal write, or writing on Close and allowed to write OnClose.
if (propertyConfig.isArray)
{
tmpVal := ""
arraySplitChar := propertyConfig.arraySplitChar
For Index, Value In this[propertyName]
tmpVal .= arraySplitChar . Value ; Remove leading delimiters when the loop is done
tmpVal := LTrim(tmpVal, arraySplitChar) ; Remove leading delimiters (,)
}
else if (propertyConfig.isObject)
{
tmpVal := this.objectToJson(this[propertyName])
}
else
{
tmpVal := this[propertyName]
}
errLvl_new := this.writeINIFileValue(iniName, tmpVal, aIniFile, aSection)
tmpStr .= "written=" errLvl_new "`n"
}
else
{
errLvl_new := false
}
errLvl := (errLvl_new OR errLvl)
; msgbox, % tmpStr
}
return errLvl
}
writeINIFile(aIniFile="")
{ ; write user changeable settings to the INI file
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, false, false)
}
writeINIFileOnClose(aIniFile="")
{ ; write non-user editable values on the close of the script.
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, true, false)
}
writeDefaultINIFile(aIniFile="", aSection="")
{ ; write default values ot the INI file, including the non-user changeable ones (i.e., installed_on)
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, false, true, aSection)
}
; Methods (helpers)
; ---------------------------------------------
writeINIFileValue(aKey, aValue, aIniFile="", aSection="")
{ ; a wrapper to IniWrite
; Input variables
; aKey/aValue -> the key/value to write to the INI file
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
if(aIniFile="")
{
aIniFile := this.iniFile
}
if(aSection="")
{
aSection := this.SectionName
}
IniWrite, %aValue%, %aIniFile%, %aSection%, %aKey%
return ErrorLevel
}
readINIFileValue(aKey, aDefValue, aIniFile="", aSection="")
{ ; a wrapper to IniRead (mostly a workaround for the fact that you can't use obj.var syntax in IniRead)
; Input variables
; aKey -> the key to read from the INI file
; aDefValue -> if the aKey cannot be read, supply this as the returned value
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; Output variables
; Value -> the value associated with the aKey
if(aIniFile="")
{
aIniFile := this.iniFile
}
if(aSection="")
{
aSection := this.SectionName
}
IniRead, OutVar, %aIniFile%, %aSection%, %aKey%, %aDefValue%
return OutVar
}
getSupportFilename_old(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
return this.getSupportFilename(fileExten, addDot, "", False, "", False)
}
getSupportFilename(fileExten, addDot=true, parentFolder="", useDefaultFolder=True, filenameSuffix="", useFullPath=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? Allows you to use "log" instead of ".log" (Default=true)
; parentFolder -> directory where the file should be located (Default="")
; useDefaultFolder -> if parentFolder="" will place the file in a default folder based on fileExten (Default={"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"})
; filenameSuffix -> a string added between the filenameNoExt and the Exten (e.g., "_" . A_ComputerName) (Default="")
; useFullPath -> return a FullPath or not. If parentFolder isa fullpath this will be ignored. (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
defaultArray := {"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"}
if(fileExten != "")
{
if(addDot)
{
replacementStr = .%fileExten%
}
else
{
replacementStr = %fileExten%
}
}
else
{
replacementStr := ""
}
if(filenameSuffix != "")
{
replacementStr := filenameSuffix . replacementStr
}
if(A_IsCompiled)
{
StringReplace, fileName, A_ScriptName,.exe,%replacementStr%, All
}
else
{
StringReplace, fileName, A_ScriptName,.ahk,%replacementStr%, All
}
if(parentFolder == "")
{
if(useDefaultFolder)
{
1stChar := SubStr(fileExten, 1, 1)
if (1stChar = ".")
{
fileExten1 := SubStr(fileExten, 2)
; MsgBox, "dot: " . %fileExten1%
}
Else
{
fileExten1 := fileExten
; MsgBox, "nul: " . %fileExten1%
}
For key, value in defaultArray
{
if( InStr(key, fileExten1) )
{
parentFolder := value
}
}
}
}
if(parentFolder != "")
{
returnName := parentFolder . "\" . fileName
}
else
{
returnName := fileName
}
if(useFullPath)
{
; see https://www.autohotkey.com/boards/viewtopic.php?p=289536#p289536
path := returnName
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
returnName := buf
; SplitPath, returnName, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; if(OutDrive = "") ; is it already a FullPath?
; {
; SplitPath, A_ScriptFullPath, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; returnName := OutDir . "\" . returnName ; OutDir includes OutDrive
; }
}
return returnName
}
validateBoolean(boolAsStr, defaultVal=false)
{ ; checks a String for true/false, Yes/No, or a number ("" is false)
; Input variables
; boolAsStr -> a string representation of a Boolean (e.g. from an INI file/STDIN)
; defaultVal -> a default Boolean that is returned if all else fails (default: false)
; Output variables
; boolAsBoolean -> a Boolean (true or false)
if(boolAsStr = "true")
{
return true
}
else if(boolAsStr = "false")
{
return false
}
else if(boolAsStr = "")
{
return false
}
else if(SubStr(boolAsStr,1,1) = "t") ; "=" is case in-sensitive ; "==" is case sensitive
{
return true
}
else if(SubStr(boolAsStr,1,1) = "f")
{
return false
}
else if(SubStr(boolAsStr,1,1) = "y")
{
return true
}
else if(SubStr(boolAsStr,1,1) = "n")
{
return false
}
else if boolAsStr is number
{
if (boolAsStr != 0)
return true
else
return false
}
; else
if (defaultVal) ; very harsh error checking for defaultVal
{
return true
}
else
{
return false
}
}
jsonToObject(ByRef src, args*) ; Jxon_Load()
{ ; copied from jxon.ahk (https://github.com/cocobelgica/AutoHotkey-JSON/tree/master) in order to not have another file to include
; Deserialize src (a JSON formatted string) to an AutoHotkey object
; Syntax:
; obj := Jxon_Load( ByRef src [ , object_base := "", array_base := "" ] )
; Parameter(s):
; src [in, ByRef] - JSON formatted string or path to the file containing JSON formatted string.
; object_base [in, opt] - an object to use as prototype for objects( {} ) created during parsing.
; array_base [in, opt] - an object to use as prototype for arrays( [] ) created during parsing.
static q := Chr(34)
key := "", is_key := false
stack := [ tree := [] ]
is_arr := { (tree): 1 }
next := q . "{[01234567890-tfn"
pos := 0
while ( (ch := SubStr(src, ++pos, 1)) != "" )
{
if InStr(" `t`n`r", ch)
continue
if !InStr(next, ch, true)
{
ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))
msg := Format("{}: line {} col {} (char {})"
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
: (next == "'") ? "Unterminated string starting at"
: (next == "\") ? "Invalid \escape"
: (next == ":") ? "Expecting ':' delimiter"
: (next == q) ? "Expecting object key enclosed in double quotes"
: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
, ln, col, pos)
throw Exception(msg, -1, ch)
}
is_array := is_arr[obj := stack[1]]
if i := InStr("{[", ch)
{
val := (proto := args[i]) ? new proto : {}
is_array? ObjPush(obj, val) : obj[key] := val
ObjInsertAt(stack, 1, val)
is_arr[val] := !(is_key := ch == "{")
next := q . (is_key ? "}" : "{[]0123456789-tfn")
}
else if InStr("}]", ch)
{
ObjRemoveAt(stack, 1)
next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
}
else if InStr(",:", ch)
{
is_key := (!is_array && ch == ",")
next := is_key ? q : q . "{[0123456789-tfn"
}
else ; string | number | true | false | null
{
if (ch == q) ; string
{
i := pos
while i := InStr(src, q,, i+1)
{
val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
static end := A_AhkVersion<"2" ? 0 : -1
if (SubStr(val, end) != "\")
break
}
if !i ? (pos--, next := "'") : 0
continue
pos := i ; update pos
val := StrReplace(val, "\/", "/")
, val := StrReplace(val, "\" . q, q)
, val := StrReplace(val, "\b", "`b")
, val := StrReplace(val, "\f", "`f")
, val := StrReplace(val, "\n", "`n")
, val := StrReplace(val, "\r", "`r")
, val := StrReplace(val, "\t", "`t")
i := 0
while i := InStr(val, "\",, i+1)
{
if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
continue 2
; \uXXXX - JSON unicode escape sequence
xxxx := Abs("0x" . SubStr(val, i+2, 4))
if (A_IsUnicode || xxxx < 0x100)
val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
}
if is_key
{
key := val, next := ":"
continue
}
}
else ; number | true | false | null
{
val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
; For numerical values, numerify integers and keep floats as is.
; I'm not yet sure if I should numerify floats in v2.0-a ...
static number := "number", integer := "integer"
if val is %number%
{
if val is %integer%
val += 0
}
; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
; SOMETIMES return strings due to certain optimizations. Since it
; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
else if (val == "true" || val == "false")
val := %value% + 0
; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
; as it would raise an exception in AHK_H(overriding built-in var)
else if (val == "null")
val := ""
; any other values are invalid, continue to trigger error
else if (pos--, next := "#")
continue
pos += i-1
}
is_array? ObjPush(obj, val) : obj[key] := val
next := obj==tree ? "" : is_array ? ",]" : ",}"
}
}
return tree[1]
}
objectToJson(obj, indent:="", lvl:=1) ; Jxon_Dump(obj, indent:="", lvl:=1)
{ ; copied from jxon.ahk (https://github.com/cocobelgica/AutoHotkey-JSON/tree/master) in order to not have another file to include
; Serialize obj to a JSON formatted string
; Syntax:
; str := Jxon_Dump( obj [ , indent := "" ] )
; Return Value:
; A JSON formatted string.
; Parameter(s):
; obj [in] - this argument has the same meaning as in JSON.Dump()
; indent [in, opt] - this argument has the same meaning as in JSON.Dump()
static q := Chr(34)
if IsObject(obj)
{
static Type := Func("Type")
if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))
is_array := 0
for k in obj
is_array := k == A_Index
until !is_array
static integer := "integer"
if indent is %integer%
{
if (indent < 0)
throw Exception("Indent parameter must be a postive integer.", -1, indent)
spaces := indent, indent := ""
Loop % spaces
indent .= " "
}
indt := ""
Loop, % indent ? lvl : 0
indt .= indent
lvl += 1, out := "" ; Make #Warn happy
for k, v in obj
{
if IsObject(k) || (k == "")
throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")
if !is_array
out .= ( ObjGetCapacity([k], 1) ? this.objectToJson(k) : q . k . q ) ;// key
. ( indent ? ": " : ":" ) ; token + padding
out .= this.objectToJson(v, indent, lvl) ; value
. ( indent ? ",`n" . indt : "," ) ; token + indent
}
if (out != "")
{
out := Trim(out, ",`n" . indent)
if (indent != "")
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
}
return is_array ? "[" . out . "]" : "{" . out . "}"
}
; Number
else if (ObjGetCapacity([obj], 1) == "")
return obj
; String (null -> not supported by AHK)
if (obj != "")
{
obj := StrReplace(obj, "\", "\\")
, obj := StrReplace(obj, "/", "\/")
, obj := StrReplace(obj, q, "\" . q)
, obj := StrReplace(obj, "`b", "\b")
, obj := StrReplace(obj, "`f", "\f")
, obj := StrReplace(obj, "`n", "\n")
, obj := StrReplace(obj, "`r", "\r")
, obj := StrReplace(obj, "`t", "\t")
static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
while RegExMatch(obj, needle, m)
obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
}
return q . obj . q
}
}
class SettingsProperty
{
__New(propertyName, iniName="", defaultValue="", isWriteOnClose=false, isArray=false, arraySplitChar="", isBoolean=false, isObject=false)
{
this.propertyName := propertyName
if(iniName="")
{
this.iniName := propertyName
}
else
{
this.iniName := iniName
}
this.defaultValue := defaultValue
this.isWriteOnClose := isWriteOnClose
this.isArray := isArray ; for backwards compatibility
this.arraySplitChar := arraySplitChar ; for backwards compatibility
this.isBoolean := isBoolean
this.isObject := isObject
}
}
;EndRegion
/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;
; v1.0 2022-01-23
; * initial draft
;
; v1.1 2022-11-12
; * changed writeDefaultINIFile(..., aSection="") and _writeINIFile(..., aSection="") so that you can write to any section (e.g., "DEFAULT_SETTINGS") and not just the Settings.SectionName value.
;
; v1.2 2023-11-18
; * updated getSupportFilename("ico"), which sets default sub-directories for filetypes, and getSupportFilename_old("ico"), which is the old version
;
; v1.3 2024-01-08
; * added ability to store objects (via jxon.ahk functions), not tested
;
;==============================================================================
*/
Code: Select all
getSupportFilename(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
return getSupportFilename2(fileExten, addDot, "", False, "", False)
}
getSupportFilename2(fileExten, addDot=true, parentFolder="", useDefaultFolder=True, filenameSuffix="", useFullPath=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? Allows you to use "log" instead of ".log" (Default=true)
; parentFolder -> directory where the file should be located (Default="")
; useDefaultFolder -> if parentFolder="" will place the file in a default folder based on fileExten (Default={"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"})
; filenameSuffix -> a string added between the filenameNoExt and the Exten (e.g., "_" . A_ComputerName) (Default="")
; useFullPath -> return a FullPath or not. If parentFolder isa fullpath this will be ignored. (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
defaultArray := {"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"}
if(fileExten != "")
{
if(addDot)
{
replacementStr = .%fileExten%
}
else
{
replacementStr = %fileExten%
}
}
else
{
replacementStr := ""
}
if(filenameSuffix != "")
{
replacementStr := filenameSuffix . replacementStr
}
if(A_IsCompiled)
{
StringReplace, fileName, A_ScriptName,.exe,%replacementStr%, All
}
else
{
StringReplace, fileName, A_ScriptName,.ahk,%replacementStr%, All
}
if(parentFolder == "")
{
if(useDefaultFolder)
{
1stChar := SubStr(fileExten, 1, 1)
if (1stChar = ".")
{
fileExten1 := SubStr(fileExten, 2)
; MsgBox, "dot: " . %fileExten1%
}
Else
{
fileExten1 := fileExten
; MsgBox, "nul: " . %fileExten1%
}
For key, value in defaultArray
{
if( InStr(key, fileExten1) )
{
parentFolder := value
}
}
}
}
if(parentFolder != "")
{
returnName := parentFolder . "\" . fileName
}
else
{
returnName := fileName
}
if(useFullPath)
{
; see https://www.autohotkey.com/boards/viewtopic.php?p=289536#p289536
path := returnName
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
returnName := buf
; SplitPath, returnName, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; if(OutDrive = "") ; is it already a FullPath?
; {
; SplitPath, A_ScriptFullPath, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; returnName := OutDir . "\" . returnName ; OutDir includes OutDrive
; }
}
return returnName
}
getTempFilename(shortFilename)
{ ; creates a full path filename based on the Windows Temp directory
; Input variables
; shortFilename -> the short filename of the temp file you wish to use (e.g., demo.htm)
; Output variables
; fullFileName -> the full path filename of the temp file you wish to use (e.g., C:\Temp\demo.htm)
SplitPath, shortFilename, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
if(OutDrive != "") ; is it already a FullPath?
{
fullFilename := OutFileName
}
fullFilename = %A_Temp%\%fullFilename%
return fullFilename
}
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;BeginRegion
/*
; 1.0 2023-11-18
; * moved getSupportFilename(...), getSupportFilename2(...), and getTempFilename(filename) from Libray.ahk
;
;==============================================================================
*/
;EndRegion
-
- Posts: 8
- Joined: 21 Sep 2023, 02:08
Re: 4 options to change the current folder in Windows Explorer
I changed the code to this and now it runs correctly, thanks a lot.
Code: Select all
ExplorerFindTab(pathToFind) {
shellWindows := ComObject("Shell.Application").Windows
Found := 0
for w in shellWindows {
try ctrl := ControlGetHwnd(w)
catch
continue
Path := w.Document.Folder.Self.Path
if (pathToFind = Path) {
TabName := w.Document.Folder.Title
topPane := UIA.ElementFromHandle(ctrl)
tabEl := topPane.FindElement({T:"TabItem", N:Path})
WinActivate(w)
tabEl.Click()
Found := 1
break
}
}
if !Found
Run("explorer.exe " pathToFind)
}
ntepa wrote: ↑25 Sep 2023, 14:49I have a function to switch to a tab if it exists. It requires UIA library to be included.windfancy3 wrote: Can you please add a function to make it possible to switch to the target tab when it already exists instead of opening a new one.
The script is for v2 though. I wasn't able to convert it to v1:Code: Select all
#Requires AutoHotkey v2.0 #Include <UIA> ^1::{ ExplorerFindTab("C:\Windows") } ; looks for an existing tab with the path. ExplorerFindTab(pathToFind) { shellWindows := ComObject("Shell.Application").Windows Found := 0 for w in shellWindows { try ctrl := ControlGetHwnd("Windows.UI.Composition.DesktopWindowContentBridge1", w) catch continue Path := w.Document.Folder.Self.Path if (pathToFind = Path) { TabName := w.Document.Folder.Title topPane := UIA.ElementFromHandle(ctrl) tabEl := topPane.FindElement({T:"TabItem", N:TabName}) WinActivate(w) tabEl.Click() Found := 1 break } } if !found Run("explorer.exe " pathToFind) }
Who is online
Users browsing this forum: haomingchen1998, mikeyww, Rohwedder, vposud and 363 guests