Windows 10 TrayTip icon resolution

Get help with using AutoHotkey and its commands and hotkeys
User avatar
kczx3
Posts: 1162
Joined: 06 Oct 2015, 21:39

Windows 10 TrayTip icon resolution

20 Dec 2015, 11:07

Has anyone else noticed that traytip icons in Windows 10 are much larger than they used to be in Win 7? The resolution looks awful and a black background is being used on the icon as well. The icon in the system tray appears fine with a transparent background. The ico is something i grabbed from iconarchive and as 128x128.
lexikos
Posts: 7085
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Windows 10 TrayTip icon resolution

11 Feb 2016, 04:40

I saw this post a while ago and have been meaning to look into it. Now I have.

This seems to be unavoidable when the notification is generated by the "legacy" Shell_NotifyIcon API (i.e. the tray icon API). I think that the system might be downscaling the icon when it is set as the tray icon. If I manually set a larger icon, the icon shown in the notification is clearly an upscaled version of a downscaled version of the original icon.

Code: Select all

TrayIcon(LoadPicture(A_AhkPath, "w48", type))
TrayTip Title, Text
Sleep 5000
ExitApp

TrayIcon(hIcon) {
    VarSetCapacity(nid, size := 428+4*A_PtrSize)
    NumPut(hIcon, NumPut(2, NumPut(1028, NumPut(A_ScriptHwnd, NumPut(size, nid))
    , "uint"), "uint"), A_PtrSize)
    return DllCall("shell32\Shell_NotifyIconA", "uint", 1, "ptr", &nid)
}
Changing "w48" to "w4" clearly shows that the tray icon set by the function really is being used in the notification.


I suppose that the only solution will be to use the actual toast notification API, which is part of the Windows Runtime (WinRT). WinRT is based on COM, but it is quite different and I think not easily integrated with AutoHotkey (even though WinRT can be used from JavaScript). I found a few C++ samples (such as this one), but translating it won't be easy (and my level of interest is insufficient). It took me a couple of hours to find a starting point for accessing any of the WinRT APIs.

If anyone's interested, there's a list of WinRT APIs which can be used from desktop apps:
https://msdn.microsoft.com/en-au/library/dn554295

The functions that would be used with DllCall are documented here (these are the lowest level functions, so aren't used by C++/C#/JavaScript programs directly):
https://msdn.microsoft.com/en-us/library/br224617

Some of the hard work has been done by Delphi programmers, although for creating apps rather than just accessing the APIs. This is also where I found a pointer to the above documentation:
http://www.thomgerdes.com/2011/12/writi ... elphi.html
User avatar
kczx3
Posts: 1162
Joined: 06 Oct 2015, 21:39

Re: Windows 10 TrayTip icon resolution

11 Feb 2016, 12:25

Thanks for the response Lexikos! While majority of this is over my head, I truly do appreciate your investigation into this. It certainly does seem like quite the task to incorporate this in AHK and truthfully, its not that big of a deal as everything does seem to display. If you end up getting the right level of interest to tackle this, let me suggest a separate command for it called ToastTip seeing as this functionality came about with Windows 8. :)
lexikos
Posts: 7085
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Windows 10 TrayTip icon resolution

12 Feb 2016, 06:32

It turns out that accessing WinRT APIs via JavaScript is relatively easy on Windows 10 - just take the JsRT wrapper I already had and add one function call. I've created an example that shows a toast notification: Example_JsRT_Toast.ahk. Requires ActiveScript.ahk and JsRT.ahk.
toast.png
toast.png (3.93 KiB) Viewed 2318 times
User avatar
kczx3
Posts: 1162
Joined: 06 Oct 2015, 21:39

Re: Windows 10 TrayTip icon resolution

14 Feb 2016, 14:51

I got it to work with my image. I might need to change my color scheme though since the blues are so close together. This was using a 256px png as well. It appears it does not work with .ico files.
Example.png
Example.png (4.81 KiB) Viewed 2295 times
lexikos
Posts: 7085
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Windows 10 TrayTip icon resolution

03 Aug 2016, 02:40

Somehow when I looked into this earlier, I overlooked the NIIF_USER (4) flag, which can be combined with the NIIF_LARGE_ICON (0x20) flag and NOTIFYICONDATA.hBalloonIcon field to specify a large icon. It works on everything from Vista to 10 - it just requires some small modifications to TrayTip.

So now there's a test build of AutoHotkey that supports large icons natively, and provides one by default on Windows 10.
User avatar
kczx3
Posts: 1162
Joined: 06 Oct 2015, 21:39

Re: Windows 10 TrayTip icon resolution

08 Aug 2016, 21:31

I know you basically fixed TrayTip with the test build you linked but this spurred my interest again so I've begun to dig in...

I added the options to define the duration (either "short" or "long") and to make the toast silent. Next, I've attempted to handle events. It seems fairly straight forward based on the documentation: https://msdn.microsoft.com/en-au/library/hh761468.

Based on your instructions on GitHub, it would seem that I can use AddObject to define a global FuncObj and attach that as the eventListener. But I'm not getting anything. Do you (or anyone else for that matter) have any tidbits of a direction/advice to give?

Code: Select all

; Only the Edge version of JsRT supports WinRT.
js := new JsRT.Edge

; Enable use of WinRT.  "Windows.UI" or "Windows" would also work.
js.ProjectWinRTNamespace("Windows.UI.Notifications")
code =
(
    function toast(template, image, text, duration, silent, app) {
        // Alias for convenience.
        var N = Windows.UI.Notifications;
        // Get the template XML as an XmlDocument.
        var toastXml = N.ToastNotificationManager.getTemplateContent(N.ToastTemplateType[template]);
	   var toastNode = toastXml.selectSingleNode('/toast');
	   // Set our duration
	   if (duration == 'long')
		toastNode.setAttribute('duration', duration);
	   if (silent == 'true') {
	     var audio = toastXml.createElement('audio');
	     audio.setAttribute('silent', silent);
		toastNode.appendChild(audio);
	   }
	   toastNode.setAttribute('launch', 'This is my payload');
        // Insert our content.
        var i = 0;
        for (let el of toastXml.getElementsByTagName("text")) {
            if (typeof text == 'string') {
                el.innerText = text;
                break;
            }
            el.innerText = text[++i];
        }
	if (image)
		toastXml.getElementsByTagName("image")[0].setAttribute("src", image);
        // Show the notification.
        var toastNotifier = N.ToastNotificationManager.createToastNotifier(app || "AutoHotkey");
	   var toastNotification = new N.ToastNotification(toastXml);
	   toastNotification.addEventListener("activated", "toastActivated");
        toastNotifier.show(toastNotification);
    }
    
    function toastActivated(a) {
	if (a.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
		MsgBox(a.detail.arguments);
	}
    }
)

; Define the toast function.
js.Exec(code)
js.AddObject("MsgBox", Func("test"))
Return

js.toast("toastText01", "", ["test,FOO", "TESTING"], "", "true")

test(a) {
	msgbox, %a%
}
lexikos
Posts: 7085
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Windows 10 TrayTip icon resolution

09 Aug 2016, 04:18

There appear to be a few problems with your example.
  • Unreachable code (I assume it's not the code you actually used).
  • You need to pass addEventListener a function, not the name of a function.
  • a.detail.kind is undefined, so you won't actually call MsgBox().
I assume you followed this MSDN example, which calls WinJS.Application.addEventListener(), not ToastNotification.addEventListener(). The documentation for the event is not clear about what the event args object is, but a.toString() shows that it is a ToastActivatedEventArgs. The documentation only lists one property: arguments (not detail).

Strangely though, a.detail returns a different ToastActivatedEventArgs object which does not have arguments. for (k in a) shows that it also has a type property which contains activated.

Note that if you do a+"", JavaScript will call toString() on the object, but if you pass the object directly to AutoHotkey and try to use it as a string, you'll just get an empty string. Similarly, undefined + "" results in "undefined" whereas if you pass undefined to AutoHotkey, it is translated to an empty string. (AutoHotkey actually receives a VARIANT of type VT_EMPTY.)
User avatar
kczx3
Posts: 1162
Joined: 06 Oct 2015, 21:39

Re: Windows 10 TrayTip icon resolution

11 Aug 2016, 21:07

Finally fully realizing that we are indeed working with javascript (which I've recently started working with at work), I tried using this PHP-like print_r() function to see what exactly was in a. Below is the msgbox with a's contents. At first, I ran it without modifying the print_r() function. This resulted in a similar product with the exception of there being no arguments property, nor the toString method. This seemed odd since I could indeed access it with a.arguments which resulted in my payload of 'This is my payload'. So I commented out the if (obj.hasOwnProperty(prop)) statement and ran it again to obtain the following:
toastNotificationEventArgs.png
toastNotificationEventArgs.png (8.36 KiB) Viewed 2010 times
I can't figure out how to use wildcards in the switch statement so I hard coded two other cases since the objects aren't classified as [object Object]. This shows it fairly well though. Windows + q will show the toast notification with a short duration and not silent. Clicking on the toast will show an AHK msgbox with a printed out and then a second msgbox displaying the json string after having been parsed with JSON.parse() and pretty-printed with print_r(). Redundant I know, but gets the point across. **Please note I used your example and tweaked it for a working test script.**

Code: Select all

#NoEnv
#Include ActiveScript.ahk
#Include JsRT.ahk

; Unlike TrayTip, this API does not require a tray icon:
#NoTrayIcon

; Toast notifications from desktop apps can only use local image files.
if !FileExist("sample.png")
    URLDownloadToFile https://autohotkey.com/boards/styles/simplicity/theme/images/announce_unread.png
        , % A_ScriptDir "\sample.png"
; The templates are described here:
;  http://msdn.com/library/windows/apps/windows.ui.notifications.toasttemplatetype.aspx
toast_template := "toastImageAndText02"
; Image path/URL must be absolute, not relative.
toast_image := A_ScriptDir "\sample.png"
; Text is an array because some templates have multiple text elements.
toast_text := ["Hello, world!", "This is the sub-text."]

; Only the Edge version of JsRT supports WinRT.
js := new JsRT.Edge
js.AddObject("MsgBox", Func("test"))

; Enable use of WinRT.  "Windows.UI" or "Windows" would also work.
js.ProjectWinRTNamespace("Windows.UI.Notifications")

code =
(
    function toast(template, image, text, duration, silent, app) {
        // Alias for convenience.
        var N = Windows.UI.Notifications;
        // Get the template XML as an XmlDocument.
        var toastXml = N.ToastNotificationManager.getTemplateContent(N.ToastTemplateType[template]);
	   var toastNode = toastXml.selectSingleNode('/toast');
	   // Set our duration
	   if (duration == 'long')
		toastNode.setAttribute('duration', duration);
	   if (silent == 'true') {
	     var audio = toastXml.createElement('audio');
	     audio.setAttribute('silent', silent);
		toastNode.appendChild(audio);
	   }
	   var args = {'prop1':'val1', 'prop2':'val2'};
	   toastNode.setAttribute('launch', '{"test1":"1234"}');
        // Insert our content.
        var i = 0;
        for (let el of toastXml.getElementsByTagName("text")) {
            if (typeof text == 'string') {
                el.innerText = text;
                break;
            }
            el.innerText = text[++i];
        }
	   if (image)
		toastXml.getElementsByTagName("image")[0].setAttribute("src", image);
        // Show the notification.
        var toastNotifier = N.ToastNotificationManager.createToastNotifier(app || "AutoHotkey");
	   var toastNotification = new N.ToastNotification(toastXml);
	   toastNotification.addEventListener("activated", toastActivated);
        toastNotifier.show(toastNotification);
    }
    
    function toastActivated(a) {
	MsgBox(print_r(a));
	if (a.type == 'activated') {
		MsgBox(print_r(JSON.parse(a.arguments)));
	}
    }
    
    /**
 * PHP-like print_r() & var_dump() equivalent for JavaScript Object
 *
 * @author Faisalman <[email protected]>
 * @license http://www.opensource.org/licenses/mit-license.php
 * @link http://gist.github.com/879208
 */
var print_r = function(obj,t){

    // define tab spacing
    var tab = t || '';
	
    // check if it's array
    var isArr = Object.prototype.toString.call(obj) === '[object Array]' ? true : false;
	
    // use {} for object, [] for array
    var str = isArr ? ('Array\n' + tab + '[\n') : ('Object\n' + tab + '{\n');

    // walk through it's properties
    for(var prop in obj){
        // if (obj.hasOwnProperty(prop)) {
            var val1 = obj[prop];
            var val2 = '';
            var type = Object.prototype.toString.call(val1);
            switch(type){
			
                // recursive if object/array
			 case '[object String]':
                    val2 = '\'' + val1 + '\'';
                    break;
                case '[object Array]':
                case '[object Object]':
                    val2 = print_r(val1, (tab + '\t'));
                    break;
			case '[object Windows.UI.Notifications.ToastNotification]':
                    val2 = print_r(val1, (tab + '\t'));
                    break;
			case '[object Windows.UI.Notifications.ToastActivatedEventArgs]':
                    val2 = print_r(val1, (tab + '\t'));
                    break;
                default:
                    val2 = val1;
            }
            str += tab + '\t' + prop + ' => ' + val2 + ',\n';
        // }
    }
	
    // remove extra comma for last property
    str = str.substring(0, str.length-2) + '\n' + tab;
	
    return isArr ? (str + ']') : (str + '}');
};
)

; Define the toast function.
js.Exec(code)
Return

#q::
js.toast(toast_template, toast_Image, toast_Text, "", "")
Return

test(a) {
	msgbox, %a%
}

; Note: If the notification wasn't hidden, it will remain after we exit.
ExitApp

Return to “Ask For Help”

Who is online

Users browsing this forum: ChauTrungPhan, haggyy, mikeyww and 75 guests