blob: 5ed1495552a0445187e5d0d852246a39c504cf80 [file] [log] [blame]
bellard5b0753e2005-03-01 21:37:28 +00001/*
thsc304f7e2008-01-22 23:25:15 +00002 * QEMU Cocoa CG display driver
ths5fafdf22007-09-16 21:08:06 +00003 *
thsc304f7e2008-01-22 23:25:15 +00004 * Copyright (c) 2008 Mike Kronenberg
ths5fafdf22007-09-16 21:08:06 +00005 *
bellard5b0753e2005-03-01 21:37:28 +00006 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
bellardda4dbf72005-03-02 22:22:43 +000024
Peter Maydelle4a096b2016-01-29 16:23:34 +000025#include "qemu/osdep.h"
26
bellard5b0753e2005-03-01 21:37:28 +000027#import <Cocoa/Cocoa.h>
Andreas Färber3bbbee12011-05-29 19:42:51 +020028#include <crt_externs.h>
bellard5b0753e2005-03-01 21:37:28 +000029
pbrook87ecb682007-11-17 17:14:51 +000030#include "qemu-common.h"
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +090031#include "ui/clipboard.h"
Paolo Bonzini28ecbae2012-11-28 12:06:30 +010032#include "ui/console.h"
Gerd Hoffmann21bae112013-12-04 14:08:04 +010033#include "ui/input.h"
Akihiko Odaki6d73bb62021-03-10 23:46:02 +090034#include "ui/kbd-state.h"
Paolo Bonzini9c17d612012-12-17 18:20:04 +010035#include "sysemu/sysemu.h"
Markus Armbruster54d31232019-08-12 07:23:59 +020036#include "sysemu/runstate.h"
Claudio Fontanab0c3cf92020-06-29 11:35:03 +020037#include "sysemu/cpu-throttle.h"
Markus Armbrustere688df62018-02-01 12:18:31 +010038#include "qapi/error.h"
Markus Armbruster16bf5232018-12-20 09:45:59 +010039#include "qapi/qapi-commands-block.h"
Philippe Mathieu-Daudé90f8c0f2020-10-12 14:15:33 +020040#include "qapi/qapi-commands-machine.h"
Markus Armbruster16bf5232018-12-20 09:45:59 +010041#include "qapi/qapi-commands-misc.h"
John Arbuckle693a3e02015-06-19 10:53:27 +010042#include "sysemu/blockdev.h"
Programmingkid9e8204b2016-08-15 22:11:06 -040043#include "qemu-version.h"
Akihiko Odakie31746e2021-03-09 21:22:25 +090044#include "qemu/cutils.h"
Markus Armbrusterdb725812019-08-12 07:23:50 +020045#include "qemu/main-loop.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020046#include "qemu/module.h"
John Arbuckleaaac7142016-03-23 14:26:18 +000047#include <Carbon/Carbon.h>
Markus Armbruster2e5b09f2019-07-09 17:20:52 +020048#include "hw/core/cpu.h"
bellardda4dbf72005-03-02 22:22:43 +000049
Brendan Shanks5e246002019-01-31 23:12:25 -080050#ifndef MAC_OS_X_VERSION_10_13
51#define MAC_OS_X_VERSION_10_13 101300
52#endif
Andreas Färber44e4c0b2009-12-13 00:45:40 +010053
Brendan Shanks5e246002019-01-31 23:12:25 -080054/* 10.14 deprecates NSOnState and NSOffState in favor of
55 * NSControlStateValueOn/Off, which were introduced in 10.13.
56 * Define for older versions
57 */
58#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
59#define NSControlStateValueOn NSOnState
60#define NSControlStateValueOff NSOffState
61#endif
bellard5b0753e2005-03-01 21:37:28 +000062
thsc304f7e2008-01-22 23:25:15 +000063//#define DEBUG
64
65#ifdef DEBUG
66#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
67#else
68#define COCOA_DEBUG(...) ((void) 0)
69#endif
70
71#define cgrect(nsrect) (*(CGRect *)&(nsrect))
thsc304f7e2008-01-22 23:25:15 +000072
73typedef struct {
74 int width;
75 int height;
thsc304f7e2008-01-22 23:25:15 +000076} QEMUScreen;
77
Akihiko Odakicc7859c2021-02-19 17:44:19 +090078static void cocoa_update(DisplayChangeListener *dcl,
79 int x, int y, int w, int h);
80
81static void cocoa_switch(DisplayChangeListener *dcl,
82 DisplaySurface *surface);
83
84static void cocoa_refresh(DisplayChangeListener *dcl);
85
Akihiko Odakicb823402021-02-25 17:42:02 +090086static NSWindow *normalWindow, *about_window;
Akihiko Odakicc7859c2021-02-19 17:44:19 +090087static const DisplayChangeListenerOps dcl_ops = {
88 .dpy_name = "cocoa",
89 .dpy_gfx_update = cocoa_update,
90 .dpy_gfx_switch = cocoa_switch,
91 .dpy_refresh = cocoa_refresh,
92};
93static DisplayChangeListener dcl = {
94 .ops = &dcl_ops,
95};
Gerd Hoffmann21bae112013-12-04 14:08:04 +010096static int last_buttons;
Gerd Hoffmann3487da62020-02-06 12:27:50 +010097static int cursor_hide = 1;
bellard5b0753e2005-03-01 21:37:28 +000098
Akihiko Odakicb823402021-02-25 17:42:02 +090099static int gArgc;
100static char **gArgv;
101static bool stretch_video;
102static NSTextField *pauseLabel;
bellard5b0753e2005-03-01 21:37:28 +0000103
Peter Maydell55888402019-02-25 10:24:33 +0000104static QemuSemaphore display_init_sem;
105static QemuSemaphore app_started_sem;
Hikaru Nishidadff742a2019-10-15 10:07:34 +0900106static bool allow_events;
Peter Maydell55888402019-02-25 10:24:33 +0000107
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +0900108static NSInteger cbchangecount = -1;
109static QemuClipboardInfo *cbinfo;
110static QemuEvent cbevent;
111
Peter Maydell60105d72019-02-25 10:24:31 +0000112// Utility functions to run specified code block with iothread lock held
Peter Maydell31819e92019-02-25 10:24:27 +0000113typedef void (^CodeBlock)(void);
Peter Maydell60105d72019-02-25 10:24:31 +0000114typedef bool (^BoolCodeBlock)(void);
Peter Maydell31819e92019-02-25 10:24:27 +0000115
116static void with_iothread_lock(CodeBlock block)
117{
118 bool locked = qemu_mutex_iothread_locked();
119 if (!locked) {
120 qemu_mutex_lock_iothread();
121 }
122 block();
123 if (!locked) {
124 qemu_mutex_unlock_iothread();
125 }
126}
127
Peter Maydell60105d72019-02-25 10:24:31 +0000128static bool bool_with_iothread_lock(BoolCodeBlock block)
129{
130 bool locked = qemu_mutex_iothread_locked();
131 bool val;
132
133 if (!locked) {
134 qemu_mutex_lock_iothread();
135 }
136 val = block();
137 if (!locked) {
138 qemu_mutex_unlock_iothread();
139 }
140 return val;
141}
142
John Arbuckleaaac7142016-03-23 14:26:18 +0000143// Mac to QKeyCode conversion
Akihiko Odakicb823402021-02-25 17:42:02 +0900144static const int mac_to_qkeycode_map[] = {
John Arbuckleaaac7142016-03-23 14:26:18 +0000145 [kVK_ANSI_A] = Q_KEY_CODE_A,
146 [kVK_ANSI_B] = Q_KEY_CODE_B,
147 [kVK_ANSI_C] = Q_KEY_CODE_C,
148 [kVK_ANSI_D] = Q_KEY_CODE_D,
149 [kVK_ANSI_E] = Q_KEY_CODE_E,
150 [kVK_ANSI_F] = Q_KEY_CODE_F,
151 [kVK_ANSI_G] = Q_KEY_CODE_G,
152 [kVK_ANSI_H] = Q_KEY_CODE_H,
153 [kVK_ANSI_I] = Q_KEY_CODE_I,
154 [kVK_ANSI_J] = Q_KEY_CODE_J,
155 [kVK_ANSI_K] = Q_KEY_CODE_K,
156 [kVK_ANSI_L] = Q_KEY_CODE_L,
157 [kVK_ANSI_M] = Q_KEY_CODE_M,
158 [kVK_ANSI_N] = Q_KEY_CODE_N,
159 [kVK_ANSI_O] = Q_KEY_CODE_O,
160 [kVK_ANSI_P] = Q_KEY_CODE_P,
161 [kVK_ANSI_Q] = Q_KEY_CODE_Q,
162 [kVK_ANSI_R] = Q_KEY_CODE_R,
163 [kVK_ANSI_S] = Q_KEY_CODE_S,
164 [kVK_ANSI_T] = Q_KEY_CODE_T,
165 [kVK_ANSI_U] = Q_KEY_CODE_U,
166 [kVK_ANSI_V] = Q_KEY_CODE_V,
167 [kVK_ANSI_W] = Q_KEY_CODE_W,
168 [kVK_ANSI_X] = Q_KEY_CODE_X,
169 [kVK_ANSI_Y] = Q_KEY_CODE_Y,
170 [kVK_ANSI_Z] = Q_KEY_CODE_Z,
ths3b46e622007-09-17 08:09:54 +0000171
John Arbuckleaaac7142016-03-23 14:26:18 +0000172 [kVK_ANSI_0] = Q_KEY_CODE_0,
173 [kVK_ANSI_1] = Q_KEY_CODE_1,
174 [kVK_ANSI_2] = Q_KEY_CODE_2,
175 [kVK_ANSI_3] = Q_KEY_CODE_3,
176 [kVK_ANSI_4] = Q_KEY_CODE_4,
177 [kVK_ANSI_5] = Q_KEY_CODE_5,
178 [kVK_ANSI_6] = Q_KEY_CODE_6,
179 [kVK_ANSI_7] = Q_KEY_CODE_7,
180 [kVK_ANSI_8] = Q_KEY_CODE_8,
181 [kVK_ANSI_9] = Q_KEY_CODE_9,
182
183 [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
184 [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
185 [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
186 [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
187 [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
188 [kVK_Tab] = Q_KEY_CODE_TAB,
189 [kVK_Return] = Q_KEY_CODE_RET,
190 [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
191 [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
192 [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
193 [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
194 [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
195 [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
196 [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
197 [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
John Arbuckleaaac7142016-03-23 14:26:18 +0000198 [kVK_Space] = Q_KEY_CODE_SPC,
199
200 [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
201 [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
202 [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
203 [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
204 [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
205 [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
206 [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
207 [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
208 [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
209 [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
210 [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
211 [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
212 [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
213 [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
214 [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
215 [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
216 [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
217 [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
218
219 [kVK_UpArrow] = Q_KEY_CODE_UP,
220 [kVK_DownArrow] = Q_KEY_CODE_DOWN,
221 [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
222 [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
223
224 [kVK_Help] = Q_KEY_CODE_INSERT,
225 [kVK_Home] = Q_KEY_CODE_HOME,
226 [kVK_PageUp] = Q_KEY_CODE_PGUP,
227 [kVK_PageDown] = Q_KEY_CODE_PGDN,
228 [kVK_End] = Q_KEY_CODE_END,
229 [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
230
231 [kVK_Escape] = Q_KEY_CODE_ESC,
232
233 /* The Power key can't be used directly because the operating system uses
234 * it. This key can be emulated by using it in place of another key such as
235 * F1. Don't forget to disable the real key binding.
236 */
237 /* [kVK_F1] = Q_KEY_CODE_POWER, */
238
239 [kVK_F1] = Q_KEY_CODE_F1,
240 [kVK_F2] = Q_KEY_CODE_F2,
241 [kVK_F3] = Q_KEY_CODE_F3,
242 [kVK_F4] = Q_KEY_CODE_F4,
243 [kVK_F5] = Q_KEY_CODE_F5,
244 [kVK_F6] = Q_KEY_CODE_F6,
245 [kVK_F7] = Q_KEY_CODE_F7,
246 [kVK_F8] = Q_KEY_CODE_F8,
247 [kVK_F9] = Q_KEY_CODE_F9,
248 [kVK_F10] = Q_KEY_CODE_F10,
249 [kVK_F11] = Q_KEY_CODE_F11,
250 [kVK_F12] = Q_KEY_CODE_F12,
251 [kVK_F13] = Q_KEY_CODE_PRINT,
252 [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
253 [kVK_F15] = Q_KEY_CODE_PAUSE,
254
Akihiko Odaki708b7252021-02-12 09:04:04 +0900255 // JIS keyboards only
256 [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
257 [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
258 [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
259 [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
260 [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
261
John Arbuckleaaac7142016-03-23 14:26:18 +0000262 /*
263 * The eject and volume keys can't be used here because they are handled at
264 * a lower level than what an Application can see.
265 */
bellard5b0753e2005-03-01 21:37:28 +0000266};
267
Andreas Färber77047bb2009-12-13 00:55:53 +0100268static int cocoa_keycode_to_qemu(int keycode)
bellard5b0753e2005-03-01 21:37:28 +0000269{
John Arbuckleaaac7142016-03-23 14:26:18 +0000270 if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
Akihiko Odaki43137392021-02-23 22:11:06 +0900271 error_report("(cocoa) warning unknown keycode 0x%x", keycode);
bellard5b0753e2005-03-01 21:37:28 +0000272 return 0;
273 }
John Arbuckleaaac7142016-03-23 14:26:18 +0000274 return mac_to_qkeycode_map[keycode];
bellard5b0753e2005-03-01 21:37:28 +0000275}
276
John Arbuckle693a3e02015-06-19 10:53:27 +0100277/* Displays an alert dialog box with the specified message */
278static void QEMU_Alert(NSString *message)
279{
280 NSAlert *alert;
281 alert = [NSAlert new];
282 [alert setMessageText: message];
283 [alert runModal];
284}
thsc304f7e2008-01-22 23:25:15 +0000285
John Arbuckle693a3e02015-06-19 10:53:27 +0100286/* Handles any errors that happen with a device transaction */
287static void handleAnyDeviceErrors(Error * err)
288{
289 if (err) {
290 QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
291 encoding: NSASCIIStringEncoding]);
292 error_free(err);
293 }
294}
thsc304f7e2008-01-22 23:25:15 +0000295
bellard5b0753e2005-03-01 21:37:28 +0000296/*
297 ------------------------------------------------------
thsc304f7e2008-01-22 23:25:15 +0000298 QemuCocoaView
bellard5b0753e2005-03-01 21:37:28 +0000299 ------------------------------------------------------
300*/
thsc304f7e2008-01-22 23:25:15 +0000301@interface QemuCocoaView : NSView
bellard5b0753e2005-03-01 21:37:28 +0000302{
thsc304f7e2008-01-22 23:25:15 +0000303 QEMUScreen screen;
304 NSWindow *fullScreenWindow;
305 float cx,cy,cw,ch,cdx,cdy;
Peter Maydell55888402019-02-25 10:24:33 +0000306 pixman_image_t *pixman_image;
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900307 QKbdState *kbd;
Peter Maydell49b9bd42013-12-08 22:59:03 +0000308 BOOL isMouseGrabbed;
thsc304f7e2008-01-22 23:25:15 +0000309 BOOL isFullscreen;
310 BOOL isAbsoluteEnabled;
thsc304f7e2008-01-22 23:25:15 +0000311}
Peter Maydell72a3e312019-02-25 10:24:28 +0000312- (void) switchSurface:(pixman_image_t *)image;
thsc304f7e2008-01-22 23:25:15 +0000313- (void) grabMouse;
314- (void) ungrabMouse;
315- (void) toggleFullScreen:(id)sender;
John Arbuckle9c3a4182017-11-07 10:14:14 +0000316- (void) handleMonitorInput:(NSEvent *)event;
Peter Maydell60105d72019-02-25 10:24:31 +0000317- (bool) handleEvent:(NSEvent *)event;
318- (bool) handleEventLocked:(NSEvent *)event;
thsc304f7e2008-01-22 23:25:15 +0000319- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
Peter Maydellf61c3872014-06-23 10:35:24 +0100320/* The state surrounding mouse grabbing is potentially confusing.
321 * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
322 * pointing device an absolute-position one?"], but is only updated on
323 * next refresh.
324 * isMouseGrabbed tracks whether GUI events are directed to the guest;
325 * it controls whether special keys like Cmd get sent to the guest,
326 * and whether we capture the mouse when in non-absolute mode.
Peter Maydellf61c3872014-06-23 10:35:24 +0100327 */
Peter Maydell49b9bd42013-12-08 22:59:03 +0000328- (BOOL) isMouseGrabbed;
thsc304f7e2008-01-22 23:25:15 +0000329- (BOOL) isAbsoluteEnabled;
330- (float) cdx;
331- (float) cdy;
332- (QEMUScreen) gscreen;
John Arbuckle3b178b72015-09-25 23:14:00 +0100333- (void) raiseAllKeys;
thsc304f7e2008-01-22 23:25:15 +0000334@end
ths3b46e622007-09-17 08:09:54 +0000335
Andreas Färber7fee1992011-06-09 20:53:32 +0200336QemuCocoaView *cocoaView;
337
thsc304f7e2008-01-22 23:25:15 +0000338@implementation QemuCocoaView
339- (id)initWithFrame:(NSRect)frameRect
340{
341 COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
ths3b46e622007-09-17 08:09:54 +0000342
thsc304f7e2008-01-22 23:25:15 +0000343 self = [super initWithFrame:frameRect];
344 if (self) {
pbrook95219892006-04-09 01:06:34 +0000345
thsc304f7e2008-01-22 23:25:15 +0000346 screen.width = frameRect.size.width;
347 screen.height = frameRect.size.height;
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900348 kbd = qkbd_state_init(dcl.con);
bellard7c206a72005-12-18 19:18:45 +0000349
thsc304f7e2008-01-22 23:25:15 +0000350 }
351 return self;
352}
bellard7c206a72005-12-18 19:18:45 +0000353
thsc304f7e2008-01-22 23:25:15 +0000354- (void) dealloc
355{
356 COCOA_DEBUG("QemuCocoaView: dealloc\n");
bellard7c206a72005-12-18 19:18:45 +0000357
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900358 if (pixman_image) {
Peter Maydell55888402019-02-25 10:24:33 +0000359 pixman_image_unref(pixman_image);
360 }
ths3b46e622007-09-17 08:09:54 +0000361
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900362 qkbd_state_free(kbd);
thsc304f7e2008-01-22 23:25:15 +0000363 [super dealloc];
364}
bellard5cbfcd02006-06-14 15:53:24 +0000365
Andreas Färberd50f71d2009-12-13 02:03:33 +0100366- (BOOL) isOpaque
367{
368 return YES;
369}
370
Peter Maydell5dd45be2014-06-23 10:35:23 +0100371- (BOOL) screenContainsPoint:(NSPoint) p
372{
373 return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
374}
375
Chen Zhang2044dff2019-06-04 17:36:00 +0800376/* Get location of event and convert to virtual screen coordinate */
377- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
378{
379 NSWindow *eventWindow = [ev window];
380 // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
381 CGRect r = CGRectZero;
382 r.origin = [ev locationInWindow];
383 if (!eventWindow) {
384 if (!isFullscreen) {
385 return [[self window] convertRectFromScreen:r].origin;
386 } else {
387 CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin;
388 CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil];
389 if (stretch_video) {
390 loc.x /= cdx;
391 loc.y /= cdy;
392 }
393 return loc;
394 }
395 } else if ([[self window] isEqual:eventWindow]) {
396 if (!isFullscreen) {
397 return r.origin;
398 } else {
399 CGPoint loc = [self convertPoint:r.origin fromView:nil];
400 if (stretch_video) {
401 loc.x /= cdx;
402 loc.y /= cdy;
403 }
404 return loc;
405 }
406 } else {
407 return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin;
408 }
409}
410
Peter Maydell13aefd32014-06-23 10:35:25 +0100411- (void) hideCursor
412{
413 if (!cursor_hide) {
414 return;
415 }
416 [NSCursor hide];
417}
418
419- (void) unhideCursor
420{
421 if (!cursor_hide) {
422 return;
423 }
424 [NSCursor unhide];
425}
426
thsc304f7e2008-01-22 23:25:15 +0000427- (void) drawRect:(NSRect) rect
428{
429 COCOA_DEBUG("QemuCocoaView: drawRect\n");
bellard5cbfcd02006-06-14 15:53:24 +0000430
thsc304f7e2008-01-22 23:25:15 +0000431 // get CoreGraphic context
Brendan Shanks5e246002019-01-31 23:12:25 -0800432 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
Brendan Shanks5e246002019-01-31 23:12:25 -0800433
thsc304f7e2008-01-22 23:25:15 +0000434 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
435 CGContextSetShouldAntialias (viewContextRef, NO);
ths3b46e622007-09-17 08:09:54 +0000436
thsc304f7e2008-01-22 23:25:15 +0000437 // draw screen bitmap directly to Core Graphics context
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900438 if (!pixman_image) {
Peter Maydell7d270b12013-12-24 02:51:47 +0000439 // Draw request before any guest device has set up a framebuffer:
440 // just draw an opaque black rectangle
441 CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
442 CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
443 } else {
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900444 int w = pixman_image_get_width(pixman_image);
445 int h = pixman_image_get_height(pixman_image);
446 int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
Akihiko Odakid9c32b82021-02-22 23:40:12 +0900447 int stride = pixman_image_get_stride(pixman_image);
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900448 CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
449 NULL,
450 pixman_image_get_data(pixman_image),
Akihiko Odakid9c32b82021-02-22 23:40:12 +0900451 stride * h,
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900452 NULL
453 );
thsc304f7e2008-01-22 23:25:15 +0000454 CGImageRef imageRef = CGImageCreate(
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900455 w, //width
456 h, //height
Akihiko Odakid9c32b82021-02-22 23:40:12 +0900457 DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900458 bitsPerPixel, //bitsPerPixel
Akihiko Odakid9c32b82021-02-22 23:40:12 +0900459 stride, //bytesPerRow
Akihiko Odakiae57d352021-03-05 21:13:04 +0900460 CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
461 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
thsc304f7e2008-01-22 23:25:15 +0000462 dataProviderRef, //provider
463 NULL, //decode
464 0, //interpolate
465 kCGRenderingIntentDefault //intent
466 );
Peter Maydellb63901d2015-05-19 09:11:17 +0100467 // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
468 const NSRect *rectList;
Peter Maydellb63901d2015-05-19 09:11:17 +0100469 NSInteger rectCount;
Peter Maydellb63901d2015-05-19 09:11:17 +0100470 int i;
471 CGImageRef clipImageRef;
472 CGRect clipRect;
ths3b46e622007-09-17 08:09:54 +0000473
Peter Maydellb63901d2015-05-19 09:11:17 +0100474 [self getRectsBeingDrawn:&rectList count:&rectCount];
475 for (i = 0; i < rectCount; i++) {
476 clipRect.origin.x = rectList[i].origin.x / cdx;
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900477 clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy;
Peter Maydellb63901d2015-05-19 09:11:17 +0100478 clipRect.size.width = rectList[i].size.width / cdx;
479 clipRect.size.height = rectList[i].size.height / cdy;
480 clipImageRef = CGImageCreateWithImageInRect(
481 imageRef,
482 clipRect
483 );
484 CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
485 CGImageRelease (clipImageRef);
bellard5b0753e2005-03-01 21:37:28 +0000486 }
thsc304f7e2008-01-22 23:25:15 +0000487 CGImageRelease (imageRef);
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900488 CGDataProviderRelease(dataProviderRef);
bellard5b0753e2005-03-01 21:37:28 +0000489 }
490}
491
thsc304f7e2008-01-22 23:25:15 +0000492- (void) setContentDimensions
bellard5b0753e2005-03-01 21:37:28 +0000493{
thsc304f7e2008-01-22 23:25:15 +0000494 COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
ths3b46e622007-09-17 08:09:54 +0000495
thsc304f7e2008-01-22 23:25:15 +0000496 if (isFullscreen) {
497 cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
498 cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
Programmingkid5d1b2ee2015-05-19 09:11:17 +0100499
500 /* stretches video, but keeps same aspect ratio */
501 if (stretch_video == true) {
502 /* use smallest stretch value - prevents clipping on sides */
503 if (MIN(cdx, cdy) == cdx) {
504 cdy = cdx;
505 } else {
506 cdx = cdy;
507 }
508 } else { /* No stretching */
509 cdx = cdy = 1;
510 }
thsc304f7e2008-01-22 23:25:15 +0000511 cw = screen.width * cdx;
512 ch = screen.height * cdy;
513 cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
514 cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
515 } else {
516 cx = 0;
517 cy = 0;
518 cw = screen.width;
519 ch = screen.height;
520 cdx = 1.0;
521 cdy = 1.0;
522 }
bellard5b0753e2005-03-01 21:37:28 +0000523}
524
Peter Maydell8d65dee2022-02-24 10:13:29 +0000525- (void) updateUIInfoLocked
Akihiko Odaki15280e82021-06-16 23:19:10 +0900526{
Peter Maydell8d65dee2022-02-24 10:13:29 +0000527 /* Must be called with the iothread lock, i.e. via updateUIInfo */
Akihiko Odaki15280e82021-06-16 23:19:10 +0900528 NSSize frameSize;
529 QemuUIInfo info;
530
531 if (!qemu_console_is_graphic(dcl.con)) {
532 return;
533 }
534
535 if ([self window]) {
536 NSDictionary *description = [[[self window] screen] deviceDescription];
537 CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
538 NSSize screenSize = [[[self window] screen] frame].size;
539 CGSize screenPhysicalSize = CGDisplayScreenSize(display);
540
541 frameSize = isFullscreen ? screenSize : [self frame].size;
542 info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
543 info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
544 } else {
545 frameSize = [self frame].size;
546 info.width_mm = 0;
547 info.height_mm = 0;
548 }
549
550 info.xoff = 0;
551 info.yoff = 0;
552 info.width = frameSize.width;
553 info.height = frameSize.height;
554
Marc-André Lureauca19ef52021-04-13 20:39:11 +0400555 dpy_set_ui_info(dcl.con, &info, TRUE);
Akihiko Odaki15280e82021-06-16 23:19:10 +0900556}
557
Peter Maydell8d65dee2022-02-24 10:13:29 +0000558- (void) updateUIInfo
559{
560 if (!allow_events) {
561 /*
562 * Don't try to tell QEMU about UI information in the application
563 * startup phase -- we haven't yet registered dcl with the QEMU UI
564 * layer, and also trying to take the iothread lock would deadlock.
565 * When cocoa_display_init() does register the dcl, the UI layer
566 * will call cocoa_switch(), which will call updateUIInfo, so
567 * we don't lose any information here.
568 */
569 return;
570 }
571
572 with_iothread_lock(^{
573 [self updateUIInfoLocked];
574 });
575}
576
Akihiko Odaki15280e82021-06-16 23:19:10 +0900577- (void)viewDidMoveToWindow
578{
579 [self updateUIInfo];
580}
581
Peter Maydell72a3e312019-02-25 10:24:28 +0000582- (void) switchSurface:(pixman_image_t *)image
thsc304f7e2008-01-22 23:25:15 +0000583{
Gerd Hoffmann5e00d3a2013-03-01 12:52:06 +0100584 COCOA_DEBUG("QemuCocoaView: switchSurface\n");
thsc304f7e2008-01-22 23:25:15 +0000585
Peter Maydell72a3e312019-02-25 10:24:28 +0000586 int w = pixman_image_get_width(image);
587 int h = pixman_image_get_height(image);
Peter Maydell381600d2014-06-23 10:35:22 +0100588 /* cdx == 0 means this is our very first surface, in which case we need
589 * to recalculate the content dimensions even if it happens to be the size
590 * of the initial empty window.
591 */
592 bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
Peter Maydelld3345a02013-12-24 02:51:46 +0000593
594 int oldh = screen.height;
595 if (isResize) {
596 // Resize before we trigger the redraw, or we'll redraw at the wrong size
597 COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
598 screen.width = w;
599 screen.height = h;
600 [self setContentDimensions];
601 [self setFrame:NSMakeRect(cx, cy, cw, ch)];
602 }
Peter Maydell8510d912013-03-18 20:28:21 +0000603
thsc304f7e2008-01-22 23:25:15 +0000604 // update screenBuffer
Akihiko Odakic0ff29d2021-02-12 09:06:29 +0900605 if (pixman_image) {
Peter Maydell55888402019-02-25 10:24:33 +0000606 pixman_image_unref(pixman_image);
607 }
thsc304f7e2008-01-22 23:25:15 +0000608
Peter Maydell55888402019-02-25 10:24:33 +0000609 pixman_image = image;
thsc304f7e2008-01-22 23:25:15 +0000610
611 // update windows
612 if (isFullscreen) {
613 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
Peter Maydelld3345a02013-12-24 02:51:46 +0000614 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
thsc304f7e2008-01-22 23:25:15 +0000615 } else {
616 if (qemu_name)
617 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
Peter Maydelld3345a02013-12-24 02:51:46 +0000618 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
thsc304f7e2008-01-22 23:25:15 +0000619 }
Peter Maydelld3345a02013-12-24 02:51:46 +0000620
621 if (isResize) {
622 [normalWindow center];
623 }
thsc304f7e2008-01-22 23:25:15 +0000624}
625
626- (void) toggleFullScreen:(id)sender
627{
628 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
629
630 if (isFullscreen) { // switch from fullscreen to desktop
631 isFullscreen = FALSE;
632 [self ungrabMouse];
633 [self setContentDimensions];
Akihiko Odaki1e8b6f22021-02-20 10:31:38 +0900634 [fullScreenWindow close];
635 [normalWindow setContentView: self];
636 [normalWindow makeKeyAndOrderFront: self];
637 [NSMenu setMenuBarVisible:YES];
thsc304f7e2008-01-22 23:25:15 +0000638 } else { // switch from desktop to fullscreen
639 isFullscreen = TRUE;
Programmingkid5d1b2ee2015-05-19 09:11:17 +0100640 [normalWindow orderOut: nil]; /* Hide the window */
thsc304f7e2008-01-22 23:25:15 +0000641 [self grabMouse];
642 [self setContentDimensions];
Akihiko Odaki1e8b6f22021-02-20 10:31:38 +0900643 [NSMenu setMenuBarVisible:NO];
644 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
645 styleMask:NSWindowStyleMaskBorderless
646 backing:NSBackingStoreBuffered
647 defer:NO];
648 [fullScreenWindow setAcceptsMouseMovedEvents: YES];
649 [fullScreenWindow setHasShadow:NO];
650 [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
651 [self setFrame:NSMakeRect(cx, cy, cw, ch)];
652 [[fullScreenWindow contentView] addSubview: self];
653 [fullScreenWindow makeKeyAndOrderFront:self];
thsc304f7e2008-01-22 23:25:15 +0000654 }
655}
656
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900657- (void) toggleKey: (int)keycode {
658 qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700659}
660
John Arbuckle9c3a4182017-11-07 10:14:14 +0000661// Does the work of sending input to the monitor
662- (void) handleMonitorInput:(NSEvent *)event
663{
664 int keysym = 0;
665 int control_key = 0;
666
667 // if the control key is down
668 if ([event modifierFlags] & NSEventModifierFlagControl) {
669 control_key = 1;
670 }
671
672 /* translates Macintosh keycodes to QEMU's keysym */
673
674 int without_control_translation[] = {
675 [0 ... 0xff] = 0, // invalid key
676
677 [kVK_UpArrow] = QEMU_KEY_UP,
678 [kVK_DownArrow] = QEMU_KEY_DOWN,
679 [kVK_RightArrow] = QEMU_KEY_RIGHT,
680 [kVK_LeftArrow] = QEMU_KEY_LEFT,
681 [kVK_Home] = QEMU_KEY_HOME,
682 [kVK_End] = QEMU_KEY_END,
683 [kVK_PageUp] = QEMU_KEY_PAGEUP,
684 [kVK_PageDown] = QEMU_KEY_PAGEDOWN,
685 [kVK_ForwardDelete] = QEMU_KEY_DELETE,
686 [kVK_Delete] = QEMU_KEY_BACKSPACE,
687 };
688
689 int with_control_translation[] = {
690 [0 ... 0xff] = 0, // invalid key
691
692 [kVK_UpArrow] = QEMU_KEY_CTRL_UP,
693 [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN,
694 [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT,
695 [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT,
696 [kVK_Home] = QEMU_KEY_CTRL_HOME,
697 [kVK_End] = QEMU_KEY_CTRL_END,
698 [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP,
699 [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN,
700 };
701
702 if (control_key != 0) { /* If the control key is being used */
703 if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
704 keysym = with_control_translation[[event keyCode]];
705 }
706 } else {
707 if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
708 keysym = without_control_translation[[event keyCode]];
709 }
710 }
711
712 // if not a key that needs translating
713 if (keysym == 0) {
714 NSString *ks = [event characters];
715 if ([ks length] > 0) {
716 keysym = [ks characterAtIndex:0];
717 }
718 }
719
720 if (keysym) {
721 kbd_put_keysym(keysym);
722 }
723}
724
Peter Maydell60105d72019-02-25 10:24:31 +0000725- (bool) handleEvent:(NSEvent *)event
thsc304f7e2008-01-22 23:25:15 +0000726{
Hikaru Nishidadff742a2019-10-15 10:07:34 +0900727 if(!allow_events) {
728 /*
729 * Just let OSX have all events that arrive before
730 * applicationDidFinishLaunching.
731 * This avoids a deadlock on the iothread lock, which cocoa_display_init()
732 * will not drop until after the app_started_sem is posted. (In theory
733 * there should not be any such events, but OSX Catalina now emits some.)
734 */
735 return false;
736 }
Peter Maydell60105d72019-02-25 10:24:31 +0000737 return bool_with_iothread_lock(^{
738 return [self handleEventLocked:event];
Peter Maydell31819e92019-02-25 10:24:27 +0000739 });
740}
thsc304f7e2008-01-22 23:25:15 +0000741
Peter Maydell60105d72019-02-25 10:24:31 +0000742- (bool) handleEventLocked:(NSEvent *)event
Peter Maydell31819e92019-02-25 10:24:27 +0000743{
Peter Maydell60105d72019-02-25 10:24:31 +0000744 /* Return true if we handled the event, false if it should be given to OSX */
Peter Maydell31819e92019-02-25 10:24:27 +0000745 COCOA_DEBUG("QemuCocoaView: handleEvent\n");
thsc304f7e2008-01-22 23:25:15 +0000746 int buttons = 0;
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700747 int keycode = 0;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100748 bool mouse_event = false;
John Arbuckle0c6c4392018-07-02 22:00:17 -0400749 static bool switched_to_fullscreen = false;
Chen Zhang2044dff2019-06-04 17:36:00 +0800750 // Location of event in virtual screen coordinates
751 NSPoint p = [self screenLocationOfEvent:event];
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900752 NSUInteger modifiers = [event modifierFlags];
753
Akihiko Odakiad7f2f82021-03-12 22:32:12 +0900754 /*
755 * Check -[NSEvent modifierFlags] here.
756 *
757 * There is a NSEventType for an event notifying the change of
758 * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
759 * are performed for any events because a modifier state may change while
760 * the application is inactive (i.e. no events fire) and we don't want to
761 * wait for another modifier state change to detect such a change.
762 *
763 * NSEventModifierFlagCapsLock requires a special treatment. The other flags
764 * are handled in similar manners.
765 *
766 * NSEventModifierFlagCapsLock
767 * ---------------------------
768 *
769 * If CapsLock state is changed, "up" and "down" events will be fired in
770 * sequence, effectively updates CapsLock state on the guest.
771 *
772 * The other flags
773 * ---------------
774 *
775 * If a flag is not set, fire "up" events for all keys which correspond to
776 * the flag. Note that "down" events are not fired here because the flags
777 * checked here do not tell what exact keys are down.
778 *
779 * If one of the keys corresponding to a flag is down, we rely on
780 * -[NSEvent keyCode] of an event whose -[NSEvent type] is
781 * NSEventTypeFlagsChanged to know the exact key which is down, which has
782 * the following two downsides:
783 * - It does not work when the application is inactive as described above.
784 * - It malfactions *after* the modifier state is changed while the
785 * application is inactive. It is because -[NSEvent keyCode] does not tell
786 * if the key is up or down, and requires to infer the current state from
787 * the previous state. It is still possible to fix such a malfanction by
788 * completely leaving your hands from the keyboard, which hopefully makes
789 * this implementation usable enough.
790 */
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900791 if (!!(modifiers & NSEventModifierFlagCapsLock) !=
792 qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
793 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
794 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
795 }
796
797 if (!(modifiers & NSEventModifierFlagShift)) {
798 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
799 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
800 }
801 if (!(modifiers & NSEventModifierFlagControl)) {
802 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
803 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
804 }
805 if (!(modifiers & NSEventModifierFlagOption)) {
806 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
807 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
808 }
809 if (!(modifiers & NSEventModifierFlagCommand)) {
810 qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
811 qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
812 }
thsc304f7e2008-01-22 23:25:15 +0000813
814 switch ([event type]) {
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700815 case NSEventTypeFlagsChanged:
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900816 switch ([event keyCode]) {
817 case kVK_Shift:
818 if (!!(modifiers & NSEventModifierFlagShift)) {
819 [self toggleKey:Q_KEY_CODE_SHIFT];
820 }
821 break;
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700822
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900823 case kVK_RightShift:
824 if (!!(modifiers & NSEventModifierFlagShift)) {
825 [self toggleKey:Q_KEY_CODE_SHIFT_R];
826 }
827 break;
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700828
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900829 case kVK_Control:
830 if (!!(modifiers & NSEventModifierFlagControl)) {
831 [self toggleKey:Q_KEY_CODE_CTRL];
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700832 }
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900833 break;
834
835 case kVK_RightControl:
836 if (!!(modifiers & NSEventModifierFlagControl)) {
837 [self toggleKey:Q_KEY_CODE_CTRL_R];
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700838 }
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900839 break;
840
841 case kVK_Option:
842 if (!!(modifiers & NSEventModifierFlagOption)) {
843 [self toggleKey:Q_KEY_CODE_ALT];
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700844 }
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900845 break;
846
847 case kVK_RightOption:
848 if (!!(modifiers & NSEventModifierFlagOption)) {
849 [self toggleKey:Q_KEY_CODE_ALT_R];
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700850 }
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900851 break;
852
853 /* Don't pass command key changes to guest unless mouse is grabbed */
854 case kVK_Command:
855 if (isMouseGrabbed &&
856 !!(modifiers & NSEventModifierFlagCommand)) {
857 [self toggleKey:Q_KEY_CODE_META_L];
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700858 }
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900859 break;
860
861 case kVK_RightCommand:
862 if (isMouseGrabbed &&
863 !!(modifiers & NSEventModifierFlagCommand)) {
864 [self toggleKey:Q_KEY_CODE_META_R];
865 }
866 break;
Ian McKellar via Qemu-develaf8862b2017-05-26 16:38:16 -0700867 }
thsc304f7e2008-01-22 23:25:15 +0000868 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700869 case NSEventTypeKeyDown:
Peter Maydell88959192013-12-08 22:59:02 +0000870 keycode = cocoa_keycode_to_qemu([event keyCode]);
thsc304f7e2008-01-22 23:25:15 +0000871
Peter Maydell88959192013-12-08 22:59:02 +0000872 // forward command key combos to the host UI unless the mouse is grabbed
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700873 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
John Arbuckle0c6c4392018-07-02 22:00:17 -0400874 /*
875 * Prevent the command key from being stuck down in the guest
876 * when using Command-F to switch to full screen mode.
877 */
878 if (keycode == Q_KEY_CODE_F) {
879 switched_to_fullscreen = true;
880 }
Peter Maydell60105d72019-02-25 10:24:31 +0000881 return false;
thsc304f7e2008-01-22 23:25:15 +0000882 }
883
884 // default
thsc304f7e2008-01-22 23:25:15 +0000885
John Arbuckle5929e362017-11-07 10:14:14 +0000886 // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700887 if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
John Arbuckle5929e362017-11-07 10:14:14 +0000888 NSString *keychar = [event charactersIgnoringModifiers];
889 if ([keychar length] == 1) {
890 char key = [keychar characterAtIndex:0];
891 switch (key) {
thsc304f7e2008-01-22 23:25:15 +0000892
John Arbuckle5929e362017-11-07 10:14:14 +0000893 // enable graphic console
894 case '1' ... '9':
895 console_select(key - '0' - 1); /* ascii math */
Peter Maydell60105d72019-02-25 10:24:31 +0000896 return true;
John Arbuckle5929e362017-11-07 10:14:14 +0000897
898 // release the mouse grab
899 case 'g':
900 [self ungrabMouse];
Peter Maydell60105d72019-02-25 10:24:31 +0000901 return true;
John Arbuckle5929e362017-11-07 10:14:14 +0000902 }
thsc304f7e2008-01-22 23:25:15 +0000903 }
Peter Maydellef2088f2017-11-07 10:14:14 +0000904 }
thsc304f7e2008-01-22 23:25:15 +0000905
Peter Maydellef2088f2017-11-07 10:14:14 +0000906 if (qemu_console_is_graphic(NULL)) {
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900907 qkbd_state_key_event(kbd, keycode, true);
thsc304f7e2008-01-22 23:25:15 +0000908 } else {
John Arbuckle9c3a4182017-11-07 10:14:14 +0000909 [self handleMonitorInput: event];
thsc304f7e2008-01-22 23:25:15 +0000910 }
911 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700912 case NSEventTypeKeyUp:
thsc304f7e2008-01-22 23:25:15 +0000913 keycode = cocoa_keycode_to_qemu([event keyCode]);
Peter Maydell88959192013-12-08 22:59:02 +0000914
915 // don't pass the guest a spurious key-up if we treated this
916 // command-key combo as a host UI action
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700917 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
Peter Maydell60105d72019-02-25 10:24:31 +0000918 return true;
Peter Maydell88959192013-12-08 22:59:02 +0000919 }
920
Peter Maydell68c0aa62013-04-17 09:16:35 +0000921 if (qemu_console_is_graphic(NULL)) {
Akihiko Odaki6d73bb62021-03-10 23:46:02 +0900922 qkbd_state_key_event(kbd, keycode, false);
thsc304f7e2008-01-22 23:25:15 +0000923 }
924 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700925 case NSEventTypeMouseMoved:
thsc304f7e2008-01-22 23:25:15 +0000926 if (isAbsoluteEnabled) {
Chen Zhang2044dff2019-06-04 17:36:00 +0800927 // Cursor re-entered into a window might generate events bound to screen coordinates
928 // and `nil` window property, and in full screen mode, current window might not be
929 // key window, where event location alone should suffice.
930 if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) {
Peter Maydellf61c3872014-06-23 10:35:24 +0100931 if (isMouseGrabbed) {
932 [self ungrabMouse];
thsc304f7e2008-01-22 23:25:15 +0000933 }
934 } else {
Peter Maydellf61c3872014-06-23 10:35:24 +0100935 if (!isMouseGrabbed) {
936 [self grabMouse];
thsc304f7e2008-01-22 23:25:15 +0000937 }
938 }
939 }
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100940 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000941 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700942 case NSEventTypeLeftMouseDown:
Akihiko Odaki4295f832021-02-12 09:07:06 +0900943 buttons |= MOUSE_EVENT_LBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100944 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000945 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700946 case NSEventTypeRightMouseDown:
thsc304f7e2008-01-22 23:25:15 +0000947 buttons |= MOUSE_EVENT_RBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100948 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000949 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700950 case NSEventTypeOtherMouseDown:
thsc304f7e2008-01-22 23:25:15 +0000951 buttons |= MOUSE_EVENT_MBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100952 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000953 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700954 case NSEventTypeLeftMouseDragged:
Akihiko Odaki4295f832021-02-12 09:07:06 +0900955 buttons |= MOUSE_EVENT_LBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100956 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000957 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700958 case NSEventTypeRightMouseDragged:
thsc304f7e2008-01-22 23:25:15 +0000959 buttons |= MOUSE_EVENT_RBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100960 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000961 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700962 case NSEventTypeOtherMouseDragged:
thsc304f7e2008-01-22 23:25:15 +0000963 buttons |= MOUSE_EVENT_MBUTTON;
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100964 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000965 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700966 case NSEventTypeLeftMouseUp:
Peter Maydellf61c3872014-06-23 10:35:24 +0100967 mouse_event = true;
968 if (!isMouseGrabbed && [self screenContainsPoint:p]) {
Chen Zhang8e23e342019-06-04 17:36:48 +0800969 /*
970 * In fullscreen mode, the window of cocoaView may not be the
971 * key window, therefore the position relative to the virtual
972 * screen alone will be sufficient.
973 */
974 if(isFullscreen || [[self window] isKeyWindow]) {
Programmingkid9e8204b2016-08-15 22:11:06 -0400975 [self grabMouse];
976 }
thsc304f7e2008-01-22 23:25:15 +0000977 }
978 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700979 case NSEventTypeRightMouseUp:
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100980 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000981 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700982 case NSEventTypeOtherMouseUp:
Gerd Hoffmann21bae112013-12-04 14:08:04 +0100983 mouse_event = true;
thsc304f7e2008-01-22 23:25:15 +0000984 break;
Brendan Shanks4ba967a2017-04-24 23:29:52 -0700985 case NSEventTypeScrollWheel:
John Arbuckleae7313e2018-01-08 13:07:07 -0500986 /*
987 * Send wheel events to the guest regardless of window focus.
988 * This is in-line with standard Mac OS X UI behaviour.
989 */
990
John Arbuckledc3c89d2018-07-09 11:02:35 -0400991 /*
Dmitry Petrovd70a5de2022-01-08 16:39:44 +0100992 * We shouldn't have got a scroll event when deltaY and delta Y
993 * are zero, hence no harm in dropping the event
John Arbuckledc3c89d2018-07-09 11:02:35 -0400994 */
Dmitry Petrovd70a5de2022-01-08 16:39:44 +0100995 if ([event deltaY] != 0 || [event deltaX] != 0) {
John Arbuckleae7313e2018-01-08 13:07:07 -0500996 /* Determine if this is a scroll up or scroll down event */
Dmitry Petrovd70a5de2022-01-08 16:39:44 +0100997 if ([event deltaY] != 0) {
998 buttons = ([event deltaY] > 0) ?
John Arbuckledc3c89d2018-07-09 11:02:35 -0400999 INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
Dmitry Petrovd70a5de2022-01-08 16:39:44 +01001000 } else if ([event deltaX] != 0) {
1001 buttons = ([event deltaX] > 0) ?
1002 INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
1003 }
1004
Akihiko Odakicc7859c2021-02-19 17:44:19 +09001005 qemu_input_queue_btn(dcl.con, buttons, true);
John Arbuckledc3c89d2018-07-09 11:02:35 -04001006 qemu_input_event_sync();
Akihiko Odakicc7859c2021-02-19 17:44:19 +09001007 qemu_input_queue_btn(dcl.con, buttons, false);
John Arbuckledc3c89d2018-07-09 11:02:35 -04001008 qemu_input_event_sync();
1009 }
Dmitry Petrovd70a5de2022-01-08 16:39:44 +01001010
John Arbuckleae7313e2018-01-08 13:07:07 -05001011 /*
Dmitry Petrovd70a5de2022-01-08 16:39:44 +01001012 * Since deltaX/deltaY also report scroll wheel events we prevent mouse
John Arbuckleae7313e2018-01-08 13:07:07 -05001013 * movement code from executing.
1014 */
1015 mouse_event = false;
thsc304f7e2008-01-22 23:25:15 +00001016 break;
1017 default:
Peter Maydell60105d72019-02-25 10:24:31 +00001018 return false;
thsc304f7e2008-01-22 23:25:15 +00001019 }
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001020
1021 if (mouse_event) {
Peter Maydell8d3a5d92015-11-26 15:19:28 +00001022 /* Don't send button events to the guest unless we've got a
1023 * mouse grab or window focus. If we have neither then this event
1024 * is the user clicking on the background window to activate and
1025 * bring us to the front, which will be done by the sendEvent
1026 * call below. We definitely don't want to pass that click through
1027 * to the guest.
1028 */
1029 if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
1030 (last_buttons != buttons)) {
Eric Blake7fb1cf12015-11-18 01:52:57 -07001031 static uint32_t bmap[INPUT_BUTTON__MAX] = {
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001032 [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
1033 [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
John Arbuckleae7313e2018-01-08 13:07:07 -05001034 [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001035 };
Akihiko Odakicc7859c2021-02-19 17:44:19 +09001036 qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons);
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001037 last_buttons = buttons;
1038 }
Peter Maydellf61c3872014-06-23 10:35:24 +01001039 if (isMouseGrabbed) {
1040 if (isAbsoluteEnabled) {
1041 /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
1042 * The check on screenContainsPoint is to avoid sending out of range values for
1043 * clicks in the titlebar.
1044 */
1045 if ([self screenContainsPoint:p]) {
Akihiko Odakicc7859c2021-02-19 17:44:19 +09001046 qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width);
1047 qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height);
Peter Maydellf61c3872014-06-23 10:35:24 +01001048 }
1049 } else {
Akihiko Odakicc7859c2021-02-19 17:44:19 +09001050 qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]);
1051 qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]);
Peter Maydellf61c3872014-06-23 10:35:24 +01001052 }
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001053 } else {
Peter Maydell60105d72019-02-25 10:24:31 +00001054 return false;
Gerd Hoffmann21bae112013-12-04 14:08:04 +01001055 }
1056 qemu_input_event_sync();
1057 }
Peter Maydell60105d72019-02-25 10:24:31 +00001058 return true;
thsc304f7e2008-01-22 23:25:15 +00001059}
1060
1061- (void) grabMouse
1062{
1063 COCOA_DEBUG("QemuCocoaView: grabMouse\n");
1064
1065 if (!isFullscreen) {
1066 if (qemu_name)
John Arbuckle5929e362017-11-07 10:14:14 +00001067 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]];
thsc304f7e2008-01-22 23:25:15 +00001068 else
John Arbuckle5929e362017-11-07 10:14:14 +00001069 [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"];
thsc304f7e2008-01-22 23:25:15 +00001070 }
Peter Maydell13aefd32014-06-23 10:35:25 +01001071 [self hideCursor];
Akihiko Odakid1929062021-02-23 00:07:14 +09001072 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
Peter Maydell49b9bd42013-12-08 22:59:03 +00001073 isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
thsc304f7e2008-01-22 23:25:15 +00001074}
1075
1076- (void) ungrabMouse
1077{
1078 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
1079
1080 if (!isFullscreen) {
1081 if (qemu_name)
1082 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
1083 else
1084 [normalWindow setTitle:@"QEMU"];
1085 }
Peter Maydell13aefd32014-06-23 10:35:25 +01001086 [self unhideCursor];
Akihiko Odakid1929062021-02-23 00:07:14 +09001087 CGAssociateMouseAndMouseCursorPosition(TRUE);
Peter Maydell49b9bd42013-12-08 22:59:03 +00001088 isMouseGrabbed = FALSE;
thsc304f7e2008-01-22 23:25:15 +00001089}
1090
Akihiko Odakid1929062021-02-23 00:07:14 +09001091- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
1092 isAbsoluteEnabled = tIsAbsoluteEnabled;
1093 if (isMouseGrabbed) {
1094 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
1095 }
1096}
Peter Maydell49b9bd42013-12-08 22:59:03 +00001097- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
thsc304f7e2008-01-22 23:25:15 +00001098- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
1099- (float) cdx {return cdx;}
1100- (float) cdy {return cdy;}
1101- (QEMUScreen) gscreen {return screen;}
John Arbuckle3b178b72015-09-25 23:14:00 +01001102
1103/*
1104 * Makes the target think all down keys are being released.
1105 * This prevents a stuck key problem, since we will not see
1106 * key up events for those keys after we have lost focus.
1107 */
1108- (void) raiseAllKeys
1109{
Peter Maydell31819e92019-02-25 10:24:27 +00001110 with_iothread_lock(^{
Akihiko Odaki6d73bb62021-03-10 23:46:02 +09001111 qkbd_state_lift_all_keys(kbd);
Peter Maydell31819e92019-02-25 10:24:27 +00001112 });
John Arbuckle3b178b72015-09-25 23:14:00 +01001113}
bellard5b0753e2005-03-01 21:37:28 +00001114@end
1115
1116
thsc304f7e2008-01-22 23:25:15 +00001117
bellard5b0753e2005-03-01 21:37:28 +00001118/*
1119 ------------------------------------------------------
thsc304f7e2008-01-22 23:25:15 +00001120 QemuCocoaAppController
bellard5b0753e2005-03-01 21:37:28 +00001121 ------------------------------------------------------
1122*/
thsc304f7e2008-01-22 23:25:15 +00001123@interface QemuCocoaAppController : NSObject
John Arbuckled9bc14f2015-09-25 23:13:59 +01001124 <NSWindowDelegate, NSApplicationDelegate>
bellard5b0753e2005-03-01 21:37:28 +00001125{
1126}
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001127- (void)doToggleFullScreen:(id)sender;
thsc304f7e2008-01-22 23:25:15 +00001128- (void)toggleFullScreen:(id)sender;
1129- (void)showQEMUDoc:(id)sender;
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001130- (void)zoomToFit:(id) sender;
Programmingkidb4c6a112015-05-19 09:11:18 +01001131- (void)displayConsole:(id)sender;
John Arbuckle8524f1c2015-06-19 10:53:27 +01001132- (void)pauseQEMU:(id)sender;
1133- (void)resumeQEMU:(id)sender;
1134- (void)displayPause;
1135- (void)removePause;
John Arbuckle27074612015-06-19 10:53:27 +01001136- (void)restartQEMU:(id)sender;
1137- (void)powerDownQEMU:(id)sender;
John Arbuckle693a3e02015-06-19 10:53:27 +01001138- (void)ejectDeviceMedia:(id)sender;
1139- (void)changeDeviceMedia:(id)sender;
John Arbuckled9bc14f2015-09-25 23:13:59 +01001140- (BOOL)verifyQuit;
John Arbucklef4747902016-03-23 14:26:17 +00001141- (void)openDocumentation:(NSString *)filename;
Programmingkid9e8204b2016-08-15 22:11:06 -04001142- (IBAction) do_about_menu_item: (id) sender;
1143- (void)make_about_window;
John Arbucklee47ec1a2017-06-13 23:17:38 -04001144- (void)adjustSpeed:(id)sender;
bellard5b0753e2005-03-01 21:37:28 +00001145@end
1146
thsc304f7e2008-01-22 23:25:15 +00001147@implementation QemuCocoaAppController
1148- (id) init
1149{
1150 COCOA_DEBUG("QemuCocoaAppController: init\n");
1151
1152 self = [super init];
1153 if (self) {
1154
1155 // create a view and add it to the window
1156 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
1157 if(!cocoaView) {
Akihiko Odaki43137392021-02-23 22:11:06 +09001158 error_report("(cocoa) can't create a view");
thsc304f7e2008-01-22 23:25:15 +00001159 exit(1);
1160 }
1161
1162 // create a window
1163 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
Brendan Shanks4ba967a2017-04-24 23:29:52 -07001164 styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
thsc304f7e2008-01-22 23:25:15 +00001165 backing:NSBackingStoreBuffered defer:NO];
1166 if(!normalWindow) {
Akihiko Odaki43137392021-02-23 22:11:06 +09001167 error_report("(cocoa) can't create window");
thsc304f7e2008-01-22 23:25:15 +00001168 exit(1);
1169 }
1170 [normalWindow setAcceptsMouseMovedEvents:YES];
John Arbucklea1dbc052015-10-13 21:51:18 +01001171 [normalWindow setTitle:@"QEMU"];
thsc304f7e2008-01-22 23:25:15 +00001172 [normalWindow setContentView:cocoaView];
1173 [normalWindow makeKeyAndOrderFront:self];
Peter Maydell49060c22013-12-24 11:54:12 +00001174 [normalWindow center];
John Arbuckled9bc14f2015-09-25 23:13:59 +01001175 [normalWindow setDelegate: self];
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001176 stretch_video = false;
John Arbuckle8524f1c2015-06-19 10:53:27 +01001177
1178 /* Used for displaying pause on the screen */
1179 pauseLabel = [NSTextField new];
1180 [pauseLabel setBezeled:YES];
1181 [pauseLabel setDrawsBackground:YES];
1182 [pauseLabel setBackgroundColor: [NSColor whiteColor]];
1183 [pauseLabel setEditable:NO];
1184 [pauseLabel setSelectable:NO];
1185 [pauseLabel setStringValue: @"Paused"];
1186 [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
1187 [pauseLabel setTextColor: [NSColor blackColor]];
1188 [pauseLabel sizeToFit];
John Arbuckle693a3e02015-06-19 10:53:27 +01001189
Programmingkid9e8204b2016-08-15 22:11:06 -04001190 [self make_about_window];
thsc304f7e2008-01-22 23:25:15 +00001191 }
1192 return self;
1193}
1194
1195- (void) dealloc
1196{
1197 COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
1198
1199 if (cocoaView)
1200 [cocoaView release];
1201 [super dealloc];
1202}
1203
bellard5b0753e2005-03-01 21:37:28 +00001204- (void)applicationDidFinishLaunching: (NSNotification *) note
1205{
thsc304f7e2008-01-22 23:25:15 +00001206 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
Hikaru Nishidadff742a2019-10-15 10:07:34 +09001207 allow_events = true;
Peter Maydell55888402019-02-25 10:24:33 +00001208 /* Tell cocoa_display_init to proceed */
1209 qemu_sem_post(&app_started_sem);
bellard5b0753e2005-03-01 21:37:28 +00001210}
1211
1212- (void)applicationWillTerminate:(NSNotification *)aNotification
1213{
thsc304f7e2008-01-22 23:25:15 +00001214 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
1215
Eric Blakecf83f142017-05-15 16:41:13 -05001216 qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
Akihiko Odaki40c01932021-02-19 20:16:52 +09001217
1218 /*
1219 * Sleep here, because returning will cause OSX to kill us
1220 * immediately; the QEMU main loop will handle the shutdown
1221 * request and terminate the process.
1222 */
1223 [NSThread sleepForTimeInterval:INFINITY];
bellard5b0753e2005-03-01 21:37:28 +00001224}
1225
Andreas Färber41ea49b2009-12-14 22:13:27 +01001226- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
1227{
1228 return YES;
1229}
1230
John Arbuckled9bc14f2015-09-25 23:13:59 +01001231- (NSApplicationTerminateReply)applicationShouldTerminate:
1232 (NSApplication *)sender
1233{
1234 COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
1235 return [self verifyQuit];
1236}
1237
Akihiko Odaki15280e82021-06-16 23:19:10 +09001238- (void)windowDidChangeScreen:(NSNotification *)notification
1239{
1240 [cocoaView updateUIInfo];
1241}
1242
1243- (void)windowDidResize:(NSNotification *)notification
1244{
1245 [cocoaView updateUIInfo];
1246}
1247
John Arbuckled9bc14f2015-09-25 23:13:59 +01001248/* Called when the user clicks on a window's close button */
1249- (BOOL)windowShouldClose:(id)sender
1250{
1251 COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
1252 [NSApp terminate: sender];
1253 /* If the user allows the application to quit then the call to
1254 * NSApp terminate will never return. If we get here then the user
1255 * cancelled the quit, so we should return NO to not permit the
1256 * closing of this window.
1257 */
1258 return NO;
1259}
1260
John Arbuckle3b178b72015-09-25 23:14:00 +01001261/* Called when QEMU goes into the background */
1262- (void) applicationWillResignActive: (NSNotification *)aNotification
1263{
1264 COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
1265 [cocoaView raiseAllKeys];
1266}
1267
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001268/* We abstract the method called by the Enter Fullscreen menu item
1269 * because Mac OS 10.7 and higher disables it. This is because of the
1270 * menu item's old selector's name toggleFullScreen:
1271 */
1272- (void) doToggleFullScreen:(id)sender
1273{
1274 [self toggleFullScreen:(id)sender];
1275}
1276
thsc304f7e2008-01-22 23:25:15 +00001277- (void)toggleFullScreen:(id)sender
bellard5b0753e2005-03-01 21:37:28 +00001278{
thsc304f7e2008-01-22 23:25:15 +00001279 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
1280
1281 [cocoaView toggleFullScreen:sender];
1282}
1283
John Arbucklef4747902016-03-23 14:26:17 +00001284/* Tries to find then open the specified filename */
1285- (void) openDocumentation: (NSString *) filename
1286{
1287 /* Where to look for local files */
Roman Bolshakov8d6fda82021-01-09 00:38:15 +03001288 NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
John Arbucklef4747902016-03-23 14:26:17 +00001289 NSString *full_file_path;
Roman Bolshakov1ff5a062021-01-02 18:07:21 +03001290 NSURL *full_file_url;
John Arbucklef4747902016-03-23 14:26:17 +00001291
1292 /* iterate thru the possible paths until the file is found */
1293 int index;
1294 for (index = 0; index < ARRAY_SIZE(path_array); index++) {
1295 full_file_path = [[NSBundle mainBundle] executablePath];
1296 full_file_path = [full_file_path stringByDeletingLastPathComponent];
1297 full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
1298 path_array[index], filename];
Roman Bolshakov1ff5a062021-01-02 18:07:21 +03001299 full_file_url = [NSURL fileURLWithPath: full_file_path
1300 isDirectory: false];
1301 if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
John Arbucklef4747902016-03-23 14:26:17 +00001302 return;
1303 }
1304 }
1305
1306 /* If none of the paths opened a file */
1307 NSBeep();
1308 QEMU_Alert(@"Failed to open file");
1309}
1310
thsc304f7e2008-01-22 23:25:15 +00001311- (void)showQEMUDoc:(id)sender
1312{
1313 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
1314
Peter Maydell1879f242020-02-28 15:36:16 +00001315 [self openDocumentation: @"index.html"];
thsc304f7e2008-01-22 23:25:15 +00001316}
1317
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001318/* Stretches video to fit host monitor size */
1319- (void)zoomToFit:(id) sender
1320{
1321 stretch_video = !stretch_video;
1322 if (stretch_video == true) {
Brendan Shanks5e246002019-01-31 23:12:25 -08001323 [sender setState: NSControlStateValueOn];
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001324 } else {
Brendan Shanks5e246002019-01-31 23:12:25 -08001325 [sender setState: NSControlStateValueOff];
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001326 }
1327}
bellard5b0753e2005-03-01 21:37:28 +00001328
Programmingkidb4c6a112015-05-19 09:11:18 +01001329/* Displays the console on the screen */
1330- (void)displayConsole:(id)sender
1331{
1332 console_select([sender tag]);
1333}
John Arbuckle8524f1c2015-06-19 10:53:27 +01001334
1335/* Pause the guest */
1336- (void)pauseQEMU:(id)sender
1337{
Peter Maydell31819e92019-02-25 10:24:27 +00001338 with_iothread_lock(^{
1339 qmp_stop(NULL);
1340 });
John Arbuckle8524f1c2015-06-19 10:53:27 +01001341 [sender setEnabled: NO];
1342 [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
1343 [self displayPause];
1344}
1345
1346/* Resume running the guest operating system */
1347- (void)resumeQEMU:(id) sender
1348{
Peter Maydell31819e92019-02-25 10:24:27 +00001349 with_iothread_lock(^{
1350 qmp_cont(NULL);
1351 });
John Arbuckle8524f1c2015-06-19 10:53:27 +01001352 [sender setEnabled: NO];
1353 [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
1354 [self removePause];
1355}
1356
1357/* Displays the word pause on the screen */
1358- (void)displayPause
1359{
1360 /* Coordinates have to be calculated each time because the window can change its size */
1361 int xCoord, yCoord, width, height;
1362 xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
1363 yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
1364 width = [pauseLabel frame].size.width;
1365 height = [pauseLabel frame].size.height;
1366 [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
1367 [cocoaView addSubview: pauseLabel];
1368}
1369
1370/* Removes the word pause from the screen */
1371- (void)removePause
1372{
1373 [pauseLabel removeFromSuperview];
1374}
1375
John Arbuckle27074612015-06-19 10:53:27 +01001376/* Restarts QEMU */
1377- (void)restartQEMU:(id)sender
1378{
Peter Maydell31819e92019-02-25 10:24:27 +00001379 with_iothread_lock(^{
1380 qmp_system_reset(NULL);
1381 });
John Arbuckle27074612015-06-19 10:53:27 +01001382}
1383
1384/* Powers down QEMU */
1385- (void)powerDownQEMU:(id)sender
1386{
Peter Maydell31819e92019-02-25 10:24:27 +00001387 with_iothread_lock(^{
1388 qmp_system_powerdown(NULL);
1389 });
John Arbuckle27074612015-06-19 10:53:27 +01001390}
1391
John Arbuckle693a3e02015-06-19 10:53:27 +01001392/* Ejects the media.
1393 * Uses sender's tag to figure out the device to eject.
1394 */
1395- (void)ejectDeviceMedia:(id)sender
1396{
1397 NSString * drive;
1398 drive = [sender representedObject];
1399 if(drive == nil) {
1400 NSBeep();
1401 QEMU_Alert(@"Failed to find drive to eject!");
1402 return;
1403 }
1404
Peter Maydell31819e92019-02-25 10:24:27 +00001405 __block Error *err = NULL;
1406 with_iothread_lock(^{
1407 qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding],
1408 false, NULL, false, false, &err);
1409 });
John Arbuckle693a3e02015-06-19 10:53:27 +01001410 handleAnyDeviceErrors(err);
1411}
1412
1413/* Displays a dialog box asking the user to select an image file to load.
1414 * Uses sender's represented object value to figure out which drive to use.
1415 */
1416- (void)changeDeviceMedia:(id)sender
1417{
1418 /* Find the drive name */
1419 NSString * drive;
1420 drive = [sender representedObject];
1421 if(drive == nil) {
1422 NSBeep();
1423 QEMU_Alert(@"Could not find drive!");
1424 return;
1425 }
1426
1427 /* Display the file open dialog */
1428 NSOpenPanel * openPanel;
1429 openPanel = [NSOpenPanel openPanel];
1430 [openPanel setCanChooseFiles: YES];
1431 [openPanel setAllowsMultipleSelection: NO];
Peter Maydellb5725382018-05-29 19:15:23 +01001432 if([openPanel runModal] == NSModalResponseOK) {
John Arbuckle693a3e02015-06-19 10:53:27 +01001433 NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1434 if(file == nil) {
1435 NSBeep();
1436 QEMU_Alert(@"Failed to convert URL to file path!");
1437 return;
1438 }
1439
Peter Maydell31819e92019-02-25 10:24:27 +00001440 __block Error *err = NULL;
1441 with_iothread_lock(^{
1442 qmp_blockdev_change_medium(true,
1443 [drive cStringUsingEncoding:
1444 NSASCIIStringEncoding],
1445 false, NULL,
1446 [file cStringUsingEncoding:
1447 NSASCIIStringEncoding],
1448 true, "raw",
1449 false, 0,
1450 &err);
1451 });
John Arbuckle693a3e02015-06-19 10:53:27 +01001452 handleAnyDeviceErrors(err);
1453 }
1454}
1455
John Arbuckled9bc14f2015-09-25 23:13:59 +01001456/* Verifies if the user really wants to quit */
1457- (BOOL)verifyQuit
1458{
1459 NSAlert *alert = [NSAlert new];
1460 [alert autorelease];
1461 [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1462 [alert addButtonWithTitle: @"Cancel"];
1463 [alert addButtonWithTitle: @"Quit"];
1464 if([alert runModal] == NSAlertSecondButtonReturn) {
1465 return YES;
1466 } else {
1467 return NO;
1468 }
1469}
1470
Programmingkid9e8204b2016-08-15 22:11:06 -04001471/* The action method for the About menu item */
1472- (IBAction) do_about_menu_item: (id) sender
1473{
1474 [about_window makeKeyAndOrderFront: nil];
1475}
1476
1477/* Create and display the about dialog */
1478- (void)make_about_window
1479{
1480 /* Make the window */
1481 int x = 0, y = 0, about_width = 400, about_height = 200;
1482 NSRect window_rect = NSMakeRect(x, y, about_width, about_height);
1483 about_window = [[NSWindow alloc] initWithContentRect:window_rect
Brendan Shanks4ba967a2017-04-24 23:29:52 -07001484 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
1485 NSWindowStyleMaskMiniaturizable
Programmingkid9e8204b2016-08-15 22:11:06 -04001486 backing:NSBackingStoreBuffered
1487 defer:NO];
1488 [about_window setTitle: @"About"];
1489 [about_window setReleasedWhenClosed: NO];
1490 [about_window center];
1491 NSView *superView = [about_window contentView];
1492
1493 /* Create the dimensions of the picture */
1494 int picture_width = 80, picture_height = 80;
1495 x = (about_width - picture_width)/2;
1496 y = about_height - picture_height - 10;
1497 NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height);
1498
Programmingkid9e8204b2016-08-15 22:11:06 -04001499 /* Make the picture of QEMU */
1500 NSImageView *picture_view = [[NSImageView alloc] initWithFrame:
1501 picture_rect];
Akihiko Odakie31746e2021-03-09 21:22:25 +09001502 char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
1503 NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c];
1504 g_free(qemu_image_path_c);
1505 NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path];
Programmingkid9e8204b2016-08-15 22:11:06 -04001506 [picture_view setImage: qemu_image];
1507 [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown];
1508 [superView addSubview: picture_view];
1509
1510 /* Make the name label */
Akihiko Odakia0f973f2021-03-09 21:22:26 +09001511 NSBundle *bundle = [NSBundle mainBundle];
1512 if (bundle) {
1513 x = 0;
1514 y = y - 25;
1515 int name_width = about_width, name_height = 20;
1516 NSRect name_rect = NSMakeRect(x, y, name_width, name_height);
1517 NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect];
1518 [name_label setEditable: NO];
1519 [name_label setBezeled: NO];
1520 [name_label setDrawsBackground: NO];
1521 [name_label setAlignment: NSTextAlignmentCenter];
1522 NSString *qemu_name = [[bundle executablePath] lastPathComponent];
1523 [name_label setStringValue: qemu_name];
1524 [superView addSubview: name_label];
1525 }
Programmingkid9e8204b2016-08-15 22:11:06 -04001526
1527 /* Set the version label's attributes */
1528 x = 0;
1529 y = 50;
1530 int version_width = about_width, version_height = 20;
1531 NSRect version_rect = NSMakeRect(x, y, version_width, version_height);
1532 NSTextField *version_label = [[NSTextField alloc] initWithFrame:
1533 version_rect];
1534 [version_label setEditable: NO];
1535 [version_label setBezeled: NO];
Brendan Shanks4ba967a2017-04-24 23:29:52 -07001536 [version_label setAlignment: NSTextAlignmentCenter];
Programmingkid9e8204b2016-08-15 22:11:06 -04001537 [version_label setDrawsBackground: NO];
1538
1539 /* Create the version string*/
1540 NSString *version_string;
1541 version_string = [[NSString alloc] initWithFormat:
Thomas Huth7e563bf2018-02-15 12:06:47 +01001542 @"QEMU emulator version %s", QEMU_FULL_VERSION];
Programmingkid9e8204b2016-08-15 22:11:06 -04001543 [version_label setStringValue: version_string];
1544 [superView addSubview: version_label];
1545
1546 /* Make copyright label */
1547 x = 0;
1548 y = 35;
1549 int copyright_width = about_width, copyright_height = 20;
1550 NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height);
1551 NSTextField *copyright_label = [[NSTextField alloc] initWithFrame:
1552 copyright_rect];
1553 [copyright_label setEditable: NO];
1554 [copyright_label setBezeled: NO];
1555 [copyright_label setDrawsBackground: NO];
Brendan Shanks4ba967a2017-04-24 23:29:52 -07001556 [copyright_label setAlignment: NSTextAlignmentCenter];
Programmingkid9e8204b2016-08-15 22:11:06 -04001557 [copyright_label setStringValue: [NSString stringWithFormat: @"%s",
1558 QEMU_COPYRIGHT]];
1559 [superView addSubview: copyright_label];
1560}
1561
John Arbucklee47ec1a2017-06-13 23:17:38 -04001562/* Used by the Speed menu items */
1563- (void)adjustSpeed:(id)sender
1564{
1565 int throttle_pct; /* throttle percentage */
1566 NSMenu *menu;
1567
1568 menu = [sender menu];
1569 if (menu != nil)
1570 {
1571 /* Unselect the currently selected item */
1572 for (NSMenuItem *item in [menu itemArray]) {
Brendan Shanks5e246002019-01-31 23:12:25 -08001573 if (item.state == NSControlStateValueOn) {
1574 [item setState: NSControlStateValueOff];
John Arbucklee47ec1a2017-06-13 23:17:38 -04001575 break;
1576 }
1577 }
1578 }
1579
1580 // check the menu item
Brendan Shanks5e246002019-01-31 23:12:25 -08001581 [sender setState: NSControlStateValueOn];
John Arbucklee47ec1a2017-06-13 23:17:38 -04001582
1583 // get the throttle percentage
1584 throttle_pct = [sender tag];
1585
Peter Maydell31819e92019-02-25 10:24:27 +00001586 with_iothread_lock(^{
1587 cpu_throttle_set(throttle_pct);
1588 });
John Arbucklee47ec1a2017-06-13 23:17:38 -04001589 COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
1590}
1591
Programmingkidb4c6a112015-05-19 09:11:18 +01001592@end
bellard5b0753e2005-03-01 21:37:28 +00001593
Peter Maydell61a2ed42019-02-25 10:24:32 +00001594@interface QemuApplication : NSApplication
1595@end
1596
1597@implementation QemuApplication
1598- (void)sendEvent:(NSEvent *)event
1599{
1600 COCOA_DEBUG("QemuApplication: sendEvent\n");
Peter Maydell55888402019-02-25 10:24:33 +00001601 if (![cocoaView handleEvent:event]) {
1602 [super sendEvent: event];
1603 }
Peter Maydell61a2ed42019-02-25 10:24:32 +00001604}
1605@end
1606
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001607static void create_initial_menus(void)
1608{
thsc304f7e2008-01-22 23:25:15 +00001609 // Add menus
1610 NSMenu *menu;
1611 NSMenuItem *menuItem;
1612
bellard5b0753e2005-03-01 21:37:28 +00001613 [NSApp setMainMenu:[[NSMenu alloc] init]];
bellard5b0753e2005-03-01 21:37:28 +00001614
thsc304f7e2008-01-22 23:25:15 +00001615 // Application menu
1616 menu = [[NSMenu alloc] initWithTitle:@""];
Programmingkid9e8204b2016-08-15 22:11:06 -04001617 [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
thsc304f7e2008-01-22 23:25:15 +00001618 [menu addItem:[NSMenuItem separatorItem]]; //Separator
1619 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
1620 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
Brendan Shanks4ba967a2017-04-24 23:29:52 -07001621 [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
thsc304f7e2008-01-22 23:25:15 +00001622 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
1623 [menu addItem:[NSMenuItem separatorItem]]; //Separator
1624 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
1625 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
1626 [menuItem setSubmenu:menu];
1627 [[NSApp mainMenu] addItem:menuItem];
1628 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
ths3b46e622007-09-17 08:09:54 +00001629
John Arbuckle8524f1c2015-06-19 10:53:27 +01001630 // Machine menu
1631 menu = [[NSMenu alloc] initWithTitle: @"Machine"];
1632 [menu setAutoenablesItems: NO];
1633 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
1634 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
1635 [menu addItem: menuItem];
1636 [menuItem setEnabled: NO];
John Arbuckle27074612015-06-19 10:53:27 +01001637 [menu addItem: [NSMenuItem separatorItem]];
1638 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
1639 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
John Arbuckle8524f1c2015-06-19 10:53:27 +01001640 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
1641 [menuItem setSubmenu:menu];
1642 [[NSApp mainMenu] addItem:menuItem];
1643
thsc304f7e2008-01-22 23:25:15 +00001644 // View menu
1645 menu = [[NSMenu alloc] initWithTitle:@"View"];
Programmingkid5d1b2ee2015-05-19 09:11:17 +01001646 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
1647 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
thsc304f7e2008-01-22 23:25:15 +00001648 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
1649 [menuItem setSubmenu:menu];
1650 [[NSApp mainMenu] addItem:menuItem];
1651
John Arbucklee47ec1a2017-06-13 23:17:38 -04001652 // Speed menu
1653 menu = [[NSMenu alloc] initWithTitle:@"Speed"];
1654
1655 // Add the rest of the Speed menu items
1656 int p, percentage, throttle_pct;
1657 for (p = 10; p >= 0; p--)
1658 {
1659 percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
1660
1661 menuItem = [[[NSMenuItem alloc]
1662 initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
1663
1664 if (percentage == 100) {
Brendan Shanks5e246002019-01-31 23:12:25 -08001665 [menuItem setState: NSControlStateValueOn];
John Arbucklee47ec1a2017-06-13 23:17:38 -04001666 }
1667
1668 /* Calculate the throttle percentage */
1669 throttle_pct = -1 * percentage + 100;
1670
1671 [menuItem setTag: throttle_pct];
1672 [menu addItem: menuItem];
1673 }
1674 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
1675 [menuItem setSubmenu:menu];
1676 [[NSApp mainMenu] addItem:menuItem];
1677
thsc304f7e2008-01-22 23:25:15 +00001678 // Window menu
1679 menu = [[NSMenu alloc] initWithTitle:@"Window"];
1680 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
1681 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1682 [menuItem setSubmenu:menu];
1683 [[NSApp mainMenu] addItem:menuItem];
1684 [NSApp setWindowsMenu:menu];
1685
1686 // Help menu
1687 menu = [[NSMenu alloc] initWithTitle:@"Help"];
1688 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
thsc304f7e2008-01-22 23:25:15 +00001689 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1690 [menuItem setSubmenu:menu];
1691 [[NSApp mainMenu] addItem:menuItem];
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001692}
1693
Peter Maydell8b00e4e2019-02-25 10:24:30 +00001694/* Returns a name for a given console */
1695static NSString * getConsoleName(QemuConsole * console)
1696{
Akihiko Odakica511602022-02-15 09:03:05 +01001697 g_autofree char *label = qemu_console_get_label(console);
1698
1699 return [NSString stringWithUTF8String:label];
Peter Maydell8b00e4e2019-02-25 10:24:30 +00001700}
1701
1702/* Add an entry to the View menu for each console */
1703static void add_console_menu_entries(void)
1704{
1705 NSMenu *menu;
1706 NSMenuItem *menuItem;
1707 int index = 0;
1708
1709 menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
1710
1711 [menu addItem:[NSMenuItem separatorItem]];
1712
1713 while (qemu_console_lookup_by_index(index) != NULL) {
1714 menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
1715 action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
1716 [menuItem setTag: index];
1717 [menu addItem: menuItem];
1718 index++;
1719 }
1720}
1721
1722/* Make menu items for all removable devices.
1723 * Each device is given an 'Eject' and 'Change' menu item.
1724 */
1725static void addRemovableDevicesMenuItems(void)
1726{
1727 NSMenu *menu;
1728 NSMenuItem *menuItem;
1729 BlockInfoList *currentDevice, *pointerToFree;
1730 NSString *deviceName;
1731
1732 currentDevice = qmp_query_block(NULL);
1733 pointerToFree = currentDevice;
Peter Maydell8b00e4e2019-02-25 10:24:30 +00001734
1735 menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
1736
1737 // Add a separator between related groups of menu items
1738 [menu addItem:[NSMenuItem separatorItem]];
1739
1740 // Set the attributes to the "Removable Media" menu item
1741 NSString *titleString = @"Removable Media";
1742 NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
1743 NSColor *newColor = [NSColor blackColor];
1744 NSFontManager *fontManager = [NSFontManager sharedFontManager];
1745 NSFont *font = [fontManager fontWithFamily:@"Helvetica"
1746 traits:NSBoldFontMask|NSItalicFontMask
1747 weight:0
1748 size:14];
1749 [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
1750 [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
1751 [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
1752
1753 // Add the "Removable Media" menu item
1754 menuItem = [NSMenuItem new];
1755 [menuItem setAttributedTitle: attString];
1756 [menuItem setEnabled: NO];
1757 [menu addItem: menuItem];
1758
1759 /* Loop through all the block devices in the emulator */
1760 while (currentDevice) {
1761 deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
1762
1763 if(currentDevice->value->removable) {
1764 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
1765 action: @selector(changeDeviceMedia:)
1766 keyEquivalent: @""];
1767 [menu addItem: menuItem];
1768 [menuItem setRepresentedObject: deviceName];
1769 [menuItem autorelease];
1770
1771 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
1772 action: @selector(ejectDeviceMedia:)
1773 keyEquivalent: @""];
1774 [menu addItem: menuItem];
1775 [menuItem setRepresentedObject: deviceName];
1776 [menuItem autorelease];
1777 }
1778 currentDevice = currentDevice->next;
1779 }
1780 qapi_free_BlockInfoList(pointerToFree);
1781}
1782
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001783@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
1784@end
1785
1786@implementation QemuCocoaPasteboardTypeOwner
1787
1788- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
1789{
1790 if (type != NSPasteboardTypeString) {
1791 return;
1792 }
1793
1794 with_iothread_lock(^{
1795 QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
1796 qemu_event_reset(&cbevent);
1797 qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
1798
1799 while (info == cbinfo &&
1800 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
1801 info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
1802 qemu_mutex_unlock_iothread();
1803 qemu_event_wait(&cbevent);
1804 qemu_mutex_lock_iothread();
1805 }
1806
1807 if (info == cbinfo) {
1808 NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
1809 length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
1810 [sender setData:data forType:NSPasteboardTypeString];
1811 [data release];
1812 }
1813
1814 qemu_clipboard_info_unref(info);
1815 });
1816}
1817
1818@end
1819
1820static QemuCocoaPasteboardTypeOwner *cbowner;
1821
1822static void cocoa_clipboard_notify(Notifier *notifier, void *data);
1823static void cocoa_clipboard_request(QemuClipboardInfo *info,
1824 QemuClipboardType type);
1825
1826static QemuClipboardPeer cbpeer = {
1827 .name = "cocoa",
Marc-André Lureau1b17f1e2021-07-19 19:42:15 +04001828 .notifier = { .notify = cocoa_clipboard_notify },
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001829 .request = cocoa_clipboard_request
1830};
1831
Marc-André Lureau1b17f1e2021-07-19 19:42:15 +04001832static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001833{
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001834 if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
1835 return;
1836 }
1837
1838 if (info != cbinfo) {
1839 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1840 qemu_clipboard_info_unref(cbinfo);
1841 cbinfo = qemu_clipboard_info_ref(info);
1842 cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
1843 [pool release];
1844 }
1845
1846 qemu_event_set(&cbevent);
1847}
1848
Marc-André Lureau1b17f1e2021-07-19 19:42:15 +04001849static void cocoa_clipboard_notify(Notifier *notifier, void *data)
1850{
1851 QemuClipboardNotify *notify = data;
1852
1853 switch (notify->type) {
1854 case QEMU_CLIPBOARD_UPDATE_INFO:
1855 cocoa_clipboard_update_info(notify->info);
1856 return;
Marc-André Lureau505dbf92021-07-19 19:49:56 +04001857 case QEMU_CLIPBOARD_RESET_SERIAL:
1858 /* ignore */
1859 return;
Marc-André Lureau1b17f1e2021-07-19 19:42:15 +04001860 }
1861}
1862
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001863static void cocoa_clipboard_request(QemuClipboardInfo *info,
1864 QemuClipboardType type)
1865{
1866 NSData *text;
1867
1868 switch (type) {
1869 case QEMU_CLIPBOARD_TYPE_TEXT:
1870 text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
1871 if (text) {
1872 qemu_clipboard_set_data(&cbpeer, info, type,
1873 [text length], [text bytes], true);
1874 [text release];
1875 }
1876 break;
1877 default:
1878 break;
1879 }
1880}
1881
Peter Maydell55888402019-02-25 10:24:33 +00001882/*
1883 * The startup process for the OSX/Cocoa UI is complicated, because
1884 * OSX insists that the UI runs on the initial main thread, and so we
1885 * need to start a second thread which runs the vl.c qemu_main():
1886 *
1887 * Initial thread: 2nd thread:
1888 * in main():
1889 * create qemu-main thread
1890 * wait on display_init semaphore
1891 * call qemu_main()
1892 * ...
1893 * in cocoa_display_init():
1894 * post the display_init semaphore
1895 * wait on app_started semaphore
1896 * create application, menus, etc
1897 * enter OSX run loop
1898 * in applicationDidFinishLaunching:
1899 * post app_started semaphore
1900 * tell main thread to fullscreen if needed
1901 * [...]
1902 * run qemu main-loop
1903 *
1904 * We do this in two stages so that we don't do the creation of the
1905 * GUI application menus and so on for command line options like --help
1906 * where we want to just print text to stdout and exit immediately.
1907 */
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001908
Peter Maydell55888402019-02-25 10:24:33 +00001909static void *call_qemu_main(void *opaque)
1910{
1911 int status;
1912
1913 COCOA_DEBUG("Second thread: calling qemu_main()\n");
1914 status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
1915 COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09001916 [cbowner release];
Peter Maydell55888402019-02-25 10:24:33 +00001917 exit(status);
1918}
1919
Akihiko Odaki40a9aad2021-07-09 01:56:19 +09001920int main (int argc, char **argv) {
Peter Maydell55888402019-02-25 10:24:33 +00001921 QemuThread thread;
1922
1923 COCOA_DEBUG("Entered main()\n");
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001924 gArgc = argc;
Akihiko Odaki40a9aad2021-07-09 01:56:19 +09001925 gArgv = argv;
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001926
Peter Maydell55888402019-02-25 10:24:33 +00001927 qemu_sem_init(&display_init_sem, 0);
1928 qemu_sem_init(&app_started_sem, 0);
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001929
Peter Maydell55888402019-02-25 10:24:33 +00001930 qemu_thread_create(&thread, "qemu_main", call_qemu_main,
1931 NULL, QEMU_THREAD_DETACHED);
1932
1933 COCOA_DEBUG("Main thread: waiting for display_init_sem\n");
1934 qemu_sem_wait(&display_init_sem);
1935 COCOA_DEBUG("Main thread: initializing app\n");
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001936
1937 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1938
1939 // Pull this console process up to being a fully-fledged graphical
1940 // app with a menubar and Dock icon
1941 ProcessSerialNumber psn = { 0, kCurrentProcess };
1942 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1943
Peter Maydell61a2ed42019-02-25 10:24:32 +00001944 [QemuApplication sharedApplication];
Peter Maydellc6fd6c72019-02-25 10:24:29 +00001945
1946 create_initial_menus();
thsc304f7e2008-01-22 23:25:15 +00001947
Peter Maydell55888402019-02-25 10:24:33 +00001948 /*
1949 * Create the menu entries which depend on QEMU state (for consoles
1950 * and removeable devices). These make calls back into QEMU functions,
1951 * which is OK because at this point we know that the second thread
1952 * holds the iothread lock and is synchronously waiting for us to
1953 * finish.
1954 */
1955 add_console_menu_entries();
1956 addRemovableDevicesMenuItems();
1957
thsc304f7e2008-01-22 23:25:15 +00001958 // Create an Application controller
1959 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
1960 [NSApp setDelegate:appController];
1961
1962 // Start the main event loop
Peter Maydell55888402019-02-25 10:24:33 +00001963 COCOA_DEBUG("Main thread: entering OSX run loop\n");
bellard5b0753e2005-03-01 21:37:28 +00001964 [NSApp run];
Peter Maydell55888402019-02-25 10:24:33 +00001965 COCOA_DEBUG("Main thread: left OSX run loop, exiting\n");
ths3b46e622007-09-17 08:09:54 +00001966
thsc304f7e2008-01-22 23:25:15 +00001967 [appController release];
bellard5b0753e2005-03-01 21:37:28 +00001968 [pool release];
bellardcae41b12006-05-22 21:25:04 +00001969
bellard5b0753e2005-03-01 21:37:28 +00001970 return 0;
1971}
thsc304f7e2008-01-22 23:25:15 +00001972
1973
1974
1975#pragma mark qemu
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +01001976static void cocoa_update(DisplayChangeListener *dcl,
Gerd Hoffmann7c20b4a2012-11-13 14:51:41 +01001977 int x, int y, int w, int h)
thsc304f7e2008-01-22 23:25:15 +00001978{
Peter Maydell6e657e62013-04-22 10:29:46 +00001979 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1980
thsc304f7e2008-01-22 23:25:15 +00001981 COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
1982
Peter Maydell55888402019-02-25 10:24:33 +00001983 dispatch_async(dispatch_get_main_queue(), ^{
1984 NSRect rect;
1985 if ([cocoaView cdx] == 1.0) {
1986 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
1987 } else {
1988 rect = NSMakeRect(
1989 x * [cocoaView cdx],
1990 ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
1991 w * [cocoaView cdx],
1992 h * [cocoaView cdy]);
1993 }
1994 [cocoaView setNeedsDisplayInRect:rect];
1995 });
Peter Maydell6e657e62013-04-22 10:29:46 +00001996
1997 [pool release];
thsc304f7e2008-01-22 23:25:15 +00001998}
1999
Gerd Hoffmannc12aeb82013-02-28 15:03:04 +01002000static void cocoa_switch(DisplayChangeListener *dcl,
Gerd Hoffmannc12aeb82013-02-28 15:03:04 +01002001 DisplaySurface *surface)
thsc304f7e2008-01-22 23:25:15 +00002002{
Peter Maydell6e657e62013-04-22 10:29:46 +00002003 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Peter Maydell55888402019-02-25 10:24:33 +00002004 pixman_image_t *image = surface->image;
thsc304f7e2008-01-22 23:25:15 +00002005
Peter Maydell6e657e62013-04-22 10:29:46 +00002006 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
Peter Maydell55888402019-02-25 10:24:33 +00002007
2008 // The DisplaySurface will be freed as soon as this callback returns.
2009 // We take a reference to the underlying pixman image here so it does
2010 // not disappear from under our feet; the switchSurface method will
2011 // deref the old image when it is done with it.
2012 pixman_image_ref(image);
2013
2014 dispatch_async(dispatch_get_main_queue(), ^{
Peter Maydell8d65dee2022-02-24 10:13:29 +00002015 [cocoaView updateUIInfo];
Peter Maydell55888402019-02-25 10:24:33 +00002016 [cocoaView switchSurface:image];
2017 });
Peter Maydell6e657e62013-04-22 10:29:46 +00002018 [pool release];
thsc304f7e2008-01-22 23:25:15 +00002019}
2020
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +01002021static void cocoa_refresh(DisplayChangeListener *dcl)
thsc304f7e2008-01-22 23:25:15 +00002022{
Peter Maydell6e657e62013-04-22 10:29:46 +00002023 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
2024
thsc304f7e2008-01-22 23:25:15 +00002025 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
John Arbuckle468a8952015-10-13 21:51:18 +01002026 graphic_hw_update(NULL);
thsc304f7e2008-01-22 23:25:15 +00002027
Gerd Hoffmann21bae112013-12-04 14:08:04 +01002028 if (qemu_input_is_absolute()) {
Peter Maydell55888402019-02-25 10:24:33 +00002029 dispatch_async(dispatch_get_main_queue(), ^{
2030 if (![cocoaView isAbsoluteEnabled]) {
2031 if ([cocoaView isMouseGrabbed]) {
2032 [cocoaView ungrabMouse];
2033 }
thsc304f7e2008-01-22 23:25:15 +00002034 }
Peter Maydell55888402019-02-25 10:24:33 +00002035 [cocoaView setAbsoluteEnabled:YES];
2036 });
thsc304f7e2008-01-22 23:25:15 +00002037 }
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09002038
2039 if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
2040 qemu_clipboard_info_unref(cbinfo);
2041 cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
2042 if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
2043 cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
2044 }
2045 qemu_clipboard_update(cbinfo);
2046 cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
2047 qemu_event_set(&cbevent);
2048 }
2049
Peter Maydell6e657e62013-04-22 10:29:46 +00002050 [pool release];
thsc304f7e2008-01-22 23:25:15 +00002051}
2052
Gerd Hoffmann5013b9e2018-03-01 11:05:37 +01002053static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
thsc304f7e2008-01-22 23:25:15 +00002054{
2055 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
2056
Peter Maydell55888402019-02-25 10:24:33 +00002057 /* Tell main thread to go ahead and create the app and enter the run loop */
2058 qemu_sem_post(&display_init_sem);
2059 qemu_sem_wait(&app_started_sem);
2060 COCOA_DEBUG("cocoa_display_init: app start completed\n");
2061
Programmingkid43227af2015-05-19 09:11:17 +01002062 /* if fullscreen mode is to be used */
Gerd Hoffmann767f9bf2018-02-02 12:10:19 +01002063 if (opts->has_full_screen && opts->full_screen) {
Peter Maydell55888402019-02-25 10:24:33 +00002064 dispatch_async(dispatch_get_main_queue(), ^{
2065 [NSApp activateIgnoringOtherApps: YES];
2066 [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil];
2067 });
Programmingkid43227af2015-05-19 09:11:17 +01002068 }
Gerd Hoffmann3487da62020-02-06 12:27:50 +01002069 if (opts->has_show_cursor && opts->show_cursor) {
2070 cursor_hide = 0;
2071 }
Programmingkid43227af2015-05-19 09:11:17 +01002072
aliguori9794f742009-03-04 19:25:22 +00002073 // register vga output callbacks
Akihiko Odakicc7859c2021-02-19 17:44:19 +09002074 register_displaychangelistener(&dcl);
Akihiko Odaki7e3e20d2021-06-16 23:19:54 +09002075
2076 qemu_event_init(&cbevent, false);
2077 cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
2078 qemu_clipboard_peer_register(&cbpeer);
thsc304f7e2008-01-22 23:25:15 +00002079}
Gerd Hoffmann5013b9e2018-03-01 11:05:37 +01002080
2081static QemuDisplay qemu_display_cocoa = {
2082 .type = DISPLAY_TYPE_COCOA,
2083 .init = cocoa_display_init,
2084};
2085
2086static void register_cocoa(void)
2087{
2088 qemu_display_register(&qemu_display_cocoa);
2089}
2090
2091type_init(register_cocoa);