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
}
@anonymous1184
Copy link
Author

anonymous1184 commented May 2, 2022

Little over a month ago (late Feb) I simplified the function... it bit me in the ass.

Simplified version looked for either a SYSTEM_TEXT role for Firefox and all the other Chromium-based browsers and the SYSTEM_DOCUMENT role for Chrome... but I guess in the later updates Google changed the way Assistive Technology is detected. So I'm gonna go back to the previous version of the function and just grab the Address bar and add the appropriate schema for Chrome.

If I were to use the fix you linked I couldn't get the proper schema or I'd need to hardcode https which is fine for the most part but then there are times where http Is actually needed. Also even though ftp:// is dead there are other protocols and totally forgot that I needed to take care of them.

Anyway is "fixed" now, but I don't like the way it is... I liked the streamlined version of it, so hopefully I get some free time to look for the proper fix rather than a simple patch). Another point of failure is the fact that other Electron or Chrome Embedded Framework browsers that have the accessibility disabled by default will struggle so it need a proper fix.

And thanks a lot for looking into this!


On that topic I started reading the on the subject and I have a good idea what is wrong and how to fix it:

https://sites.google.com/a/chromium.org/dev/developers/design-documents/accessibility

Windows: Chrome calls NotifyWinEvent with EVENT_SYSTEM_ALERT and the custom object id of 1. If it subsequently receives a WM_GETOBJECT call for that custom object id, it assumes that assistive technology is running.

I just need some free time , which is a luxury I currently don't have but as soon as I'm able I'll update it.

@tdalon
Copy link

tdalon commented May 2, 2022

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

@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