Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse capture implementation question #5

Open
PaulBol opened this issue Dec 17, 2018 · 5 comments
Open

Mouse capture implementation question #5

PaulBol opened this issue Dec 17, 2018 · 5 comments

Comments

@PaulBol
Copy link

PaulBol commented Dec 17, 2018

Something else that I noticed...

UngrabWindow in XplatUICocoa.cs includes these lines

if (LastEnteredHwnd != IntPtr.Zero && LastEnteredHwnd != grabbed)
{
	var lparam = IntPtr.Zero; // TODO: should contain mouse coords? See WindowsEventResponder.TranslateMouseEvent()
	var wparam = (IntPtr)(NSEvent.CurrentModifierFlags.ToWParam() | Mac.Extensions.ButtonMaskToWParam(NSEvent.CurrentPressedMouseButtons));
	SendMessage(grabbed, Msg.WM_MOUSELEAVE, wparam, lparam);
	SendMessage(LastEnteredHwnd, Msg.WM_MOUSE_ENTER, wparam, lparam);
}

This leads to a nested call of WndProc with WM_MOUSELEAVE when the mouse button is released (WM_LBUTTONUP) while captured.

My code was not prepared to deal with this. I'm not daring to suggest this is a bug in XplatUICocoa.cs that needs to be fixed. Only wondering if it should be PostMessage instead of SendMessage so that the button up message handling is completed before WM_MOUSELEAVE. I figured out this is what System.Windows.Forms.CarbonInternal.MouseHandler does in TranslateMessage.

@filipnavara
Copy link
Collaborator

filipnavara commented Dec 17, 2018

I will have to go back through the history to see why we did it this way. The problem with PostMessage is that it could potentially result in wrong order in relation to WM_MOUSEMOVE events (eg. receiving WM_MOUSEMOVE before WM_MOUSE_ENTER).

I'll hopefully have more information soon, just wanted to let you know that I am looking into it.

@PaulBol
Copy link
Author

PaulBol commented Dec 18, 2018

Thanks for your feedback. I see your point. I was thinking that PostMessage may be closer to the Windows and Carbon implementation but it may not be worth risking to break something else. I have a workaround in my code so it's fine for me.

@filipnavara
Copy link
Collaborator

filipnavara commented Dec 18, 2018

I'd love it to be closer to the Windows implementation, but emulating it accurately would likely incur too big of a performance penalty. There are two things to consider:

  1. How message queues operate on Windows and how are the messages prioritized. Is there an accurate way to translate that into Cocoa?
  2. How the TrackMouseEvent API behaves and how is it internally implemented in respect to message prioritization.

Luckily the answers could be found in MSDN, Wine and ReactOS code:

The important part about message queues is documented in the PeekMessage API documentation. If no filter is specified, messages are processed in the following order:

  • Sent messages (from other processes; otherwise they are delivered synchronously)
  • Posted messages
  • Input (hardware) messages and system internal events
  • Sent messages (again)
  • WM_PAINT messages
  • WM_TIMER messages

There are separate queues/flags for each of the above categories. According to ReactOS test suite (and probably the Wine one as well; both excelent sources of the precise message sequences used on Windows) and source code the WM_MOUSELEAVE message is indeed posted and as such would have higher priority than any other input messages (such as WM_MOUSEMOVE). Once there are no posted messages (hence no WM_MOUSELEAVE) the internal input messages are delivered to the application. Only at this point the actual window for any WM_MOUSEMOVE message is determined. There's no WM_MOUSE_ENTER on Windows (a concept only present in the Mono WinForms implementation) and the relevant OnMouseEnter messages are generated from the WM_MOUSEMOVE messages. Basically the worst inconsistency that can happen due to the implementation details is that you would receive delayed OnMouseLeave followed by OnMouseEnter after the next mouse move (unless my mental picture is wrong, which could easily be the case).

I didn't investigate how Windows/Wine/ReactOS handle the case of SetCapture/ReleaseCapture APIs and whether the WM_MOUSELEAVE event is generated in that case and how. Most likely ReleaseCapture simply simulates low-level mouse move by 0 pixels and let's the system figure out which messages need to be generated and into which queue.

On Cocoa emulating all this is tricky. We don't have cross-process SendMessage. PostMessage is emulated by creating native Cocoa events and running them through the Cocoa message pump. The Cocoa messages in the pump are processed by the native Cocoa mechanism (ie. routing through NSWindow, NSView and NSResponder chains). We receive the transleted mouse events in MonoView, MonoWindow and WindowsEventResponder - our classes delivered from NSView, NSWindow and NSResponder respectively. The mouse messages (such as WM_MOUSEMOVE) are generated there and delivered using SendMessage to skip going through the message queue again.

Moreover, Cocoa has no concept of "mouse enter" / "mouse leave" events, so they have to emulated. In addition to that the "mouse move" in Cocoa behaves differently from Windows and it's expensive. While Windows sends WM_MOUSEMOVE to one specific control (since Windows don't make distinction between windows and controls) on Cocoa it's delivered to NSWindow and then any further dispatching is handled by the NSWindow. Specifically for mouse move events it uses a concept of tracking areas. We register a tracking area for each MonoView, which unfortunately results in the side effect that the mouse move events are delivered to the all the controls in the hierarchy under the mouse cursor (eg. when a Button is inside a Panel then both underlying MonoView objects would be notified when mouse moves over the button). Converting this to the Windows messages is quite tricky, rather easy to break and technically challenging to get right, unfortunately.

@PaulBol
Copy link
Author

PaulBol commented Dec 19, 2018

Thanks for explaining the background. You clearly have a far more profound knowledge of the message loop implementation. My idea of simply replacing SendMessage with PostMessage apparently means much more of a change than I had thought and is not worth the risk. Should we close this issue?

@filipnavara
Copy link
Collaborator

Let's keep the issue open so we can track the problem, but I cannot promise it will be fixed any time soon. We are currently facing some issue internally that may be related to it, but it uses our internal controls, message filters and we didn't isolate it yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants