macOS: Synthesize cursor updates on QCocoaWindow::setWindowCursor

The original issue for doing this was that invalidateCursorRectsForView
would not result in an updateCursor callback in certain scenarios on
macOS 11 and below.

In macOS 13, improvements to how tracking areas work now result in
also missing cursorUpdate calls when the mouse is pressed, which
makes sense for tracking areas in general (you don't want a drag
over a text field to reset the cursor to the I-bream), but not for
our specific case of setting a cursor synchronously.

To ensure the cursor is updated immediately, even if the mouse is
pressed, we synthesize a updateCursor event, just like we did for
the invalidateCursorRectsForView workaround.

It's up to clients of QWindow to manage their setCursor calls to
not happen during a drag operation, which we already manage in
Qt Widgets.

Pick-to: 6.5 6.2
Change-Id: I67d6e0f8e270b40da9879828455f4de943da7839
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-04-11 19:35:52 +02:00
parent 1f5c4af500
commit 75995d8e58

View File

@ -1788,26 +1788,28 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor)
view.cursor = cursor;
// We're not using the the legacy cursor rects API to manage our
// cursor, but calling this function also invalidates AppKit's
// view of whether or not we need a cursorUpdate callback for
// our tracking area.
[m_view.window invalidateCursorRectsForView:m_view];
if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSMonterey) {
// There's a bug in AppKit where calling invalidateCursorRectsForView when
// there's an override cursor active (for example when hovering over the
// window frame), will not result in a cursorUpdate: callback. To work around
// this we synthesize a cursor update event and call the callback ourselves.
// We only do this is if the window would normally receive cursor updates.
auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
qCDebug(lcQpaMouse) << "Synthesizing cursor update";
[m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
location:locationInWindow modifierFlags:0 timestamp:0
windowNumber:m_view.window.windowNumber context:nil
eventNumber:0 trackingNumber:0 userData:0]];
}
// We've informed AppKit that we need a cursorUpdate, but cursor
// updates for tracking areas are deferred in some cases, such as
// when the mouse is down, whereas we want a synchronous update.
// To ensure an updated cursor we synthesize a cursor update event
// now if the window is otherwise allowed to change the cursor.
auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
qCDebug(lcQpaMouse) << "Synthesizing cursor update";
[m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
location:locationInWindow modifierFlags:0 timestamp:0
windowNumber:m_view.window.windowNumber context:nil
eventNumber:0 trackingNumber:0 userData:0]];
}
}