Skip to content

Instantly share code, notes, and snippets.

@Lorenzo501
Last active July 8, 2025 14:26
Show Gist options
  • Save Lorenzo501/7c0cea6f4422906e8d467a354df00452 to your computer and use it in GitHub Desktop.
Save Lorenzo501/7c0cea6f4422906e8d467a354df00452 to your computer and use it in GitHub Desktop.
/*
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) + "&timestamp=" + 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(, &currentTaskbarY,, &taskbarHeight, taskbarToActivate)
while (currentTaskbarY != (A_ScreenHeight - taskbarHeight))
{
WinActivate(taskbarToActivate)
Sleep(10)
try WinGetPos(, &currentTaskbarY,,, 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(, &currentTaskbarY,,, "ahk_class Shell_TrayWnd")
while ((currentTaskbarY ?? 0) != this.DeactivatedTaskbarY)
{
Sleep(10)
try WinGetPos(, &currentTaskbarY,,, "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(, &currentCursorY)
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)
<?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>
@Lorenzo501
Copy link
Author

A lot of this script was made by utilizing my WinEvent Monitor.ahk for debugging

@Lorenzo501
Copy link
Author

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