Skip to content

Instantly share code, notes, and snippets.

@KnIfER
Last active March 22, 2024 01:13
Show Gist options
  • Save KnIfER/1c968deb61a6eb893842a286a7d12524 to your computer and use it in GitHub Desktop.
Save KnIfER/1c968deb61a6eb893842a286a7d12524 to your computer and use it in GitHub Desktop.
Chrome open bookmarked pages in new tab by default. (single left click open new tab in foreground )
global click_state := 0
global leftDwn := 0
GroupAdd, browser_gp, ahk_exe chrome.exe
Acc_Init(Function := "") {
Static h
If Not h
h:=DllCall("LoadLibrary","Str","oleacc","Ptr")
If Function
return DllCall("GetProcAddress", "Ptr", h, "AStr", Function, "Ptr")
}
Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}
Acc_Error(p="") {
static setting:=0
return p=""?setting:setting:=p
}
Acc_GetStateText(nState)
{
nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0)
VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize)
DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1)
Return sState
}
; Acc_Child(Acc, ChildId=0) {
; try child:=Acc.accChild(ChildId)
; return child?Acc_Query(child):
; }
Acc_Children(Acc) {
if ComObjType(Acc,"Name") != "IAccessible"
ErrorLevel := "Invalid IAccessible Object"
else {
Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
if DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*",cChildren)=0 {
Loop %cChildren%
i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child):
return Children.MaxIndex()?Children:
} else
ErrorLevel := "AccessibleChildren DllCall Failed"
}
if Acc_Error()
throw Exception(ErrorLevel,-1)
}
Acc_Location(Acc, ChildId=0, byref Position="") { ; adapted from Sean's code
try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
catch
return
Position := "x" NumGet(x,0,"int") " y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")
return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int")}
}
Acc_State(Acc, ChildId=0) {
try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object"
}
Acc_ObjectFromWindow(hWnd, idObject = -4)
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0
Return ComObjEnwrap(9,pacc,1)
}
Acc_ObjectFromPoint(ByRef ChildIdOut := "", x := 0, y := 0) {
static S_OK := 0
static address := Acc_Init("AccessibleObjectFromPoint")
point := x & 0xFFFFFFFF | y << 32
if (point = 0) {
DllCall("User32\GetCursorPos", "Int64*", point)
}
pAcc := 0
VarSetCapacity(child, A_PtrSize * 2 + 8, 0)
NTSTATUS := DllCall(address, "Int64", point, "Ptr*", pAcc, "Ptr", &child, "UInt")
if (NTSTATUS != S_OK) {
throw Exception("AccessibleObjectFromPoint() failed.", -1, A_LastError)
}
ChildIdOut := NumGet(child, 8, "UInt")
return ComObj(9, pAcc, 1)
}
inGroup(GroupName)
{
IfWinActive, ahk_group %GroupName%
return true
}
indexOf(str, t, st=0) {
StringGetPos, ret, str, %t%, , st
return ret
}
#SingleInstance Force
GroupAdd, browser_gp, ahk_exe chrome.exe
; GroupAdd, browser_gp, ahk_exe msedge.exe
global leftDwn := 0
#Include acc_lite.ahk
clickLeft(dwn=true) {
if(dwn!=leftDwn || dwn) {
leftDwn := dwn
if(dwn) {
send {LButton down}
} else
send {LButton up}
; Msgbee("clickLeft" dwn)
}
; else
; Msgbee("clickLeft nono " dwn)
}
StartsWith(str, t, only=false) {
l := StrLen(str)
n := StrLen(t)
if(l-n>=0 && SubStr(str, 1, n) = t) {
if(!only || indexOf(str, t, n)<0) {
return 1
}
}
return 0
}
$LButton::
WinGetTitle, ti
MouseGetPos, xpos, ypos, MouseWindowUID, MouseControlID
WinGetPos,x,y,w,h,A
if(xpos>=0 && xpos<=w && ypos>=0 && ypos<=h) {
browser := inGroup("browser_gp")
if(click_state=0) {
if browser and not InStr(ti, "New Tab"){
newTab := false
; open bookmarked pages in new tab
WinGetClass, WindowClass, ahk_id %MouseWindowUID%
if(WindowClass="Chrome_WidgetWin_2") { ; folder panel, valid for chrome v120.
; for edge (not handled) the window class is still Chrome_WidgetWin_1 and you need to judge by width
CoordMode, Mouse, Screen
MouseGetPos, sX, sY
WinGetPos, x, , , , ahk_id %MouseWindowUID%
CoordMode, Mouse, Relative
if(sX - x > 34) {
; click on the favicon means to open url in current tab (same as before)
acc := Acc_ObjectFromPoint(ChildIdOut, sX, sY)
text := acc.accName(0)
; xx(text)
if(!StartsWith(text, "<<")
&& !StartsWith(text, "{{")
&& !StartsWith(text, "页面")
&& !StartsWith(text, "复制")
&& true) {
newTab := true
}
}
} else if(ypos < 160 && ypos > 110 && A_Cursor="Arrow") { ; Judge clicks on the horizontal bookmark bar.
; you need to adjust the min and max ypos on your computer
CoordMode, Mouse, Screen
MouseGetPos, sX, sY
CoordMode, Mouse, Relative
acc := Acc_ObjectFromPoint(ChildIdOut, sX, sY)
text := acc.accName(0)
if(text = "website name") {
if(!InStr(ti, "website name")) { ; maybe the url is same as current tab, and in that case dont open new tab.
newTab := true
}
}
if(StartsWith(text, "http") ; and InStr(text, "Untitled")
|| text="xxx"
|| text="yyy") {
; clicked bookmark bar item is not folder, then open new tab!
newTab := true
}
}
; xx(ypos " " A_Cursor)
if newTab {
Send ^+{click}
return
}
}
}
; Msgbee("123")
clickLeft(1)
} else {
clickLeft(1)
}
return
$LButton up::
if(click_state=3)
click_state := 0
clickLeft(0) ; allows for original left click dragging
return
@KnIfER
Copy link
Author

KnIfER commented Mar 17, 2024

Many people have this default requirement - in the vast majority of cases, left-clicking on a bookmark item (including the horizontal bookmark bar and vertical folder list view ) should open the bookmark URL in a new tab. However, Chrome does not provide this option. This can be solved using AHK, which works almost perfectly.

AutoHotKey (a scripting language for automating hotkeys) can be considered as Windows' user-script engine, which has been around since the XP era, with many users and abundant resources, capable of solving many pain points. It is simple yet elegant, and in the obscure corners of forums, there are many works that impress me.

Returning to the main topic, how can you make Chrome open a new page when clicking on a bookmark? Chrome can do this by ctrl+shift+click, or ctrl+click, or middle-click on the bookmark to open in a new page. However, these methods either require holding additional keyboard keys or just open the new page in the background, none of them meet the simple requirement of "single left-clicking on a bookmark to open a new page."


After some thought, I first focused on the tooltip because when the mouse hovers over a bookmark, a tooltip window pops up with two lines of text containing the bookmark name and URL. I tried to retrieve the text inside it, but was not very successful.

To display more text content, the new version of Chrome (v100 and above) no longer uses the win32 native tooltips_class32 for tooltips, so you can't simply use ControlGetText to retrieve the text. Even if you could retrieve it, you can't quickly trigger the tooltip window. Originally, I thought that if it was a native tooltip, maybe it's possible to get the text content of the tooltip without triggering the tooltip (because of toolInfo.lpszText = LPSTR_TEXTCALLBACK; pszText callback and the TTN_GETDISPINFO window message)?


Text recognition or…?

Now, Chrome is using its own Chrome_WidgetWin_1 (views system) to draw tooltip pop-ups.

If you want to get the text within, what’s the best way to do it?

Text recognition?

The Windows Magnifier has a small feature where, if you click the speaker button on it, and then click on another window interface, it will read out the text aloud, somewhat like a screen text capture.

image

This text capture feature is based on the Accessibility API. AHK has relevant libraries, and here’s a patchwork version:

acc_lite.ahk

By using the Acc_ObjectFromPoint function from the mouse position to create an acc object, and then calling acc.accName(0) you can get the text content. Simple!

In fact, many methods are transferred from C++ code, and then they can be transferred back, applying to other applications.


Mouse text capture!

Since we can obtain the text content under the mouse, there’s no need to bother with tooltips anymore (it was a detour).

I tried and it successfully captures the full name of bookmarks. It works for Chrome, Edge, and even other programs!

test_capture.ahk

#SingleInstance Force

#Include path\to\acc_lite.ahk

CoordMode, Mouse, Screen

AccGetText(acc) {
	; WinGet, hwnd, ID, A
	WinGetClass, WindowClass, ahk_id %hwnd%
	t := acc.accName(0)
	t .= " / " acc.accValue(0)
	t .= " / "
	For Each, Child In Acc_Children(acc) {
		; If (5 or Acc_Location(acc, child).w) {
			Try
			{
				t .= acc.accName(child) "`n"
			}
		; }
	}
	return " hwnd := "  hwnd " acc := " acc "`n t := " t  " " ErrorLevel
	; return t
}

1::
	MouseGetPos, xpos, ypos, MouseWindowUID, MouseControlID
	
	acc := Acc_ObjectFromPoint(ChildIdOut, xpos, ypos)
	msgbox, % AccGetText(acc)
return

Finally, solving the issue of opening bookmarks in new tabs.

There are two scenarios.

First, horizontal Bookmarks Bar

My bookmarks bar mostly consists of folders, with only three exceptions.

In that case, treat the few exceptions as special cases and add Ctrl+Shift when clicking, while keeping the clicks on the rest items unchanged.

In short, distinguish whether to add Ctrl+Shift to the left mouse click based on the combination of mouse click position and mouse text capture result!

Second, Folder view : Vertical List Panel of Bookmarks.

For Chrome v120, the window class for a folder view (when expanded from bookmark bar) is Chrome_WidgetWin_2.

For folder view, most can be opened in new tab pages with Ctrl+Shift, while a few, such as JavaScript code, are exceptions and can be distinguished by the bookmark name, for example, <<I'm javascript bookmarklet>> as bookmark name.

In the folder view, folders do not respond when clicked with Ctrl+Shift, so there’s no need to distinguish them.

If you still wish to open bookmarks in the same tab with left-click, you can:

  1. Based on the click position, if it’s on the left, around the fav icon , then open in the same tab as before.
  2. Add a global switch in the script.
  3. Use a third-party bookmark extension, such as my Bookmarks Extension - the best Multi-Tab Bookmark Manager!

cheers.

@KnIfER
Copy link
Author

KnIfER commented Mar 18, 2024

www.autohotkey.com/boards/viewtopic.php?f=6&t=94568&p=419785&hilit=chrome+bookmark+new+tab#p419785

similar solution . it use accDescription . sendBlindLButton is better than listening lbutton up

;!-Alt/+-Shift/^-Control/#-win
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
;#Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

/****************************************
GUI For Logger (uncomment if you want to look at logs)
*/
Gui +AlwaysOnTop Resize
Gui, Add, Edit, x5 y5 w228 h1658 vConsole
Gui, Show, x3535 y0 w238 h1666, Logger AHK


/****************************************
LOGGER Logic (uncomment if you want to look at logs. uncomment all lines with log() below!)
*/
log(msg) {
    FormatTime, TimeString, A_Now, HH:mm:ss ; Generates a time stamp
    GuiControlGet, Console
    GuiControl, , Console, %Console%[%TimeString%] %msg%`r`n ; GUI write
}


/****************************************
Chrome Fix
*/
LButton::
{
    MouseGetPos,,,guideUnderCursor
	WinGetClass, Title, ahk_id %guideUnderCursor%
	WinGetActiveTitle, activeTitle
	
	if (Title == "Chrome_WidgetWin_1") and not WinActive("ahk_exe Chrome.exe"){
		log("LMB inside Inactive Chrome")
        sendLButton()
	} else
    {
        log("LMB Outside Chrome")
		sendBlindLButton()
    }
    
    Return 
}

^LButton::
{
    MouseGetPos,,,guideUnderCursor
	WinGetClass, Title, ahk_id %guideUnderCursor%
	WinGetActiveTitle, activeTitle

	if (Title == "Chrome_WidgetWin_1") and not WinActive("ahk_exe Chrome.exe")
    {
        log("Ctrl+LMB inside Inactive Chrome")
		sendLButton(true)
	} 
    else
    {
        log("Ctrl+LMB Outside Chrome")
		sendBlindLButton()
    }
    
    Return 
}   

sendLButton(ctrl=false) 
{
    desc := ""
    objectP := Acc_ObjectFromPoint()
    try desc := objectP.accDescription(0)
    if (desc ~= "i)^http|www") and not ctrl 
    {
        log("Bookmark: Sending Ctrl+LMB")
        Send ^{Click}{Alt down}{Alt up}
    }
    else if (desc ~= "i)^http|www") and ctrl 
    {
        log("Bookmark: Sending LMB")
        Send {Click}
    } 
    else 
    {
        log("Not a Bookmark")
		sendBlindLButton()
    }
}

sendBlindLButton()
{
	Send {Blind}{LButton Down}
	KeyWait LButton
	Send {Blind}{LButton Up}
}

#IfWinActive ahk_exe chrome.exe
    ^LButton:: 
        log("Ctrl+LMB inside Active Chrome")
		sendLButton(true)    
	return

    LButton::
        log("LMB inside Active Chrome")
		sendLButton()    
	return
		
    $Enter::
        log("Enter inside Active Chrome")
        path := "4.1.1.1.1.2.5.3"
        exist := WinExist()
        objectW := Acc_ObjectFromWindow(exist, 0)
        Send % (Acc_Get("Focus", path,, objectW) = 0 ? "!" : "") "{Enter}"
    return
#IfWinActive

@KnIfER
Copy link
Author

KnIfER commented Mar 22, 2024

update : actually lbutton up is better.

image

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