accept more disk image extensions (David Still)
[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     /* Display an open dialog box if no argument were passed or
421        if qemu was launched from the finder ( the Finder passes "-psn" ) */
422
423     if( gArgc <= 1 || strncmp (gArgv[1], "-psn", 4) == 0)
424     {
425         NSOpenPanel *op = [[NSOpenPanel alloc] init];
426         
427         cocoa_resize(&current_ds, 640, 400);
428         
429         [op setPrompt:@"Boot image"];
430         
431         [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
432         
433         [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",@"dmg",@"qcow",@"cow",@"cloop",@"vmdk",nil]
434               modalForWindow:window modalDelegate:self
435               didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
436     }
437     else
438     {
439         /* or Launch Qemu, with the global args */
440         [self startEmulationWithArgc:gArgc argv:gArgv];
441     }
442 }
443
444 - (void)applicationWillTerminate:(NSNotification *)aNotification
445 {
446     printf("Application will terminate\n");
447     qemu_system_shutdown_request();
448     /* In order to avoid a crash */
449     exit(0);
450 }
451
452 - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
453 {
454     if(returnCode == NSCancelButton)
455     {
456         exit(0);
457     }
458     
459     if(returnCode == NSOKButton)
460     {
461         char *bin = "qemu";
462         char *img = (char*)[ [ sheet filename ] cString];
463         
464         char **argv = (char**)malloc( sizeof(char*)*3 );
465         
466         asprintf(&argv[0], "%s", bin);
467         asprintf(&argv[1], "-hda");
468         asprintf(&argv[2], "%s", img);
469         
470         printf("Using argc %d argv %s -hda %s\n", 3, bin, img);
471         
472         [self startEmulationWithArgc:3 argv:(char**)argv];
473     }
474 }
475
476 - (void)startEmulationWithArgc:(int)argc argv:(char**)argv
477 {
478     int status;
479     /* Launch Qemu */
480     printf("starting qemu...\n");
481     status = qemu_main (argc, argv);
482     exit(status);
483 }
484 @end
485
486 /*
487  ------------------------------------------------------
488     Application Creation
489  ------------------------------------------------------
490 */
491
492 /* Dock Connection */
493 typedef struct CPSProcessSerNum
494 {
495         UInt32                lo;
496         UInt32                hi;
497 } CPSProcessSerNum;
498
499 extern OSErr    CPSGetCurrentProcess( CPSProcessSerNum *psn);
500 extern OSErr    CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
501 extern OSErr    CPSSetFrontProcess( CPSProcessSerNum *psn);
502
503 /* Menu Creation */
504 static void setApplicationMenu(void)
505 {
506     /* warning: this code is very odd */
507     NSMenu *appleMenu;
508     NSMenuItem *menuItem;
509     NSString *title;
510     NSString *appName;
511     
512     appName = @"Qemu";
513     appleMenu = [[NSMenu alloc] initWithTitle:@""];
514     
515     /* Add menu items */
516     title = [@"About " stringByAppendingString:appName];
517     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
518
519     [appleMenu addItem:[NSMenuItem separatorItem]];
520
521     title = [@"Hide " stringByAppendingString:appName];
522     [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
523
524     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
525     [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
526
527     [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
528
529     [appleMenu addItem:[NSMenuItem separatorItem]];
530
531     title = [@"Quit " stringByAppendingString:appName];
532     [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
533
534     
535     /* Put menu into the menubar */
536     menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
537     [menuItem setSubmenu:appleMenu];
538     [[NSApp mainMenu] addItem:menuItem];
539
540     /* Tell the application object that this is now the application menu */
541     [NSApp setAppleMenu:appleMenu];
542
543     /* Finally give up our references to the objects */
544     [appleMenu release];
545     [menuItem release];
546 }
547
548 /* Create a window menu */
549 static void setupWindowMenu(void)
550 {
551     NSMenu      *windowMenu;
552     NSMenuItem  *windowMenuItem;
553     NSMenuItem  *menuItem;
554
555     windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
556     
557     /* "Minimize" item */
558     menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
559     [windowMenu addItem:menuItem];
560     [menuItem release];
561     
562     /* Put menu into the menubar */
563     windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
564     [windowMenuItem setSubmenu:windowMenu];
565     [[NSApp mainMenu] addItem:windowMenuItem];
566     
567     /* Tell the application object that this is now the window menu */
568     [NSApp setWindowsMenu:windowMenu];
569
570     /* Finally give up our references to the objects */
571     [windowMenu release];
572     [windowMenuItem release];
573  
574 }
575
576 static void CustomApplicationMain (argc, argv)
577 {
578     NSAutoreleasePool   *pool = [[NSAutoreleasePool alloc] init];
579     QemuCocoaGUIController *gui_controller;
580     CPSProcessSerNum PSN;
581     
582     [NSApplication sharedApplication];
583     
584     if (!CPSGetCurrentProcess(&PSN))
585         if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
586             if (!CPSSetFrontProcess(&PSN))
587                 [NSApplication sharedApplication];
588                 
589     /* Set up the menubar */
590     [NSApp setMainMenu:[[NSMenu alloc] init]];
591     setApplicationMenu();
592     setupWindowMenu();
593
594     /* Create SDLMain and make it the app delegate */
595     gui_controller = [[QemuCocoaGUIController alloc] init];
596     [NSApp setDelegate:gui_controller];
597     
598     /* Start the main event loop */
599     [NSApp run];
600     
601     [gui_controller release];
602     [pool release];
603 }
604
605 /* Real main of qemu-cocoa */
606 int main(int argc, char **argv)
607 {
608     gArgc = argc;
609     gArgv = argv;
610     
611     CustomApplicationMain (argc, argv);
612     
613     return 0;
614 }