Reacting to SHChangeNotify calls to shell32.dll (taskbar icon flickering issue)

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
fanfare
Posts: 2
Joined: 20 Jul 2018, 23:42
Contact:

Reacting to SHChangeNotify calls to shell32.dll (taskbar icon flickering issue)

21 Jul 2018, 01:01

I've been trying to mess with a script for some time now, and I feel like it is really close, but it's just not quite there.

There is an annoying flicker that happens on all the icons in the taskbar once in a while (sometimes all the time). This can be reproduced on demand by moving a file to the recycle bin, when nothing else is in the recycle bin, such as demonstrated here:

Image

After doing much research, I discovered that the flickering comes about when a program requests that the icons on the system be redrawn (for example, due to a file association change). The specific request is a 'SHChangeNotify' call made to shell32.dll. For example, the following ahk script will reproduce the very flickering that is so annoying:

Code: Select all

DllCall("Shell32\SHChangeNotify", Int, 0x8000000, UInt,0, Ptr,0, Ptr,0)
Just as an aside - I had thought of ways in the past to try and 'freeze' the taskbar visually while still letting a user click on the icons - and had some success with the following script:

Code: Select all

ControlGet, rebar, hwnd,, MSTaskListWClass1, ahk_class Shell_TrayWnd
WinSet, Style, 0x16000000, ahk_id %rebar%
This sets the style for the icon area in the taskbar to no longer have WM_CHILD style - which when triggered will keep a ghost copy of the taskbar icons in place, while still letting a user click on the icons. It can be recovered back to full interactiveness by changing the style back to 0x56000000 -- re-enabling WM_CHILD.

Now - when it comes to the SHChangeNotify part, I found a very useful ahk script by forum user SKAN ( https://autohotkey.com/board/topic/8413 ... ation-spy/ ) that actively listens for SHChangeNotify calls made to shell32 and outputs the messages to a box, live as they happen.

I figured the two could maybe be combined - so that when a request is made to shell32 to do some kind of redrawing, ahk could quickly trigger the dropping of WM_CHILD on the taskbar, and then re-instating it about 500ms later, after the flickering had finished, so visually, you wouldn't see the flickering happen.

I attempted to do so and.. it works -- but not all of the time. It looks like it's some kind of race condition. The system performs the redrawing pretty much the exact same time my script attempts to freeze the taskbar, so it either:

1) freezes it in time (so you dont see any transition)
2) freezes it a few milliseconds too late (so you see the flicker)
3) freezes mid-way during a flicker, so the taskbar icons disappear, and my script freezes it, so you end up with 500ms of no icons, then back to normal.

I've tried all I could think of messing with putting 'critical' in different places in the script, and running the script with 'real time' priority, attempting to prioritize what I'm trying to do, but I simply do not know if it is even possible to perform the WinSet taskbar freeze faster than shell32 performs the icon redrawing. My only thoughts left are either to somehow truly intercept the call being made to shell32, doing the WinSet command, and then passing on the real request to shell32 (i dont think this is possible) - or something like, using the https://github.com/tariqporter/Gdip library to occasionally create a hidden bitmap clone of the taskbar, waiting in memory, then temporarily paint it on top of the taskbar in-place when the SHChangeNotify call is made, and drop it a few milliseconds later, covering up the flickering (maybe it would be faster than WinSet? Not sure - sort of doubtful).

Below is a basic script that can be used to trigger a temporary freezing (yet still clickable) state of the taskbar.. I made it in such a way that the request to the initial function quickly returns, without using sleep or anything to trigger the secondary function which un-freezes the taskbar:

Code: Select all

#SingleInstance, Force
#Persistent
OnExit("recovershell")

global snubflashingactive := 0

ControlGet, controlrebar, hwnd,, MSTaskListWClass1, ahk_class Shell_TrayWnd
global rebar := controlrebar

recovershell() {
  if (snubflashingactive = 1) {
    taskbarunfreeze()
    ExitApp
  }
}

taskbarunfreeze() {
  SetTimer, taskbarunfreeze, Off
  WinSet, Style, 0x56000000, ahk_id %rebar%
  global snubflashingactive := 0
}

snubflashingicons() {
  if (snubflashingactive = 0) {
    global snubflashingactive := 1
    WinSet, Style, 0x16000000, ahk_id %rebar%
    SetTimer, taskbarunfreeze, 500
    return
  }
  else {
    return
  }
}

; call this to freeze the taskbar
snubflashingicons()
Again Here is the script by SKAN, Shell Change Notify Spy:

https://autohotkey.com/board/topic/8413 ... ation-spy/

Code: Select all

; Shell Change Notification Spy - By SKAN                            Created : 24-Aug-2012
; http://dl.dropbox.com/u/6428211/AutoHotkey/ShellChangeNotification/ShChangeNotifySpy.ahk

#SingleInstance, Force
SetWorkingDir, %A_ScriptDir%
OnExit, QuitScript

Gui +AlwaysOnTop +LastFound
hGui := WinExist()
Gui, Font, S8
Gui, Add, ListView, +Grid w600 r50, EVENT|Values
LV_SetImageList( DllCall( "ImageList_Create", Int,2, Int,20, Int,0x18, Int,1, Int,1 ), 1 )
LV_ModifyCol( 1, 150 )
Gui, Show, y150, Shell Change Notification Spy

MsgNo := DllCall( "RegisterWindowMessage", Str,"SHCHANGENOTIFY" )
OnMessage( MsgNo, "ShChangeNotify" )

DriveGet, DriveList, List, Fixed
Entries := StrLen( DriveList )
VarSetCapacity( $SHChangeNotifyEntry, 8 * Entries, 0 )
Loop, Parse, DriveList
   Off  := ( A_Index - 1 ) * 8
 , PIDL := PathGetPIDL( A_LoopField ":" )
 , NumPut( PIDL, $SHChangeNotifyEntry, Off+0 )
 , NumPut( True, $SHChangeNotifyEntry, Off+4 )

SHCNR_ID := DllCall( "Shell32\SHChangeNotifyRegister", UInt,hGui
                   , UInt, 0x8000|0x1000|0x2|0x1
                   , Int,  0xC0581E0|0x7FFFFFFF|0x80000000
                   , UInt, MsgNo
                   , Int,  Entries
                   , UInt, &$SHChangeNotifyEntry )
Return


GuiClose:
GuiEscape:
QuitScript:
 OnExit
 DllCall( "Shell32\SHChangeNotifyDeregister", UInt,SHCNR_ID )
 ExitApp


ShChangeNotify( wParam, lParam, msg, hwnd ) {
 hLock := DllCall( "Shell32\SHChangeNotification_Lock"
                 , UInt,wParam, UInt,lParam, UIntP,pppidl, UIntP,plEvent )

 Event := "SHCNE_UNDEFINED"
 IfEqual,plEvent,0x7FFFFFFF ,SetEnv,Event, SHCNE_ALLEVENTS
 IfEqual,plEvent,0x08000000 ,SetEnv,Event, SHCNE_ASSOCCHANGED
 IfEqual,plEvent,0x00000800 ,SetEnv,Event, SHCNE_ATTRIBUTES
 IfEqual,plEvent,0x00000002 ,SetEnv,Event, SHCNE_CREATE
 IfEqual,plEvent,0x00000004 ,SetEnv,Event, SHCNE_DELETE
 IfEqual,plEvent,0x0002381F ,SetEnv,Event, SHCNE_DISKEVENTS
 IfEqual,plEvent,0x00000100 ,SetEnv,Event, SHCNE_DRIVEADD
 IfEqual,plEvent,0x00010000 ,SetEnv,Event, SHCNE_DRIVEADDGUI
 IfEqual,plEvent,0x00000080 ,SetEnv,Event, SHCNE_DRIVEREMOVED
 IfEqual,plEvent,0x04000000 ,SetEnv,Event, SHCNE_EXTENDED_EVENT
 IfEqual,plEvent,0x00040000 ,SetEnv,Event, SHCNE_FREESPACE
 IfEqual,plEvent,0x0C0581E0 ,SetEnv,Event, SHCNE_GLOBALEVENTS
 IfEqual,plEvent,0x80000000 ,SetEnv,Event, SHCNE_INTERRUPT
 IfEqual,plEvent,0x00000020 ,SetEnv,Event, SHCNE_MEDIAINSERTED
 IfEqual,plEvent,0x00000040 ,SetEnv,Event, SHCNE_MEDIAREMOVED
 IfEqual,plEvent,0x00000008 ,SetEnv,Event, SHCNE_MKDIR
 IfEqual,plEvent,0x00000200 ,SetEnv,Event, SHCNE_NETSHARE
 IfEqual,plEvent,0x00000400 ,SetEnv,Event, SHCNE_NETUNSHARE
 IfEqual,plEvent,0x00020000 ,SetEnv,Event, SHCNE_RENAMEFOLDER
 IfEqual,plEvent,0x00000001 ,SetEnv,Event, SHCNE_RENAMEITEM
 IfEqual,plEvent,0x00000010 ,SetEnv,Event, SHCNE_RMDIR
 IfEqual,plEvent,0x00004000 ,SetEnv,Event, SHCNE_SERVERDISCONNECT
 IfEqual,plEvent,0x00001000 ,SetEnv,Event, SHCNE_UPDATEDIR
 IfEqual,plEvent,0x00008000 ,SetEnv,Event, SHCNE_UPDATEIMAGE
 IfEqual,plEvent,0x00002000 ,SetEnv,Event, SHCNE_UPDATEITEM

 If Val2 := PIDLGetPath( NumGet( pppidl+4 ) )
 LV_Insert( 1, "", "", Val2 )

 Val1 := PIDLGetPath( NumGet( pppidl+0 ) )
 LV_Insert( 1, "", Event, Val1 )

 DllCall( "Shell32\SHChangeNotification_Unlock", UInt,hLock )
}

PathGetPIDL( sPath ) {
 Return DllCall( "Shell32\ILCreateFromPath" ( A_IsUnicode ? "W":"A" ), Str,sPath, UInt )
}

PIDLGetPath( PIDL ) {
 VarSetCapacity( sPath, 520, 0 )
 DllCall( "Shell32\SHGetPathFromIDList" ( A_IsUnicode ? "W":"A" ), UInt,PIDL, Str,sPath )
Return sPath
}
Note the following part of the script in the above link:

Code: Select all

MsgNo := DllCall( "RegisterWindowMessage", Str,"SHCHANGENOTIFY" )
OnMessage( MsgNo, "ShChangeNotify" )
If you changed it to:

Code: Select all

OnMessage( MsgNo, "snubflashingicons" )
where 'snubflashingicons' is a function in my script to trigger my taskbar freezer/unfreezer, it will either work or almost work, as mentioned in my bullet points above. Like I said, it's really close (and a very hackish solution to a problem that I don't know if Microsoft will ever fix, since I've investigated thoroughly and traces of this visual glitch are present even in Windows 95, sadly) .. but I'm hoping someone here might have some insight, or alternative solutions to getting this problem solved once and for all.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot] and 172 guests