AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post your working scripts, libraries and tools for AHK v1.1 and older
goldfish1063
Posts: 4
Joined: 12 Mar 2023, 06:51

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by goldfish1063 » 14 Mar 2023, 22:01

Sorry, i meant that its not hiding the other scripts tray icons

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by FanaticGuru » 16 Mar 2023, 11:34

goldfish1063 wrote:
14 Mar 2023, 22:01
Sorry, i meant that its not hiding the other scripts tray icons

Yea, I finally got the Windows update that totally breaks the TrayIcon library.

It looks like the entire internal structure of how Windows handles the tray has been changed.

I will have to look for a different way to have one script hide another scripts tray icon.

It looks like others have also discovered this issue: https://www.autohotkey.com/boards/viewtopic.php?p=499459#p499459

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by FanaticGuru » 16 Mar 2023, 18:16

Updated in First Post

Change Log: Update 2023 03 16
  • Removed TrayIcon library dependency and replaced with KillTrayIcon function
A Windows 11 update made major changes to how the tray icon area works. I think it actually looks better with rounded corners and just a slightly softer look.

But the changes were more than just cosmetic. The internals also changed which broke the TrayIcon library. This script only needs that library for one thing and that was to remove tray icons. That ability has been replaced with the one function KillTrayIcon. In just very little testing, it actually seems to work better. The old tray icon area was very wonky in its timing with sometimes significant delays to create, move, or remove icons.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

goldfish1063
Posts: 4
Joined: 12 Mar 2023, 06:51

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by goldfish1063 » 17 Mar 2023, 11:21

works perfectly now. thanks

JPMuir
Posts: 26
Joined: 06 Mar 2022, 07:31

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by JPMuir » 21 Mar 2023, 08:41

; AHK Startup
; Fanatic Guru
;
; Version: 2023 03 16
Thanks to Fanatic Guru. In addition to Windows 11, Windows Update 2023-03 for Windows 10- Version 22H2 for x64 based systems (KB5023696) also caused consolidation of tray icons failure. I updated the AHK_Startup and all is well here. Many thanks for the update. I also had some issues with installing V2 of Autohotkey causing problems. Now I see how AHK Startup (Consolidate AHK Scripts' Tray Icons) added "Includes flags to allow for running of both v1 and v2 scripts".

likethevegetable
Posts: 81
Joined: 05 May 2021, 08:54

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by likethevegetable » 22 Apr 2023, 20:57

Just curious if there are plans to port this over to v2?

I was thinking of doing it myself, but want to see if (the more skilled) author or members were planning to.

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by FanaticGuru » 23 Apr 2023, 17:30

likethevegetable wrote:
22 Apr 2023, 20:57
Just curious if there are plans to port this over to v2?

I was thinking of doing it myself, but want to see if (the more skilled) author or members were planning to.

The v1 version can start both v1 and v2 scripts if the flags are set to tell it which version to use for each script.

But even though it works fine, I will probably convert it completely to v2 in the future as eventually I want to completely convert all the scripts that I use every day and this is a script I use every day.

Everything new I write in the future will be v2.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

likethevegetable
Posts: 81
Joined: 05 May 2021, 08:54

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by likethevegetable » 24 Apr 2023, 07:53

Great, I am in the process of also converting my scripts to v2. Will stay tuned.

PhantomHelix
Posts: 1
Joined: 01 May 2023, 02:11

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by PhantomHelix » 01 May 2023, 02:18

I re-created my Scripts for V2 and have downloaded your newest version of AHK Startup,
after editing the paths in the " Join " section and try to run the script i get this error
am i supposed to edit this aswell?



Error: Missing space or operator before this.

Specifically: "" Script.RunPath """ """ Script.Path "" "",,, Pid)

102: If !Script.Status
103: Continue
▶ 107: Run("""" Script.RunPath """ """ Script.Path "" "",,, Pid)
108: Scripts[Script_Name,"Pid"] := Pid
109: }

The program will exit.

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by FanaticGuru » 03 May 2023, 11:37

PhantomHelix wrote:
01 May 2023, 02:18
I re-created my Scripts for V2 and have downloaded your newest version of AHK Startup,
after editing the paths in the " Join " section and try to run the script i get this error
am i supposed to edit this aswell?



Error: Missing space or operator before this.

Specifically: "" Script.RunPath """ """ Script.Path "" "",,, Pid)

102: If !Script.Status
103: Continue
▶ 107: Run("""" Script.RunPath """ """ Script.Path "" "",,, Pid)
108: Scripts[Script_Name,"Pid"] := Pid
109: }

The program will exit.

No, you should not have to edit that section. You only need to edit the Join section that has the script paths. They need to be valid paths with proper placement of quotes like shown in the examples. You need to also put "/v2" flags like shown in the examples for each v2 script.

And you may need to edit this line if Autohotkey.exe for v2 is not installed in the default location: RunPathV2 := A_ProgramFiles "\AutoHotkey\v2\AutoHotkey.exe"

Also this is a v1 script and needs to be run as a v1 script. It can start v2 scripts but this script is actually v1. The line in the error message looks like v2 syntax.

The v1 line should look something like this Run, % """" Script.RunPath """ """ Script.Path """",,, Pid

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

likethevegetable
Posts: 81
Joined: 05 May 2021, 08:54

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by likethevegetable » 30 Jun 2023, 12:28

PhantomHelix wrote:
01 May 2023, 02:18
I re-created my Scripts for V2 and have downloaded your newest version of AHK Startup,
after editing the paths in the " Join " section and try to run the script i get this error
am i supposed to edit this aswell?



Error: Missing space or operator before this.

Specifically: "" Script.RunPath """ """ Script.Path "" "",,, Pid)

102: If !Script.Status
103: Continue
▶ 107: Run("""" Script.RunPath """ """ Script.Path "" "",,, Pid)
108: Scripts[Script_Name,"Pid"] := Pid
109: }

The program will exit.
Just curious to know if you've got this working for v2? If so, a share to the v2 forum would be much appreciated!

franklin_clinton
Posts: 1
Joined: 29 Jun 2023, 20:01

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by franklin_clinton » 30 Jun 2023, 16:19

AHK2 Port
Hello everyone! Apologies if I'm posting in the wrong thread. it's my first time.
This is literal port for this script (no conventional way of doing things in ahk2, no code optimization for v2, nothing new).. Changed some data types and converted every line of this script to meet v2 syntax.. Finished this a couple hours ago everything fair and working as v1 code

PS: None of this is my work, I'm just a simple man practicing ahk, and Tray menu items was something important to me. I didn't come up with this code. Credits to the ones who did @FanaticGuru, @lexikos (as far as I can tell from the comments).. and anyone who developed this...

Configurations: Are pretty much the same
- Add script files as Array items in path_config (line 33) as follows ["path", "version", false|true] last 2 parameters are optional
- Change run_path1 (line 38) to AutoHotkey v1 executable (or A_AhkPath if v1 is set as default)
- Change run_path2 (line 39) to AutoHotkey v2 executable (or A_AhkPath if v2 is set as default)

Code: Select all

; AHK Startup
; Fanatic Guru - ported to v2 by <someone>
;
; Version: 2023 03 16
;
; Startup Script for Startup Folder to Run on Bootup.
;{-----------------------------------------------
; Runs the Scripts Defined in the Files Array
; Removes the Scripts' Tray Icons leaving only AHK Startup
; Creates a ToolTip for the One Tray Icon Showing the Startup Scripts
; If AHK Startup is Exited All Startup Scripts are Exited
; Includes a "Load" menu for a list of scripts that are not currently loaded
; Includes flags to allow for running of both v1 and v2 scripts
;}

;{ ------------------------------------------ 	  INIT - ENVIROMENT		------------------------------------------

#Requires AutoHotkey v2.0
SendMode("Input")  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir A_ScriptDir  ; Ensures a consistent starting directory.
#SingleInstance force  ; Ensures that only the last executed instance of script is running
DetectHiddenWindows(true)

;} ------------------------------------------ 	END INIT - ENVIRONMENT	------------------------------------------


;{ ------------------------------------------	   INIT - VARIABLES		------------------------------------------


; use * or *.smth ; it's directory recursive tho; or use complete file path
; directory paths like "C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1\" or "C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1" are not handled

path_config := [
	["C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1\*.ahk", "v1", false],
	["C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v2\*.ahk", "v2", false]
]

run_path1 := "C:\Users\UNKNOWN\AppData\Local\_share\AutoHotkey\v1.1.36.02\AutoHotkeyU64.exe"
run_path2 := A_AhkPath

scripts := []


SubMenu := Map()


;} ------------------------------------------	 END INIT - VARIABLES	------------------------------------------


;{ ------------------------------------------ 		 AUTO-EXECUTE		------------------------------------------



; process path_config files, configs
for item in path_config {
	path := item[1]
	version := item[2]
	; status
	if (item.length = 3)
		status := item[3]
	else
		status := true
	; run_path
	if version = "v1"
		run_path := run_path1
	else
		run_path := run_path2

	Loop Files, path, "R" {
		scripts.push(
			Map(
				"path", A_LoopFileFullPath
				, "name", A_LoopFileName
				, "run_path", run_path
				, "status", status
			)
		)
	}
}


; Run All the Scripts with Status true, Keep Their Pid
for script in scripts{
	if !script["status"]
		continue
	Run(Format("
	(
	"{1}" "{2}"
	)", script["run_path"], script["path"]),,, &pid) 
	script["pid"] := pid
}






OnExit ExitSub ; Gosub to ExitSub when this Script Exits


; Build Menu and TrayTip then Remove Tray Icons
	TrayTipBuild()
	MenuBuild()
	OnMessage(0x404, AHK_NOTIFYICON) ; Hook Events for Tray Icon (used for Tray Icon cleanup on mouseover)
	OnMessage(0x7E, AHK_DISPLAYCHANGE) ; Hook Events for Display Change (used for Tray Icon cleanup on resolution change)
	TrayIconRemove(10)


;} ------------------------------------------	   END AUTO-EXECUTE		------------------------------------------


;{ ------------------------------------------		  HOTKEYS			------------------------------------------
	~#^!Escape::ExitApp ; <-- Terminate Script
;} ------------------------------------------		END HOTKEYS			------------------------------------------


;{ ------------------------------------------ 		SUBROUTINES 		------------------------------------------

TrayTipBuild() {
	Tip_Text := ""
	for script in scripts
		if script["status"]
			Tip_Text .= script["name"] "`n"
	Sort(Tip_Text)
	Tip_Text := trimAtDelim(Trim(Tip_Text, " `n"))
	A_IconTip := Tip_Text ; Tooltip is limited to first 127 characters
}

; Stop All the Scripts with Status true (Called When this Scripts Exits)
ExitSub(*) {
	for script in scripts {
		WinKill("ahk_pid " . script["pid"])
	}
	
}

;} ------------------------------------------     END_SUBROUTINES		------------------------------------------


;{ ------------------------------------------	  SUBROUTINES - GUI		------------------------------------------

MenuBuild() {
	; try Menu, SubMenu_Load, DeleteAll ; SubMenu_Load does not always exist
	SubMenu_Load := Menu()
	A_TrayMenu.Delete()

	

	for script in scripts {
		if script["status"] {
			pid := script["pid"]

			; add a menu to submenu
			SubMenu[pid] := Menu()
			SubMenu[pid].Add("View Lines", ScriptCommand)
			SubMenu[pid].Add("View Variables", ScriptCommand)
			SubMenu[pid].Add("View Hotkeys", ScriptCommand)
			SubMenu[pid].Add("View Key History", ScriptCommand)
			SubMenu[pid].Add()
			SubMenu[pid].Add("Open", ScriptCommand)
			SubMenu[pid].Add("Edit", ScriptCommand)
			SubMenu[pid].Add("Pause", ScriptCommand)
			SubMenu[pid].Add("Suspend", ScriptCommand)
			SubMenu[pid].Add("Reload", ScriptCommand)
			SubMenu[pid].Add("Exit", ScriptCommand)
			A_TrayMenu.Add(script["name"], SubMenu[pid])
		} else {
			SubMenu_Load.Add(script["name"], ScriptCommand_Load)
		}
	}


	; try A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	if (DllCall("GetMenuItemCount", "ptr", SubMenu_Load.Handle)) {	; SubMenu_Load items count
		A_TrayMenu.Add()
		A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	}
	A_TrayMenu.AddStandard()
	try A_TrayMenu.Default := SubMenu_Load ; SubMenu_Load does not always exist
}


ScriptCommand(ItemName, junk , ItemParent) {
	; cmd := Map("Item", wParam)
	
	cmd := Map(
		"Open", 65300,
		"Reload", 65400,
		"Edit", 65401,
		"Pause", 65403,
		"Suspend", 65404,
		"Exit", 65405,
		"View Lines", 65406,
		"View Variables", 65407,
		"View Hotkeys", 65408,
		"View Key History", 65409,
	)

	pid := 0 
	for k, v in SubMenu {
		if (v = ItemParent)
			pid := k
	}

	if (ItemName = "Reload") {
		for script in scripts 
			if (script["pid"] = pid) {
				SubMenu[pid].Delete()
				PostMessage(0x111, cmd["Exit"],,, "ahk_pid " .  pid)
				Run(Format("
				(
				"{1}" "{2}"
				)", script["run_path"], script["path"]),,, &pid) 
				script["pid"] := pid

				break
			}
		MenuBuild() ; need to rebuild menu because changed Pid is used in menu names
		TrayIconRemove(8)
	} else {
		if (ItemName = "Pause" or ItemName = "Suspend")
			SubMenu[pid].ToggleCheck(ItemName)

		PostMessage(0x111, cmd[ItemName],,, "ahk_pid " . pid)
	}

	if (ItemName = "Exit") {
		for script in scripts 
			if (script["pid"] = pid) {
				script["status"] := false
				
				break
			}

		; Rebuild Menu and TrayTip
		MenuBuild()
		TrayTipBuild()
	}
}

ScriptCommand_Load(ItemName, *) {
	script_path := ""
	script_runpath := ""

	for script in scripts 
		if (script["name"] = ItemName) {
			; Run Script and Keep Info
			Run(Format("
			(
			"{1}" "{2}"
			)", script["run_path"], script["path"]),,, &pid)		
			script["pid"] := pid
			script["status"] := true

			break
		}

		MenuBuild()
		TrayTipBuild()	
		TrayIconRemove(8)
}

;} ------------------------------------------   END SUBROUTINES - GUI	------------------------------------------

;{ ------------------------------------------		 FUNCTIONS			------------------------------------------

TrayIconRemove(attempts) {
	Loop attempts	; Try To Remove Over Time Because Icons May Lag Especially During Bootup
	{
		for script in scripts
			if script["status"] {
				; v1
				; WinGet, OutputVar, List , WinTitle, WinText, ExcludeTitle, ExcludeText
				hWnds := WinGetList("ahk_pid " . script["pid"])

				for i in hWnds 
					KillTrayIcon(i)
			}
		Sleep A_index**2 * 200
	}
}


; Lexikos 
KillTrayIcon(scriptHwnd) {

	static NIM_DELETE := 2, AHK_NOTIFYICON := 1028, size := 936+4*A_PtrSize
	
	; v1 VarSetCapacity(TargetVar [, RequestedCapacity, FillByte])
	; v2 VarSetStrCapacity(&TargetVar [, RequestedCapacity])
	; doesn't work with numput for reason 1 in the following
	; https://www.autohotkey.com/boards/viewtopic.php?p=334037#p334037
	nic := Buffer(size)

	; granted := VarSetStrCapacity(&nic, size)

	; v1 NumPut(Number, VarOrAddress [, Offset, Type])
	; v2 NumPut Type, Number[, Type2, Number2, ...] Target [, Offset]
	NumPut("uint", size, nic, 0)
	NumPut("uptr", scriptHwnd, nic, A_PtrSize)
	NumPut("uint", AHK_NOTIFYICON, nic, A_PtrSize*2)
	return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_DELETE, "ptr", nic)
}

trimAtDelim(String, Length:=124, Delim:="`n", Tail:="...") {
	if (StrLen(String)>Length) {
		RegExMatch(SubStr(String, 1, Length+1),"(.*)" Delim, &Match)
		Result := Match . Tail
	} else {
		Result := String
	}
	return Result
}

AHK_NOTIFYICON(wParam, lParam, uMsg, hWnd) ; OnMessage(0x404, "AHK_NOTIFYICON")
{
; Cleanup Tray Icons on MouseOver
	if (lParam = 0x200) ; WM_MOUSEMOVE := 0x200
		TrayIconRemove(1)
}

AHK_DISPLAYCHANGE(wParam, lParam, *) { ; OnMessage(0x7E, "AHK_DISPLAYCHANGE")
; Cleanup Tray Icons on Resolution Change
	
	TrayIconRemove(8) ; Resolution Change can take a moment so try over time
}


;} ------------------------------------------	   END FUNCTIONS		------------------------------------------

Jasonosaj
Posts: 50
Joined: 02 Feb 2022, 15:02
Location: California

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by Jasonosaj » 24 Jul 2023, 19:09

franklin_clinton wrote:
30 Jun 2023, 16:19
AHK2 Port
Hello everyone! Apologies if I'm posting in the wrong thread. it's my first time.
This is literal port for this script (no conventional way of doing things in ahk2, no code optimization for v2, nothing new).. Changed some data types and converted every line of this script to meet v2 syntax.. Finished this a couple hours ago everything fair and working as v1 code

PS: None of this is my work, I'm just a simple man practicing ahk, and Tray menu items was something important to me. I didn't come up with this code. Credits to the ones who did @FanaticGuru, @lexikos (as far as I can tell from the comments).. and anyone who developed this...

Configurations: Are pretty much the same
- Add script files as Array items in path_config (line 33) as follows ["path", "version", false|true] last 2 parameters are optional
- Change run_path1 (line 38) to AutoHotkey v1 executable (or A_AhkPath if v1 is set as default)
- Change run_path2 (line 39) to AutoHotkey v2 executable (or A_AhkPath if v2 is set as default)

Code: Select all

; AHK Startup
; Fanatic Guru - ported to v2 by <someone>
;
; Version: 2023 03 16
;
; Startup Script for Startup Folder to Run on Bootup.
;{-----------------------------------------------
; Runs the Scripts Defined in the Files Array
; Removes the Scripts' Tray Icons leaving only AHK Startup
; Creates a ToolTip for the One Tray Icon Showing the Startup Scripts
; If AHK Startup is Exited All Startup Scripts are Exited
; Includes a "Load" menu for a list of scripts that are not currently loaded
; Includes flags to allow for running of both v1 and v2 scripts
;}

;{ ------------------------------------------ 	  INIT - ENVIROMENT		------------------------------------------

#Requires AutoHotkey v2.0
SendMode("Input")  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir A_ScriptDir  ; Ensures a consistent starting directory.
#SingleInstance force  ; Ensures that only the last executed instance of script is running
DetectHiddenWindows(true)

;} ------------------------------------------ 	END INIT - ENVIRONMENT	------------------------------------------


;{ ------------------------------------------	   INIT - VARIABLES		------------------------------------------


; use * or *.smth ; it's directory recursive tho; or use complete file path
; directory paths like "C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1\" or "C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1" are not handled

path_config := [
	["C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v1\*.ahk", "v1", false],
	["C:\Users\UNKNOWN\AppData\Local\_config\ahk\startup\v2\*.ahk", "v2", false]
]

run_path1 := "C:\Users\UNKNOWN\AppData\Local\_share\AutoHotkey\v1.1.36.02\AutoHotkeyU64.exe"
run_path2 := A_AhkPath

scripts := []


SubMenu := Map()


;} ------------------------------------------	 END INIT - VARIABLES	------------------------------------------


;{ ------------------------------------------ 		 AUTO-EXECUTE		------------------------------------------



; process path_config files, configs
for item in path_config {
	path := item[1]
	version := item[2]
	; status
	if (item.length = 3)
		status := item[3]
	else
		status := true
	; run_path
	if version = "v1"
		run_path := run_path1
	else
		run_path := run_path2

	Loop Files, path, "R" {
		scripts.push(
			Map(
				"path", A_LoopFileFullPath
				, "name", A_LoopFileName
				, "run_path", run_path
				, "status", status
			)
		)
	}
}


; Run All the Scripts with Status true, Keep Their Pid
for script in scripts{
	if !script["status"]
		continue
	Run(Format("
	(
	"{1}" "{2}"
	)", script["run_path"], script["path"]),,, &pid) 
	script["pid"] := pid
}






OnExit ExitSub ; Gosub to ExitSub when this Script Exits


; Build Menu and TrayTip then Remove Tray Icons
	TrayTipBuild()
	MenuBuild()
	OnMessage(0x404, AHK_NOTIFYICON) ; Hook Events for Tray Icon (used for Tray Icon cleanup on mouseover)
	OnMessage(0x7E, AHK_DISPLAYCHANGE) ; Hook Events for Display Change (used for Tray Icon cleanup on resolution change)
	TrayIconRemove(10)


;} ------------------------------------------	   END AUTO-EXECUTE		------------------------------------------


;{ ------------------------------------------		  HOTKEYS			------------------------------------------
	~#^!Escape::ExitApp ; <-- Terminate Script
;} ------------------------------------------		END HOTKEYS			------------------------------------------


;{ ------------------------------------------ 		SUBROUTINES 		------------------------------------------

TrayTipBuild() {
	Tip_Text := ""
	for script in scripts
		if script["status"]
			Tip_Text .= script["name"] "`n"
	Sort(Tip_Text)
	Tip_Text := trimAtDelim(Trim(Tip_Text, " `n"))
	A_IconTip := Tip_Text ; Tooltip is limited to first 127 characters
}

; Stop All the Scripts with Status true (Called When this Scripts Exits)
ExitSub(*) {
	for script in scripts {
		WinKill("ahk_pid " . script["pid"])
	}
	
}

;} ------------------------------------------     END_SUBROUTINES		------------------------------------------


;{ ------------------------------------------	  SUBROUTINES - GUI		------------------------------------------

MenuBuild() {
	; try Menu, SubMenu_Load, DeleteAll ; SubMenu_Load does not always exist
	SubMenu_Load := Menu()
	A_TrayMenu.Delete()

	

	for script in scripts {
		if script["status"] {
			pid := script["pid"]

			; add a menu to submenu
			SubMenu[pid] := Menu()
			SubMenu[pid].Add("View Lines", ScriptCommand)
			SubMenu[pid].Add("View Variables", ScriptCommand)
			SubMenu[pid].Add("View Hotkeys", ScriptCommand)
			SubMenu[pid].Add("View Key History", ScriptCommand)
			SubMenu[pid].Add()
			SubMenu[pid].Add("Open", ScriptCommand)
			SubMenu[pid].Add("Edit", ScriptCommand)
			SubMenu[pid].Add("Pause", ScriptCommand)
			SubMenu[pid].Add("Suspend", ScriptCommand)
			SubMenu[pid].Add("Reload", ScriptCommand)
			SubMenu[pid].Add("Exit", ScriptCommand)
			A_TrayMenu.Add(script["name"], SubMenu[pid])
		} else {
			SubMenu_Load.Add(script["name"], ScriptCommand_Load)
		}
	}


	; try A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	if (DllCall("GetMenuItemCount", "ptr", SubMenu_Load.Handle)) {	; SubMenu_Load items count
		A_TrayMenu.Add()
		A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	}
	A_TrayMenu.AddStandard()
	try A_TrayMenu.Default := SubMenu_Load ; SubMenu_Load does not always exist
}


ScriptCommand(ItemName, junk , ItemParent) {
	; cmd := Map("Item", wParam)
	
	cmd := Map(
		"Open", 65300,
		"Reload", 65400,
		"Edit", 65401,
		"Pause", 65403,
		"Suspend", 65404,
		"Exit", 65405,
		"View Lines", 65406,
		"View Variables", 65407,
		"View Hotkeys", 65408,
		"View Key History", 65409,
	)

	pid := 0 
	for k, v in SubMenu {
		if (v = ItemParent)
			pid := k
	}

	if (ItemName = "Reload") {
		for script in scripts 
			if (script["pid"] = pid) {
				SubMenu[pid].Delete()
				PostMessage(0x111, cmd["Exit"],,, "ahk_pid " .  pid)
				Run(Format("
				(
				"{1}" "{2}"
				)", script["run_path"], script["path"]),,, &pid) 
				script["pid"] := pid

				break
			}
		MenuBuild() ; need to rebuild menu because changed Pid is used in menu names
		TrayIconRemove(8)
	} else {
		if (ItemName = "Pause" or ItemName = "Suspend")
			SubMenu[pid].ToggleCheck(ItemName)

		PostMessage(0x111, cmd[ItemName],,, "ahk_pid " . pid)
	}

	if (ItemName = "Exit") {
		for script in scripts 
			if (script["pid"] = pid) {
				script["status"] := false
				
				break
			}

		; Rebuild Menu and TrayTip
		MenuBuild()
		TrayTipBuild()
	}
}

ScriptCommand_Load(ItemName, *) {
	script_path := ""
	script_runpath := ""

	for script in scripts 
		if (script["name"] = ItemName) {
			; Run Script and Keep Info
			Run(Format("
			(
			"{1}" "{2}"
			)", script["run_path"], script["path"]),,, &pid)		
			script["pid"] := pid
			script["status"] := true

			break
		}

		MenuBuild()
		TrayTipBuild()	
		TrayIconRemove(8)
}

;} ------------------------------------------   END SUBROUTINES - GUI	------------------------------------------

;{ ------------------------------------------		 FUNCTIONS			------------------------------------------

TrayIconRemove(attempts) {
	Loop attempts	; Try To Remove Over Time Because Icons May Lag Especially During Bootup
	{
		for script in scripts
			if script["status"] {
				; v1
				; WinGet, OutputVar, List , WinTitle, WinText, ExcludeTitle, ExcludeText
				hWnds := WinGetList("ahk_pid " . script["pid"])

				for i in hWnds 
					KillTrayIcon(i)
			}
		Sleep A_index**2 * 200
	}
}


; Lexikos 
KillTrayIcon(scriptHwnd) {

	static NIM_DELETE := 2, AHK_NOTIFYICON := 1028, size := 936+4*A_PtrSize
	
	; v1 VarSetCapacity(TargetVar [, RequestedCapacity, FillByte])
	; v2 VarSetStrCapacity(&TargetVar [, RequestedCapacity])
	; doesn't work with numput for reason 1 in the following
	; https://www.autohotkey.com/boards/viewtopic.php?p=334037#p334037
	nic := Buffer(size)

	; granted := VarSetStrCapacity(&nic, size)

	; v1 NumPut(Number, VarOrAddress [, Offset, Type])
	; v2 NumPut Type, Number[, Type2, Number2, ...] Target [, Offset]
	NumPut("uint", size, nic, 0)
	NumPut("uptr", scriptHwnd, nic, A_PtrSize)
	NumPut("uint", AHK_NOTIFYICON, nic, A_PtrSize*2)
	return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_DELETE, "ptr", nic)
}

trimAtDelim(String, Length:=124, Delim:="`n", Tail:="...") {
	if (StrLen(String)>Length) {
		RegExMatch(SubStr(String, 1, Length+1),"(.*)" Delim, &Match)
		Result := Match . Tail
	} else {
		Result := String
	}
	return Result
}

AHK_NOTIFYICON(wParam, lParam, uMsg, hWnd) ; OnMessage(0x404, "AHK_NOTIFYICON")
{
; Cleanup Tray Icons on MouseOver
	if (lParam = 0x200) ; WM_MOUSEMOVE := 0x200
		TrayIconRemove(1)
}

AHK_DISPLAYCHANGE(wParam, lParam, *) { ; OnMessage(0x7E, "AHK_DISPLAYCHANGE")
; Cleanup Tray Icons on Resolution Change
	
	TrayIconRemove(8) ; Resolution Change can take a moment so try over time
}


;} ------------------------------------------	   END FUNCTIONS		------------------------------------------
I'm still trying to figure out v2 and wanted this to work SOOOO BADLY. Not sure what the issue was on my end but I hacked my around the hurdles and came up with the following. All kuddos go to those above. Hope it helps someone.

Code: Select all

; AHK Startup
; Fanatic Guru - ported to v2 by <someone>
;
; Version: 2023 03 16
;
; Startup Script for Startup Folder to Run on Bootup.
;{-----------------------------------------------
; Runs the Scripts Defined in the Files Array
; Removes the Scripts' Tray Icons leaving only AHK Startup
; Creates a ToolTip for the One Tray Icon Showing the Startup Scripts
; If AHK Startup is Exited All Startup Scripts are Exited
; Includes a "Load" menu for a list of scripts that are not currently loaded
; Includes flags to allow for running of both v1 and v2 scripts
;}

;{ ------------------------------------------ 	  INIT - ENVIROMENT		------------------------------------------

#Requires AutoHotkey v2.0
#SingleInstance force  ; Ensures that only the last executed instance of script is running
SendMode("Input")  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir A_ScriptDir  ; Ensures a consistent starting directory.
DetectHiddenWindows(true)

;{ ------------------------------------------	   INIT - VARIABLES		------------------------------------------

; use * or *.smth ; it's directory recursive tho; or use complete file path

runPathV1         := "C:\Program Files\AutoHotkey\AutoHotkeyU64.exe"
runPathV2         := "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe"

; Directories containing scripts that will be launched; NOT recursive (allows nested lib folders). Does not start files in folders containing "USE AS NEEDED"
scriptFolders	  	:= 	[ A_ScriptDir "\Scripts - Active\*.ahk"
				    	, A_ScriptDir "\Scripts - Active\z - USE AS NEEDED\*.ahk"
						, A_ScriptDir "\Scripts - Active - v2\*.ahk"
						, A_ScriptDir "\Scripts - Active - v2\z - USE AS NEEDED\*.ahk"]


scripts 		  	:= 	[]
SubMenu 			:= 	Map()

For i, v in scriptFolders
{
	Loop Files, v, "F"
	{
		scripts.push(Map("Path", A_LoopFileFullPath
					, "Name", A_LoopFileName
					; Change the value in "InStr" to use whatever convention you want to used to identfy v1 v. v2 scripts
					, "Runtime", (inStr(A_LoopFileFullPath,"v1")) ? runPathV1 : runPathV2
					;	Change the value in "InStr" to identify folder that will not launch automatically
					, "Autorun", (inStr(A_LoopFileFullPath, "USE AS NEEDED")) ? 0 : 1
					, "PID", 1))
	}
}

;{ ------------------------------------------ 		 AUTO-EXECUTE		------------------------------------------

; Run All the Scripts with Status true, Keep Their Pid
for index, value in scripts
{
	sleep(500)
	if (value["Autorun"] == 0)
		continue
	Run(Format('"{1}" ' '"{2}"', value["Runtime"], value["Path"]),,, &pid)
	value["PID"] := PID
}

OnExit ExitSub ; Gosub to ExitSub when this Script Exits

; Build Menu and TrayTip then Remove Tray Icons
	TrayTipBuild()
	MenuBuild()
	OnMessage(0x404, AHK_NOTIFYICON) ; Hook Events for Tray Icon (used for Tray Icon cleanup on mouseover)
	OnMessage(0x7E, AHK_DISPLAYCHANGE) ; Hook Events for Display Change (used for Tray Icon cleanup on resolution change)
	TrayIconRemove(10)

;{ ------------------------------------------		  HOTKEYS			------------------------------------------
	~#^!Escape::ExitApp ; <-- Terminate Script

;{ ------------------------------------------ 		SUBROUTINES 		------------------------------------------

TrayTipBuild() {
	Tip_Text := ""
	for index, value in scripts
		if (value["Autorun"] == 1)
			Tip_Text .= value["Name"] "`n"
	Sort(Tip_Text)
	Tip_Text := trimAtDelim(Trim(Tip_Text, " `n"))
	A_IconTip := Tip_Text ; Tooltip is limited to first 127 characters
}

; Stop All the Scripts with Status true (Called When this Scripts Exits)
ExitSub(*) {
	for index, value in scripts {
		WinKill("ahk_pid " . value["PID"])
	}
}

;{ ------------------------------------------	  SUBROUTINES - GUI		------------------------------------------

MenuBuild() {
	SubMenu_Load := Menu()
	A_TrayMenu.Delete()

	for index, value in scripts {
		if value["Autorun"] {
			pid := value["PID"]

			; add a menu to submenu
			SubMenu[pid] := Menu()
			SubMenu[pid].Add("View Lines", ScriptCommand)
			SubMenu[pid].Add("View Variables", ScriptCommand)
			SubMenu[pid].Add("View Hotkeys", ScriptCommand)
			SubMenu[pid].Add("View Key History", ScriptCommand)
			SubMenu[pid].Add()
			SubMenu[pid].Add("Open", ScriptCommand)
			SubMenu[pid].Add("Edit", ScriptCommand)
			SubMenu[pid].Add("Pause", ScriptCommand)
			SubMenu[pid].Add("Suspend", ScriptCommand)
			SubMenu[pid].Add("Reload", ScriptCommand)
			SubMenu[pid].Add("Exit", ScriptCommand)
			A_TrayMenu.Add(value["Name"], SubMenu[pid])
		} else {
			SubMenu_Load.Add(value["Name"], ScriptCommand_Load)
		}
	}

	; try A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	if (DllCall("GetMenuItemCount", "ptr", SubMenu_Load.Handle)) {	; SubMenu_Load items count
		A_TrayMenu.Add()
		A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
	}
	A_TrayMenu.AddStandard()
	try A_TrayMenu.Default := SubMenu_Load ; SubMenu_Load does not always exist
}


ScriptCommand(ItemName, junk , ItemParent) {
	; cmd := Map("Item", wParam)
	cmd := Map(
		"Open", 65300,
		"Reload", 65400,
		"Edit", 65401,
		"Pause", 65403,
		"Suspend", 65404,
		"Exit", 65405,
		"View Lines", 65406,
		"View Variables", 65407,
		"View Hotkeys", 65408,
		"View Key History", 65409,
	)

	pid := 0 
	for k, v in SubMenu {
		if (v = ItemParent)
			pid := k
	}

	if (ItemName == "Reload") {
		for index, value in scripts 
			if (value["PID"] = pid) {
				SubMenu[pid].Delete()
				PostMessage(0x111, cmd["Exit"],,, "ahk_pid " .  pid)
				Run(Format('"{1}" ' '"{2}"', value["Runtime"], value["Path"]),,, &pid) 
				value["PID"] := pid
				break
			}
		MenuBuild() ; need to rebuild menu because changed Pid is used in menu names
		TrayIconRemove(8)
	} else {
		if (ItemName == "Pause" || ItemName == "Suspend")
			SubMenu[pid].ToggleCheck(ItemName)

		PostMessage(0x111, cmd[ItemName],,, "ahk_pid " . pid)
	}

	if (ItemName = "Exit") {
		for index, value in scripts 
			if (value["PID"] == pid) {
				value["Autorun"] := 0
				break
			}

		; Rebuild Menu and TrayTip
		MenuBuild()
		TrayTipBuild()
	}
}

ScriptCommand_Load(ItemName, *) {
	script_path := ""
	script_runpath := ""

	for index, value in scripts 
		if (value["Name"] == ItemName) {
			; Run Script and Keep Info
			Run(Format('"{1}" ' '"{2}"', value["Runtime"], value["Path"]),,, &pid)		
			value["PID"] := pid
			value["Autorun"] := 1

			break
		}

		MenuBuild()
		TrayTipBuild()	
		TrayIconRemove(8)
}

;{ ------------------------------------------		 FUNCTIONS			------------------------------------------

TrayIconRemove(attempts) {
	Loop attempts	; Try To Remove Over Time Because Icons May Lag Especially During Bootup
	{
		for index, value in scripts
			if value["Autorun"] {
				; v1
				hWnds := WinGetList("ahk_pid " . value["PID"])

				for i in hWnds 
					KillTrayIcon(i)
			}
		Sleep A_index**2 * 200
	}
}

; Lexikos 
KillTrayIcon(scriptHwnd) {
	static NIM_DELETE := 2, AHK_NOTIFYICON := 1028, size := 936+4*A_PtrSize
	
	; v1 VarSetCapacity(TargetVar [, RequestedCapacity, FillByte])
	; v2 VarSetStrCapacity(&TargetVar [, RequestedCapacity])
	; doesn't work with numput for reason 1 in the following
	; https://www.autohotkey.com/boards/viewtopic.php?p=334037#p334037
	nic := Buffer(size)

	; granted := VarSetStrCapacity(&nic, size)

	; v1 NumPut(Number, VarOrAddress [, Offset, Type])
	; v2 NumPut Type, Number[, Type2, Number2, ...] Target [, Offset]
	NumPut("uint", size, nic, 0)
	NumPut("uptr", scriptHwnd, nic, A_PtrSize)
	NumPut("uint", AHK_NOTIFYICON, nic, A_PtrSize*2)
	return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_DELETE, "ptr", nic)
}

trimAtDelim(String, Length:=124, Delim:="`n", Tail:="...") {
	if (StrLen(String)>Length) {
		RegExMatch(SubStr(String, 1, Length+1),"(.*)" Delim, &Match)
		Result := Match[1] . Tail
	} else {
		Result := String
	}
	return Result
}

AHK_NOTIFYICON(wParam, lParam, uMsg, hWnd) ; OnMessage(0x404, "AHK_NOTIFYICON")
{
; Cleanup Tray Icons on MouseOver
	if (lParam = 0x200) ; WM_MOUSEMOVE := 0x200
		TrayIconRemove(1)
}

AHK_DISPLAYCHANGE(wParam, lParam, *) { ; OnMessage(0x7E, "AHK_DISPLAYCHANGE")
; Cleanup Tray Icons on Resolution Change
	
	TrayIconRemove(8) ; Resolution Change can take a moment so try over time
}

;} ------------------------------------------	   END FUNCTIONS		------------------------------------------

User avatar
fade2gray
Posts: 85
Joined: 21 Apr 2015, 12:28

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by fade2gray » 19 Aug 2023, 17:26

Excellent tool, reminiscent of SKAN's HandyTM.

This is my attempt to port @FanaticGuru's code, and I think I've manage to replicate the same functionality of the original. Of course, I had to incorporate much of both @franklin_clinton's and @Jasonosaj's code. 👍

Edit: I discovered an oversight in @franklin_clinton's conversion, in that orphaned items remain in the tray menu after launching a non-persistent scripts - possibly inhereted in @Jasonosaj's conversion also.

Example: Use converted AHK Startup to launch a 'run once and exit' script.
Capture.PNG
Capture.PNG (57.41 KiB) Viewed 1361 times

I've rectified this by adding some extra code at line: 268.

Code: Select all

; AHK Startup  - version: 2023 08 19
; Fanatic Guru - ported to v2 by fade2gray (credit to franklin_clinton & Jasonosaj)
; Version: 2023 08 19

; INITIALIZATION ENVIROMENT
#Requires AutoHotkey v2.0
#SingleInstance force
SendMode("Input")
SetWorkingDir A_ScriptDir
DetectHiddenWindows(true)
; INITIALIZATION ENVIROMENT END

; INITIALIZATION  VARIABLES
; Folder: all files in that folder and subfolders
; Relative Paths: .\ at beginning is the folder of the script, each additional . steps back one folder
; Wildcards: * and ? can be used
; Flags to the right are used to indicate additional instructions, can use tabs for readability
; Flags are #v1, #v2 and #NoRun.
; The #v2 flag is optional and will default to v2 if no version flags are included)
; Note: it is not necessary to have v1 or v2 in the path.

ScriptPaths := [ ; Examples (the array can be saved to a script and loaded with the #Include directive)
    "..\v1_test\test-v1-x.ahk" "#v1",
    ".\v2_test\test-v2-y.ahk",
    "C:\Users\username\AHK_test_scripts\v1\*.ahk" "#v1",
    "C:\Users\username\AHK_test_scripts\v2\*.ahk", "#NoRun",
    "C:\Users\username\AHK_test_scripts\v2\test-v2-1.ahk" "#v2",
    "C:\Users\username\AHK_test_scripts\v2\test-v2-2.ahk" "#v2",
    "C:\Users\username\AHK_test_scripts\v2\test-v2-3.ahk" "#v2",
]

RunPath_v1 := A_ProgramFiles "\AutoHotkey\AutoHotkey.exe"
RunPath_v2 := A_ProgramFiles "\AutoHotkey\v2\AutoHotkey.exe"
ScriptsInfo := []
SubMenu := Map()
; INITIALIZATION  VARIABLES END

; AUTO-EXECUTE
for ScriptPath in ScriptPaths {
    RunScript := ScriptPath ~= "#NoRun" ? false : true
    RunPath := ScriptPath ~= "#v1" ? RunPath_v1 : RunPath_v2
    ScriptPath := RegExReplace(ScriptPath, "#NoRun|#v1|#v2")
    RegExMatch(ScriptPath,"^(\.*)\\", &Match)

    If Match{ ; Look for relative pathing
        R := StrLen(Match.1)
        if (R=1)
            ScriptPath := A_ScriptDir SubStr(ScriptPath,R+1)
        else if (R>1)
            ScriptPath := SubStr(A_ScriptDir,1,InStr(A_ScriptDir,"\",,-1,R-3)) SubStr(ScriptPath,R+2)
    }

    if (RegExMatch((SplitPath(ScriptPath, &ScriptName)),"\\$") or not ScriptName ~= "\."){ ; If File ends in \ assume it is a folder
        ScriptPath := RTrim(ScriptPath, "\")
        Loop Files ScriptPath "\*.*", "R"{ ; Get full path of all files in folder and subfolders
            ScriptPath := A_LoopFileFullPath
            Store_ScriptsInfo()
        }
    } else if (ScriptName ~= "\*|\?"){
        Loop Files ScriptPath, "R"{ ; Get full path of all matching files in folder and subfolders
            ScriptPath := A_LoopFileFullPath
            Store_ScriptsInfo()
        }
    } else {
        Store_ScriptsInfo()
    }
}

; Execute Qualifying Scripts
for ScriptInfo in ScriptsInfo
{
    if !ScriptInfo["RunScript"]
        continue
    ExecuteScript(ScriptInfo)
}

OnExit ExitSub ; Gosub to ExitSub when this Script Exits

; Build Menu and TrayTip then Remove Tray Icons
TrayTipBuild()
MenuBuild()
OnMessage(0x404, AHK_NOTIFYICON) ; Hook Events for Tray Icon (used for Tray Icon cleanup on mouseover)
OnMessage(0x7E, AHK_DISPLAYCHANGE) ; Hook Events for Display Change (used for Tray Icon cleanup on resolution change)
TrayIconRemove(10)
; AUTO-EXECUTE END

; HOTKEYS
    ~#^!Escape::ExitApp ; <-- Terminate Script
; HOTKEYS END

; SUBROUTINES
TrayTipBuild(){
    Tip_Text := ""
    for ScriptPath in ScriptsInfo
        if ScriptPath["RunScript"]
            Tip_Text .= ScriptPath["ScriptName"] "`n"
    Sort(Tip_Text)
    Tip_Text := trimAtDelim(Trim(Tip_Text, " `n"))
    A_IconTip := Tip_Text ; Tooltip is limited to first 127 characters
}

; Stop All the Scripts with Status true (Called When this Scripts Exits)
ExitSub(*){
    for ScriptInfo in ScriptsInfo {
        If (ScriptInfo["RunScript"] and ScriptInfo["pid"])
            ProcessClose ScriptInfo["pid"]
    }
}
; SUBROUTINES END

; GUI SUBROUTINES
MenuBuild(){
    ; try Menu, SubMenu_Load, DeleteAll ; SubMenu_Load does not always exist
    SubMenu_Load := Menu()
    A_TrayMenu.Delete()

    for ScriptInfo in ScriptsInfo {
        if ScriptInfo["RunScript"] {
            pid := ScriptInfo["pid"]

            ; add a menu to submenu
            SubMenu[pid] := Menu()
            SubMenu[pid].Add("View Lines", ScriptCommand)
            SubMenu[pid].Add("View Variables", ScriptCommand)
            SubMenu[pid].Add("View Hotkeys", ScriptCommand)
            SubMenu[pid].Add("View Key History", ScriptCommand)
            SubMenu[pid].Add()
            SubMenu[pid].Add("Open", ScriptCommand)
            SubMenu[pid].Add("Edit", ScriptCommand)
            SubMenu[pid].Add("Pause", ScriptCommand)
            SubMenu[pid].Add("Suspend", ScriptCommand)
            SubMenu[pid].Add("Reload", ScriptCommand)
            SubMenu[pid].Add("Exit", ScriptCommand)
            A_TrayMenu.Add(ScriptInfo["ScriptName"], SubMenu[pid])
        } else {
            SubMenu_Load.Add(ScriptInfo["ScriptName"], ScriptCommand_Load)
        }
    }

    ; try A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
    if (DllCall("GetMenuItemCount", "ptr", SubMenu_Load.Handle)){	; SubMenu_Load items count
        A_TrayMenu.Add()
        A_TrayMenu.Add("Load", SubMenu_Load) ; SubMenu_Load does not always exist
    }
    A_TrayMenu.AddStandard()
    try A_TrayMenu.Default := SubMenu_Load ; SubMenu_Load does not always exist
}

ScriptCommand(ItemName, junk , ItemParent){
    ; cmd := Map("Item", wParam)
    cmd := Map(
        "Open", 65300,
        "Reload", 65400,
        "Edit", 65401,
        "Pause", 65403,
        "Suspend", 65404,
        "Exit", 65405,
        "View Lines", 65406,
        "View Variables", 65407,
        "View Hotkeys", 65408,
        "View Key History", 65409,
    )

    pid := 0 
    for k, v in SubMenu {
        if (v = ItemParent)
            pid := k
    }

    if (ItemName = "Reload"){
        for ScriptInfo in ScriptsInfo 
            if (ScriptInfo["pid"] = pid){
                SubMenu[pid].Delete()
                PostMessage(0x111, cmd["Exit"],,, "ahk_pid " pid)
                ExecuteScript(ScriptInfo)
                break
            }
        MenuBuild() ; need to rebuild menu because changed Pid is used in menu names
        TrayIconRemove(8)
    } else {
        if (ItemName = "Pause" or ItemName = "Suspend")
            SubMenu[pid].ToggleCheck(ItemName)

        PostMessage(0x111, cmd[ItemName],,, "ahk_pid " pid)
    }

    if (ItemName = "Exit"){
        for ScriptInfo in ScriptsInfo 
            if (ScriptInfo["pid"] = pid){
                ProcessClose ScriptInfo["pid"]
                ScriptInfo["RunScript"] := false
                break
            }

        ; Rebuild Menu and TrayTip
        MenuBuild()
        TrayTipBuild()
    }
}

ScriptCommand_Load(ItemName, *){
    for ScriptInfo in ScriptsInfo 
        if (ScriptInfo["ScriptName"] = ItemName){
            ExecuteScript(ScriptInfo)
            ScriptInfo["RunScript"] := true
            break
        }

        MenuBuild()
        TrayTipBuild()	
        TrayIconRemove(8)
}
; GUI SUBROUTINES END

; FUNCTIONS
TrayIconRemove(attempts){
    Loop attempts	; Try To Remove Over Time Because Icons May Lag Especially During Bootup
    {
        for ScriptInfo in ScriptsInfo
            if ScriptInfo["RunScript"] {
                ; v1
                ; WinGet, OutputVar, List , WinTitle, WinText, ExcludeTitle, ExcludeText
                hWnds := WinGetList("ahk_pid " ScriptInfo["pid"])

                for i in hWnds 
                    KillTrayIcon(i)
            }
        Sleep A_index**2 * 200
    }
}

; Credit Lexikos 
KillTrayIcon(scriptHwnd){

    static NIM_DELETE := 2, AHK_NOTIFYICON := 1028, size := 936+4*A_PtrSize
    
    ; v1 VarSetCapacity(TargetVar [, RequestedCapacity, FillByte])
    ; v2 VarSetStrCapacity(&TargetVar [, RequestedCapacity])
    ; doesn't work with numput for reason 1 in the following
    ; https://www.autohotkey.com/boards/viewtopic.php?p=334037#p334037
    nic := Buffer(size)

    ; granted := VarSetStrCapacity(&nic, size)

    ; v1 NumPut(Number, VarOrAddress [, Offset, Type])
    ; v2 NumPut Type, Number[, Type2, Number2, ...] Target [, Offset]
    NumPut("uint", size, nic, 0)
    NumPut("uptr", scriptHwnd, nic, A_PtrSize)
    NumPut("uint", AHK_NOTIFYICON, nic, A_PtrSize*2)
    return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_DELETE, "ptr", nic)
}

trimAtDelim(String, Length:=124, Delim:="`n", Tail:="..."){
    if (StrLen(String)>Length){
        RegExMatch(SubStr(String, 1, Length+1),"(.*)" Delim, &Match)
        Result := Match Tail
    } else {
        Result := String
    }
    return Result
}

AHK_NOTIFYICON(wParam, lParam, uMsg, hWnd){ ; OnMessage(0x404, "AHK_NOTIFYICON")
; Cleanup Tray Icons on MouseOver
    if (lParam = 0x200) ; WM_MOUSEMOVE := 0x200
        TrayIconRemove(1)

    ; Remove orphaned entries of non-persistent scripts that remain in tray menu.
    for ScriptInfo in ScriptsInfo
        if (ScriptInfo["RunScript"] and !ProcessExist(ScriptInfo["pid"])){
            ScriptInfo["RunScript"] := 0
            MenuBuild()
            TrayTipBuild()
        }
}

AHK_DISPLAYCHANGE(wParam, lParam, *){ ; OnMessage(0x7E, "AHK_DISPLAYCHANGE")
; Cleanup Tray Icons on Resolution Change
    TrayIconRemove(8) ; Resolution Change can take a moment so try over time
}

ExecuteScript(ScriptInfo){
    Run(
        Format(
            '"{1}"' " " '"{2}"',
            ScriptInfo["RunPath"],
            ScriptInfo["ScriptPath"]
        )
        ,,, &pid
    )
    ScriptInfo["pid"] := pid
}

Store_ScriptsInfo(){
    SplitPath ScriptPath, &ScriptName
    ScriptsInfo.Push(
        Map(
            "ScriptName", ScriptName,
            "ScriptPath", ScriptPath,
            "RunPath", RunPath,
            "RunScript", RunScript,
        )
    )
}
;{ FUNCTIONS END

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by FanaticGuru » 31 Aug 2023, 15:05

AHK Startup v2

An AutoHotkey v2 version can now be found here: viewtopic.php?f=83&t=120981

This was more difficult to convert than I anticipated. It hit on many of the major differences between v1 and v2. Dynamic Variables, Object Structures, and GUI/Menu items being some of the big ones. v2 does each of these quite a bit differently.

The v2 version is probably a little more efficiently coded and easier to understand.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

gibbons6546
Posts: 31
Joined: 07 Apr 2020, 11:53

Re: AHK Startup (Consolidate AHK Scripts' Tray Icons)

Post by gibbons6546 » 26 Jan 2024, 21:57

Hello all,

I have been attempting to run version 2023 03 16 of AHK Startup but am getting a particular error that did not arise in the previous version. Every time I run this new version of AHK startup I get an error for each script that it attempts to run. The error only occurs when AHK Startup attempts to launch a script from a shortcut file (*.lnk file). If you point it to launch an *.ahk file, it will launch that file without issue.

In the previous version, AHK Startup was able to launch AHK files from their *.lnk shortcut files. Now, version 2023 03 16 of AHK Startup generates the following error when it attempts to launch an AHK file via a shortcut file. Thank you for any assistance.
Error at line 1.

Line Text: L
Error: This line does not contain a recognized action.

The program will exit.

Post Reply

Return to “Scripts and Functions (v1)”