@@ -100,7 +100,9 @@ func (ni *NotifyIcon) wndProc(hwnd win.HWND, msg uint16, wParam uintptr) {
100100 case win .WM_LBUTTONDOWN :
101101 ni .mouseDownPublisher .Publish (int (win .GET_X_LPARAM (wParam )), int (win .GET_Y_LPARAM (wParam )), LeftButton )
102102
103- case win .WM_LBUTTONUP :
103+ // We treat keyboard selection of the icon identically to a left-click.
104+ // All three messages use the same format for wParam.
105+ case win .NIN_KEYSELECT , win .NIN_SELECT , win .WM_LBUTTONUP :
104106 if ni .activeContextMenus > 0 {
105107 win .PostMessage (hwnd , win .WM_CANCELMODE , 0 , 0 )
106108 break
@@ -197,8 +199,9 @@ func (ni *NotifyIcon) doContextMenu(hwnd win.HWND, x, y int32) {
197199}
198200
199201func isTaskbarPresent () bool {
200- var abd win.APPBARDATA
201- abd .CbSize = uint32 (unsafe .Sizeof (abd ))
202+ abd := win.APPBARDATA {
203+ CbSize : uint32 (unsafe .Sizeof (win.APPBARDATA {})),
204+ }
202205 return win .SHAppBarMessage (win .ABM_GETTASKBARPOS , & abd ) != 0
203206}
204207
@@ -227,7 +230,9 @@ func newNotificationIconWindow() (*notifyIconWindow, error) {
227230 niwCfg := windowCfg {
228231 Window : niw ,
229232 ClassName : notifyIconWindowClass ,
230- Style : win .WS_OVERLAPPEDWINDOW ,
233+ // Creating the window with WS_DISABLED in an effort to dissuade screen
234+ // readers from treating the hidden window as focusable content.
235+ Style : win .WS_OVERLAPPEDWINDOW | win .WS_DISABLED ,
231236 // Always create the window at the origin, thus ensuring that the window
232237 // resides on the desktop's primary monitor, which is the same monitor where
233238 // the taskbar notification area resides. This ensures that the window's
@@ -239,6 +244,10 @@ func newNotificationIconWindow() (*notifyIconWindow, error) {
239244 if err := initWindowWithCfg (& niwCfg ); err != nil {
240245 return nil , err
241246 }
247+
248+ // By default the window has the "client" role, which suggests content.
249+ // Assigning the "window" role instead.
250+ niw .Accessibility ().SetRole (AccRoleWindow )
242251 return niw , nil
243252}
244253
@@ -275,19 +284,10 @@ func newShellNotificationIcon(guid *windows.GUID) (*shellNotificationIcon, error
275284 return shellIcon , nil
276285 }
277286
278- if guid != nil {
279- // If we're using a GUID, an add operation can fail if a previous instance
280- // using this GUID terminated abnormally and its notification icon was left
281- // behind on the taskbar. Preemptively delete any pre-existing icon.
282- if delCmd := shellIcon .newCmd (win .NIM_DELETE ); delCmd != nil {
283- // The previous instance would have used a different, now-defunct HWND, so
284- // we can't use one here...
285- delCmd .nid .HWnd = win .HWND (0 )
286- // We expect delCmd.execute() to fail if there isn't a pre-existing icon,
287- // so no error checking for this call.
288- delCmd .execute ()
289- }
290- }
287+ // If we're using a GUID, an add operation can fail if a previous instance
288+ // using this GUID terminated abnormally and its notification icon was left
289+ // behind on the taskbar. Preemptively delete any pre-existing icon.
290+ shellIcon .clearAnyPreExisting ()
291291
292292 // Add our notify icon to the status area and make sure it is hidden.
293293 addCmd := shellIcon .newCmd (win .NIM_ADD )
@@ -300,13 +300,32 @@ func newShellNotificationIcon(guid *windows.GUID) (*shellNotificationIcon, error
300300 return shellIcon , nil
301301}
302302
303+ // clearAnyPreExisting deletes any GUID-based notification icon that might
304+ // still exist after either the shell restarts or this app restarts. Either
305+ // way, re-adding an icon with the same GUID will fail unless we delete the
306+ // previous instance first.
307+ func (i * shellNotificationIcon ) clearAnyPreExisting () {
308+ // Only meaningful for GUID-based icons.
309+ if i .guid == nil {
310+ return
311+ }
312+
313+ if delCmd := i .newCmd (win .NIM_DELETE ); delCmd != nil {
314+ // The previous instance would have used a different, now-defunct HWND, so
315+ // we can't use one here...
316+ delCmd .nid .HWnd = win .HWND (0 )
317+ // We expect delCmd.execute() to fail if there isn't a pre-existing icon,
318+ // so no error checking for this call.
319+ delCmd .execute ()
320+ }
321+ }
322+
303323func (i * shellNotificationIcon ) setOwner (ni * NotifyIcon ) {
304324 // Only icons identified via GUID use the owner field; non-GUID icons share
305325 // the same window and thus need to be looked up via notifyIconIDs.
306- if i .guid = = nil {
307- return
326+ if i .guid ! = nil {
327+ i . window . owner = ni
308328 }
309- i .window .owner = ni
310329}
311330
312331func (i * shellNotificationIcon ) Dispose () error {
@@ -455,6 +474,13 @@ func (cmd *niCmd) setVisible(v bool) {
455474}
456475
457476func (cmd * niCmd ) execute () error {
477+ var addShowTip bool
478+ if cmd .op == win .NIM_ADD && (cmd .nid .UFlags & win .NIF_SHOWTIP ) != 0 {
479+ // NIF_SHOWTIP is a v4 flag. Don't include it in flags for NIM_ADD, which
480+ // is a v1 operation. We add it back in below, after we've upgraded to v4.
481+ addShowTip = true
482+ cmd .nid .UFlags ^= win .NIF_SHOWTIP
483+ }
458484 if ! win .Shell_NotifyIcon (cmd .op , & cmd .nid ) {
459485 return lastError (fmt .Sprintf ("Shell_NotifyIcon(%d, %#v)" , cmd .op , cmd .nid ))
460486 }
@@ -473,7 +499,14 @@ func (cmd *niCmd) execute() error {
473499 verCmd .op = win .NIM_SETVERSION
474500 // Use Vista+ behaviour.
475501 verCmd .nid .UVersion = win .NOTIFYICON_VERSION_4
476- return verCmd .execute ()
502+ if err := verCmd .execute (); err != nil || ! addShowTip {
503+ return err
504+ }
505+
506+ showTipCmd := * cmd
507+ showTipCmd .op = win .NIM_MODIFY
508+ showTipCmd .nid .UFlags |= win .NIF_SHOWTIP
509+ return showTipCmd .execute ()
477510}
478511
479512// NotifyIcon represents an icon in the taskbar notification area.
@@ -551,6 +584,11 @@ func (ni *NotifyIcon) reAddToTaskbar() {
551584 // track this once the add command successfully executes.
552585 prevID := ni .shellIcon .id
553586
587+ // If we're using a GUID, an add operation can fail if a previous instance
588+ // using this GUID terminated abnormally and its notification icon was left
589+ // behind on the taskbar. Preemptively delete any pre-existing icon.
590+ ni .shellIcon .clearAnyPreExisting ()
591+
554592 cmd := ni .shellIcon .newCmd (win .NIM_ADD )
555593 cmd .setCallbackMessage (notifyIconMessageID )
556594 cmd .setVisible (ni .visible )
0 commit comments