NotifyTrayClick() : Notifies when AHK tray icon is clicked

Post your working scripts, libraries and tools
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

NotifyTrayClick() : Notifies when AHK tray icon is clicked

17 Sep 2020, 18:52

Introduction:
 
Back in classic days, @lexikos discovered and described a simple way to hook mouse events on the script's tray icon. Since then I have
used this AHK_NOTIFYICON method in some of my scripts, but every time I had to figure it out on how to customize it for specific needs.
AHK_NOTIFYICON mesage handler will receive 9 click notifications, 3 each for Left, Middle and Right mouse buttons and how nice it would be
if we had labels, one for each? Something like individual GuiContextMenu label where we handle right-click event for every GUI.
Most surely it will not happen!.
I give you the second best solution. NotifyTrayClick() : A function based implementation which provides individual labels for all 9 click events.
 
How to use it:
  • 1) Copy/paste my function in to your script.
    2) Handle events by using any/some/all of the following labels. (You may use functions instead of labels... or a mix of both)

    Code: Select all

    NotifyTrayClick_201:   ; Left click (Button down)
    NotifyTrayClick_202:   ; Left click (Button up)
    NotifyTrayClick_203:   ; Left double click
    NotifyTrayClick_204:   ; Right click (Button down)
    NotifyTrayClick_205:   ; Right click (Button up)
    NotifyTrayClick_206:   ; Right double click
    NotifyTrayClick_207:   ; Middle click (Button down)
    NotifyTrayClick_208:   ; Middle click (Button up)
    NotifyTrayClick_209:   ; Middle double click
Notes:
  • NotifyTrayClick_201 label (Left click down) is not of much use. You will receive the event only when you release the mouse left button.
  • I suggest using only Button up/Double click event for one mouse button.
    If you attempt to all handle three events i.e., Button down/Button up/Double click for a button, then Double click event will be followed
    by a Button down event. It is easy to work-around though. A demo is available in examples.
  • The mere existence of a label will suppress its original functionality. If you have NotifyTrayClick_205: (Right click up) in your script,
    then right click menu wouldn't work and script can't be exited. Bind ExitApp to a hotkey when your script is in testing stage.
  • The cursor has to be over tray icon to receive a "button up" event. For eg. If you click on the tray icon - move the cursor away from the icon - release
    the button, then script will never receive the event.
 
 
NotifyTrayClick(DoubleClickTime)
 
By default, the function will auto-initialize with DoubleClickTime of 250 ms. This time duration is used by the function to differentiate single
from double clicks. To override it with a custom value, say 300 ms, call the function from auto-execute section, like: NotifyTrayClick(300).
Or, if you want to apply the system default value, pass API function GetDoubleClickTime() as parameter.
:arrow:  NotifyTrayClick( DllCall("GetDoubleClickTime") )
Note: DoubleClickTime will be applied to single click of a button, only if its counter-part double click handler is present in script.

The function:
 

Code: Select all

NotifyTrayClick(P*) {              ;  v0.41 by SKAN on D39E/D39N @ tiny.cc/notifytrayclick
Static Msg, Fun:="NotifyTrayClick", NM:=OnMessage(0x404,Func(Fun),-1),  Chk,T:=-250,Clk:=1
  If ( (NM := Format(Fun . "_{:03X}", Msg := P[2])) && P.Count()<4 )
     Return ( T := Max(-5000, 0-(P[1] ? Abs(P[1]) : 250)) )
  Critical
  If ( ( Msg<0x201 || Msg>0x209 ) || ( IsFunc(NM) || Islabel(NM) )=0 )
     Return
  Chk := (Fun . "_" . (Msg<=0x203 ? "203" : Msg<=0x206 ? "206" : Msg<=0x209 ? "209" : ""))
  SetTimer, %NM%,  %  (Msg==0x203        || Msg==0x206        || Msg==0x209)
    ? (-1, Clk:=2) : ( Clk=2 ? ("Off", Clk:=1) : ( IsFunc(Chk) || IsLabel(Chk) ? T : -1) )
Return True
}
v0.41: Code fixed. Thanks @Relayer
 
 
Examples:

Simple example: Show Tray menu with left click / Suppress right click menu

Code: Select all

#SingleInstance, Force
Return                 ; end of auto-execute section

NotifyTrayClick_202:   ; Left click (Button up)
  Menu, Tray, Show
Return

NotifyTrayClick_205:   ; Right click (Button up)
Return                 ; Supress right click menu

; Copy paste NotifyTrayClick() below
 
Complex example
 
 
Click History Script
 
 
Related: Start menu like Tray menu
User avatar
elModo7
Posts: 189
Joined: 01 Sep 2017, 02:38
GitHub: elModo7
Location: Spain
Contact:

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

18 Sep 2020, 01:45

Quite useful, thank you!
:beer:
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

18 Sep 2020, 04:23

elModo7 wrote: Quite useful, thank you!
:D :thumbup:
User avatar
SirSocks
Posts: 208
Joined: 26 Oct 2018, 08:14

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

18 Sep 2020, 07:07

WOW! This is amazing! This should be included with the next update of ahk! I often want to see what's on my clipboard, I think I'll utilize this to display a gui of my clipboard.

Code: Select all

#NoEnv 
SendMode Input 
#Persistent
#SingleInstance Force
#InstallMousehook
Return

NotifyTrayClick_207:   ; middle click (Button down)
gui, add,text,, %clipboard%
gui, show
keywait, mbutton
gui, destroy
return

NotifyTrayClick(P*) {              ;  v0.39 by SKAN on D39E/D39I @ tiny.cc/notifytrayclick
Static Msg, Fun:="NotifyTrayClick", NM:=OnMessage(0x404,Func(Fun),-1),  Chk,T:=-250,Clk:=1
Critical
  If ( (NM := Format(Fun . "_{:03X}", Msg := P[2])) && P.Count()<4 )
     Return ( T := Max(-5000, 0-(P[1] ? Abs(P[1]) : 250)) )
  If ( ( Msg<0x201 || Msg>0x209 ) || ( IsFunc(NM) || Islabel(NM) )=0 )
     Return
  Chk := (Fun . "_" . (Msg<=0x203 ? "203" : Msg<=0x206 ? "206" : Msg<=0x209 ? "209" : ""))
  SetTimer, %NM%,  %  (Msg==0x203        || Msg==0x206        || Msg==0x209)
    ? (-1, Clk:=2) : ( Clk=2 ? ("Off", Clk:=1) : ( IsFunc(Chk) || IsLabel(Chk) ? T : -1) )
Return True
}

/*
NotifyTrayClick_201:   ; Left click (Button down)
NotifyTrayClick_202:   ; Left click (Button up)
NotifyTrayClick_203:   ; Left double click
NotifyTrayClick_204:   ; Right click (Button down)
NotifyTrayClick_205:   ; Right click (Button up)
NotifyTrayClick_206:   ; Right double click
NotifyTrayClick_207:   ; Middle click (Button down)
NotifyTrayClick_208:   ; Middle click (Button up)
NotifyTrayClick_209:   ; Middle double click
*/
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

18 Sep 2020, 07:58

SirSocks wrote:

Code: Select all

keywait, mbutton
Ah! I forgot to document this (I have updated the OP now)
If you click on the tray icon - move the cursor away from the icon - release the button, then script will never receive the button up event.
KeyWait is a good alternative. Thanks for the input. :) :thumbup:
User avatar
Relayer
Posts: 138
Joined: 30 Sep 2013, 13:09
Location: Delaware, USA

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

25 Sep 2020, 12:12

Skan,

I've been playing with your function and I noticed a couple of things. One was that it sets the host script to Critical ON because the initial call is not a new thread as is the OnMessage call. Moving the 'Critical' statement to after the initialization fixed that. Also, each value of NM, i.e. function name, generates a separate distinct timer. So, while the script appears to use one timer, it can be many. I verified this using Lexiko's GetTimer() function. This came to light when I had a left click defined but no left double click... a double would trigger the single whereas I thought it should ignore it. I needed an array to keep track of pending timers because there could be one for right click down and up pending while waiting for a double right click. I also gave the user a choice to neuter the native actions of a type of click or not. I took a crack at rewriting the script for my needs. In my version I set it up to accept a call table for each type of action... not sure if I am satisfied with that yet. It is not as terse as your style but someone may find it useful.

Code: Select all

/*
---------------------------------------------------------------------------------------
Function      : onTrayClick(P*)
Description   : Sets up a way to respond to mouse clicks on a tray icon using OnMessage
              : 0x404 ="WM_NOTIFYICON".  P[2] or LParam contains the click state.
              : 
Parameters    : P needs to be variadic so that it can collect info from OnMessage.
              : Call this function once with P as an array containing:
              :   key = 1 => new delay for double click sensing, default = 250 mSec.
              :   key = L => vector for left click
              :   key = L2 => vector for double left click
              :   key = Rd => vector for right click DOWN
              :   key = Ru => vector for right click UP
              :   key = R2 => vector for double right click
              :   key = Md => vector for middle click DOWN
              :   key = Mu => vector for middle click UP
              :   key = M2 => vector for double middle click
              : Add a '+' to the key (prefix or suffix) to enable native action in addition to vector action.
              : 
Returns       : True if action defined with a vector without '+'... this neuters native actions.
              : "" if not defined with a vector... this allows native actions.
              : 
Remarks       : Left-down (513 or 0x201) only received after left-up triggered so is basically useless.
              : Note that every mouse movement on hover triggers code 512 (0x200); this is ignored.
              : 
              : WM_NOTIFYICON sends the following on double click:
              :   down, up, double click, up
              : 
              : 
              : 
---------------------------------------------------------------------------------------
*/
onTrayClick(P*)
{
	static NM, T := 0, Clk := 1, vector := [], pending := []
	if (T = 0)
	{
		for k, v in P[1]
		{
			switch StrReplace(k, "+")
			{
				case 1   : vector[1]   := v,                  tmp := True
				case "L" : vector[514] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "L2": vector[515] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "Rd": vector[516] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "Ru": vector[517] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "R2": vector[518] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "Md": vector[519] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "Mu": vector[520] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
				case "M2": vector[521] := [v, InStr(k, "+")], tmp := (isFunc(v) || isLabel(v))
			}
			if (!tmp)
			{
				Msgbox % A_ThisFunc . ":`nCallback """ . v . """ is not a function or label!"
				T := 0 ;in case of multiple failed calls
				Return False
			}
		}
		if (vector.Length() < 512)
		{
			Msgbox % A_ThisFunc . ":`nNo callback function(s) or label(s) defined."
			T := 0 ;in case of multiple failed calls
			Return False
		}
		OnMessage(0x404, Func(A_ThisFunc), -1)
		Return ( T := Max(-5000, 0 - (vector[1] ? Abs(vector[1]) : 250)) )
	}
	Critical ;will be turned off as OnMessage thread ends at 'return'
		if (tmp := (P[2] = 515 || P[2] = 518 || P[2] = 521))
			while % q := pending.Pop()
				SetTimer, %q%, % ("Off", Clk := 2)
		if ( ( P[2] < 513 || P[2] > 521 ) or !( IsFunc(NM := vector[P[2], 1]) || Islabel(NM) ) )
			Return ;this return will allow native actions not redefined here to take place normally

		SetTimer, %NM%, % tmp ? (-1, Clk := 2) : ( Clk = 2 ? ("Off", Clk := 1) : (T, pending.Push(NM)) )
	Return vector[P[2], 2] ? "" : True
}
Relayer
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

25 Sep 2020, 13:26

Hi @Relayer
Thanks for the wonderful feedback and for sharing your version :thumbup:
 
Thanks for the tip on critical.. For the rest of your code, I will have to study it.
I will reply after a thorough test.

:)
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

25 Sep 2020, 17:05

@Relayer
 
I'm yet to study your code.
I've fixed the Critical part of the code.
 
Relayer wrote:
25 Sep 2020, 12:12
when I had a left click defined but no left double click... a double would trigger the single whereas I thought it should ignore it.
 
That is the intended effect.
For eg., if the user wants to Run, notepad.exe on left single click, it should run as many times the user left clicks the tray icon.
If the user wants to suppress the script's main window from showing, then the script has to "handle" that event.
In case the user does want the native functionality for left double click, then it can be worked around as follows:

Code: Select all

#SingleInstance, Force
NotifyTrayClick(250)
Return                 ; end of auto-execute section

NotifyTrayClick_202:   ; Left click (Button up)
  If ( LeftDblClick and not (LeftDblClick:=0) )
    Return
  Menu, Tray, Show
Return

NotifyTrayClick_203:   ; Left double click
  LeftDblClick:=1
  DllCall("PostMessage", "Ptr",A_ScriptHwnd, "Int",0x111, "Ptr",65300, "Ptr",0, "Ptr") ; Open = 65300
Return

; Copy paste NotifyTrayClick() below
 
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

25 Sep 2020, 17:23

On a related note:
Seemingly, Tray menu can be trimmed as follows:

Code: Select all

#NoEnv
#SingleInstance, Force
#Persistent

hMenu := MenuGetHandle("Tray")
DllCall("RemoveMenu", "Ptr",hMenu, "Int",65300, "Int",0 ) ; Open
DllCall("RemoveMenu", "Ptr",hMenu, "Int",65301, "Int",0 ) ; Help
DllCall("RemoveMenu", "Ptr",hMenu, "Int",65308, "Int",0 ) ; Seperator
DllCall("RemoveMenu", "Ptr",hMenu, "Int",65302, "Int",0 ) ; Window Spy
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65303, "Int",0 ) ; Reload the script
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65304, "Int",0 ) ; Edit this script
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65305, "Int",0 ) ; Suspend Hotkeys
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65306, "Int",0 ) ; Pause script
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65309, "Int",0 ) ; Seperator
;DllCall("RemoveMenu", "Ptr",hMenu, "Int",65307, "Int",0 ) ; Exit

Return
lexikos
Posts: 7088
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

25 Sep 2020, 19:20

SKAN wrote:
25 Sep 2020, 17:23
Seemingly, Tray menu can be trimmed as follows:
AutoHotkey v2 allows the standard items to be deleted or modified by name or position, the same as any other items; e.g. A_TrayMenu.Delete("&Open"). It is possible to assign icons or override the function of an item without recreating all of the other standard items.

RemoveMenu also works, but you'd use hMenu := A_TrayMenu.Handle instead of MenuGetHandle.
User avatar
SKAN
Posts: 853
Joined: 29 Sep 2013, 16:58

Re: NotifyTrayClick() : Notifies when AHK tray icon is clicked

26 Sep 2020, 05:27

@lexikos : Thanks for the info. :)

Return to “Scripts and Functions”

Who is online

Users browsing this forum: gwarble and 32 guests