#include "qemu/cutils.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
+#include "qemu/error-report.h"
#include <Carbon/Carbon.h>
#include "hw/core/cpu.h"
-#ifndef MAC_OS_X_VERSION_10_13
-#define MAC_OS_X_VERSION_10_13 101300
-#endif
-
-/* 10.14 deprecates NSOnState and NSOffState in favor of
- * NSControlStateValueOn/Off, which were introduced in 10.13.
- * Define for older versions
- */
-#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
-#define NSControlStateValueOn NSOnState
-#define NSControlStateValueOff NSOffState
+#ifndef MAC_OS_VERSION_14_0
+#define MAC_OS_VERSION_14_0 140000
#endif
//#define DEBUG
static void cocoa_refresh(DisplayChangeListener *dcl);
-static NSWindow *normalWindow;
static const DisplayChangeListenerOps dcl_ops = {
.dpy_name = "cocoa",
.dpy_gfx_update = cocoa_update,
static DisplayChangeListener dcl = {
.ops = &dcl_ops,
};
-static int last_buttons;
+static QKbdState *kbd;
static int cursor_hide = 1;
static int left_command_key_enabled = 1;
static bool swap_opt_cmd;
-static bool stretch_video;
+static CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
static NSTextField *pauseLabel;
static bool allow_events;
static QemuClipboardInfo *cbinfo;
static QemuEvent cbevent;
-// Utility functions to run specified code block with iothread lock held
+// Utility functions to run specified code block with the BQL held
typedef void (^CodeBlock)(void);
typedef bool (^BoolCodeBlock)(void);
-static void with_iothread_lock(CodeBlock block)
+static void with_bql(CodeBlock block)
{
- bool locked = qemu_mutex_iothread_locked();
+ bool locked = bql_locked();
if (!locked) {
- qemu_mutex_lock_iothread();
+ bql_lock();
}
block();
if (!locked) {
- qemu_mutex_unlock_iothread();
+ bql_unlock();
}
}
-static bool bool_with_iothread_lock(BoolCodeBlock block)
+static bool bool_with_bql(BoolCodeBlock block)
{
- bool locked = qemu_mutex_iothread_locked();
+ bool locked = bql_locked();
bool val;
if (!locked) {
- qemu_mutex_lock_iothread();
+ bql_lock();
}
val = block();
if (!locked) {
- qemu_mutex_unlock_iothread();
+ bql_unlock();
}
return val;
}
@interface QemuCocoaView : NSView
{
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;
*/
- (BOOL) isMouseGrabbed;
- (BOOL) isAbsoluteEnabled;
-- (float) cdx;
-- (float) cdy;
- (QEMUScreen) gscreen;
- (void) raiseAllKeys;
@end
static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
{
- QemuCocoaView *cocoaView = userInfo;
+ QemuCocoaView *view = userInfo;
NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
- if ([cocoaView isMouseGrabbed] && [cocoaView handleEvent:event]) {
+ if ([view isMouseGrabbed] && [view handleEvent:event]) {
COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
return NULL;
}
self = [super initWithFrame:frameRect];
if (self) {
+ NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
+ NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingInVisibleRect;
+
+ NSTrackingArea *trackingArea =
+ [[NSTrackingArea alloc] initWithRect:CGRectZero
+ options:options
+ owner:self
+ userInfo:nil];
+
+ [self addTrackingArea:trackingArea];
+ [trackingArea release];
screen.width = frameRect.size.width;
screen.height = frameRect.size.height;
- kbd = qkbd_state_init(dcl.con);
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
+ [self setClipsToBounds:YES];
+#endif
}
return self;
pixman_image_unref(pixman_image);
}
- qkbd_state_free(kbd);
-
if (eventsTap) {
CFRelease(eventsTap);
}
return YES;
}
-- (BOOL) screenContainsPoint:(NSPoint) p
+- (void) viewDidMoveToWindow
{
- return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
+ [self resizeWindow];
}
-/* Get location of event and convert to virtual screen coordinate */
-- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
+- (void) selectConsoleLocked:(unsigned int)index
{
- 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;
+ QemuConsole *con = qemu_console_lookup_by_index(index);
+ if (!con) {
+ return;
}
+
+ unregister_displaychangelistener(&dcl);
+ qkbd_state_switch_console(kbd, con);
+ dcl.con = con;
+ register_displaychangelistener(&dcl);
+ [self updateUIInfo];
}
- (void) hideCursor
// get CoreGraphic context
CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
- CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
+ CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
CGContextSetShouldAntialias (viewContextRef, NO);
// draw screen bitmap directly to Core Graphics context
[self getRectsBeingDrawn:&rectList count:&rectCount];
for (i = 0; i < rectCount; i++) {
- clipRect.origin.x = rectList[i].origin.x / cdx;
- clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy;
- clipRect.size.width = rectList[i].size.width / cdx;
- clipRect.size.height = rectList[i].size.height / cdy;
+ clipRect = rectList[i];
+ clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
clipImageRef = CGImageCreateWithImageInRect(
imageRef,
clipRect
}
}
-- (void) setContentDimensions
+- (NSSize)fixAspectRatio:(NSSize)max
{
- COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
+ NSSize scaled;
+ NSSize fixed;
- if (isFullscreen) {
- cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
- cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
+ scaled.width = screen.width * max.height;
+ scaled.height = screen.height * max.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;
+ /*
+ * Here screen is our guest's output size, and max is the size of the
+ * largest possible area of the screen we can display on.
+ * We want to scale up (screen.width x screen.height) by either:
+ * 1) max.height / screen.height
+ * 2) max.width / screen.width
+ * With the first scale factor the scale will result in an output height of
+ * max.height (i.e. we will fill the whole height of the available screen
+ * space and have black bars left and right) and with the second scale
+ * factor the scaling will result in an output width of max.width (i.e. we
+ * fill the whole width of the available screen space and have black bars
+ * top and bottom). We need to pick whichever keeps the whole of the guest
+ * output on the screen, which is to say the smaller of the two scale
+ * factors.
+ * To avoid doing more division than strictly necessary, instead of directly
+ * comparing scale factors 1 and 2 we instead calculate and compare those
+ * two scale factors multiplied by (screen.height * screen.width).
+ */
+ if (scaled.width < scaled.height) {
+ fixed.width = scaled.width / screen.height;
+ fixed.height = max.height;
} else {
- cx = 0;
- cy = 0;
- cw = screen.width;
- ch = screen.height;
- cdx = 1.0;
- cdy = 1.0;
+ fixed.width = max.width;
+ fixed.height = scaled.height / screen.width;
}
+
+ return fixed;
+}
+
+- (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 (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
+ [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
+ [[self window] center];
+ } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
+ [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]];
+ [[self window] center];
+ } else {
+ [[self window] setContentSize:[self fixAspectRatio:[self frame].size]];
+ }
+}
+
+- (void) updateBounds
+{
+ [self setBoundsSize:NSMakeSize(screen.width, screen.height)];
}
- (void) updateUIInfoLocked
{
- /* Must be called with the iothread lock, i.e. via updateUIInfo */
+ /* Must be called with the BQL, i.e. via updateUIInfo */
NSSize frameSize;
QemuUIInfo info;
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);
return;
}
- with_iothread_lock(^{
+ with_bql(^{
[self updateUIInfoLocked];
});
}
-- (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];
+ [self updateBounds];
}
// update screenBuffer
}
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
}
if (keysym) {
- kbd_put_keysym(keysym);
+ QemuTextConsole *con = QEMU_TEXT_CONSOLE(dcl.con);
+ qemu_text_console_put_keysym(con, keysym);
}
}
- (bool) handleEvent:(NSEvent *)event
{
- return bool_with_iothread_lock(^{
+ return bool_with_bql(^{
return [self handleEventLocked:event];
});
}
{
/* Return true if we handled the event, false if it should be given to OSX */
COCOA_DEBUG("QemuCocoaView: handleEvent\n");
- int buttons = 0;
+ InputButton button;
int keycode = 0;
- bool mouse_event = false;
- // Location of event in virtual screen coordinates
- NSPoint p = [self screenLocationOfEvent:event];
NSUInteger modifiers = [event modifierFlags];
/*
}
break;
}
- break;
+ return true;
case NSEventTypeKeyDown:
keycode = cocoa_keycode_to_qemu([event keyCode]);
// enable graphic console
case '1' ... '9':
- console_select(key - '0' - 1); /* ascii math */
+ [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
return true;
// release the mouse grab
}
}
- if (qemu_console_is_graphic(NULL)) {
+ if (qemu_console_is_graphic(dcl.con)) {
qkbd_state_key_event(kbd, keycode, true);
} else {
[self handleMonitorInput: event];
}
- break;
+ return true;
case NSEventTypeKeyUp:
keycode = cocoa_keycode_to_qemu([event keyCode]);
return true;
}
- if (qemu_console_is_graphic(NULL)) {
+ if (qemu_console_is_graphic(dcl.con)) {
qkbd_state_key_event(kbd, keycode, false);
}
- break;
- 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;
+ return true;
case NSEventTypeScrollWheel:
/*
* Send wheel events to the guest regardless of window focus.
* 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 */
- if ([event deltaY] != 0) {
- buttons = ([event deltaY] > 0) ?
+ if ([event deltaY] != 0) {
+ button = ([event deltaY] > 0) ?
INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
- } else if ([event deltaX] != 0) {
- buttons = ([event deltaX] > 0) ?
+ } else if ([event deltaX] != 0) {
+ button = ([event deltaX] > 0) ?
INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
- }
-
- qemu_input_queue_btn(dcl.con, buttons, true);
- qemu_input_event_sync();
- qemu_input_queue_btn(dcl.con, buttons, false);
- qemu_input_event_sync();
+ } else {
+ /*
+ * We shouldn't have got a scroll event when deltaY and delta Y
+ * are zero, hence no harm in dropping the event
+ */
+ return true;
}
- /*
- * Since deltaX/deltaY also report scroll wheel events we prevent mouse
- * movement code from executing.
- */
- mouse_event = false;
- break;
+ qemu_input_queue_btn(dcl.con, button, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(dcl.con, button, false);
+ qemu_input_event_sync();
+
+ return true;
default:
return false;
}
+}
+
+- (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
+{
+ if (!isMouseGrabbed) {
+ return;
+ }
- if (mouse_event) {
- /* 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
- * is the user clicking on the background window to activate and
- * 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]);
- }
+ 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 {
- return false;
+ 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];
}
- return true;
+
+ [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
{
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)
+ [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
+ else
+ [[self window] 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:]
{
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)
+ [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
+ else
+ [[self window] setTitle:@"QEMU"];
[self unhideCursor];
CGAssociateMouseAndMouseCursorPosition(TRUE);
isMouseGrabbed = FALSE;
+ [self raiseAllButtons];
}
- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
}
- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
-- (float) cdx {return cdx;}
-- (float) cdy {return cdy;}
- (QEMUScreen) gscreen {return screen;}
/*
*/
- (void) raiseAllKeys
{
- with_iothread_lock(^{
+ with_bql(^{
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
{
}
- (void)doToggleFullScreen:(id)sender;
-- (void)toggleFullScreen:(id)sender;
- (void)showQEMUDoc:(id)sender;
- (void)zoomToFit:(id) sender;
- (void)displayConsole:(id)sender;
@implementation QemuCocoaAppController
- (id) init
{
+ NSWindow *window;
+
COCOA_DEBUG("QemuCocoaAppController: init\n");
self = [super init];
}
// create a window
- normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
+ window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
backing:NSBackingStoreBuffered defer:NO];
- if(!normalWindow) {
+ if(!window) {
error_report("(cocoa) can't create window");
exit(1);
}
- [normalWindow setAcceptsMouseMovedEvents:YES];
- [normalWindow setTitle:@"QEMU"];
- [normalWindow setContentView:cocoaView];
- [normalWindow makeKeyAndOrderFront:self];
- [normalWindow center];
- [normalWindow setDelegate: self];
- stretch_video = false;
+ [window setAcceptsMouseMovedEvents:YES];
+ [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
+ [window setContentView:cocoaView];
+ [window makeKeyAndOrderFront:self];
+ [window center];
+ [window setDelegate: self];
/* Used for displaying pause on the screen */
pauseLabel = [NSTextField new];
{
COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
- with_iothread_lock(^{
+ with_bql(^{
shutdown_action = SHUTDOWN_ACTION_POWEROFF;
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
});
[cocoaView updateUIInfo];
}
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+ [cocoaView grabMouse];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+ [cocoaView resizeWindow];
+ [cocoaView ungrabMouse];
+}
+
- (void)windowDidResize:(NSNotification *)notification
{
+ [cocoaView updateBounds];
[cocoaView updateUIInfo];
}
return NO;
}
+- (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
*/
- (void) doToggleFullScreen:(id)sender
{
- [self toggleFullScreen:(id)sender];
-}
-
-- (void)toggleFullScreen:(id)sender
-{
- COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
-
- [cocoaView toggleFullScreen:sender];
+ [[cocoaView window] toggleFullScreen:sender];
}
- (void) setFullGrab:(id)sender
/* Stretches video to fit host monitor size */
- (void)zoomToFit:(id) sender
{
- stretch_video = !stretch_video;
- if (stretch_video == true) {
+ NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
+
+ [[cocoaView window] setStyleMask:styleMask];
+ [sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
+ [cocoaView resizeWindow];
+}
+
+- (void)toggleZoomInterpolation:(id) sender
+{
+ if (zoom_interpolation == kCGInterpolationNone) {
+ zoom_interpolation = kCGInterpolationLow;
[sender setState: NSControlStateValueOn];
} else {
+ zoom_interpolation = kCGInterpolationNone;
[sender setState: NSControlStateValueOff];
}
}
/* Displays the console on the screen */
- (void)displayConsole:(id)sender
{
- console_select([sender tag]);
+ with_bql(^{
+ [cocoaView selectConsoleLocked:[sender tag]];
+ });
}
/* Pause the guest */
- (void)pauseQEMU:(id)sender
{
- with_iothread_lock(^{
+ with_bql(^{
qmp_stop(NULL);
});
[sender setEnabled: NO];
/* Resume running the guest operating system */
- (void)resumeQEMU:(id) sender
{
- with_iothread_lock(^{
+ with_bql(^{
qmp_cont(NULL);
});
[sender setEnabled: NO];
{
/* Coordinates have to be calculated each time because the window can change its size */
int xCoord, yCoord, width, height;
- xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
- yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
+ xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
+ yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
width = [pauseLabel frame].size.width;
height = [pauseLabel frame].size.height;
[pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
/* Restarts QEMU */
- (void)restartQEMU:(id)sender
{
- with_iothread_lock(^{
+ with_bql(^{
qmp_system_reset(NULL);
});
}
/* Powers down QEMU */
- (void)powerDownQEMU:(id)sender
{
- with_iothread_lock(^{
+ with_bql(^{
qmp_system_powerdown(NULL);
});
}
}
__block Error *err = NULL;
- with_iothread_lock(^{
+ with_bql(^{
qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
NULL, false, false, &err);
});
}
__block Error *err = NULL;
- with_iothread_lock(^{
+ with_bql(^{
qmp_blockdev_change_medium([drive cStringUsingEncoding:
NSASCIIStringEncoding],
NULL,
// get the throttle percentage
throttle_pct = [sender tag];
- with_iothread_lock(^{
+ with_bql(^{
cpu_throttle_set(throttle_pct);
});
COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
// View menu
menu = [[NSMenu alloc] initWithTitle:@"View"];
[menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
- [menu addItem: [[[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: [[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];
menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
[menuItem setSubmenu:menu];
[[NSApp mainMenu] addItem:menuItem];
return;
}
- with_iothread_lock(^{
+ with_bql(^{
QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
qemu_event_reset(&cbevent);
qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
while (info == cbinfo &&
info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
- qemu_mutex_unlock_iothread();
+ bql_unlock();
qemu_event_wait(&cbevent);
- qemu_mutex_lock_iothread();
+ bql_lock();
}
if (info == cbinfo) {
int status;
COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
- qemu_mutex_lock_iothread();
+ bql_lock();
status = qemu_default_main();
- qemu_mutex_unlock_iothread();
+ bql_unlock();
COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
[cbowner release];
exit(status);
}
-static int cocoa_main()
+static int cocoa_main(void)
{
QemuThread thread;
COCOA_DEBUG("Entered %s()\n", __func__);
- qemu_mutex_unlock_iothread();
+ bql_unlock();
qemu_thread_create(&thread, "qemu_main", call_qemu_main,
NULL, QEMU_THREAD_DETACHED);
COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
dispatch_async(dispatch_get_main_queue(), ^{
- NSRect rect;
- 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]);
- }
+ NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
[cocoaView setNeedsDisplayInRect:rect];
});
}
pixman_image_ref(image);
dispatch_async(dispatch_get_main_queue(), ^{
- [cocoaView updateUIInfo];
[cocoaView switchSurface:image];
});
}
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
- graphic_hw_update(NULL);
+ graphic_hw_update(dcl->con);
- if (qemu_input_is_absolute()) {
+ if (qemu_input_is_absolute(dcl->con)) {
dispatch_async(dispatch_get_main_queue(), ^{
if (![cocoaView isAbsoluteEnabled]) {
if ([cocoaView isMouseGrabbed]) {
[QemuApplication sharedApplication];
- create_initial_menus();
-
- /*
- * Create the menu entries which depend on QEMU state (for consoles
- * and removeable devices). These make calls back into QEMU functions,
- * which is OK because at this point we know that the second thread
- * holds the iothread lock and is synchronously waiting for us to
- * finish.
- */
- add_console_menu_entries();
- addRemovableDevicesMenuItems();
-
// Create an Application controller
QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
[NSApp setDelegate:controller];
/* if fullscreen mode is to be used */
if (opts->has_full_screen && opts->full_screen) {
- [NSApp activateIgnoringOtherApps: YES];
- [controller toggleFullScreen: nil];
+ [[cocoaView window] toggleFullScreen: nil];
}
if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
[controller setFullGrab: nil];
left_command_key_enabled = 0;
}
+ if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
+ [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
+ }
+
+ if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
+ zoom_interpolation = kCGInterpolationLow;
+ }
+
+ create_initial_menus();
+ /*
+ * Create the menu entries which depend on QEMU state (for consoles
+ * and removable devices). These make calls back into QEMU functions,
+ * which is OK because at this point we know that the second thread
+ * holds the BQL and is synchronously waiting for us to
+ * finish.
+ */
+ add_console_menu_entries();
+ addRemovableDevicesMenuItems();
+
+ dcl.con = qemu_console_lookup_default();
+ kbd = qkbd_state_init(dcl.con);
+
// register vga output callbacks
register_displaychangelistener(&dcl);
+ [cocoaView updateUIInfo];
qemu_event_init(&cbevent, false);
cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];