0
votes

I created a tracking area in NSView that should only track mouse moved events when the window is the key window. However, I noticed that sometimes, when the window is overlapped by another window which is currently key, the background window which is no longer the key window, still receives mouseMoved: events.

Here is my code in NSView subclass:

if (_trackingArea != nil) {
    [self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc]
                 initWithRect:trackingFrame
                 options:(NSTrackingAreaOptions)(NSTrackingMouseMoved | NSTrackingActiveInKeyWindow)
                 owner:self
                 userInfo:nil];
[self addTrackingArea:_trackingArea];

I added NSLog(@"is window key %d", [[self window] isKeyWindow]) in -mouseMoved: and it clearly shows that the background window is NOT key, though it still receives the mouse moved events.

This artifact disappears after I click the background window to make it key and then click the foreground window back again to make it key. Then the background window stops receiving mouse moved events.

Is this a bug of NSTrackingArea, is there any way to work around it?

UPDATE: I noticed that this bug only appears when the background window gets programmatically resized, while the foreground window has keyboard focus.

1
Please see the code in my question. It's NSTrackingMouseMoved | NSTrackingActiveInKeyWindow. The code refers to a NSView subclass placed in the background window. - user1548418
Sorry, the foreground window doesn't matter, it's a simple window with push buttons. The important window is the background window. It contains the subclassed NSView with NSTrackingArea. Imagine the following scenario: The background window is showing and the foreground window is also showing on top and is the key window. I click a button in the foreground window and the background window gets programmatically resized. After that, the background window's NSView begins receiving mouseMoved: calls even when the mouse is on a push button of the foreground window and it's still the key window. - user1548418
As if the foreground wndow becomes transparent for mouse move event tracking, and the events end up in the background window, which is not key window and so it should not receive anything according to the option NSTrackingActiveInKeyWindow. - user1548418
That sounds like a bug to me. Did you try to isolate the issue in a simplified demo app? Would love to take a look. - Kentzo
Sorry for the delay. Yes, I have isolated the issue in a simple demo app and the problem reproduces perfectly. I will also file a bug report to Apple. Here is the link: dropbox.com/s/ogmjl4kfhh2vs7f/TestNSTrackingArea.zip?dl=0 - user1548418

1 Answers

0
votes

The way I interpret the documentation matches with yours and I'd expect the TA to get active only when the parent window is key. However, it's obviously is not the case and it's not a new bug as I was able to reproduce it on 10.14

Internally there are a number of private methods that control what and when gets installed:

  • -[NSView _installTrackingArea:] / -[NSView _uninstallTrackingArea:]
  • -[NSView _enableTrackingArea:] / -[NSView disableTrackingArea:]
  • -[NSWindow _addMouseMovedListener:] / -[NSWindow _removeMouseMovedListener:]

If you add symbolic breakpoints to them and log the object being passed you'll see the following after pressing the button:

-updateTrackingAreas 0x600002148c80
Uninstall TA 0x60000213f3e0
Remove MML 0x60000213f3e0
Install TA 0x600002148c80
Disable TA 0x600002148c80
Remove MML 0x600002148c80
Add MML 0x600002148c80
Uninstall TA 0x6000021447d0
Install TA 0x60000212d7c0

0x600002148c80 is the Tracking Area being added to the view. As you can see during installation it is disabled and associated mouse-moved-listener (MML) is removed to be immediately added back.

Either this is a bug or documentation is lacking.

I suggest to rewrite -[NSView updateTrackingAreas]:

  • Call [super updateTrackingAreas] because the documentation suggests it
  • Only re-add TA to reflect changes of frame, options or owner
  • Do not rely on TA's options alone and double check that conditions are right (window is key, app is active etc). In other words treat TA's options to act upon future changes in state.
  • Check (or assert) state inside the event handlers: chances are they may get called in response to something else