38b4760f9b8730e84762f147a75a4cd1b2a249d8
[qemu] / cocoa.m
1 /*
2  * QEMU Cocoa display driver
3  * 
4  * Copyright (c) 2005 Pierre d'Herbemont
5  *                    many code/inspiration from SDL 1.2 code (LGPL)
6  * 
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  */
25 /*
26     Todo :    x  miniaturize window 
27               x  center the window
28               -  save window position
29               -  handle keyboard event
30               -  handle mouse event
31               -  non 32 bpp support
32               -  full screen
33               -  mouse focus
34               x  simple graphical prompt to demo
35               -  better graphical prompt
36 */
37
38 #import <Cocoa/Cocoa.h>
39
40 #include "vl.h"
41
42 NSWindow *window = NULL;
43 NSQuickDrawView *qd_view = NULL;
44
45
46 int gArgc;
47 char **gArgv;
48 DisplayState current_ds;
49
50 /* main defined in qemu/vl.c */
51 int qemu_main(int argc, char **argv);
52
53 /* To deal with miniaturization */
54 @interface QemuWindow : NSWindow
55 { }
56 @end
57
58
59 /*
60  ------------------------------------------------------
61     Qemu Video Driver
62  ------------------------------------------------------
63 */
64
65 /*
66  ------------------------------------------------------
67     cocoa_update
68  ------------------------------------------------------
69 */
70 static void cocoa_update(DisplayState *ds, int x, int y, int w, int h)
71 {
72     //printf("updating x=%d y=%d w=%d h=%d\n", x, y, w, h);
73
74     /* Use QDFlushPortBuffer() to flush content to display */
75     RgnHandle dirty = NewRgn ();
76     RgnHandle temp  = NewRgn ();
77
78     SetEmptyRgn (dirty);
79
80     /* Build the region of dirty rectangles */
81     MacSetRectRgn (temp, x, y,
82                         x + w, y + h);
83     MacUnionRgn (dirty, temp, dirty);
84                 
85     /* Flush the dirty region */
86     QDFlushPortBuffer ( [ qd_view  qdPort ], dirty );
87     DisposeRgn (dirty);
88     DisposeRgn (temp);
89 }
90
91 /*
92  ------------------------------------------------------
93     cocoa_resize
94  ------------------------------------------------------
95 */
96 static void cocoa_resize(DisplayState *ds, int w, int h)
97 {
98     const int device_bpp = 32;
99     static void *screen_pixels;
100     static int  screen_pitch;
101     NSRect contentRect;
102     
103     //printf("resizing to %d %d\n", w, h);
104     
105     contentRect = NSMakeRect (0, 0, w, h);
106     if(window)
107     {
108         [window close];
109         [window release];
110     }
111     window = [ [ QemuWindow alloc ] initWithContentRect:contentRect
112                                   styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
113                                   backing:NSBackingStoreBuffered defer:NO];
114     if(!window)
115     {
116         fprintf(stderr, "(cocoa) can't create window\n");
117         exit(1);
118     }
119     
120     if(qd_view)
121         [qd_view release];
122     
123     qd_view = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ];
124     
125     if(!qd_view)
126     {
127          fprintf(stderr, "(cocoa) can't create qd_view\n");
128         exit(1);
129     }
130     
131     [ window setAcceptsMouseMovedEvents:YES ];
132     [ window setTitle:@"Qemu" ];
133     [ window setReleasedWhenClosed:NO ];
134     
135     /* Set screen to black */
136     [ window setBackgroundColor: [NSColor blackColor] ];
137     
138     /* set window position */
139     [ window center ];
140     
141     [ qd_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
142     [ [ window contentView ] addSubview:qd_view ];
143     [ qd_view release ];
144     [ window makeKeyAndOrderFront:nil ];
145     
146     /* Careful here, the window seems to have to be onscreen to do that */
147     LockPortBits ( [ qd_view qdPort ] );
148     screen_pixels = GetPixBaseAddr ( GetPortPixMap ( [ qd_view qdPort ] ) );
149     screen_pitch  = GetPixRowBytes ( GetPortPixMap ( [ qd_view qdPort ] ) );
150     UnlockPortBits ( [ qd_view qdPort ] );
151     { 
152             int vOffset = [ window frame ].size.height - 
153                 [ qd_view frame ].size.height - [ qd_view frame ].origin.y;
154             
155             int hOffset = [ qd_view frame ].origin.x;
156                     
157             screen_pixels += (vOffset * screen_pitch) + hOffset * (device_bpp/8);
158     }
159     ds->data = screen_pixels;
160     ds->linesize = screen_pitch;
161     ds->depth = device_bpp;
162     ds->width = w;
163     ds->height = h;
164     
165     current_ds = *ds;
166 }
167
168 /*
169  ------------------------------------------------------
170     keymap conversion
171  ------------------------------------------------------
172 */
173
174 static int keymap[] =
175 {
176     30, //'a' 0x0
177     31,  //'s'
178     32,  //'d'
179     33,  //'f'
180     35,  //'h'
181     34,  //'g'
182     44,  //'z'
183     45,  //'x'
184     46,  //'c'
185     47,  //'v'
186     0,   // 0  0x0a
187     48,  //'b'
188     16,  //'q'
189     17,  //'w'
190     18,  //'e'
191     19,  //'r' 
192     21,  //'y' 0x10
193     20,  //'t'
194     2,  //'1'
195     3,  //'2'
196     4,  //'3'
197     5,  //'4'
198     7,  //'6'
199     6,  //'5'
200     0,  //'='
201     10,  //'9'
202     8,  //'7' 0x1A
203     0,  //'-' 
204     9,  //'8' 
205     11,  //'0' 
206     27,  //']' 
207     24,  //'o' 
208     22,  //'u' 0x20
209     26,  //'['
210     23,  //'i'
211     25,  //'p'
212     28,  //'\n'
213     38,  //'l'
214     36,  //'j'
215     40,  //'"'
216     37,  //'k'
217     39,  //';'
218     15,  //'\t' 0x30
219     0,  //' '
220     0,  //'`'
221     14,  //'<backspace>'
222     0,  //'' 0x34
223     0,  //'<esc>'
224     0,  //'<esc>'
225     /* Not completed to finish see http://www.libsdl.org/cgi/cvsweb.cgi/SDL12/src/video/quartz/SDL_QuartzKeys.h?rev=1.6&content-type=text/x-cvsweb-markup */
226 };
227
228 static int cocoa_keycode_to_qemu(int keycode)
229 {
230     if(sizeof(keymap) <= keycode)
231     {
232         printf("(cocoa) warning unknow keycode 0x%x\n", keycode);
233         return 0;
234     }
235     return keymap[keycode];
236 }
237
238 /*
239  ------------------------------------------------------
240     cocoa_refresh
241  ------------------------------------------------------
242 */
243 static void cocoa_refresh(DisplayState *ds)
244 {
245     //printf("cocoa_refresh \n");
246     NSDate *distantPast;
247     NSEvent *event;
248     NSAutoreleasePool *pool;
249     int grab = 1;
250     
251     pool = [ [ NSAutoreleasePool alloc ] init ];
252     distantPast = [ NSDate distantPast ];
253     
254     if (is_active_console(vga_console)) 
255         vga_update_display();
256     do {
257         event = [ NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
258                         inMode: NSDefaultRunLoopMode dequeue:YES ];
259         if (event != nil) {
260             switch ([event type]) {
261                 case NSKeyDown:
262                     if(grab)
263                     {
264                         int keycode = cocoa_keycode_to_qemu([event keyCode]);
265                         
266                         if (keycode & 0x80)
267                             kbd_put_keycode(0xe0);
268                         kbd_put_keycode(keycode & 0x7f);
269                     }
270                     break;
271                 case NSKeyUp:
272                     if(grab)
273                     {
274                         int keycode = cocoa_keycode_to_qemu([event keyCode]);
275
276                         if (keycode & 0x80)
277                             kbd_put_keycode(0xe0);
278                         kbd_put_keycode(keycode | 0x80);
279                     }
280                     break;
281                 case NSScrollWheel:
282                 
283                 case NSLeftMouseDown:
284                 case NSLeftMouseUp:
285                 
286                 case NSOtherMouseDown:
287                 case NSRightMouseDown:
288                 
289                 case NSOtherMouseUp:
290                 case NSRightMouseUp:
291                 
292                 case NSMouseMoved:
293                 case NSOtherMouseDragged:
294                 case NSRightMouseDragged:
295                 case NSLeftMouseDragged:
296                 
297                 default: [NSApp sendEvent:event];
298             }
299         }
300     } while(event != nil);
301 }
302
303 /*
304  ------------------------------------------------------
305     cocoa_cleanup
306  ------------------------------------------------------
307 */
308
309 static void cocoa_cleanup(void) 
310 {
311
312 }
313
314 /*
315  ------------------------------------------------------
316     cocoa_display_init
317  ------------------------------------------------------
318 */
319
320 void cocoa_display_init(DisplayState *ds, int full_screen)
321 {
322     ds->dpy_update = cocoa_update;
323     ds->dpy_resize = cocoa_resize;
324     ds->dpy_refresh = cocoa_refresh;
325     
326     cocoa_resize(ds, 640, 400);
327     
328     atexit(cocoa_cleanup);
329 }
330
331 /*
332  ------------------------------------------------------
333     Interface with Cocoa
334  ------------------------------------------------------
335 */
336
337
338 /*
339  ------------------------------------------------------
340     QemuWindow
341     Some trick from SDL to use miniwindow
342  ------------------------------------------------------
343 */
344 static void QZ_SetPortAlphaOpaque ()
345 {    
346     /* Assume 32 bit if( bpp == 32 )*/
347     if ( 1 ) {
348     
349         uint32_t    *pixels = (uint32_t*) current_ds.data;
350         uint32_t    rowPixels = current_ds.linesize / 4;
351         uint32_t    i, j;
352         
353         for (i = 0; i < current_ds.height; i++)
354             for (j = 0; j < current_ds.width; j++) {
355         
356                 pixels[ (i * rowPixels) + j ] |= 0xFF000000;
357             }
358     }
359 }
360
361 @implementation QemuWindow
362 - (void)miniaturize:(id)sender
363 {
364         
365     /* make the alpha channel opaque so anim won't have holes in it */
366     QZ_SetPortAlphaOpaque ();
367     
368     [ super miniaturize:sender ];
369     
370 }
371 - (void)display
372 {    
373     /* 
374         This method fires just before the window deminaturizes from the Dock.
375         
376         We'll save the current visible surface, let the window manager redraw any
377         UI elements, and restore the SDL surface. This way, no expose event 
378         is required, and the deminiaturize works perfectly.
379     */
380     
381     /* make sure pixels are fully opaque */
382     QZ_SetPortAlphaOpaque ();
383     
384     /* save current visible SDL surface */
385     [ self cacheImageInRect:[ qd_view frame ] ];
386     
387     /* let the window manager redraw controls, border, etc */
388     [ super display ];
389     
390     /* restore visible SDL surface */
391     [ self restoreCachedImage ];
392 }
393
394 @end
395
396
397 /*
398  ------------------------------------------------------
399     QemuCocoaGUIController
400     NSApp's delegate - indeed main object
401  ------------------------------------------------------
402 */
403
404 @interface QemuCocoaGUIController : NSObject
405 {
406 }
407 - (void)applicationDidFinishLaunching: (NSNotification *) note;
408 - (void)applicationWillTerminate:(NSNotification *)aNotification;
409
410 - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
411
412 - (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
413 @end
414
415 @implementation QemuCocoaGUIController
416 /* Called when the internal event loop has just started running */
417 - (void)applicationDidFinishLaunching: (NSNotification *) note
418 {
419     
420     /* Do whatever we want here : set up a pc list... */
421     {
422         NSOpenPanel *op = [[NSOpenPanel alloc] init];
423         
424         cocoa_resize(&current_ds, 640, 400);
425         
426         [op setPrompt:@"Boot image"];
427         
428         [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
429         
430         [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",nil]
431               modalForWindow:window modalDelegate:self
432               didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
433     }
434     
435     /* or Launch Qemu, with the global args */
436     //[self startEmulationWithArgc:gArgc argv:gArgv];
437 }
438
439 - (void)applicationWillTerminate:(NSNotification *)aNotification
440 {
441     printf("Application will terminate\n");
442     qemu_system_shutdown_request();
443     /* In order to avoid a crash */
444     exit(0);
445 }
446
447 - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
448 {
449     if(returnCode == NSCancelButton)
450     {
451         exit(0);
452     }
453     
454     if(returnCode == NSOKButton)
455     {
456         char *bin = "qemu";
457         char *img = (char*)[ [ sheet filename ] cString];
458         
459         char **argv = (char**)malloc( sizeof(char*)*3 );
460         
461         asprintf(&argv[0], "%s", bin);
462         asprintf(&argv[1], "-hda");
463         asprintf(&argv[2], "%s", img);
464         
465         printf("Using argc %d argv %s -hda %s\n", 3, bin, img);
466         
467         [self startEmulationWithArgc:3 argv:(char**)argv];
468     }
469 }
470
471 - (void)startEmulationWithArgc:(int)argc argv:(char**)argv
472 {
473     int status;
474     /* Launch Qemu */
475     printf("starting qemu...\n");
476     status = qemu_main (argc, argv);
477     exit(status);
478 }
479 @end
480
481 /*
482  ------------------------------------------------------
483     Application Creation
484  ------------------------------------------------------
485 */
486
487 /* Dock Connection */
488 typedef struct CPSProcessSerNum
489 {
490         UInt32                lo;
491         UInt32                hi;
492 } CPSProcessSerNum;
493
494 extern OSErr    CPSGetCurrentProcess( CPSProcessSerNum *psn);
495 extern OSErr    CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
496 extern OSErr    CPSSetFrontProcess( CPSProcessSerNum *psn);
497
498 /* Menu Creation */
499 static void setApplicationMenu(void)
500 {
501     /* warning: this code is very odd */
502     NSMenu *appleMenu;
503     NSMenuItem *menuItem;
504     NSString *title;
505     NSString *appName;
506     
507     appName = @"Qemu";
508     appleMenu = [[NSMenu alloc] initWithTitle:@""];
509     
510     /* Add menu items */
511     title = [@"About " stringByAppendingString:appName];
512     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
513
514     [appleMenu addItem:[NSMenuItem separatorItem]];
515
516     title = [@"Hide " stringByAppendingString:appName];
517     [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
518
519     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
520     [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
521
522     [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
523
524     [appleMenu addItem:[NSMenuItem separatorItem]];
525
526     title = [@"Quit " stringByAppendingString:appName];
527     [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
528
529     
530     /* Put menu into the menubar */
531     menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
532     [menuItem setSubmenu:appleMenu];
533     [[NSApp mainMenu] addItem:menuItem];
534
535     /* Tell the application object that this is now the application menu */
536     [NSApp setAppleMenu:appleMenu];
537
538     /* Finally give up our references to the objects */
539     [appleMenu release];
540     [menuItem release];
541 }
542
543 /* Create a window menu */
544 static void setupWindowMenu(void)
545 {
546     NSMenu      *windowMenu;
547     NSMenuItem  *windowMenuItem;
548     NSMenuItem  *menuItem;
549
550     windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
551     
552     /* "Minimize" item */
553     menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
554     [windowMenu addItem:menuItem];
555     [menuItem release];
556     
557     /* Put menu into the menubar */
558     windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
559     [windowMenuItem setSubmenu:windowMenu];
560     [[NSApp mainMenu] addItem:windowMenuItem];
561     
562     /* Tell the application object that this is now the window menu */
563     [NSApp setWindowsMenu:windowMenu];
564
565     /* Finally give up our references to the objects */
566     [windowMenu release];
567     [windowMenuItem release];
568  
569 }
570
571 static void CustomApplicationMain (argc, argv)
572 {
573     NSAutoreleasePool   *pool = [[NSAutoreleasePool alloc] init];
574     QemuCocoaGUIController *gui_controller;
575     CPSProcessSerNum PSN;
576     
577     [NSApplication sharedApplication];
578     
579     if (!CPSGetCurrentProcess(&PSN))
580         if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
581             if (!CPSSetFrontProcess(&PSN))
582                 [NSApplication sharedApplication];
583                 
584     /* Set up the menubar */
585     [NSApp setMainMenu:[[NSMenu alloc] init]];
586     setApplicationMenu();
587     setupWindowMenu();
588
589     /* Create SDLMain and make it the app delegate */
590     gui_controller = [[QemuCocoaGUIController alloc] init];
591     [NSApp setDelegate:gui_controller];
592     
593     /* Start the main event loop */
594     [NSApp run];
595     
596     [gui_controller release];
597     [pool release];
598 }
599
600 /* Real main of qemu-cocoa */
601 int main(int argc, char **argv)
602 {
603     gArgc = argc;
604     gArgv = argv;
605     
606     CustomApplicationMain (argc, argv);
607     
608     return 0;
609 }