@@ -100,7 +100,9 @@ func (ni *NotifyIcon) wndProc(hwnd win.HWND, msg uint16, wParam uintptr) {
100
100
case win .WM_LBUTTONDOWN :
101
101
ni .mouseDownPublisher .Publish (int (win .GET_X_LPARAM (wParam )), int (win .GET_Y_LPARAM (wParam )), LeftButton )
102
102
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 :
104
106
if ni .activeContextMenus > 0 {
105
107
win .PostMessage (hwnd , win .WM_CANCELMODE , 0 , 0 )
106
108
break
@@ -197,8 +199,9 @@ func (ni *NotifyIcon) doContextMenu(hwnd win.HWND, x, y int32) {
197
199
}
198
200
199
201
func 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
+ }
202
205
return win .SHAppBarMessage (win .ABM_GETTASKBARPOS , & abd ) != 0
203
206
}
204
207
@@ -227,7 +230,9 @@ func newNotificationIconWindow() (*notifyIconWindow, error) {
227
230
niwCfg := windowCfg {
228
231
Window : niw ,
229
232
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 ,
231
236
// Always create the window at the origin, thus ensuring that the window
232
237
// resides on the desktop's primary monitor, which is the same monitor where
233
238
// the taskbar notification area resides. This ensures that the window's
@@ -239,6 +244,10 @@ func newNotificationIconWindow() (*notifyIconWindow, error) {
239
244
if err := initWindowWithCfg (& niwCfg ); err != nil {
240
245
return nil , err
241
246
}
247
+
248
+ // By default the window has the "client" role, which suggests content.
249
+ // Assigning the "window" role instead.
250
+ niw .Accessibility ().SetRole (AccRoleWindow )
242
251
return niw , nil
243
252
}
244
253
@@ -275,19 +284,10 @@ func newShellNotificationIcon(guid *windows.GUID) (*shellNotificationIcon, error
275
284
return shellIcon , nil
276
285
}
277
286
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 ()
291
291
292
292
// Add our notify icon to the status area and make sure it is hidden.
293
293
addCmd := shellIcon .newCmd (win .NIM_ADD )
@@ -300,13 +300,32 @@ func newShellNotificationIcon(guid *windows.GUID) (*shellNotificationIcon, error
300
300
return shellIcon , nil
301
301
}
302
302
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
+
303
323
func (i * shellNotificationIcon ) setOwner (ni * NotifyIcon ) {
304
324
// Only icons identified via GUID use the owner field; non-GUID icons share
305
325
// 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
308
328
}
309
- i .window .owner = ni
310
329
}
311
330
312
331
func (i * shellNotificationIcon ) Dispose () error {
@@ -455,6 +474,13 @@ func (cmd *niCmd) setVisible(v bool) {
455
474
}
456
475
457
476
func (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
+ }
458
484
if ! win .Shell_NotifyIcon (cmd .op , & cmd .nid ) {
459
485
return lastError (fmt .Sprintf ("Shell_NotifyIcon(%d, %#v)" , cmd .op , cmd .nid ))
460
486
}
@@ -473,7 +499,14 @@ func (cmd *niCmd) execute() error {
473
499
verCmd .op = win .NIM_SETVERSION
474
500
// Use Vista+ behaviour.
475
501
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 ()
477
510
}
478
511
479
512
// NotifyIcon represents an icon in the taskbar notification area.
@@ -551,6 +584,11 @@ func (ni *NotifyIcon) reAddToTaskbar() {
551
584
// track this once the add command successfully executes.
552
585
prevID := ni .shellIcon .id
553
586
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
+
554
592
cmd := ni .shellIcon .newCmd (win .NIM_ADD )
555
593
cmd .setCallbackMessage (notifyIconMessageID )
556
594
cmd .setVisible (ni .visible )
0 commit comments