Darwin Cocoa patches:

- Add 'zoom-interpolation' to smooth scaled display with 'zoom-to-fit' (Carwyn)
 - Set clipsToBounds on macOS 14 to fix window clipping (David)
 - Use NSWindow's ability to resize (Akihiko)
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAmXm/GcACgkQ4+MsLN6t
 wN6/hw//erpUlp7YR1Ra+BtVbn9GA8UeXITYN03FSdz45b9DVTwA6C1kid3ljZWG
 OhlT8QlXcp4lXRUrGkeVwF5EiBjTT5YGAlzQ9+FnZSo+KSMEtPm9ixmARJgzp0Lg
 rLKmIA0YMEeWuknR/DngyRBFT+P3z4/IdTTtVYYd+vUnuWvmUYVk81hh6mlsBC3U
 bDenS1IFGWET+FinNRhB8ib+JGbxsaij1m7rcIhOW06cg3uBLcgCbvFUGOWmHDAm
 sVYoOq/4gXZMZyvlhzxtPt51OqIBa4wxRIKss4sDlpnvvb8sJ16PWGw7CMb/9TC8
 0lTzaSNs8Z+fqU5bmfUMIuLu36j/8eN5nxvcrg+vwTXTPmJ6z0j7oP7jJod1cwFq
 ZeIEtN5QBKCY5i+vYf7ve2frUUf3sS2TKjssFjghlfYksVMRkjLZjyLJVqTl3YP3
 5FxOZ89bKvSFtbFczC0ErpAP9HpqplTGqmbUSAXA4EsGG/X4fkH7ElZS8fAgD5oB
 nsEKS7BCXA5k9Vswu6wBO9bvFxp0puy/uIVabK8tOBZ5WjQeDPfM94QTEDGKYvK4
 Tpa4vnvdDJYB6x5WK3onVIAdYvuM0DT5/jECpdlNXQPmh3glfoHkAkM540gXtqfO
 ooS6fvvDhdB0gj8FMd4AgiiL3h4Tt+yREq/DJ0kuHti1z1iqOnk=
 =I4BB
 -----END PGP SIGNATURE-----

Merge tag 'darwin-20240305' of https://github.com/philmd/qemu into staging

Darwin Cocoa patches:

- Add 'zoom-interpolation' to smooth scaled display with 'zoom-to-fit' (Carwyn)
- Set clipsToBounds on macOS 14 to fix window clipping (David)
- Use NSWindow's ability to resize (Akihiko)

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAmXm/GcACgkQ4+MsLN6t
# wN6/hw//erpUlp7YR1Ra+BtVbn9GA8UeXITYN03FSdz45b9DVTwA6C1kid3ljZWG
# OhlT8QlXcp4lXRUrGkeVwF5EiBjTT5YGAlzQ9+FnZSo+KSMEtPm9ixmARJgzp0Lg
# rLKmIA0YMEeWuknR/DngyRBFT+P3z4/IdTTtVYYd+vUnuWvmUYVk81hh6mlsBC3U
# bDenS1IFGWET+FinNRhB8ib+JGbxsaij1m7rcIhOW06cg3uBLcgCbvFUGOWmHDAm
# sVYoOq/4gXZMZyvlhzxtPt51OqIBa4wxRIKss4sDlpnvvb8sJ16PWGw7CMb/9TC8
# 0lTzaSNs8Z+fqU5bmfUMIuLu36j/8eN5nxvcrg+vwTXTPmJ6z0j7oP7jJod1cwFq
# ZeIEtN5QBKCY5i+vYf7ve2frUUf3sS2TKjssFjghlfYksVMRkjLZjyLJVqTl3YP3
# 5FxOZ89bKvSFtbFczC0ErpAP9HpqplTGqmbUSAXA4EsGG/X4fkH7ElZS8fAgD5oB
# nsEKS7BCXA5k9Vswu6wBO9bvFxp0puy/uIVabK8tOBZ5WjQeDPfM94QTEDGKYvK4
# Tpa4vnvdDJYB6x5WK3onVIAdYvuM0DT5/jECpdlNXQPmh3glfoHkAkM540gXtqfO
# ooS6fvvDhdB0gj8FMd4AgiiL3h4Tt+yREq/DJ0kuHti1z1iqOnk=
# =I4BB
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 05 Mar 2024 11:05:11 GMT
# gpg:                using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE
# gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full]
# Primary key fingerprint: FAAB E75E 1291 7221 DCFD  6BB2 E3E3 2C2C DEAD C0DE

* tag 'darwin-20240305' of https://github.com/philmd/qemu:
  ui/cocoa: Remove stretch_video flag
  ui/cocoa: Call console_select() with the BQL
  ui/cocoa: Make window resizable
  ui/cocoa: Remove normalWindow
  ui/cocoa: Let the platform toggle fullscreen
  ui/cocoa: Fix pause label coordinates
  ui/cocoa: Scale with NSView instead of Core Graphics
  ui/cocoa: Release specific mouse buttons
  ui/cocoa: Immediately call [-QemuCocoaView handleMouseEvent:buttons:]
  ui/cocoa: Split [-QemuCocoaView handleEventLocked:]
  ui/cocoa: Fix window clipping on macOS 14
  ui/cocoa: add zoom-interpolation display option

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2024-03-08 18:19:25 +00:00
commit 84644ac1b0
2 changed files with 273 additions and 311 deletions

View File

@ -1421,6 +1421,9 @@
# turned off the host window will be resized instead. Defaults to # turned off the host window will be resized instead. Defaults to
# "off". (Since 8.2) # "off". (Since 8.2)
# #
# @zoom-interpolation: Apply interpolation to smooth output when
# zoom-to-fit is enabled. Defaults to "off". (Since 9.0)
#
# Since: 7.0 # Since: 7.0
## ##
{ 'struct': 'DisplayCocoa', { 'struct': 'DisplayCocoa',
@ -1428,7 +1431,8 @@
'*left-command-key': 'bool', '*left-command-key': 'bool',
'*full-grab': 'bool', '*full-grab': 'bool',
'*swap-opt-cmd': 'bool', '*swap-opt-cmd': 'bool',
'*zoom-to-fit': 'bool' '*zoom-to-fit': 'bool',
'*zoom-interpolation': 'bool'
} } } }
## ##

View File

@ -54,6 +54,10 @@
#define MAC_OS_X_VERSION_10_13 101300 #define MAC_OS_X_VERSION_10_13 101300
#endif #endif
#ifndef MAC_OS_VERSION_14_0
#define MAC_OS_VERSION_14_0 140000
#endif
/* 10.14 deprecates NSOnState and NSOffState in favor of /* 10.14 deprecates NSOnState and NSOffState in favor of
* NSControlStateValueOn/Off, which were introduced in 10.13. * NSControlStateValueOn/Off, which were introduced in 10.13.
* Define for older versions * Define for older versions
@ -89,7 +93,6 @@ static void cocoa_switch(DisplayChangeListener *dcl,
static void cocoa_refresh(DisplayChangeListener *dcl); static void cocoa_refresh(DisplayChangeListener *dcl);
static NSWindow *normalWindow;
static const DisplayChangeListenerOps dcl_ops = { static const DisplayChangeListenerOps dcl_ops = {
.dpy_name = "cocoa", .dpy_name = "cocoa",
.dpy_gfx_update = cocoa_update, .dpy_gfx_update = cocoa_update,
@ -99,12 +102,11 @@ static const DisplayChangeListenerOps dcl_ops = {
static DisplayChangeListener dcl = { static DisplayChangeListener dcl = {
.ops = &dcl_ops, .ops = &dcl_ops,
}; };
static int last_buttons;
static int cursor_hide = 1; static int cursor_hide = 1;
static int left_command_key_enabled = 1; static int left_command_key_enabled = 1;
static bool swap_opt_cmd; static bool swap_opt_cmd;
static bool stretch_video; static CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
static NSTextField *pauseLabel; static NSTextField *pauseLabel;
static bool allow_events; static bool allow_events;
@ -304,20 +306,17 @@ static void handleAnyDeviceErrors(Error * err)
*/ */
@interface QemuCocoaView : NSView @interface QemuCocoaView : NSView
{ {
NSTrackingArea *trackingArea;
QEMUScreen screen; QEMUScreen screen;
NSWindow *fullScreenWindow;
float cx,cy,cw,ch,cdx,cdy;
pixman_image_t *pixman_image; pixman_image_t *pixman_image;
QKbdState *kbd; QKbdState *kbd;
BOOL isMouseGrabbed; BOOL isMouseGrabbed;
BOOL isFullscreen;
BOOL isAbsoluteEnabled; BOOL isAbsoluteEnabled;
CFMachPortRef eventsTap; CFMachPortRef eventsTap;
} }
- (void) switchSurface:(pixman_image_t *)image; - (void) switchSurface:(pixman_image_t *)image;
- (void) grabMouse; - (void) grabMouse;
- (void) ungrabMouse; - (void) ungrabMouse;
- (void) toggleFullScreen:(id)sender;
- (void) setFullGrab:(id)sender; - (void) setFullGrab:(id)sender;
- (void) handleMonitorInput:(NSEvent *)event; - (void) handleMonitorInput:(NSEvent *)event;
- (bool) handleEvent:(NSEvent *)event; - (bool) handleEvent:(NSEvent *)event;
@ -333,8 +332,6 @@ static void handleAnyDeviceErrors(Error * err)
*/ */
- (BOOL) isMouseGrabbed; - (BOOL) isMouseGrabbed;
- (BOOL) isAbsoluteEnabled; - (BOOL) isAbsoluteEnabled;
- (float) cdx;
- (float) cdy;
- (QEMUScreen) gscreen; - (QEMUScreen) gscreen;
- (void) raiseAllKeys; - (void) raiseAllKeys;
@end @end
@ -365,6 +362,9 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
screen.width = frameRect.size.width; screen.width = frameRect.size.width;
screen.height = frameRect.size.height; screen.height = frameRect.size.height;
kbd = qkbd_state_init(dcl.con); kbd = qkbd_state_init(dcl.con);
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
[self setClipsToBounds:YES];
#endif
} }
return self; return self;
@ -392,46 +392,43 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
return YES; 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 */ - (void) frameUpdated
- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
{ {
NSWindow *eventWindow = [ev window]; [self removeTrackingRect];
// XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
CGRect r = CGRectZero; if ([self window]) {
r.origin = [ev locationInWindow]; NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
if (!eventWindow) { NSTrackingMouseEnteredAndExited |
if (!isFullscreen) { NSTrackingMouseMoved;
return [[self window] convertRectFromScreen:r].origin; trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame]
} else { options:options
CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin; owner:self
CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil]; userInfo:nil];
if (stretch_video) { [self addTrackingArea:trackingArea];
loc.x /= cdx; [self updateUIInfo];
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;
} }
} }
- (void) viewDidMoveToWindow
{
[self resizeWindow];
[self frameUpdated];
}
- (void) viewWillMoveToWindow:(NSWindow *)newWindow
{
[self removeTrackingRect];
}
- (void) hideCursor - (void) hideCursor
{ {
if (!cursor_hide) { if (!cursor_hide) {
@ -455,7 +452,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
// get CoreGraphic context // get CoreGraphic context
CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext]; CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
CGContextSetShouldAntialias (viewContextRef, NO); CGContextSetShouldAntialias (viewContextRef, NO);
// draw screen bitmap directly to Core Graphics context // draw screen bitmap directly to Core Graphics context
@ -497,10 +494,8 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
[self getRectsBeingDrawn:&rectList count:&rectCount]; [self getRectsBeingDrawn:&rectList count:&rectCount];
for (i = 0; i < rectCount; i++) { for (i = 0; i < rectCount; i++) {
clipRect.origin.x = rectList[i].origin.x / cdx; clipRect = rectList[i];
clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy; clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
clipRect.size.width = rectList[i].size.width / cdx;
clipRect.size.height = rectList[i].size.height / cdy;
clipImageRef = CGImageCreateWithImageInRect( clipImageRef = CGImageCreateWithImageInRect(
imageRef, imageRef,
clipRect clipRect
@ -513,39 +508,33 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} }
} }
- (void) setContentDimensions - (NSSize) screenSafeAreaSize
{ {
COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 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;
}
if (isFullscreen) { - (void) resizeWindow
cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; {
cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
/* stretches video, but keeps same aspect ratio */ if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
if (stretch_video == true) { [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
/* use smallest stretch value - prevents clipping on sides */ [[self window] center];
if (MIN(cdx, cdy) == cdx) { } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
cdy = cdx; [[self window] setContentSize:[self screenSafeAreaSize]];
} else { [[self window] center];
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;
} else {
cx = 0;
cy = 0;
cw = screen.width;
ch = screen.height;
cdx = 1.0;
cdy = 1.0;
} }
} }
- (void) updateBounds
{
[self setBoundsSize:NSMakeSize(screen.width, screen.height)];
}
- (void) updateUIInfoLocked - (void) updateUIInfoLocked
{ {
/* Must be called with the BQL, i.e. via updateUIInfo */ /* Must be called with the BQL, i.e. via updateUIInfo */
@ -561,9 +550,10 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]; CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
NSSize screenSize = [[[self window] screen] frame].size; NSSize screenSize = [[[self window] screen] frame].size;
CGSize screenPhysicalSize = CGDisplayScreenSize(display); CGSize screenPhysicalSize = CGDisplayScreenSize(display);
bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
CVDisplayLinkRef displayLink; CVDisplayLinkRef displayLink;
frameSize = isFullscreen ? screenSize : [self frame].size; frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) { if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink); CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
@ -610,31 +600,20 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
}); });
} }
- (void)viewDidMoveToWindow
{
[self updateUIInfo];
}
- (void) switchSurface:(pixman_image_t *)image - (void) switchSurface:(pixman_image_t *)image
{ {
COCOA_DEBUG("QemuCocoaView: switchSurface\n"); COCOA_DEBUG("QemuCocoaView: switchSurface\n");
int w = pixman_image_get_width(image); int w = pixman_image_get_width(image);
int h = pixman_image_get_height(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 (w != screen.width || h != screen.height) {
if (isResize) {
// Resize before we trigger the redraw, or we'll redraw at the wrong size // 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); COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
screen.width = w; screen.width = w;
screen.height = h; screen.height = h;
[self setContentDimensions]; [self resizeWindow];
[self setFrame:NSMakeRect(cx, cy, cw, ch)]; [self updateBounds];
} }
// update screenBuffer // update screenBuffer
@ -643,51 +622,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} }
pixman_image = 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 - (void) setFullGrab:(id)sender
@ -799,11 +733,8 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
{ {
/* Return true if we handled the event, false if it should be given to OSX */ /* Return true if we handled the event, false if it should be given to OSX */
COCOA_DEBUG("QemuCocoaView: handleEvent\n"); COCOA_DEBUG("QemuCocoaView: handleEvent\n");
int buttons = 0; InputButton button;
int keycode = 0; int keycode = 0;
bool mouse_event = false;
// Location of event in virtual screen coordinates
NSPoint p = [self screenLocationOfEvent:event];
NSUInteger modifiers = [event modifierFlags]; NSUInteger modifiers = [event modifierFlags];
/* /*
@ -947,7 +878,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} }
break; break;
} }
break; return true;
case NSEventTypeKeyDown: case NSEventTypeKeyDown:
keycode = cocoa_keycode_to_qemu([event keyCode]); keycode = cocoa_keycode_to_qemu([event keyCode]);
@ -983,7 +914,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} else { } else {
[self handleMonitorInput: event]; [self handleMonitorInput: event];
} }
break; return true;
case NSEventTypeKeyUp: case NSEventTypeKeyUp:
keycode = cocoa_keycode_to_qemu([event keyCode]); keycode = cocoa_keycode_to_qemu([event keyCode]);
@ -996,153 +927,151 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
if (qemu_console_is_graphic(NULL)) { if (qemu_console_is_graphic(NULL)) {
qkbd_state_key_event(kbd, keycode, false); qkbd_state_key_event(kbd, keycode, false);
} }
break; 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];
}
}
}
mouse_event = true;
break;
case NSEventTypeLeftMouseDown:
buttons |= MOUSE_EVENT_LBUTTON;
mouse_event = true;
break;
case NSEventTypeRightMouseDown:
buttons |= MOUSE_EVENT_RBUTTON;
mouse_event = true;
break;
case NSEventTypeOtherMouseDown:
buttons |= MOUSE_EVENT_MBUTTON;
mouse_event = true;
break;
case NSEventTypeLeftMouseDragged:
buttons |= MOUSE_EVENT_LBUTTON;
mouse_event = true;
break;
case NSEventTypeRightMouseDragged:
buttons |= MOUSE_EVENT_RBUTTON;
mouse_event = true;
break;
case NSEventTypeOtherMouseDragged:
buttons |= MOUSE_EVENT_MBUTTON;
mouse_event = true;
break;
case NSEventTypeLeftMouseUp:
mouse_event = true;
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];
}
}
break;
case NSEventTypeRightMouseUp:
mouse_event = true;
break;
case NSEventTypeOtherMouseUp:
mouse_event = true;
break;
case NSEventTypeScrollWheel: case NSEventTypeScrollWheel:
/* /*
* Send wheel events to the guest regardless of window focus. * Send wheel events to the guest regardless of window focus.
* This is in-line with standard Mac OS X UI behaviour. * This is in-line with standard Mac OS X UI behaviour.
*/ */
/*
* We shouldn't have got a scroll event when deltaY and delta Y
* are zero, hence no harm in dropping the event
*/
if ([event deltaY] != 0 || [event deltaX] != 0) {
/* Determine if this is a scroll up or scroll down event */ /* Determine if this is a scroll up or scroll down event */
if ([event deltaY] != 0) { if ([event deltaY] != 0) {
buttons = ([event deltaY] > 0) ? button = ([event deltaY] > 0) ?
INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
} else if ([event deltaX] != 0) { } else if ([event deltaX] != 0) {
buttons = ([event deltaX] > 0) ? button = ([event deltaX] > 0) ?
INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT; INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
} } else {
/*
qemu_input_queue_btn(dcl.con, buttons, true); * We shouldn't have got a scroll event when deltaY and delta Y
qemu_input_event_sync(); * are zero, hence no harm in dropping the event
qemu_input_queue_btn(dcl.con, buttons, false); */
qemu_input_event_sync(); return true;
} }
/* qemu_input_queue_btn(dcl.con, button, true);
* Since deltaX/deltaY also report scroll wheel events we prevent mouse qemu_input_event_sync();
* movement code from executing. qemu_input_queue_btn(dcl.con, button, false);
*/ qemu_input_event_sync();
mouse_event = false;
break; return true;
default: default:
return false; return false;
} }
}
if (mouse_event) { - (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
/* Don't send button events to the guest unless we've got a {
* mouse grab or window focus. If we have neither then this event if (!isMouseGrabbed) {
* is the user clicking on the background window to activate and return;
* bring us to the front, which will be done by the sendEvent
* call below. We definitely don't want to pass that click through
* to the guest.
*/
if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
(last_buttons != buttons)) {
static uint32_t bmap[INPUT_BUTTON__MAX] = {
[INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
[INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
[INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON
};
qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons);
last_buttons = buttons;
}
if (isMouseGrabbed) {
if (isAbsoluteEnabled) {
/* 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);
}
} 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]);
}
} else {
return false;
}
qemu_input_event_sync();
} }
return true;
with_bql(^{
qemu_input_queue_btn(dcl.con, button, down);
});
[self handleMouseEvent:event];
}
- (void) handleMouseEvent:(NSEvent *)event
{
if (!isMouseGrabbed) {
return;
}
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]);
}
qemu_input_event_sync();
});
}
- (void) mouseExited:(NSEvent *)event
{
if (isAbsoluteEnabled && isMouseGrabbed) {
[self ungrabMouse];
}
}
- (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];
}
- (void) otherMouseUp:(NSEvent *)event
{
[self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
} }
- (void) grabMouse - (void) grabMouse
{ {
COCOA_DEBUG("QemuCocoaView: grabMouse\n"); COCOA_DEBUG("QemuCocoaView: grabMouse\n");
if (!isFullscreen) { if (qemu_name)
if (qemu_name) [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
[normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]]; else
else [[self window] setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
[normalWindow setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
}
[self hideCursor]; [self hideCursor];
CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
@ -1152,15 +1081,14 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
{ {
COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
if (!isFullscreen) { if (qemu_name)
if (qemu_name) [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
[normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; else
else [[self window] setTitle:@"QEMU"];
[normalWindow setTitle:@"QEMU"];
}
[self unhideCursor]; [self unhideCursor];
CGAssociateMouseAndMouseCursorPosition(TRUE); CGAssociateMouseAndMouseCursorPosition(TRUE);
isMouseGrabbed = FALSE; isMouseGrabbed = FALSE;
[self raiseAllButtons];
} }
- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
@ -1171,8 +1099,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} }
- (BOOL) isMouseGrabbed {return isMouseGrabbed;} - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
- (float) cdx {return cdx;}
- (float) cdy {return cdy;}
- (QEMUScreen) gscreen {return screen;} - (QEMUScreen) gscreen {return screen;}
/* /*
@ -1186,6 +1112,15 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
qkbd_state_lift_all_keys(kbd); 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 @end
@ -1200,7 +1135,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
{ {
} }
- (void)doToggleFullScreen:(id)sender; - (void)doToggleFullScreen:(id)sender;
- (void)toggleFullScreen:(id)sender;
- (void)showQEMUDoc:(id)sender; - (void)showQEMUDoc:(id)sender;
- (void)zoomToFit:(id) sender; - (void)zoomToFit:(id) sender;
- (void)displayConsole:(id)sender; - (void)displayConsole:(id)sender;
@ -1221,6 +1155,8 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
@implementation QemuCocoaAppController @implementation QemuCocoaAppController
- (id) init - (id) init
{ {
NSWindow *window;
COCOA_DEBUG("QemuCocoaAppController: init\n"); COCOA_DEBUG("QemuCocoaAppController: init\n");
self = [super init]; self = [super init];
@ -1234,19 +1170,20 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
} }
// create a window // create a window
normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
backing:NSBackingStoreBuffered defer:NO]; backing:NSBackingStoreBuffered defer:NO];
if(!normalWindow) { if(!window) {
error_report("(cocoa) can't create window"); error_report("(cocoa) can't create window");
exit(1); exit(1);
} }
[normalWindow setAcceptsMouseMovedEvents:YES]; [window setAcceptsMouseMovedEvents:YES];
[normalWindow setTitle:@"QEMU"]; [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
[normalWindow setContentView:cocoaView]; [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
[normalWindow makeKeyAndOrderFront:self]; [window setContentView:cocoaView];
[normalWindow center]; [window makeKeyAndOrderFront:self];
[normalWindow setDelegate: self]; [window center];
[window setDelegate: self];
/* Used for displaying pause on the screen */ /* Used for displaying pause on the screen */
pauseLabel = [NSTextField new]; pauseLabel = [NSTextField new];
@ -1312,9 +1249,21 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
[cocoaView updateUIInfo]; [cocoaView updateUIInfo];
} }
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
[cocoaView grabMouse];
}
- (void)windowDidExitFullScreen:(NSNotification *)notification
{
[cocoaView resizeWindow];
[cocoaView ungrabMouse];
}
- (void)windowDidResize:(NSNotification *)notification - (void)windowDidResize:(NSNotification *)notification
{ {
[cocoaView updateUIInfo]; [cocoaView updateBounds];
[cocoaView frameUpdated];
} }
/* Called when the user clicks on a window's close button */ /* Called when the user clicks on a window's close button */
@ -1330,6 +1279,14 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
return NO; return NO;
} }
- (NSApplicationPresentationOptions) window:(NSWindow *)window
willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
{
return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
}
/* /*
* Called when QEMU goes into the background. Note that * Called when QEMU goes into the background. Note that
* [-NSWindowDelegate windowDidResignKey:] is used here instead of * [-NSWindowDelegate windowDidResignKey:] is used here instead of
@ -1349,14 +1306,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
*/ */
- (void) doToggleFullScreen:(id)sender - (void) doToggleFullScreen:(id)sender
{ {
[self toggleFullScreen:(id)sender]; [[cocoaView window] toggleFullScreen:sender];
}
- (void)toggleFullScreen:(id)sender
{
COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
[cocoaView toggleFullScreen:sender];
} }
- (void) setFullGrab:(id)sender - (void) setFullGrab:(id)sender
@ -1403,10 +1353,19 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
/* Stretches video to fit host monitor size */ /* Stretches video to fit host monitor size */
- (void)zoomToFit:(id) sender - (void)zoomToFit:(id) sender
{ {
stretch_video = !stretch_video; NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
if (stretch_video == true) {
[[cocoaView window] setStyleMask:styleMask];
[sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
}
- (void)toggleZoomInterpolation:(id) sender
{
if (zoom_interpolation == kCGInterpolationNone) {
zoom_interpolation = kCGInterpolationLow;
[sender setState: NSControlStateValueOn]; [sender setState: NSControlStateValueOn];
} else { } else {
zoom_interpolation = kCGInterpolationNone;
[sender setState: NSControlStateValueOff]; [sender setState: NSControlStateValueOff];
} }
} }
@ -1414,7 +1373,9 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
/* Displays the console on the screen */ /* Displays the console on the screen */
- (void)displayConsole:(id)sender - (void)displayConsole:(id)sender
{ {
console_select([sender tag]); with_bql(^{
console_select([sender tag]);
});
} }
/* Pause the guest */ /* Pause the guest */
@ -1444,8 +1405,8 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
{ {
/* Coordinates have to be calculated each time because the window can change its size */ /* Coordinates have to be calculated each time because the window can change its size */
int xCoord, yCoord, width, height; int xCoord, yCoord, width, height;
xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
width = [pauseLabel frame].size.width; width = [pauseLabel frame].size.width;
height = [pauseLabel frame].size.height; height = [pauseLabel frame].size.height;
[pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
@ -1671,7 +1632,10 @@ static void create_initial_menus(void)
menu = [[NSMenu alloc] initWithTitle:@"View"]; menu = [[NSMenu alloc] initWithTitle:@"View"];
[menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]; menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease];
[menuItem setState: stretch_video ? NSControlStateValueOn : NSControlStateValueOff]; [menuItem setState: [[cocoaView window] styleMask] & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
[menu addItem: menuItem];
menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom Interpolation" action:@selector(toggleZoomInterpolation:) keyEquivalent:@""] autorelease];
[menuItem setState: zoom_interpolation == kCGInterpolationLow ? NSControlStateValueOn : NSControlStateValueOff];
[menu addItem: menuItem]; [menu addItem: menuItem];
menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
[menuItem setSubmenu:menu]; [menuItem setSubmenu:menu];
@ -1962,16 +1926,7 @@ static void cocoa_update(DisplayChangeListener *dcl,
COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSRect rect; NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
if ([cocoaView cdx] == 1.0) {
rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
} else {
rect = NSMakeRect(
x * [cocoaView cdx],
([cocoaView gscreen].height - y - h) * [cocoaView cdy],
w * [cocoaView cdx],
h * [cocoaView cdy]);
}
[cocoaView setNeedsDisplayInRect:rect]; [cocoaView setNeedsDisplayInRect:rect];
}); });
} }
@ -2048,8 +2003,7 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
/* if fullscreen mode is to be used */ /* if fullscreen mode is to be used */
if (opts->has_full_screen && opts->full_screen) { if (opts->has_full_screen && opts->full_screen) {
[NSApp activateIgnoringOtherApps: YES]; [[cocoaView window] toggleFullScreen: nil];
[controller toggleFullScreen: nil];
} }
if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) { if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
[controller setFullGrab: nil]; [controller setFullGrab: nil];
@ -2067,7 +2021,11 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
} }
if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) { if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
stretch_video = true; [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
}
if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
zoom_interpolation = kCGInterpolationLow;
} }
create_initial_menus(); create_initial_menus();