Last active
July 8, 2025 14:26
-
-
Save Lorenzo501/7c0cea6f4422906e8d467a354df00452 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This multi-purpose script is meant to be used via pinned taskbar icons and you can even have Tabs Outliner minimized at the startup of your computer. | |
IMPORTANT: install the Tabs Outliner extension: https://chromewebstore.google.com/detail/tabs-outliner/eggkanocgddhmamlbiijnphhppkpkmkl | |
Go to the Tabs Outliner options and then go to the general tab and put a checkmark next to: `Open Tabs Outliner window on Chrome startup.` | |
Go to the Tabs Outliner shortcuts and set Alt+X as the shortcut for `Activate the extension` (chrome://extensions/shortcuts). | |
Move this script to: %UserProfile%\Downloads\AutoHotkey | |
Go to the properties of the Chrome icon in the taskbar and set these values: | |
Target: "%UserProfile%\Downloads\AutoHotkey\Minimized Tabs Outliner.ahk" --duo | |
Start in: %UserProfile%\Downloads\AutoHotkey | |
Optionally if you have Tabs Outliner pinned to the taskbar (recommended for opening only Tabs Outliner, not its options), go to the properties of the icon there and set these values: | |
Target: "%UserProfile%\Downloads\AutoHotkey\Minimized Tabs Outliner.ahk" --single | |
Start in: %UserProfile%\Downloads\AutoHotkey | |
Optionally to run Tabs Outliner minimized at the startup of your computer (recommended), go to `Task Scheduler > Action > Import Task...` and then use the XML file. | |
********** TABS OUTLINER BACKUPS ********** | |
Three ways to make backups of your saved tabs that you should know about. | |
HTML/TREE BACKUP | |
The easiest way to make a backup is to save the webpage and then you can view it later without needing Tabs Outliner. Trees can even be dragged from the HTML backups into Tabs | |
Outliner in v1.4.141 and earlier versions, but in v1.4.153 it is no longer possible. A feature of Minimized Tabs Outliner is that you can also use the buttons: `Export Tree To File` | |
and `View Exported Tree`. This uses the TREE file extension instead and has the benefit of allowing you to drag items into the main wnd. After which you can even expand any | |
collapsed items and that might not be possible in v1.4.141 with HTML backups. I'd recommend making both a HTML and TREE backup at the same time, so you get the best of both worlds. | |
RESTORABLE BACKUP | |
Tabs Outliner stores the tabs in these two folders, make a backup of both of them: | |
%LocalAppData%\Google\Chrome\User Data\Default\IndexedDB\chrome-extension_eggkanocgddhmamlbiijnphhppkpkmkl_0.indexeddb.blob | |
%LocalAppData%\Google\Chrome\User Data\Default\IndexedDB\chrome-extension_eggkanocgddhmamlbiijnphhppkpkmkl_0.indexeddb.leveldb | |
To restore a backup entirely (e.g. to transfer tabs to a new Chrome installation): | |
1) Uninstall Tabs Outliner. | |
2) Copy and paste the two folders into: %LocalAppData%\Google\Chrome\User Data\Default\IndexedDB | |
3) Reinstall Tabs Outliner. | |
*/ | |
#Requires AutoHotkey 2.0 | |
#SingleInstance Off | |
; Show the Tabs Outliner extension in the chrome web store when it hasn't been installed and configured yet | |
if (!(hasTabsOutlinerExtension := DirExist(EnvGet("LocalAppData") "\Google\Chrome\User Data\Default\Extensions\eggkanocgddhmamlbiijnphhppkpkmkl"))) | |
{ | |
Run("chrome.exe https://chromewebstore.google.com/detail/tabs-outliner/eggkanocgddhmamlbiijnphhppkpkmkl --start-maximized --disable-features=GlobalMediaControls") | |
MsgBox("Tabs Outliner has not been installed and configured yet!",, "0x40000 Iconx") | |
ExitApp() | |
} | |
; Early attempt to start chrome normally (otherwise trying again later in case a chrome process does exist then) | |
if (A_Args.Length > 0 && A_Args[1] = "--duo" && ProcessExist("chrome.exe")) | |
{ | |
Run("chrome.exe --start-maximized --disable-features=GlobalMediaControls") | |
ExitApp() | |
} | |
A_DetectHiddenWindows := true | |
; To make the script work when an admin window is active | |
if (!InStr(A_AhkPath, "_UIA.exe")) | |
{ | |
; The non-UIA process will launch this script with UIA as long as there's no process with UIA already | |
if (!WinExist(A_ScriptFullPath " ahk_exe AutoHotkey64_UIA.exe")) | |
Run("*UIAccess " A_ScriptFullPath (A_Args.Length > 0 ? " " A_Args[1] : "")) | |
ExitApp() | |
} | |
else if (WinGetList(A_ScriptFullPath " ahk_exe AutoHotkey64_UIA.exe").Length > 1) | |
ExitApp() | |
A_DetectHiddenWindows := false | |
#NoTrayIcon | |
Persistent() | |
OnError((*) => (ListLines(), Suspend(), BufferKeyboardInputs(), BufferLatestClick(), Thread("NoTimers"), SetTimer(Suspend, -1), 0)) ; Lambda returns last value (zero shows error) | |
OnExit((*) => ( | |
tabsOutlinerId := WinExist("Tabs Outliner ahk_exe chrome.exe"), | |
(A_Args.Length = 1 && A_Args[1] = "--single" ? WinActivate() : WinMinimize()), | |
WinSetTransparent("Off"), | |
TaskbarUtilities.AddTab(tabsOutlinerId), | |
TaskbarUtilities.Show(), | |
0 ; Lambda returns last value (non-zero avoids exit) | |
)) | |
interfaceImprovementsJS := " | |
( | |
if (!window.hasAddedCustomizeListener) { | |
window.addEventListener("load", customize) // Executes later in case the document hasn't fully loaded yet | |
window.hasAddedCustomizeListener = true; | |
} | |
customize(); // Executes straight away in case the document has fully loaded already | |
// This fn can be safely executed multiple times before and after the document has fully loaded | |
function customize() { | |
arrayHtml = document.getElementsByClassName("winNTASC"); tabsOutlinerWnd = arrayHtml[arrayHtml.length - 1]; | |
tabsOutlinerWnd.classList.add("tabsOutlinerWnd"); | |
try | |
{ | |
tabsOutlinerWnd.children[0].classList.add("tabsOutlinerWnd"); | |
tabsOutlinerWnd.children[1].children[0].classList.add("tabsOutlinerWnd"); | |
tabsOutlinerWnd.children[1].children[0].children[0].children[0].classList.add("tabsOutlinerWnd"); | |
} | |
catch {} | |
try | |
{ | |
exportTreeToFileButton = document.getElementById("newWindowActionLink"); | |
exportTreeToFileButton.title = "Export Tree To File\n(Collapsed item data gets preserved and you can drag items to the main window)"; | |
exportTreeToFileButton.classList.remove("actionLink"); | |
if (!exportTreeToFileButton.hasAddedExportTreeListener) | |
{ | |
exportTreeToFileButton.removeEventListener("dragstart", actionLink_ondragstart); | |
exportTreeToFileButton.removeEventListener("click", actionLink_onclick); | |
exportTreeToFileButton.addEventListener("click", async function(event) { | |
exportDataString = await chrome.runtime.sendMessage({ request: "message2bkg_getCurrentSessionAsJsonString" }); | |
var exportDataBlob = new Blob([exportDataString], { type: "application/octet-stream" }); | |
filename = "tree-exported-" + (new Date()).toDateString().replace(/ /g, "-") + ".tree"; | |
a = document.createElement("a"); | |
a.href = URL.createObjectURL(exportDataBlob); | |
a.download = filename; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(a.href); // Release memory | |
}); | |
exportTreeToFileButton.hasAddedExportTreeListener = true; | |
} | |
} | |
catch {} | |
if (!document.getElementById("viewExportedTree")) { | |
hiddenViewExportedTreeButton = document.createElement("input"); | |
hiddenViewExportedTreeButton.type = "file"; | |
hiddenViewExportedTreeButton.id = "viewExportedTree"; | |
hiddenViewExportedTreeButton.accept = ".tree"; | |
hiddenViewExportedTreeButton.style.cssText = "visibility: hidden; width: 0px; height: 0px;"; | |
hiddenViewExportedTreeButton.addEventListener("change", handleFileSelect_viewExportedFile, false); | |
document.body.appendChild(hiddenViewExportedTreeButton); | |
try | |
{ | |
viewExportedTreeButton = document.getElementById("cloneViewButton"); | |
viewExportedTreeButton.title = "View Exported Tree\n(Collapsed item data gets preserved and you can drag items to the main window)"; | |
viewExportedTreeButton.removeEventListener("click", cloneView); | |
viewExportedTreeButton.addEventListener("click", function(event) { hiddenViewExportedTreeButton.click(); }); | |
} | |
catch {} | |
} | |
sheet = document.styleSheets[0]; rules = sheet.cssRules; | |
sheet.insertRule(".tabsOutlinerWnd { visibility: hidden !important; height: 0px !important; padding: 0px !important; margin: 0px !important; }", sheet.cssRules.length); | |
sheet.insertRule("#newWindowActionLink::before { background: #6e6e6e url(../../img/icon.png) 0% 0% / 30px 30px !important; content: ''; width: 100%; height: 100%; display: block; }" | |
, sheet.cssRules.length); | |
sheet.insertRule("#cloneViewButton::before { background: #a5a5a5 url(../../img/icon.png) 0% 0% / 30px 30px !important; }", sheet.cssRules.length); | |
sheet.insertRule("div#scrollToLastNodeCompensator { height: 17.6em !important; }", sheet.cssRules.length); | |
sheet.insertRule("#mainViewMessagesStack { display: none !important; }", sheet.cssRules.length); | |
sheet.insertRule(".modal-bg, #proFeatureUsedInLiteModeAlert { visibility: hidden !important; padding: 0px !important; }", sheet.cssRules.length); | |
if (!window.hasAddedTrySwitchModeListener) { | |
window.addEventListener("focus", () => { | |
try { to_messages.hideMessage("gopro"); } catch {} | |
try { setProMode(); } catch { | |
try { window["isContextMenuGoProBanerVisible"] = false; window["isKeysAndcontextMenuActionsEnabled"] = true; } | |
catch { msg2view_setLicenseState_valid({"isLicenseValid":true, "isUserEmailAccessible":true, "isLicenseKeyPresent":true, "userInfoEmail":"", "licenseKey":""}); } | |
} | |
}); | |
window.hasAddedTrySwitchModeListener = true; | |
} | |
window.scrollTo(0, document.body.scrollHeight - window.innerHeight); | |
} | |
function viewTree(path, timestamp, fileSize, isLocal, isUserSelectedFile) { | |
chrome.windows.create({ // will open in new window | |
url:"backup/backupview/view_tree.html?path=" + encodeURIComponent(path) + "×tamp=" + timestamp.getTime() + "&fileSize=" + fileSize + "&isLocal=" + isLocal | |
+ "&isUserSelectedFile=" + isUserSelectedFile, | |
width:500 | |
}, null/*callback*/); | |
} | |
function handleFileSelect_viewExportedFile(evt) { | |
var files = evt.target.files; // FileList object | |
for (var i = 0, file; file = files[i]; i++) | |
viewExportedFile(file); | |
} | |
function viewExportedFile(file) { | |
var reader = new FileReader(); // Read the content of the file | |
// This event listener will be triggered when the reading operation is completed | |
reader.onload = function(event) { | |
var fileContent = event.target.result; | |
var exportDataBlob = new Blob([fileContent], { type: file.type }); | |
window.webkitRequestFileSystem(window.TEMPORARY, exportDataBlob.size + 100, function(fs) { // Request the temporary filesystem | |
// Once we have access to the filesystem, create a file | |
var filePath = "selectedFile"; // Define the file path | |
fs.root.getFile(filePath, { create: true, exclusive: false }, function(fileEntry) { | |
fileEntry.createWriter(function(fileWriter) { // Create a FileWriter object for writing to the file | |
fileWriter.truncate(0); | |
fileWriter.onwriteend = function() { | |
fileWriter.write(exportDataBlob); // After truncating, write the new content | |
fileWriter.onwriteend = function() { viewTree(filePath, file.lastModifiedDate, file.size, true, true); }; // After writing, read the file from the filesystem | |
}; | |
fileWriter.onerror = function(err) { console.log("Error while writing to the file:", err); }; | |
}, fsErrorHandler); | |
}, fsErrorHandler); | |
}, fsErrorHandler); | |
}; | |
reader.readAsText(file); | |
function fsErrorHandler(error) { console.log("Filesystem Error:", error); } | |
} | |
)" | |
A_TitleMatchMode := 3 ; For detecting windows with a title that matches the given WinTitle exactly (e.g. to match "Tabs Outliner" when requested, skipping "Tabs Outliner Options") | |
A_CoordModeMouse := "Screen" | |
A_KeyDelay := -1 ; For pasting with ControlSend and also for Send b/c it reverts to the Event SendMode when another AHK script installs a native low-level keyboard/mouse hook | |
A_KeyDuration := -1 ; For Send b/c it reverts to the Event SendMode when another AHK script installs a native low-level keyboard/mouse hook | |
A_MouseDelay := -1 ; For a buffered left-, right- or double-click with Send b/c it reverts to the Event SendMode when another AHK script installs a native low-level keyboard/mouse hook | |
A_WinDelay := -1 | |
EVENT_OBJECT_SHOW := 0x8002 | |
EVENT_OBJECT_NAMECHANGE := 0x800C | |
tabsOutliner := {IsBeingPrepared: false} ; Ad hoc object, usable w/ dot notation (no need for class) | |
disposableNewTabChrome := {Id: 0} ; Ad hoc object, usable w/ dot notation (no need for class) | |
; The first scope executes at startup (using no flag or the experimental startup flag will trigger this expanded single mode) | |
if (A_Args.Length = 0 || A_Args[1] = "--uninhibited-clicks") | |
{ | |
EVENT_OBJECT_LOCATIONCHANGE := 0x800B | |
ProcessWaitClose("LogonUI.exe") ; Gotta wait until it closes before the taskbar deactivate timer repeatedly activates the desktop (the logon screen will otherwise lose keyboard focus) | |
WinWait("ahk_class Progman") ; Gotta wait until it exists before activating it to deactivate the taskbar | |
TaskbarUtilities.ToggleDeactivateTimer() | |
TaskbarUtilities.WaitDeactivate() | |
if (!TaskbarUtilities.HasDeactivateTimedOut) | |
TaskbarUtilities.MakeInvisible() | |
; Skipping this with the --uninhibited-clicks flag means you can both use your cursor and start chrome normally in the meantime, but it might hurt stability and cause unforeseen issues | |
if (A_Args.Length = 0) | |
BufferLatestClick(true) ; Start buffering the latest click | |
BufferKeyboardInputs(true,, () => (BufferKeyboardInputs(), BufferLatestClick())) ; Start buffering keys and let Ctrl+Alt+Del terminate both keyboard and mouse buffering | |
windowToActivate := WinExist("A") ; The currently active window gets reactivated in the end (only for any buffered keyboard input, the taskbar also gets activated on top if clicked) | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedTabsOutlinerEvent) | |
} | |
else if (A_Args[1] = "--single") | |
{ | |
BufferLatestClick(true) ; Start buffering the latest click | |
BufferKeyboardInputs(true,, () => (BufferKeyboardInputs(), BufferLatestClick())) ; Start buffering keys and let Ctrl+Alt+Del terminate both keyboard and mouse buffering | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedTabsOutlinerEvent) | |
} | |
else if (A_Args[1] = "--duo") | |
{ | |
if (ProcessExist("chrome.exe")) | |
{ | |
Run("chrome.exe --start-maximized --disable-features=GlobalMediaControls") | |
ExitApp() | |
} | |
else | |
{ | |
; Buffering cannot be skipped in this mode; it's the only reliable protection against stability issues caused by user interference, which tests have shown to occur | |
BufferLatestClick(true) ; Start buffering the latest click | |
BufferKeyboardInputs(true,, () => (BufferKeyboardInputs(), BufferLatestClick())) ; Start buffering keys and let Ctrl+Alt+Del terminate both keyboard and mouse buffering | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedNewTabEvent) | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedTabsOutlinerEvent) | |
SetTimer(OnTabsOutlinerTimeout, -20000) | |
Run("chrome.exe --start-maximized --disable-features=GlobalMediaControls") | |
} | |
} | |
else | |
throw ValueError("Parameter invalid, use either --single or --duo (unset and the experimental parameter is expanded single`nmode to be executed at startup)", -1, A_Args[1]) | |
if (A_Args.Length = 0 || A_Args[1] = "--uninhibited-clicks" || A_Args[1] = "--single") | |
{ | |
if (ProcessExist("chrome.exe")) | |
{ | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedDisposableNewTabEvent) | |
SetTimer(() => OnTabsOutlinerTimeout(true), -20000) | |
Run("chrome.exe --new-window") | |
HandleNameChangedDisposableNewTabEvent(hWinEventHook, event, hWnd, *) | |
{ | |
static isBeingUsed := false | |
try | |
if (InStr(WinGetTitle(hWnd), "Google Chrome",, -1) && !isBeingUsed) | |
{ | |
isBeingUsed := true | |
WinSetTransparent(0, hWnd) | |
DllCall("UnhookWinEvent", "Ptr", hWinEventHook) | |
TaskbarUtilities.DeleteTab(hWnd) | |
disposableNewTabChrome.Id := hWnd | |
Send("{Alt down}") | |
while (!WinExist("Tabs Outliner ahk_exe chrome.exe")) | |
{ | |
Sleep(500) ; (Default=500) Decreasing this makes it faster but maybe less reliable (i.e. multiple opening or none) and increasing it might make it even more reliable | |
WinActivate(hWnd) | |
ControlSend("x", hWnd) | |
} | |
Send("{Alt up}") | |
} | |
} | |
} | |
else | |
SetTimer(OnTabsOutlinerTimeout, -20000), ForceRunTabsOutliner(), SetTimer(ForceRunTabsOutliner, 1000) | |
} | |
#e::throw Error("Macro must be stuck? Self-initiated error inspection mode") ; This hotkey becomes available once keyboard inputs are no longer being buffered (Ctrl+Alt+Del stops buffering) | |
;********** LIBRARY ********** | |
; This is for a repeating timer to finish reliably running Tabs Outliner at startup (can retry for 20 seconds total) | |
ForceRunTabsOutliner() | |
{ | |
static startTime := A_TickCount | |
if (tabsOutliner.IsBeingPrepared || A_TickCount - startTime > 20000) | |
{ | |
SetTimer(, 0) | |
return | |
} | |
Run("chrome.exe --silent-launch --start-maximized --disable-features=GlobalMediaControls") | |
SetTimer(OnTabsOutlinerTimeout, -20000) ; Resets the final timeout because it doesn't apply as long as chrome is refusing to launch silently | |
} | |
OnTabsOutlinerTimeout(isShortcutUsed := false) | |
{ | |
if (tabsOutliner.IsBeingPrepared) | |
return | |
BufferKeyboardInputs() ; Stop buffering keys | |
BufferLatestClick() ; Stop buffering the latest click | |
if (isShortcutUsed) | |
{ | |
WinClose(disposableNewTabChrome.Id) | |
WinActivate("ahk_exe chrome.exe") | |
Sleep(100) | |
Send("^tchrome://extensions/shortcuts{Enter}") | |
Sleep(100) | |
MsgBox("Go to the Tabs Outliner shortcuts and set Alt+X as the shortcut for `"Activate the extension`"",, "0x40000 Icon!") | |
ExitApp() | |
} | |
else if (A_Args.Length > 0 && A_Args[1] = "--duo") | |
{ | |
WinActivate("ahk_exe chrome.exe") | |
Sleep(100) | |
Send("^lchrome-extension://eggkanocgddhmamlbiijnphhppkpkmkl/options.html{Enter}") | |
Sleep(300) | |
} | |
else | |
Run("chrome.exe chrome-extension://eggkanocgddhmamlbiijnphhppkpkmkl/options.html --start-maximized --disable-features=GlobalMediaControls") | |
MsgBox("Go to the GENERAL tab and put a checkmark next to:`n`"Open Tabs Outliner window on Chrome startup.`"",, "0x40000 Icon!") | |
ExitApp() | |
} | |
HandleNameChangedNewTabEvent(hWinEventHook, event, hWnd, *) | |
{ | |
static isBeingDeactivated := false | |
try | |
if (InStr(WinGetTitle(hWnd), "Google Chrome",, -1) && !isBeingDeactivated) | |
{ | |
isBeingDeactivated := true | |
DllCall("Sleep", "UInt", 100) ; Necessary to make the deactivation work (this DllCall workaround prevents other events from being handled in the meantime) | |
WinActivate("ahk_class Progman") ; Makes the window visually inactive straight away, instead of later when the window has already been visually active | |
DllCall("UnhookWinEvent", "Ptr", hWinEventHook) | |
} | |
} | |
HandleNameChangedTabsOutlinerEvent(hWinEventHook, event, hWnd, *) | |
{ | |
try shouldExecute := (WinGetTitle(hWnd) = "_crx_eggkanocgddhmamlbiijnphhppkpkmkl" || WinExist("Tabs Outliner ahk_exe chrome.exe ahk_id " hWnd)) && !tabsOutliner.IsBeingPrepared | |
if (shouldExecute ?? false) | |
{ | |
tabsOutliner.IsBeingPrepared := true | |
WinSetTransparent(0, hWnd) | |
DllCall("UnhookWinEvent", "Ptr", hWinEventHook) | |
HookEvent(EVENT_OBJECT_SHOW, HandleShowedStatusBarEvent) | |
TaskbarUtilities.DeleteTab(hWnd) | |
WinWait("Tabs Outliner ahk_exe chrome.exe") | |
MoveAndResize() | |
HookEvent(EVENT_OBJECT_NAMECHANGE, HandleNameChangedDevToolsEvent, WinGetPID()) | |
global isDevToolsEventBeingHandled := false | |
SetTimer(RestartDevToolsIfHandlerNotBusy, 1000) | |
WinActivate() | |
Send("{F12}") | |
} | |
HandleShowedStatusBarEvent(hWinEventHook, event, hWnd, *) | |
{ | |
static isBeingHidden := false | |
A_TitleMatchMode := "RegEx" ; Changes only the mode of the thread made for this specific WinEvent handler | |
try | |
if (WinExist("^$ ahk_class Chrome_WidgetWin_1") && !isBeingHidden) ; Titleless chrome status bar (^ = start of line, $ = end of line) | |
{ | |
isBeingHidden := true | |
WinHide() | |
DllCall("UnhookWinEvent", "Ptr", hWinEventHook) | |
} | |
} | |
HandleNameChangedDevToolsEvent(hWinEventHook, event, hWnd, *) | |
{ | |
try shouldExecute := WinGetTitle(hWnd) = "DevToolsApp" && !isDevToolsEventBeingHandled | |
if (shouldExecute ?? false) | |
{ | |
global isDevToolsEventBeingHandled := true | |
try WinSetTransparent(0, hWnd) ; DevTools has a tendency to close right after it opens | |
catch | |
{ | |
global isDevToolsEventBeingHandled := false | |
Exit() | |
} | |
DllCall("UnhookWinEvent", "Ptr", hWinEventHook) | |
TaskbarUtilities.DeleteTab(hWnd) | |
if (disposableNewTabChrome.Id) | |
WinClose(disposableNewTabChrome.Id) | |
A_DetectHiddenWindows := true ; Enables only the hidden window detection of the thread made for this specific WinEvent handler (to make sure there's an usable last found wnd) | |
WinExist(hWnd) | |
Sleep(500) ; Making sure that both Tabs Outliner and DevTools is loaded to avoid issues | |
if (LoadDevToolsConsole()) | |
{ | |
ClipSend(interfaceImprovementsJS, 3) | |
WinHide() ; Allows you to use `WinShow("DevTools - chrome-extension")` in case you need to see what happened in DevTools (will automatically close when Tabs Outliner closes) | |
WinSetTransparent("Off") | |
TaskbarUtilities.AddTab(hWnd) | |
} | |
else | |
{ | |
WinSetTransparent("Off") | |
MsgBox("Failed to load DevTools console",, "0x40000 Icon!") | |
} | |
try WinActivate(windowToActivate), Sleep(200) ; Activate relevant wnd to release buffered inputs on | |
BufferKeyboardInputs(, true) ; Stop buffering keys and sending them out | |
BufferLatestClick(, true) ; Stop buffering the latest click and sending it out | |
ExitApp() | |
} | |
} | |
RestartDevToolsIfHandlerNotBusy() | |
{ | |
if (!isDevToolsEventBeingHandled) | |
WinActivate("Tabs Outliner ahk_exe chrome.exe"), Send("{F12}") | |
} | |
; Returns True when the DevTools console is rdy and False when it failed to load | |
LoadDevToolsConsole() | |
{ | |
ResetClipboard() | |
loop (7) | |
{ | |
WinActivate() | |
Send("{Ctrl down}") | |
Sleep(100) ; This is necessary to make `Ctrl` reliably modify sent keys | |
ControlSend("``") ; Instructs DevTools to open `What's new` or the console, or to close the console | |
Sleep(400) ; (Default=100) Increasing this might make it more reliable. Execution of the instruction above by Chrome could take place after console input is already detected | |
loop (10) | |
{ | |
Send("{Ctrl up}") | |
WinActivate() | |
ControlSend("k") ; Sends input to the console if it's open yet | |
Send("{Ctrl down}") | |
ControlSend("ac") ; Selects all and copies it | |
; Detects whether or not the console has successfully received input | |
if (ClipWait(0.1)) | |
{ | |
loop parse (A_Clipboard) | |
if (A_LoopField != "k") | |
{ | |
ResetClipboard() | |
continue 2 ; Executes the next outer loop iteration | |
} | |
Send("{Ctrl up}") | |
global clipChangeToIgnore := A_Clipboard | |
; Prevents an infinite loop in OnClipboardChange | |
if (clipPrevious = clipChangeToIgnore) | |
global clipPrevious := "" | |
OnClipboardChange((*) => (Critical(-1), A_Clipboard = clipChangeToIgnore ? A_Clipboard := clipPrevious : Exit())) ; Causes any remaining copy action to be ignored | |
return true | |
} | |
} | |
} | |
Send("{Ctrl up}") | |
A_Clipboard := clipPrevious | |
return false | |
ResetClipboard() | |
{ | |
global clipPrevious := ClipboardAll() | |
A_Clipboard := "" ; Clears the clipboard so that ClipWait can be used | |
} | |
} | |
ClipSend(textToSend, count := 1) | |
{ | |
clipPreviousOriginally := clipPrevious | |
global clipPrevious := textToSend ; Makes OnClipboardChange able to reset to `textToSend` while ClipSend is executing | |
A_Clipboard := textToSend | |
Send("{Ctrl down}") | |
loop (count) | |
{ | |
Sleep(500) ; (Default=100) Increasing this (or the other one) might make pasting more reliable | |
WinActivate() | |
ControlSend("v{Enter}") | |
} | |
Sleep(500) ; (Default=500) Increasing this (or the other one) might make pasting more reliable | |
Send("{Ctrl up}") | |
global clipPrevious := clipPreviousOriginally ; Makes OnClipboardChange able to reset to the original value of `clipPrevious` (necessary as long as the target wnd exists) | |
A_Clipboard := clipPrevious | |
} | |
; When Tabs Outliner is the last found wnd, invisible and maximized, then this fn will fix it how it was meant to | |
MoveAndResize() => (WinRestore(), WinMove(-8, 0, A_ScreenWidth * 0.1930, A_ScreenHeight + 9)) | |
} | |
/** | |
* Buffering the latest left-, right- or double-click to send later | |
* @param {Integer} mode Turn on/off | |
* @param {Integer} shouldSendBufferedClick When you stop buffering the latest click, you can set this to True if you want to release the buffered latest click as well | |
*/ | |
BufferLatestClick(mode := false, shouldSendBufferedClick := false) | |
{ | |
static cursorX, cursorY, taskbarToActivate, shouldRightClick := false, shouldDoubleClick := false, _ := InitializeWindowGroups() | |
if (!mode) | |
{ | |
HotIf(MouseIsOverBlacklistedWindow) | |
try Hotkey("*LButton", "Off") | |
try Hotkey("*RButton", "Off") | |
HotIf() | |
try Hotkey("*LButton", "Off") | |
try Hotkey("*RButton", "Off") | |
if (shouldSendBufferedClick && IsSet(cursorX)) | |
{ | |
if (taskbarToActivate) | |
TaskbarUtilities.Activate(taskbarToActivate) | |
MouseGetPos(&cursorXPrevious, &cursorYPrevious) | |
if (shouldRightClick) | |
Send("{Click " cursorX " " cursorY " R}") | |
else if (shouldDoubleClick) | |
Send("{Click " cursorX " " cursorY " 2}") | |
else | |
Send("{Click " cursorX " " cursorY "}") | |
Send("{Click " cursorXPrevious " " cursorYPrevious " 0}") | |
} | |
} | |
else | |
{ | |
HotIf(MouseIsOverBlacklistedWindow) | |
Hotkey("*LButton", (*) => HandleLatestClick(true)) | |
Hotkey("*RButton", (*) => HandleLatestClick(true)) | |
HotIf() | |
Hotkey("*LButton", (*) => HandleLatestClick()) | |
Hotkey("*RButton", (*) => HandleLatestClick()) | |
} | |
InitializeWindowGroups() | |
{ | |
; Any of these that get clicked will be reactivated | |
GroupAdd("Taskbar", "ahk_class Shell_TrayWnd") ; [Win10 & Win11] Main taskbar | |
GroupAdd("Taskbar", "ahk_class Shell_SecondaryTrayWnd") ; [Win10 & Win11] Taskbar of 2nd monitor | |
; These will be blocked instead of postponed | |
GroupAdd("Blacklist", "Start ahk_class Windows.UI.Core.CoreWindow") ; [Win10 & Win11] Start menu | |
GroupAdd("Blacklist", "Search ahk_class Windows.UI.Core.CoreWindow") ; [Win10 & Win11] Windows Search | |
GroupAdd("Blacklist", "Task View ahk_class Windows.UI.Core.CoreWindow") ; [Win10] Win+Tab window | |
GroupAdd("Blacklist", "ahk_class MultitaskingViewFrame") ; [Win10] Alt+Tab window | |
;GroupAdd("Blacklist", "ahk_class TaskListThumbnailWnd") ; [Win10] Preview window (won't need this though b/c it won't become the foreground window) | |
GroupAdd("Blacklist", "ahk_class XamlExplorerHostIslandWindow") ; [Win11] Alt+Tab window, Win+Tab window & Preview window | |
GroupAdd("Blacklist", "ahk_class NotifyIconOverflowWindow") ; [Win10] Hidden icons tray window | |
GroupAdd("Blacklist", "ahk_class TopLevelWindowForOverflowXamlIsland") ; [Win11] Hidden icons tray window | |
GroupAdd("Blacklist", "ahk_exe ShellExperienceHost.exe") ; [Win10 & Win11] Network Connections window, Volume Control window, Date and Time Information window & Action center window | |
GroupAdd("Blacklist", "ahk_class #32770") ; [Win10 & Win11] Any dialog box, such as the Properties window, Find window & MsgBox window | |
;GroupAdd("Blacklist", "ahk_class #32768") ; [Win10 & Win11] Right-click menu (won't need this with right-click buffering active) | |
;GroupAdd("Blacklist", "ahk_class WorkerW") ; [Win10 & Win11] Invisible worker window (overlays the desktop once Win+Tab gets used, desktop click gets buffered so no need for this) | |
} | |
HandleLatestClick(isMouseOverBlacklistedWindow := false) | |
{ | |
; Any MsgBox window created by this script is allowed to be clicked b/c it'd otherwise prevent the thread from continuing, in which case the mouse could remain blocked | |
if (MouseIsOver("ahk_class #32770 ahk_pid " WinGetPID(A_ScriptHwnd))) | |
{ | |
Send(InStr(A_ThisHotkey, "LButton") ? "{LButton}" : "{RButton}") | |
return | |
} | |
; Show loading cursor until the script exits | |
if (!IsSet(cursorX)) | |
{ | |
cursors := Map("Arrow", 0, "Hand", 0, "Help", 0, "IBeam", 0, "NWPen", 0, "SizeAll", 0, "SizeNESW", 0, "SizeNS", 0, "SizeNWSE", 0, "SizeWE", 0) | |
OnExit((*) => (ResetCursor(), 0)) ; Lambda returns last value (non-zero avoids exit) | |
for (iteratedValueName in cursors) | |
{ | |
cursors[iteratedValueName] := RegRead("HKEY_CURRENT_USER\Control Panel\Cursors", iteratedValueName) | |
RegWrite("%SYSTEMROOT%\Cursors\aero_working.ani", "REG_EXPAND_SZ", "HKEY_CURRENT_USER\Control Panel\Cursors", iteratedValueName) | |
} | |
DllCall("SystemParametersInfo", "UInt", SPI_SETCURSORS := 0x0057, "UInt", 0, "Ptr", 0, "UInt", 0) | |
for (iteratedValueName, iteratedCursorFilePath in cursors) | |
RegWrite(iteratedCursorFilePath, "REG_SZ", "HKEY_CURRENT_USER\Control Panel\Cursors", iteratedValueName) | |
} | |
if (isMouseOverBlacklistedWindow) | |
return | |
if (A_ThisHotkey = "*RButton") | |
shouldRightClick := true, shouldDoubleClick := false | |
else if (A_PriorHotkey = A_ThisHotkey && A_TimeSincePriorHotkey < 500) | |
shouldDoubleClick := true, shouldRightClick := false | |
else | |
shouldDoubleClick := false, shouldRightClick := false | |
MouseGetPos(&cursorX, &cursorY) | |
taskbarToActivate := MouseIsOver("ahk_group Taskbar") ; Attempts to retrieve a clicked taskbar to activate later | |
ResetCursor() => DllCall("SystemParametersInfo", "UInt", SPI_SETCURSORS := 0x0057, "UInt", 0, "Ptr", 0, "UInt", 0) | |
} | |
MouseIsOver(winTitle, winText?, excludeTitle?, excludeText?) => (MouseGetPos(,, &winId), WinExist(winTitle " ahk_id " winId, winText?, excludeTitle?, excludeText?)) | |
MouseIsOverBlacklistedWindow(*) => MouseIsOver("ahk_group Blacklist") | |
} | |
class TaskbarUtilities | |
{ | |
static HasDeactivateTimedOut => A_TickCount - this.StartTime > 900 | |
static __New() | |
{ | |
this.IsInvisible := false | |
this.DeactivatedTaskbarY := A_ScreenHeight - 2 | |
iid_ITaskbarList := "{56FDF342-FD6D-11d0-958A-006097C9A090}" ; Interface id (iid) | |
clsid_TaskbarList := "{56FDF344-FD6D-11d0-958A-006097C9A090}" ; Class id (clsid) | |
this.Tbl := ComObject(clsid_TaskbarList, iid_ITaskbarList) ; Creates the TaskbarList object | |
loop (10) | |
{ | |
try ComCall(3, this.Tbl) ; Calls this.Tbl.HrInit(), which initializes the TaskbarList object | |
catch | |
Sleep(50) | |
else | |
break | |
} | |
} | |
; To reliably activate a specific taskbar | |
static Activate(taskbarToActivate) | |
{ | |
try WinGetPos(, ¤tTaskbarY,, &taskbarHeight, taskbarToActivate) | |
while (currentTaskbarY != (A_ScreenHeight - taskbarHeight)) | |
{ | |
WinActivate(taskbarToActivate) | |
Sleep(10) | |
try WinGetPos(, ¤tTaskbarY,,, taskbarToActivate) | |
} | |
} | |
; To make it less bothersome by deactivating the taskbar | |
static ToggleDeactivateTimer() | |
{ | |
static isEnabled := false | |
SetTimer(Deactivate, (isEnabled := !isEnabled) ? 30 : 0) | |
Deactivate() => WinActivate("ahk_class Progman") | |
} | |
static WaitDeactivate() | |
{ | |
this.StartTime := A_TickCount | |
try WinGetPos(, ¤tTaskbarY,,, "ahk_class Shell_TrayWnd") | |
while ((currentTaskbarY ?? 0) != this.DeactivatedTaskbarY) | |
{ | |
Sleep(10) | |
try WinGetPos(, ¤tTaskbarY,,, "ahk_class Shell_TrayWnd") | |
if (this.HasDeactivateTimedOut) | |
break | |
} | |
this.ToggleDeactivateTimer() | |
} | |
; To prevent anything except the user from reactivating the taskbar | |
static MakeInvisible() | |
{ | |
WinSetTransparent(0, "ahk_class Shell_TrayWnd") | |
this.IsInvisible := true | |
SetTimer(this._ShowOnHover := ObjBindMethod(this, "ShowOnHover"), 20) | |
Hotkey("~LWin", (*) => this.Show()) | |
} | |
static ShowOnHover() | |
{ | |
try | |
{ | |
MouseGetPos(, ¤tCursorY) | |
if (currentCursorY >= this.DeactivatedTaskbarY) | |
this.Show() | |
} | |
} | |
static Show() | |
{ | |
if (this.IsInvisible) | |
{ | |
this.ToggleDeactivateTimer(), this.WaitDeactivate() | |
try WinSetTransparent("Off", "ahk_class Shell_TrayWnd") | |
SetTimer(this._ShowOnHover, 0), Hotkey("~LWin", (*) => this.Show(), "Off"), this.IsInvisible := false | |
} | |
} | |
static AddTab(winId) => ComCall(4, this.Tbl, "Ptr", winId) ; Calls this.Tbl.AddTab(winId), which adds a tab to the TaskbarList | |
static DeleteTab(winId) => ComCall(5, this.Tbl, "Ptr", winId) ; Calls this.Tbl.DeleteTab(winId), which deletes a tab from the TaskbarList | |
} | |
/** | |
* Buffering keyboard inputs to send later. If `mode` is the default value False, it returns all the buffered inputs as a string: bufferedkeys := BufferKeyboardInputs() | |
* @param {Integer} mode Turn on/off | |
* @param {Integer} shouldSendBufferedkeys When you stop buffering keys, you can set this to True if you want to release the buffered keys as well | |
* @param {() => String} terminationHotkeyFnObj Ctrl+Alt+Del is a termination hotkey which works while buffering is active (use this as a last resort to stop buffering keys or w/e) | |
* @returns {String} All the inputs that were being buffered | |
*/ | |
BufferKeyboardInputs(mode := false, shouldSendBufferedkeys := false, terminationHotkeyFnObj := BufferKeyboardInputs) | |
{ | |
static ih, inputs := "" | |
if (!mode) | |
{ | |
try ih.Stop() | |
if (shouldSendBufferedkeys) | |
Send(inputs) | |
return inputs | |
} | |
else | |
inputs := "" | |
ih := InputHook("I1 L0 *") | |
ih.NotifyNonText := true | |
ih.VisibleNonText := false | |
ih.OnKeyDown := (ih, VK, SC) => OnKey(ih, VK, SC, "Down") | |
ih.OnKeyUp := (ih, VK, SC) => OnKey(ih, VK, SC, "Up") | |
ih.KeyOpt("{All}", "N S") | |
ih.Start() | |
OnKey(ih, VK, SC, UD) | |
{ | |
Critical() | |
key := GetKeyName(Format("vk{1:x}sc{2:x}", VK, SC)) | |
if (key = "Delete" && GetKeyState("Ctrl", "P") && GetKeyState("Alt", "P")) | |
inputs .= "{Ctrl up}{Alt up}", terminationHotkeyFnObj() | |
else | |
inputs .= Format("{{1} {2}}", key, UD) | |
} | |
} | |
HookEvent(event, fnObj, pid := 0) => DllCall("SetWinEventHook", "UInt", event, "UInt", event, "Ptr", 0, "Ptr", CallbackCreate(fnObj), "UInt", pid, "UInt", 0, "UInt", 0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-16"?> | |
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> | |
<Triggers> | |
<LogonTrigger> | |
<Enabled>true</Enabled> | |
</LogonTrigger> | |
</Triggers> | |
<Settings> | |
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> | |
<IdleSettings> | |
<StopOnIdleEnd>false</StopOnIdleEnd> | |
</IdleSettings> | |
<AllowStartOnDemand>true</AllowStartOnDemand> | |
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit> | |
<Priority>5</Priority> | |
</Settings> | |
<Actions Context="Author"> | |
<Exec> | |
<Command>"%ProgramFiles%\AutoHotkey\v2\AutoHotkey.exe"</Command> | |
<Arguments>"%UserProfile%\Downloads\AutoHotkey\Minimized Tabs Outliner.ahk"</Arguments> | |
<WorkingDirectory>%UserProfile%\Downloads\AutoHotkey</WorkingDirectory> | |
</Exec> | |
</Actions> | |
</Task> |
I'm currently testing OnTabsOutlinerTimeout
and ForceRunTabsOutliner
with 25 seconds. Chrome has srsly messed up their silent launch feature
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A lot of this script was made by utilizing my WinEvent Monitor.ahk for debugging