It allows making the window full screen by clicking full screen button
provided by the platform (the left-top green button) and save some code.
Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com>
---
ui/cocoa.m | 423 ++++++++++++++++++++++++++++++-------------------------------
1 file changed, 207 insertions(+), 216 deletions(-)
diff --git a/ui/cocoa.m b/ui/cocoa.m
index cb6090905999..518fae26f6f4 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -303,20 +303,17 @@ static void handleAnyDeviceErrors(Error * err)
*/
@interface QemuCocoaView : NSView
{
+ NSTrackingArea *trackingArea;
QEMUScreen screen;
- NSWindow *fullScreenWindow;
- float cx,cy,cw,ch,cdx,cdy;
pixman_image_t *pixman_image;
QKbdState *kbd;
BOOL isMouseGrabbed;
- BOOL isFullscreen;
BOOL isAbsoluteEnabled;
CFMachPortRef eventsTap;
}
- (void) switchSurface:(pixman_image_t *)image;
- (void) grabMouse;
- (void) ungrabMouse;
-- (void) toggleFullScreen:(id)sender;
- (void) setFullGrab:(id)sender;
- (void) handleMonitorInput:(NSEvent *)event;
- (bool) handleEvent:(NSEvent *)event;
@@ -332,8 +329,6 @@ - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
*/
- (BOOL) isMouseGrabbed;
- (BOOL) isAbsoluteEnabled;
-- (float) cdx;
-- (float) cdy;
- (QEMUScreen) gscreen;
- (void) raiseAllKeys;
@end
@@ -391,46 +386,43 @@ - (BOOL) isOpaque
return YES;
}
-- (BOOL) screenContainsPoint:(NSPoint) p
+- (void) removeTrackingRect
{
- return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
+ if (trackingArea) {
+ [self removeTrackingArea:trackingArea];
+ [trackingArea release];
+ trackingArea = nil;
+ }
}
-/* Get location of event and convert to virtual screen coordinate */
-- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
+- (void) frameUpdated
{
- NSWindow *eventWindow = [ev window];
- // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
- CGRect r = CGRectZero;
- r.origin = [ev locationInWindow];
- if (!eventWindow) {
- if (!isFullscreen) {
- return [[self window] convertRectFromScreen:r].origin;
- } else {
- CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin;
- CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil];
- if (stretch_video) {
- loc.x /= cdx;
- loc.y /= cdy;
- }
- return loc;
- }
- } else if ([[self window] isEqual:eventWindow]) {
- if (!isFullscreen) {
- return r.origin;
- } else {
- CGPoint loc = [self convertPoint:r.origin fromView:nil];
- if (stretch_video) {
- loc.x /= cdx;
- loc.y /= cdy;
- }
- return loc;
- }
- } else {
- return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin;
+ [self removeTrackingRect];
+
+ if ([self window]) {
+ NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
+ NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved;
+ trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame]
+ options:options
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+ [self updateUIInfo];
}
}
+- (void) viewDidMoveToWindow
+{
+ [self resizeWindow];
+ [self frameUpdated];
+}
+
+- (void) viewWillMoveToWindow:(NSWindow *)newWindow
+{
+ [self removeTrackingRect];
+}
+
- (void) hideCursor
{
if (!cursor_hide) {
@@ -510,36 +502,43 @@ - (void) drawRect:(NSRect) rect
}
}
-- (void) setContentDimensions
+- (NSSize) fixZoomedFullScreenSize:(NSSize)proposedSize
{
- COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
+ NSSize size;
- if (isFullscreen) {
- cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
- cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
+ size.width = (CGFloat)screen.width * proposedSize.height;
+ size.height = (CGFloat)screen.height * proposedSize.width;
- /* stretches video, but keeps same aspect ratio */
- if (stretch_video == true) {
- /* use smallest stretch value - prevents clipping on sides */
- if (MIN(cdx, cdy) == cdx) {
- cdy = cdx;
- } else {
- cdx = cdy;
- }
- } else { /* No stretching */
- cdx = cdy = 1;
- }
- cw = screen.width * cdx;
- ch = screen.height * cdy;
- cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
- cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
+ if (size.width < size.height) {
+ size.width /= screen.height;
+ size.height = proposedSize.height;
} else {
- cx = 0;
- cy = 0;
- cw = screen.width;
- ch = screen.height;
- cdx = 1.0;
- cdy = 1.0;
+ size.width = proposedSize.width;
+ size.height /= screen.width;
+ }
+
+ return size;
+}
+
+- (NSSize) screenSafeAreaSize
+{
+ NSSize size = [[[self window] screen] frame].size;
+ NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
+ size.width -= insets.left + insets.right;
+ size.height -= insets.top + insets.bottom;
+ return size;
+}
+
+- (void) resizeWindow
+{
+ [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
+
+ if (!stretch_video) {
+ [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
+ [[self window] center];
+ } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
+ [[self window] setContentSize:[self fixZoomedFullScreenSize:[self screenSafeAreaSize]]];
+ [[self window] center];
}
}
@@ -563,9 +562,10 @@ - (void) updateUIInfoLocked
CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
NSSize screenSize = [[[self window] screen] frame].size;
CGSize screenPhysicalSize = CGDisplayScreenSize(display);
+ bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
CVDisplayLinkRef displayLink;
- frameSize = isFullscreen ? screenSize : [self frame].size;
+ frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
@@ -612,31 +612,19 @@ - (void) updateUIInfo
});
}
-- (void)viewDidMoveToWindow
-{
- [self updateUIInfo];
-}
-
- (void) switchSurface:(pixman_image_t *)image
{
COCOA_DEBUG("QemuCocoaView: switchSurface\n");
int w = pixman_image_get_width(image);
int h = pixman_image_get_height(image);
- /* cdx == 0 means this is our very first surface, in which case we need
- * to recalculate the content dimensions even if it happens to be the size
- * of the initial empty window.
- */
- bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
- int oldh = screen.height;
- if (isResize) {
+ if (w != screen.width || h != screen.height) {
// Resize before we trigger the redraw, or we'll redraw at the wrong size
COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
screen.width = w;
screen.height = h;
- [self setContentDimensions];
- [self setFrame:NSMakeRect(cx, cy, cw, ch)];
+ [self resizeWindow];
}
// update screenBuffer
@@ -645,51 +633,6 @@ - (void) switchSurface:(pixman_image_t *)image
}
pixman_image = image;
-
- // update windows
- if (isFullscreen) {
- [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
- [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
- } else {
- if (qemu_name)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
- [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
- }
-
- if (isResize) {
- [normalWindow center];
- }
-}
-
-- (void) toggleFullScreen:(id)sender
-{
- COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
-
- if (isFullscreen) { // switch from fullscreen to desktop
- isFullscreen = FALSE;
- [self ungrabMouse];
- [self setContentDimensions];
- [fullScreenWindow close];
- [normalWindow setContentView: self];
- [normalWindow makeKeyAndOrderFront: self];
- [NSMenu setMenuBarVisible:YES];
- } else { // switch from desktop to fullscreen
- isFullscreen = TRUE;
- [normalWindow orderOut: nil]; /* Hide the window */
- [self grabMouse];
- [self setContentDimensions];
- [NSMenu setMenuBarVisible:NO];
- fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
- styleMask:NSWindowStyleMaskBorderless
- backing:NSBackingStoreBuffered
- defer:NO];
- [fullScreenWindow setAcceptsMouseMovedEvents: YES];
- [fullScreenWindow setHasShadow:NO];
- [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
- [self setFrame:NSMakeRect(cx, cy, cw, ch)];
- [[fullScreenWindow contentView] addSubview: self];
- [fullScreenWindow makeKeyAndOrderFront:self];
- }
}
- (void) setFullGrab:(id)sender
@@ -803,7 +746,6 @@ - (bool) handleEventLocked:(NSEvent *)event
COCOA_DEBUG("QemuCocoaView: handleEvent\n");
int buttons = 0;
int keycode = 0;
- NSPoint p = [self screenLocationOfEvent:event];
NSUInteger modifiers = [event modifierFlags];
/*
@@ -999,48 +941,6 @@ - (bool) handleEventLocked:(NSEvent *)event
qkbd_state_key_event(kbd, keycode, false);
}
return true;
- case NSEventTypeMouseMoved:
- if (isAbsoluteEnabled) {
- // Cursor re-entered into a window might generate events bound to screen coordinates
- // and `nil` window property, and in full screen mode, current window might not be
- // key window, where event location alone should suffice.
- if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) {
- if (isMouseGrabbed) {
- [self ungrabMouse];
- }
- } else {
- if (!isMouseGrabbed) {
- [self grabMouse];
- }
- }
- }
- return [self handleMouseEvent:event];
- case NSEventTypeLeftMouseDown:
- return [self handleMouseEvent:event button:MOUSE_EVENT_LBUTTON down:true];
- case NSEventTypeRightMouseDown:
- return [self handleMouseEvent:event button:MOUSE_EVENT_RBUTTON down:true];
- case NSEventTypeLeftMouseDragged:
- return [self handleMouseEvent:event button:MOUSE_EVENT_LBUTTON down:true];
- case NSEventTypeRightMouseDragged:
- return [self handleMouseEvent:event button:MOUSE_EVENT_RBUTTON down:true];
- case NSEventTypeOtherMouseDragged:
- return [self handleMouseEvent:event button:MOUSE_EVENT_MBUTTON down:true];
- case NSEventTypeLeftMouseUp:
- if (!isMouseGrabbed && [self screenContainsPoint:p]) {
- /*
- * In fullscreen mode, the window of cocoaView may not be the
- * key window, therefore the position relative to the virtual
- * screen alone will be sufficient.
- */
- if(isFullscreen || [[self window] isKeyWindow]) {
- [self grabMouse];
- }
- }
- return [self handleMouseEvent:event button:MOUSE_EVENT_LBUTTON down:false];
- case NSEventTypeRightMouseUp:
- return [self handleMouseEvent:event button:MOUSE_EVENT_RBUTTON down:false];
- case NSEventTypeOtherMouseUp:
- return [self handleMouseEvent:event button:MOUSE_EVENT_MBUTTON down:false];
case NSEventTypeScrollWheel:
/*
* Send wheel events to the guest regardless of window focus.
@@ -1077,53 +977,117 @@ - (bool) handleEventLocked:(NSEvent *)event
}
}
-- (bool) handleMouseEvent:(NSEvent *)event
+- (void) handleMouseEvent:(NSEvent *)event
{
if (!isMouseGrabbed) {
- return false;
+ return;
}
- if (isAbsoluteEnabled) {
- NSPoint p = [self screenLocationOfEvent:event];
- /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
- * The check on screenContainsPoint is to avoid sending out of range values for
- * clicks in the titlebar.
- */
- if ([self screenContainsPoint:p]) {
- qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width);
- qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height);
+ with_bql(^{
+ if (isAbsoluteEnabled) {
+ CGFloat d = (CGFloat)screen.height / [self frame].size.height;
+ NSPoint p = [event locationInWindow];
+ /* Note that the origin for Cocoa mouse coords is bottom left, not top left. */
+ qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x * d, 0, screen.width);
+ qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
+ } else {
+ qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, [event deltaX]);
+ qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, [event deltaY]);
}
- } else {
- qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]);
- qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]);
+
+ qemu_input_event_sync();
+ });
+}
+
+- (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
+{
+ if (!isMouseGrabbed) {
+ return;
}
- qemu_input_event_sync();
+ with_bql(^{
+ qemu_input_queue_btn(dcl.con, button, down);
+ });
- return true;
+ [self handleMouseEvent:event];
}
-- (bool) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
+- (void) mouseExited:(NSEvent *)event
{
- if (!isMouseGrabbed && ![[self window] isKeyWindow]) {
- return false;
+ if (isAbsoluteEnabled && isMouseGrabbed) {
+ [self ungrabMouse];
}
+}
- qemu_input_queue_btn(dcl.con, button, down);
+- (void) mouseEntered:(NSEvent *)event
+{
+ if (isAbsoluteEnabled && !isMouseGrabbed) {
+ [self grabMouse];
+ }
+}
+
+- (void) mouseMoved:(NSEvent *)event
+{
+ [self handleMouseEvent:event];
+}
+
+- (void) mouseDown:(NSEvent *)event
+{
+ [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
+}
+
+- (void) rightMouseDown:(NSEvent *)event
+{
+ [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
+}
+
+- (void) otherMouseDown:(NSEvent *)event
+{
+ [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
+}
+
+- (void) mouseDragged:(NSEvent *)event
+{
+ [self handleMouseEvent:event];
+}
+
+- (void) rightMouseDragged:(NSEvent *)event
+{
+ [self handleMouseEvent:event];
+}
+
+- (void) otherMouseDragged:(NSEvent *)event
+{
+ [self handleMouseEvent:event];
+}
+
+- (void) mouseUp:(NSEvent *)event
+{
+ if (!isMouseGrabbed) {
+ [self grabMouse];
+ }
+
+ [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
+}
+
+- (void) rightMouseUp:(NSEvent *)event
+{
+ [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
+}
- return [self handleMouseEvent:event];
+- (void) otherMouseUp:(NSEvent *)event
+{
+ [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
}
- (void) grabMouse
{
COCOA_DEBUG("QemuCocoaView: grabMouse\n");
- if (!isFullscreen) {
- if (qemu_name)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
- else
- [normalWindow setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
- }
+ if (qemu_name)
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
+ else
+ [normalWindow setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
[self hideCursor];
CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
@@ -1133,15 +1097,14 @@ - (void) ungrabMouse
{
COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
- if (!isFullscreen) {
- if (qemu_name)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
- else
- [normalWindow setTitle:@"QEMU"];
- }
+ if (qemu_name)
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
+ else
+ [normalWindow setTitle:@"QEMU"];
[self unhideCursor];
CGAssociateMouseAndMouseCursorPosition(TRUE);
isMouseGrabbed = FALSE;
+ [self raiseAllButtons];
}
- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
@@ -1152,8 +1115,6 @@ - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
}
- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
-- (float) cdx {return cdx;}
-- (float) cdy {return cdy;}
- (QEMUScreen) gscreen {return screen;}
/*
@@ -1167,6 +1128,15 @@ - (void) raiseAllKeys
qkbd_state_lift_all_keys(kbd);
});
}
+
+- (void) raiseAllButtons
+{
+ with_bql(^{
+ qemu_input_queue_btn(dcl.con, INPUT_BUTTON_LEFT, false);
+ qemu_input_queue_btn(dcl.con, INPUT_BUTTON_RIGHT, false);
+ qemu_input_queue_btn(dcl.con, INPUT_BUTTON_MIDDLE, false);
+ });
+}
@end
@@ -1181,7 +1151,6 @@ @interface QemuCocoaAppController : NSObject
{
}
- (void)doToggleFullScreen:(id)sender;
-- (void)toggleFullScreen:(id)sender;
- (void)showQEMUDoc:(id)sender;
- (void)zoomToFit:(id) sender;
- (void)displayConsole:(id)sender;
@@ -1223,7 +1192,8 @@ - (id) init
exit(1);
}
[normalWindow setAcceptsMouseMovedEvents:YES];
- [normalWindow setTitle:@"QEMU"];
+ [normalWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [normalWindow setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
[normalWindow setContentView:cocoaView];
[normalWindow makeKeyAndOrderFront:self];
[normalWindow center];
@@ -1293,10 +1263,21 @@ - (void)windowDidChangeScreen:(NSNotification *)notification
[cocoaView updateUIInfo];
}
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+ [cocoaView grabMouse];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+ [cocoaView resizeWindow];
+ [cocoaView ungrabMouse];
+}
+
- (void)windowDidResize:(NSNotification *)notification
{
[cocoaView updateBounds];
- [cocoaView updateUIInfo];
+ [cocoaView frameUpdated];
}
/* Called when the user clicks on a window's close button */
@@ -1312,6 +1293,23 @@ - (BOOL)windowShouldClose:(id)sender
return NO;
}
+- (NSSize) window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
+{
+ if (stretch_video) {
+ return [cocoaView fixZoomedFullScreenSize:proposedSize];
+ }
+
+ return NSMakeSize([cocoaView gscreen].width, [cocoaView gscreen].height);
+}
+
+- (NSApplicationPresentationOptions) window:(NSWindow *)window
+ willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
+
+{
+ return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
+ NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+}
+
/*
* Called when QEMU goes into the background. Note that
* [-NSWindowDelegate windowDidResignKey:] is used here instead of
@@ -1331,14 +1329,7 @@ - (void) windowDidResignKey: (NSNotification *)aNotification
*/
- (void) doToggleFullScreen:(id)sender
{
- [self toggleFullScreen:(id)sender];
-}
-
-- (void)toggleFullScreen:(id)sender
-{
- COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
-
- [cocoaView toggleFullScreen:sender];
+ [normalWindow toggleFullScreen:sender];
}
- (void) setFullGrab:(id)sender
@@ -1389,6 +1380,7 @@ - (void)zoomToFit:(id) sender
if (stretch_video == true) {
[sender setState: NSControlStateValueOn];
} else {
+ [cocoaView resizeWindow];
[sender setState: NSControlStateValueOff];
}
}
@@ -2021,8 +2013,7 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
/* if fullscreen mode is to be used */
if (opts->has_full_screen && opts->full_screen) {
- [NSApp activateIgnoringOtherApps: YES];
- [controller toggleFullScreen: nil];
+ [normalWindow toggleFullScreen: nil];
}
if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
[controller setFullGrab: nil];
--
2.43.0
© 2016 - 2024 Red Hat, Inc.