Skip to content

Instantly share code, notes, and snippets.

@anonymous1184
Last active September 18, 2024 03:38
Show Gist options
  • Save anonymous1184/7cce378c9dfdaf733cb3ca6df345b140 to your computer and use it in GitHub Desktop.
Save anonymous1184/7cce378c9dfdaf733cb3ca6df345b140 to your computer and use it in GitHub Desktop.
GetUrl()

GetUrl()

It retrieves the URL (protocol included) of any browser via either MSAA framework or UI Automation interfaces.

Testing made with the most used* browsers as of September 2023 (versions as of October 2023).

* Chrome, Edge, Firefox and Opera (market share above 1%).

It uses the WinTitle & Last Found Window mechanism, so it receives up to 4 parameters (all optional) as described in the WinExist() docs function.

Function signature:

url := GetUrl([WinTitle, WinText, ExcludeTitle, ExcludeText])

Examples

  • F1 will retrieve the URL from the active window.
  • F2 will retrieve the URL of the last found window.
  • F3 will retrieve the URL when the browser is not active.
#Requires AutoHotkey v2.0

F1:: {
    url := GetUrl("A")
    if (url) {
        MsgBox(url, "Active window URL", 0x40040)
    } else {
        MsgBox("Couldn't retrieve an URL from the active window.", "Error", 0x40010)
    }
}

F2:: {
    WinExist("Mozilla Firefox")
    url := GetUrl()
    if (url) {
        MsgBox(url, "Current URL in Firefox", 0x40040)
    } else {
        MsgBox("Couldn't retrieve Firefox URL.", "Error", 0x40010)
    }
}

F3:: {
    url := GetUrl("ahk_exe firefox.exe")
    if (url) {
        MsgBox(url, "Current URL in Firefox", 0x40040)
    } else {
        MsgBox("Couldn't retrieve Firefox URL.", "Error", 0x40010)
    }
}

Files

Files marked with an asterisk work standalone (ie, don't require a library).

Rename to GetUrl.ahk if using libraries of functions.

Links to Accessibility libraries:

#Requires AutoHotkey v2.0
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl() { ; Active Window Only
static S_OK := 0, TreeScope_Descendants := 4, UIA_ControlTypePropertyId := 30003, UIA_DocumentControlTypeId := 50030, UIA_EditControlTypeId := 50004, UIA_ValueValuePropertyId := 30045
hWnd := WinGetID("A")
IUIAutomation := ComObject("{FF48DBA4-60EF-4201-AA87-54103EEF594E}", "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}")
eRoot := ComValue(13, 0)
HRESULT := ComCall(6, IUIAutomation, "Ptr", hWnd, "Ptr*", eRoot)
if (HRESULT != S_OK) {
throw Error("IUIAutomation::ElementFromHandle()", -1, HRESULT)
}
winClass := WinGetClass("A")
ctrlTypeId := (winClass ~= "Chrome" ? UIA_DocumentControlTypeId : UIA_EditControlTypeId)
value := Buffer(8 + 2 * A_PtrSize, 0)
NumPut("UShort", 3, value, 0)
NumPut("Ptr", ctrlTypeId, value, 8)
condition := ComValue(13, 0)
if (A_PtrSize = 8) {
HRESULT := ComCall(23, IUIAutomation, "UInt", UIA_ControlTypePropertyId, "Ptr", value, "Ptr*", condition)
} else {
HRESULT := ComCall(23, IUIAutomation, "UInt", UIA_ControlTypePropertyId, "UInt64", NumGet(value, 0, "UInt64"), "UInt64", NumGet(value, 8, "UInt64"), "Ptr*", condition)
}
if (HRESULT != S_OK) {
throw Error("IUIAutomation::CreatePropertyCondition()", -1, HRESULT)
}
eFirst := ComValue(13, 0)
HRESULT := ComCall(5, eRoot, "UInt", TreeScope_Descendants, "Ptr", condition, "Ptr*", eFirst)
if (HRESULT != S_OK) {
throw Error("IUIAutomationElement::GetRootElement()", -1, HRESULT)
}
propertyValue := Buffer(8 + 2 * A_PtrSize)
HRESULT := ComCall(10, eFirst, "UInt", UIA_ValueValuePropertyId, "Ptr", propertyValue)
if (HRESULT != S_OK) {
throw Error("IUIAutomationElement::GetCurrentPropertyValue()", -1, HRESULT)
}
ObjRelease(eFirst.Ptr)
ObjRelease(eRoot.Ptr)
try {
pProperty := NumGet(propertyValue, 8, "Ptr")
return StrGet(pProperty, "UTF-16")
}
}
#Requires AutoHotkey v1.1
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl() { ; Active Window Only
static S_OK := 0, TreeScope_Descendants := 4, UIA_ControlTypePropertyId := 30003, UIA_DocumentControlTypeId := 50030, UIA_EditControlTypeId := 50004, UIA_ValueValuePropertyId := 30045
WinGet hWnd, ID, A
WinGetClass winClass, A
eRoot := condition := eFirst := 0
IUIAutomation := ComObjCreate("{FF48DBA4-60EF-4201-AA87-54103EEF594E}", "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}")
HRESULT := DllCall(NumGet(NumGet(IUIAutomation + 0) + 6 * A_PtrSize), "Ptr", IUIAutomation, "Ptr", hWnd, "Ptr*", eRoot)
if (HRESULT != S_OK) {
throw Exception("IUIAutomation::ElementFromHandle()", -1, HRESULT)
}
ctrlTypeId := (winClass ~= "Chrome" ? UIA_DocumentControlTypeId : UIA_EditControlTypeId)
VarSetCapacity(value, 8 + 2 * A_PtrSize, 0)
NumPut(3, value, 0, "UShort")
NumPut(ctrlTypeId, value, 8, "Ptr")
if (A_PtrSize = 8) {
HRESULT := DllCall(NumGet(NumGet(IUIAutomation + 0) + 23 * A_PtrSize), "Ptr", IUIAutomation, "UInt", UIA_ControlTypePropertyId, "Ptr", &value, "Ptr*", condition)
} else {
HRESULT := DllCall(NumGet(NumGet(IUIAutomation + 0) + 23 * A_PtrSize), "Ptr", IUIAutomation, "UInt", UIA_ControlTypePropertyId, "UInt64", NumGet(value, 0, "UInt64"), "UInt64", NumGet(value, 8, "UInt64"), "Ptr*", condition)
}
if (HRESULT != S_OK) {
throw Exception("IUIAutomation::CreatePropertyCondition()", -1, HRESULT)
}
HRESULT := DllCall(NumGet(NumGet(eRoot + 0) + 5 * A_PtrSize), "Ptr", eRoot, "UInt", TreeScope_Descendants, "Ptr", condition, "Ptr*", eFirst)
if (HRESULT != S_OK) {
throw Exception("IUIAutomationElement::FindFirst()", -1, HRESULT)
}
VarSetCapacity(propertyValue, 8 + 2 * A_PtrSize, 0)
HRESULT := DllCall(NumGet(NumGet(eFirst + 0) + 10 * A_PtrSize), "Ptr", eFirst, "UInt", UIA_ValueValuePropertyId, "Ptr", &propertyValue)
if (HRESULT != S_OK) {
throw Exception("IUIAutomationElement::GetCurrentPropertyValue()", -1, HRESULT)
}
ObjRelease(eRoot)
ObjRelease(condition)
ObjRelease(eFirst)
ObjRelease(IUIAutomation)
try {
pProperty := NumGet(propertyValue, 8, "Ptr")
return StrGet(pProperty, "UTF-16")
}
}
#Requires AutoHotkey v2.0
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl(WinTitle*) {
static OBJID_CLIENT := 4294967292, WM_GETOBJECT := 61
active := WinExist("A")
target := WinExist(WinTitle*)
if (!target) {
return
}
objId := OBJID_CLIENT
wClass := WinGetClass()
if (wClass ~= "Chrome") {
appPid := WinGetPID()
target := WinExist("ahk_pid" appPid)
if (active != target) {
objId := 0
}
}
oAcc := Acc_ObjectFromWindow(target, objId)
if (wClass ~= "Chrome") {
try {
SendMessage(WM_GETOBJECT, 0, 1, "Chrome_RenderWidgetHostHWND1")
oAcc.accName(0)
}
}
oAcc := GetUrl_Recurse(oAcc)
try return oAcc.accValue(0)
}
GetUrl_Recurse(oAcc) {
if (ComObjType(oAcc, "Name") != "IAccessible") {
return
}
if (oAcc.accValue(0) ~= "^[\w-]+:") {
return oAcc
}
try for (accChild in Acc_Children(oAcc)) {
oAcc := GetUrl_Recurse(accChild)
if (IsObject(oAcc)) {
return oAcc
}
}
}
#Requires AutoHotkey v1.1
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl(WinTitle*) {
static OBJID_CLIENT := 4294967292, WM_GETOBJECT := 61
active := WinExist("A")
target := WinExist(WinTitle*)
if (!target) {
return
}
objId := OBJID_CLIENT
WinGetClass wClass
if (wClass ~= "Chrome") {
WinGet appPid, PID
target := WinExist("ahk_pid" appPid)
if (active != target) {
objId := 0
}
}
oAcc := Acc_ObjectFromWindow(target, objId)
if (wClass ~= "Chrome") {
try {
SendMessage WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1
oAcc.accName(0)
}
}
oAcc := GetUrl_Recurse(oAcc)
try return oAcc.accValue(0)
}
GetUrl_Recurse(oAcc) {
if (ComObjType(oAcc, "Name") != "IAccessible") {
return
}
if (oAcc.accValue(0) ~= "^[\w-]+:") {
return oAcc
}
try for _, accChild in Acc_Children(oAcc) {
oAcc := GetUrl_Recurse(accChild)
if (IsObject(oAcc)) {
return oAcc
}
}
}
#Requires AutoHotkey v2.0
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl(WinTitle*) {
active := WinExist("A")
target := WinExist(WinTitle*)
wClass := WinGetClass()
root := UIA.ElementFromHandle(target)
static eCondition := UIA.PropertyCondition({ ControlType: "Edit" })
; Gecko family
if (wClass ~= "Mozilla") {
edit := root.FindFirst(eCondition)
return edit.GetCurrentPropertyValue(UIA.Property.ValueValue)
}
; Chromium-based, active
if (active = target) {
static dCondition := UIA.PropertyCondition({ ControlType: "Document" })
edit := root.FindFirst(dCondition)
return edit.GetCurrentPropertyValue(UIA.Property.ValueValue)
}
; Chromium-based, inactive
static tCondition := UIA.PropertyCondition({ ControlType: "ToolBar" })
toolBar := root.FindFirst(tCondition)
edit := toolBar.FindFirst(eCondition)
url := edit.GetCurrentPropertyValue(UIA.Property.ValueValue)
wTitle := WinGetTitle()
; Google Chrome
if (InStr(wTitle, "- Google Chrome") && url && !(url ~= "^\w+:")) {
static mCondition := UIA.PropertyCondition({ ControlType: "MenuItem" })
menuItem := toolBar.FindFirst(mCondition)
rect := menuItem.CurrentBoundingRectangle
w := rect.right - rect.left, h := rect.bottom - rect.top
url := "http" (w > h * 2 ? "" : "s") "://" url
}
; Microsoft Edge
static edge := "- Microsoft" Chr(0x200b) " Edge" ; Zero-width space
if (InStr(wTitle, edge) && url && !(url ~= "^\w+:")) {
url := "http://" url
}
return url
}
#Requires AutoHotkey v1.1
; Version: 2023.10.05.1
; https://gist.github.com/7cce378c9dfdaf733cb3ca6df345b140
GetUrl(WinTitle*) {
active := WinExist("A")
target := WinExist(WinTitle*)
WinGetClass wClass
root := UIA_Interface().ElementFromHandle(target)
; Gecko family
if (wClass ~= "Mozilla") {
return root.FindFirstByType("Edit").CurrentValue
}
; Chromium-based, active
if (active = target) {
return root.FindFirstByType("Document").CurrentValue
}
; Chromium-based, inactive
toolBar := root.FindFirstByType("ToolBar")
url := toolBar.FindFirstByType("Edit").CurrentValue
WinGetTitle wTitle
; Google Chrome
if (InStr(wTitle, "- Google Chrome") && url && !(url ~= "^\w+:")) {
rect := toolBar.FindFirstByType("MenuItem").CurrentBoundingRectangle
w := rect.r - rect.l, h := rect.b - rect.t
url := "http" (w > h * 2 ? "" : "s") "://" url
}
; Microsoft Edge
static edge := "- Microsoft" Chr(0x200b) " Edge" ; Zero-width space
if (InStr(wTitle, edge) && url && !(url ~= "^\w+:")) {
url := "http://" url
}
return url
}
@tdalon
Copy link

tdalon commented May 2, 2022

Maybe look in the previous linked forum entry at the alternative solution which does not require the Acc Lib.

@anonymous1184
Copy link
Author

If I remember well the check if the Accname property matches Address isn't a good one because language setting specific.

Is the same that does the function you linked, which BTW doesn't work on Firefox (takes like 10 seconds to fail in which them the app is non-responsive because it goes trough all the accessible elements on each tab) and will absolutely won't work on other browsers unless you call IUIAutomation::CreatePropertyCondition with a handle per browser per language.

And yeah, that's why I changed the function: to simplify it and to avoid language specific pitfalls...

Maybe look in the previous linked forum entry at the alternative solution which does not require the Acc Lib.

I use a lot MSAA and is way easier to read/write code with it than enumerating interfaces through COM/pointers keeping tabs of object references and such.

If you don't want to include the whole Acc library you could always embed the DLL calls in the function and it'll remove the dependency but I fail to see the benefit of that... the function will look as cramped as the one in the forums.

In the end both do the exact same thing, they recursively walk the Accessibility Tree look until a match is found for the string... difference with MSAA is what is supported 100% within the Chromium code base unlike IUIAutomation which is the most limited and that you don't need to use a different IUIAutomation::ElementFromHandle depending on the address bar string.

I guess that you can use something like IUIAutomation::CreateOrConditionFromArray in order to add as many strings as possible or even something other to grab the actual URL (element value) but I don't have any experience with those interfaces... ergo, I fixed my version and now tells Chrome there's Assistive Technology.

The benefits is that it works on pretty much every browser and not just Chrome (cannot test more than a handful) and works with http/https protocols and internal pages (like chrome://, about:preferences and such).

Again, thanks a lot for the report and insights.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment