Cannot change tray icon after resolution change

Report problems with documented functionality
User avatar
snaphat
Posts: 4
Joined: 28 Mar 2021, 14:14
Contact:

Cannot change tray icon after resolution change

Post by snaphat » 28 Mar 2021, 14:22

After a resolution change with scaling, the specified icon for a script becomes corrupted (fuzzy), which is expected in Windows. In an attempt to work around the issue I am attempting to change the tray icon. However, commands to switch the tray icon do not work afterwards. The corrupted icon remains.

For example the following would not actually change the icon:

Code: Select all

Menu,Tray,Icon,OriginalIcon.ico

...
resolution change
...

Menu,Tray,Icon,DifferentIcon.ico
joefiesta
Posts: 497
Joined: 24 Jan 2016, 13:54
Location: Pa., USA

Re: Cannot change tray icon after resolution change

Post by joefiesta » 28 Mar 2021, 17:54

well, gee.. what is the code for "resolution change"?

without that, no one can test this.

And, are you talking about changing the resolution of your monitor? why would you do that? every time I change my monkitor's resolution, it totally screws up a gazillion things. But, then again, I customize a gazillion things about windows.
User avatar
snaphat
Posts: 4
Joined: 28 Mar 2021, 14:14
Contact:

Re: Cannot change tray icon after resolution change

Post by snaphat » 28 Mar 2021, 23:42

Sorry for being unclear, I did mean a monitor resolution change from say 4k to 2k, etc. The resolution change is via the Windows settings dialog so there isn't any code for that part. It's not my script with the issue, as far as I can tell the same issue occurs with any script. I'm not running any custom windows managers or anything like that, just stock Windows without any hacked UX dlls, etc.

To reproduce the issue, based off of testing, the above commands should suffice with a combination of two hot-keys and icons. I could put together a 'working' script with the some icons in a zip if it's really necessary, but I don't really think it is...

Relatedly, AHK also doesn't remove its own icon when exiting a script via the ExitApp command after a resolution change either (i.e. you get a stale icon in the taskbar until you mouse over). I suspect this is for the same reason. My best guess (without looking at ahk's source code or really looking into the details of how windows icons are implemented), is that for some reason the handle to the icon is becoming invalid or the handle to the control more specifically.

As for why I'm changing resolution, it's unrelated to my script. Sometimes, I have to do it for games or work, etc. I'd just like the icon to work correctly so that I don't need to reload the script simply because I changed resolutions. In my actual script, I was attempting to work around the well-known issue with system tray icons becoming fuzzy after resolution changes by refreshing the icon used in my script myself. It's here: https://github.com/snaphat/virtual_desktop_enhancer.

Earlier today, I employed a kludgy work around within the current version of my script to refresh the icon by restarting the script and programmatically mousing over any stale icons left in the tasktray area, but I'd rather the issue be fixed within AHK. I'm aware that I could probably just call additional Windows APIs from within my script to workaround the issue as well, but I'd rather not.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Cannot change tray icon after resolution change

Post by lexikos » 29 Mar 2021, 05:57

Note that there are two distinct notions of "icon" here.
  1. An icon, used in various parts of the UI, and referenced by a handle of type HICON.
  2. A tray icon (or "notification icon"), which is referenced by a combination of the window handle which owns the tray icon and an ID defined by the program.

I have confirmed that after changing screen DPI, the tray icon no longer updates and is not removed when the program exits.

This actually has nothing to do with the screen resolution or DPI. It is an AutoHotkey bug caused by incorrect (or incorrectly documented) OS behaviour.
When the taskbar is created, it registers a message with the TaskbarCreated string and then broadcasts this message to all top-level windows. When your taskbar application receives this message, it should assume that any taskbar icons it added have been removed and add them again.
This is exactly what AutoHotkey does. However, it seems the TaskbarCreated message is also sent by the system when the screen DPI changes, and in that case the icons have not been removed, so when AutoHotkey attempts to add the icon, it fails. Subsequent updates to the tray icon are not attempted, because it was assumed that if adding the icon failed, the icon is not present.

After working this out, I found details of a very similar bug in another project.

You can work around it by using KillTrayIcon.

Code: Select all

KillTrayIcon(A_ScriptHwnd) ; Remove the icon from the tray.
Menu Tray, NoIcon ; Ensure AutoHotkey thinks there's no icon.
Menu Tray, Icon ; Recreate the icon.

KillTrayIcon(scriptHwnd) {
    static NIM_DELETE := 2, AHK_NOTIFYICON := 1028
    VarSetCapacity(nic, size := 936+4*A_PtrSize)
    NumPut(size, nic, 0, "uint")
    NumPut(scriptHwnd, nic, A_PtrSize)
    NumPut(AHK_NOTIFYICON, nic, A_PtrSize*2, "uint")
    return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_DELETE, "ptr", &nic)
}

As far as I know, all versions of Windows represent the system tray ("notification area") with a ToolbarWindow32 control; therefore they aren't so much tray icons as they are tray buttons.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Cannot change tray icon after resolution change

Post by lexikos » 29 Mar 2021, 06:55

On second thought, you can work around it by intercepting the TaskbarCreate message and handling it on AutoHotkey's behalf.

Code: Select all

OnMessage(DllCall("RegisterWindowMessage", "str", "TaskbarCreated"), "TaskbarCreated")

TaskbarCreated() {
    if (A_IconFile = "") {
        ; Explicitly set an icon.
        Menu Tray, Icon, % A_AhkPath
        ; Restore A_IconFile to its previous empty value (in case it affects script behaviour).
        Menu Tray, Icon, *
    }
    else {
        ; Reapply icon.
        Menu Tray, Icon, % A_IconFile, % A_IconNumber
    }
    return 0  ; Prevent default handling.
}
This should also solve the blurriness.

However, that won't work if the taskbar really was just created (i.e. explorer.exe was restarted). To cover both cases:

Code: Select all

OnMessage(DllCall("RegisterWindowMessage", "str", "TaskbarCreated"), "TaskbarCreated")

TaskbarCreated() {
    if !HasTrayIcon()
        return  ; Allow default handling.
    if (A_IconFile = "") {
        ; Explicitly set an icon.
        Menu Tray, Icon, % A_AhkPath
        ; Restore A_IconFile to its previous empty value (in case it affects script behaviour).
        Menu Tray, Icon, *
    }
    else {
        ; Reapply icon.
        Menu Tray, Icon, % A_IconFile, % A_IconNumber
    }
    return 0  ; Prevent default handling.
}

HasTrayIcon(scriptHwnd := 0) {
    static NIM_MODIFY := 1, AHK_NOTIFYICON := 1028
    VarSetCapacity(nic, size := 936+4*A_PtrSize)
    NumPut(size, nic, 0, "uint")
    NumPut(scriptHwnd ? scriptHwnd : A_ScriptHwnd, nic, A_PtrSize)
    NumPut(AHK_NOTIFYICON, nic, A_PtrSize*2, "uint")
    return DllCall("Shell32\Shell_NotifyIcon", "uint", NIM_MODIFY, "ptr", &nic)
}
User avatar
snaphat
Posts: 4
Joined: 28 Mar 2021, 14:14
Contact:

Re: Cannot change tray icon after resolution change

Post by snaphat » 29 Mar 2021, 20:49

Thanks for the prompt response and investigation. I am impressed! Both methods do indeed fix the icon handle so that icon properly changes, can be removed, etc. However, after testing, it appears that even with the fixes, if a DPI change occurs after the start of AHK, the icon will still have a blur to it(notably, less of a blur though. Before, it would just look completely garbled). Moreover, switching to a different icon (that hasn't been loaded prior) at the point will result in the new icon being blurry as well.

Changing the DPI setting back to the setting that was applied when AHK started results in the icon(s) rendering correctly. For example, if an application was started at 125% and then scaling was set to 100%, the icon will look blurry. If I then switch back to 125% it will look correct. It's as if the DPI original setting is being remembered.

I experimented with forcing the DPI awareness of the AHK executable via the windows registry; however, no matter what the context is, it doesn't change the blurry behavior. I know Windows is applying the different context awareness settings though, as task manager shows the current DPI awareness settings I applied for AHK.

Is there an internal DPI setting that AHK uses?

Example of the blurriness after DPI change vs restarting AHK:
image.png
image.png (2.24 KiB) Viewed 1031 times
On similar note, what you found out must be what is wrong with Google Backup and Sync as well. That application's icon behaves exactly the same as AHK after a DPI change. I wonder if I can apply the same technique to fix its broken icon... that's just me thinking out loud though, no need to answer that question :-)

Edit: I haven't investigated this much, but it appears I cannot right-click on the AHK icon either after it is reapplied via your code until I swap virtual desktops. Then right-clicking works normally.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Cannot change tray icon after resolution change

Post by lexikos » 29 Mar 2021, 21:39

AutoHotkey loads the icon at specific sizes for best quality (changing DPI aside). It gets the size for the tray by using the equivalent of SysGet with 49 (SM_CXSMICON). I'd guess this value probably doesn't update if you change DPI while the script is running (but you can verify by calling SysGet). In other words, the size is probably based on the DPI when the script started (but calculated by the system, not AutoHotkey). There's probably something I can do to fix it.

In the meantime, I think you can override it by loading the icon manually with LoadPicture and passing it to the Menu command using the "HICON:" prefix.

I considered automatically reloading the icon from file when the DPI changes, but that could cause unexpected side effects (e.g. if the file only existed temporarily or has been modified while the script is running). Keeping a higher resolution version of the icon in memory and scaling down as needed isn't a solution because many icons have multiple bitmaps, optimized for different sizes. (And currently some of the default tray icons are only 16x16 anyway.)

I right clicked several times while testing, to see if the icon updated when un/pausing/suspending the script. It worked for me.
User avatar
snaphat
Posts: 4
Joined: 28 Mar 2021, 14:14
Contact:

Re: Cannot change tray icon after resolution change

Post by snaphat » 30 Mar 2021, 16:52

SM_CXSIMON indeed doesn't appear to update, but, I'm not sure if that's because perhaps GetSystemMetricsForDpi() isn't being used by AHK? You probably already know this, but it sounds like an application is suppose to hook the WM_DPICHANGED event and adjust everything. Perhaps, that contains the "new" recommended sizes? https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows

I tried using LoadImage and HICON, but no dice on it looking better. I was doing the following (at start and within the TaskbarCreated callback):
```
hicon := LoadPicture("app.ico",,IMAGE_ICON)
Menu Tray, Icon, HICON:*%hicon%
```

Edit: The documentation, implementation, and changing APIs for DPI scaling just seems so awful to be honest. It's no wonder so many applications have issues with DPI scaling... It's reminding me that I tried to build a simple rust application to snap window borders together, but the APIs for ClientRect, etc. behave inconsistently as well due to API nonawareness/awareness/scaling.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Cannot change tray icon after resolution change

Post by lexikos » 30 Mar 2021, 21:47

It appears that GetSystemMetricsForDpi is a Windows 10-only function which merely gets the same value as GetSystemMetrics and then does some math. It would need to be used dynamically so AutoHotkey can load on older OSes, but it doesn't seem worth the trouble. For doing the math myself, the question is just whether GetSystemMetrics returns base values or values scaled according to the initial DPI, and where to get both the initial and current DPI values. It's probably all fairly trivial; I just haven't looked into any of it yet.

When using LoadPicture, you probably need to specify which size you want.
Post Reply

Return to “Bug Reports”