initial Cocoa support (Pierre d'Herbemont)


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1313 c046a42c-6fe2-441c-8c8c-71466251a162
diff --git a/Makefile.target b/Makefile.target
index 25f4719..5c82e38 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -346,6 +346,10 @@
 ifdef CONFIG_SDL
 VL_OBJS+=sdl.o
 endif
+ifdef CONFIG_COCOA
+VL_OBJS+=cocoa.o
+COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa
+endif
 ifdef CONFIG_SLIRP
 DEFINES+=-I$(SRC_PATH)/slirp
 SLIRP_OBJS=cksum.o if.o ip_icmp.o ip_input.o ip_output.o \
@@ -373,7 +377,10 @@
 endif
 
 $(QEMU_SYSTEM): $(VL_OBJS) libqemu.a
-	$(CC) $(VL_LDFLAGS) -o $@ $^ $(LIBS) $(SDL_LIBS) $(VL_LIBS)
+	$(CC) $(VL_LDFLAGS) -o $@ $^ $(LIBS) $(SDL_LIBS) $(COCOA_LIBS) $(VL_LIBS)
+
+cocoa.o: cocoa.m
+	$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
 
 sdl.o: sdl.c keymaps.c sdl_keysym.h
 	$(CC) $(CFLAGS) $(DEFINES) $(SDL_CFLAGS) -c -o $@ $<
diff --git a/cocoa.m b/cocoa.m
new file mode 100644
index 0000000..ade7f61
--- /dev/null
+++ b/cocoa.m
@@ -0,0 +1,607 @@
+/*
+ * QEMU Cocoa display driver
+ * 
+ * Copyright (c) 2005 Pierre d'Herbemont
+ *                    many code/inspiration from SDL 1.2 code (LGPL)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/*
+    Todo :    x  miniaturize window 
+              x  center the window
+              -  save window position
+              -  handle keyboard event
+              -  handle mouse event
+              -  non 32 bpp support
+              -  full screen
+              -  mouse focus
+              x  simple graphical prompt to demo
+              -  better graphical prompt
+*/
+#include "vl.h"
+#import <Cocoa/Cocoa.h>
+
+NSWindow *window = NULL;
+NSQuickDrawView *qd_view = NULL;
+
+
+int gArgc;
+char **gArgv;
+DisplayState current_ds;
+
+/* main defined in qemu/vl.c */
+int qemu_main(int argc, char **argv);
+
+/* To deal with miniaturization */
+@interface QemuWindow : NSWindow
+{ }
+@end
+
+
+/*
+ ------------------------------------------------------
+    Qemu Video Driver
+ ------------------------------------------------------
+*/
+
+/*
+ ------------------------------------------------------
+    cocoa_update
+ ------------------------------------------------------
+*/
+static void cocoa_update(DisplayState *ds, int x, int y, int w, int h)
+{
+    //printf("updating x=%d y=%d w=%d h=%d\n", x, y, w, h);
+
+    /* Use QDFlushPortBuffer() to flush content to display */
+    RgnHandle dirty = NewRgn ();
+    RgnHandle temp  = NewRgn ();
+
+    SetEmptyRgn (dirty);
+
+    /* Build the region of dirty rectangles */
+    MacSetRectRgn (temp, x, y,
+                        x + w, y + h);
+    MacUnionRgn (dirty, temp, dirty);
+                
+    /* Flush the dirty region */
+    QDFlushPortBuffer ( [ qd_view  qdPort ], dirty );
+    DisposeRgn (dirty);
+    DisposeRgn (temp);
+}
+
+/*
+ ------------------------------------------------------
+    cocoa_resize
+ ------------------------------------------------------
+*/
+static void cocoa_resize(DisplayState *ds, int w, int h)
+{
+    const int device_bpp = 32;
+    static void *screen_pixels;
+    static int  screen_pitch;
+    NSRect contentRect;
+    
+    //printf("resizing to %d %d\n", w, h);
+    
+    contentRect = NSMakeRect (0, 0, w, h);
+    if(window)
+    {
+        [window close];
+        [window release];
+    }
+    window = [ [ QemuWindow alloc ] initWithContentRect:contentRect
+                                  styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
+                                  backing:NSBackingStoreBuffered defer:NO];
+    if(!window)
+    {
+        fprintf(stderr, "(cocoa) can't create window\n");
+        exit(1);
+    }
+    
+    if(qd_view)
+        [qd_view release];
+    
+    qd_view = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ];
+    
+    if(!qd_view)
+    {
+         fprintf(stderr, "(cocoa) can't create qd_view\n");
+        exit(1);
+    }
+    
+    [ window setAcceptsMouseMovedEvents:YES ];
+    [ window setTitle:@"Qemu" ];
+    [ window setReleasedWhenClosed:NO ];
+    
+    /* Set screen to black */
+    [ window setBackgroundColor: [NSColor blackColor] ];
+    
+    /* set window position */
+    [ window center ];
+    
+    [ qd_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
+    [ [ window contentView ] addSubview:qd_view ];
+    [ qd_view release ];
+    [ window makeKeyAndOrderFront:nil ];
+    
+    /* Careful here, the window seems to have to be onscreen to do that */
+    LockPortBits ( [ qd_view qdPort ] );
+    screen_pixels = GetPixBaseAddr ( GetPortPixMap ( [ qd_view qdPort ] ) );
+    screen_pitch  = GetPixRowBytes ( GetPortPixMap ( [ qd_view qdPort ] ) );
+    UnlockPortBits ( [ qd_view qdPort ] );
+    { 
+            int vOffset = [ window frame ].size.height - 
+                [ qd_view frame ].size.height - [ qd_view frame ].origin.y;
+            
+            int hOffset = [ qd_view frame ].origin.x;
+                    
+            screen_pixels += (vOffset * screen_pitch) + hOffset * (device_bpp/8);
+    }
+    ds->data = screen_pixels;
+    ds->linesize = screen_pitch;
+    ds->depth = device_bpp;
+    ds->width = w;
+    ds->height = h;
+    
+    current_ds = *ds;
+}
+
+/*
+ ------------------------------------------------------
+    keymap conversion
+ ------------------------------------------------------
+*/
+
+static int keymap[] =
+{
+    30, //'a' 0x0
+    31,  //'s'
+    32,  //'d'
+    33,  //'f'
+    35,  //'h'
+    34,  //'g'
+    44,  //'z'
+    45,  //'x'
+    46,  //'c'
+    47,  //'v'
+    0,   // 0  0x0a
+    48,  //'b'
+    16,  //'q'
+    17,  //'w'
+    18,  //'e'
+    19,  //'r' 
+    21,  //'y' 0x10
+    20,  //'t'
+    2,  //'1'
+    3,  //'2'
+    4,  //'3'
+    5,  //'4'
+    7,  //'6'
+    6,  //'5'
+    0,  //'='
+    10,  //'9'
+    8,  //'7' 0x1A
+    0,  //'-' 
+    9,  //'8' 
+    11,  //'0' 
+    27,  //']' 
+    24,  //'o' 
+    22,  //'u' 0x20
+    26,  //'['
+    23,  //'i'
+    25,  //'p'
+    28,  //'\n'
+    38,  //'l'
+    36,  //'j'
+    40,  //'"'
+    37,  //'k'
+    39,  //';'
+    15,  //'\t' 0x30
+    0,  //' '
+    0,  //'`'
+    14,  //'<backspace>'
+    0,  //'' 0x34
+    0,  //'<esc>'
+    0,  //'<esc>'
+    /* 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 */
+};
+
+static int cocoa_keycode_to_qemu(int keycode)
+{
+    if(sizeof(keymap) <= keycode)
+    {
+        printf("(cocoa) warning unknow keycode 0x%x\n", keycode);
+        return 0;
+    }
+    return keymap[keycode];
+}
+
+/*
+ ------------------------------------------------------
+    cocoa_refresh
+ ------------------------------------------------------
+*/
+static void cocoa_refresh(DisplayState *ds)
+{
+    //printf("cocoa_refresh \n");
+    NSDate *distantPast;
+    NSEvent *event;
+    NSAutoreleasePool *pool;
+    int grab = 1;
+    
+    pool = [ [ NSAutoreleasePool alloc ] init ];
+    distantPast = [ NSDate distantPast ];
+    
+    if (is_active_console(vga_console)) 
+        vga_update_display();
+    do {
+        event = [ NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
+                        inMode: NSDefaultRunLoopMode dequeue:YES ];
+        if (event != nil) {
+            switch ([event type]) {
+                case NSKeyDown:
+                    if(grab)
+                    {
+                        int keycode = cocoa_keycode_to_qemu([event keyCode]);
+                        
+                        if (keycode & 0x80)
+                            kbd_put_keycode(0xe0);
+                        kbd_put_keycode(keycode & 0x7f);
+                    }
+                    break;
+                case NSKeyUp:
+                    if(grab)
+                    {
+                        int keycode = cocoa_keycode_to_qemu([event keyCode]);
+
+                        if (keycode & 0x80)
+                            kbd_put_keycode(0xe0);
+                        kbd_put_keycode(keycode | 0x80);
+                    }
+                    break;
+                case NSScrollWheel:
+                
+                case NSLeftMouseDown:
+                case NSLeftMouseUp:
+                
+                case NSOtherMouseDown:
+                case NSRightMouseDown:
+                
+                case NSOtherMouseUp:
+                case NSRightMouseUp:
+                
+                case NSMouseMoved:
+                case NSOtherMouseDragged:
+                case NSRightMouseDragged:
+                case NSLeftMouseDragged:
+                
+                default: [NSApp sendEvent:event];
+            }
+        }
+    } while(event != nil);
+}
+
+/*
+ ------------------------------------------------------
+    cocoa_cleanup
+ ------------------------------------------------------
+*/
+
+static void cocoa_cleanup(void) 
+{
+
+}
+
+/*
+ ------------------------------------------------------
+    cocoa_display_init
+ ------------------------------------------------------
+*/
+
+void cocoa_display_init(DisplayState *ds, int full_screen)
+{
+    ds->dpy_update = cocoa_update;
+    ds->dpy_resize = cocoa_resize;
+    ds->dpy_refresh = cocoa_refresh;
+    
+    cocoa_resize(ds, 640, 400);
+    
+    atexit(cocoa_cleanup);
+}
+
+/*
+ ------------------------------------------------------
+    Interface with Cocoa
+ ------------------------------------------------------
+*/
+
+
+/*
+ ------------------------------------------------------
+    QemuWindow
+    Some trick from SDL to use miniwindow
+ ------------------------------------------------------
+*/
+static void QZ_SetPortAlphaOpaque ()
+{    
+    /* Assume 32 bit if( bpp == 32 )*/
+    if ( 1 ) {
+    
+        uint32_t    *pixels = (uint32_t*) current_ds.data;
+        uint32_t    rowPixels = current_ds.linesize / 4;
+        uint32_t    i, j;
+        
+        for (i = 0; i < current_ds.height; i++)
+            for (j = 0; j < current_ds.width; j++) {
+        
+                pixels[ (i * rowPixels) + j ] |= 0xFF000000;
+            }
+    }
+}
+
+@implementation QemuWindow
+- (void)miniaturize:(id)sender
+{
+        
+    /* make the alpha channel opaque so anim won't have holes in it */
+    QZ_SetPortAlphaOpaque ();
+    
+    [ super miniaturize:sender ];
+    
+}
+- (void)display
+{    
+    /* 
+        This method fires just before the window deminaturizes from the Dock.
+        
+        We'll save the current visible surface, let the window manager redraw any
+        UI elements, and restore the SDL surface. This way, no expose event 
+        is required, and the deminiaturize works perfectly.
+    */
+    
+    /* make sure pixels are fully opaque */
+    QZ_SetPortAlphaOpaque ();
+    
+    /* save current visible SDL surface */
+    [ self cacheImageInRect:[ qd_view frame ] ];
+    
+    /* let the window manager redraw controls, border, etc */
+    [ super display ];
+    
+    /* restore visible SDL surface */
+    [ self restoreCachedImage ];
+}
+
+@end
+
+
+/*
+ ------------------------------------------------------
+    QemuCocoaGUIController
+    NSApp's delegate - indeed main object
+ ------------------------------------------------------
+*/
+
+@interface QemuCocoaGUIController : NSObject
+{
+}
+- (void)applicationDidFinishLaunching: (NSNotification *) note;
+- (void)applicationWillTerminate:(NSNotification *)aNotification;
+
+- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
+
+- (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
+@end
+
+@implementation QemuCocoaGUIController
+/* Called when the internal event loop has just started running */
+- (void)applicationDidFinishLaunching: (NSNotification *) note
+{
+    
+    /* Do whatever we want here : set up a pc list... */
+    {
+        NSOpenPanel *op = [[NSOpenPanel alloc] init];
+        
+        cocoa_resize(&current_ds, 640, 400);
+        
+        [op setPrompt:@"Boot image"];
+        
+        [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
+        
+        [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",nil]
+              modalForWindow:window modalDelegate:self
+              didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
+    }
+    
+    /* or Launch Qemu, with the global args */
+    //[self startEmulationWithArgc:gArgc argv:gArgv];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification
+{
+    printf("Application will terminate\n");
+    qemu_system_shutdown_request();
+    /* In order to avoid a crash */
+    exit(0);
+}
+
+- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
+{
+    if(returnCode == NSCancelButton)
+    {
+        exit(0);
+    }
+    
+    if(returnCode == NSOKButton)
+    {
+        char *bin = "qemu";
+        char *img = (char*)[ [ sheet filename ] cString];
+        
+        char **argv = (char**)malloc( sizeof(char*)*3 );
+        
+        asprintf(&argv[0], "%s", bin);
+        asprintf(&argv[1], "-hda");
+        asprintf(&argv[2], "%s", img);
+        
+        printf("Using argc %d argv %s -hda %s\n", 3, bin, img);
+        
+        [self startEmulationWithArgc:3 argv:(char**)argv];
+    }
+}
+
+- (void)startEmulationWithArgc:(int)argc argv:(char**)argv
+{
+    int status;
+    /* Launch Qemu */
+    printf("starting qemu...\n");
+    status = qemu_main (argc, argv);
+    exit(status);
+}
+@end
+
+/*
+ ------------------------------------------------------
+    Application Creation
+ ------------------------------------------------------
+*/
+
+/* Dock Connection */
+typedef struct CPSProcessSerNum
+{
+        UInt32                lo;
+        UInt32                hi;
+} CPSProcessSerNum;
+
+extern OSErr    CPSGetCurrentProcess( CPSProcessSerNum *psn);
+extern OSErr    CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
+extern OSErr    CPSSetFrontProcess( CPSProcessSerNum *psn);
+
+/* Menu Creation */
+static void setApplicationMenu(void)
+{
+    /* warning: this code is very odd */
+    NSMenu *appleMenu;
+    NSMenuItem *menuItem;
+    NSString *title;
+    NSString *appName;
+    
+    appName = @"Qemu";
+    appleMenu = [[NSMenu alloc] initWithTitle:@""];
+    
+    /* Add menu items */
+    title = [@"About " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
+
+    [appleMenu addItem:[NSMenuItem separatorItem]];
+
+    title = [@"Hide " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+    [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
+
+    [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+    [appleMenu addItem:[NSMenuItem separatorItem]];
+
+    title = [@"Quit " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+    
+    /* Put menu into the menubar */
+    menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+    [menuItem setSubmenu:appleMenu];
+    [[NSApp mainMenu] addItem:menuItem];
+
+    /* Tell the application object that this is now the application menu */
+    [NSApp setAppleMenu:appleMenu];
+
+    /* Finally give up our references to the objects */
+    [appleMenu release];
+    [menuItem release];
+}
+
+/* Create a window menu */
+static void setupWindowMenu(void)
+{
+    NSMenu      *windowMenu;
+    NSMenuItem  *windowMenuItem;
+    NSMenuItem  *menuItem;
+
+    windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
+    
+    /* "Minimize" item */
+    menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
+    [windowMenu addItem:menuItem];
+    [menuItem release];
+    
+    /* Put menu into the menubar */
+    windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
+    [windowMenuItem setSubmenu:windowMenu];
+    [[NSApp mainMenu] addItem:windowMenuItem];
+    
+    /* Tell the application object that this is now the window menu */
+    [NSApp setWindowsMenu:windowMenu];
+
+    /* Finally give up our references to the objects */
+    [windowMenu release];
+    [windowMenuItem release];
+ 
+}
+
+static void CustomApplicationMain (argc, argv)
+{
+    NSAutoreleasePool   *pool = [[NSAutoreleasePool alloc] init];
+    QemuCocoaGUIController *gui_controller;
+    CPSProcessSerNum PSN;
+    
+    [NSApplication sharedApplication];
+    
+    if (!CPSGetCurrentProcess(&PSN))
+        if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
+            if (!CPSSetFrontProcess(&PSN))
+                [NSApplication sharedApplication];
+                
+    /* Set up the menubar */
+    [NSApp setMainMenu:[[NSMenu alloc] init]];
+    setApplicationMenu();
+    setupWindowMenu();
+
+    /* Create SDLMain and make it the app delegate */
+    gui_controller = [[QemuCocoaGUIController alloc] init];
+    [NSApp setDelegate:gui_controller];
+    
+    /* Start the main event loop */
+    [NSApp run];
+    
+    [gui_controller release];
+    [pool release];
+}
+
+/* Real main of qemu-cocoa */
+int main(int argc, char **argv)
+{
+    gArgc = argc;
+    gArgv = argv;
+    
+    CustomApplicationMain (argc, argv);
+    
+    return 0;
+}
diff --git a/configure b/configure
index 6b83e38..e713003 100755
--- a/configure
+++ b/configure
@@ -83,6 +83,7 @@
 linux="no"
 kqemu="no"
 kernel_path=""
+cocoa="no"
 
 # OS specific
 targetos=`uname -s`
@@ -178,6 +179,8 @@
   ;; 
   --kernel-path=*) kernel_path=${opt#--kernel-path=}
   ;; 
+  --enable-cocoa) cocoa="yes" ; sdl="no"
+  ;; 
   esac
 done
 
@@ -403,6 +406,9 @@
 echo "target list       $target_list"
 echo "gprof enabled     $gprof"
 echo "static build      $static"
+if test "$darwin" = "yes" ; then
+    echo "Cocoa support     $cocoa"
+fi
 echo "SDL support       $sdl"
 echo "SDL static link   $sdl_static"
 echo "mingw32 support   $mingw32"
@@ -676,6 +682,11 @@
     fi
 fi
 
+if test "$cocoa" = "yes" ; then
+    echo "#define CONFIG_COCOA 1" >> $config_h
+    echo "CONFIG_COCOA=yes" >> $config_mak
+fi
+
 done # for target in $targets
 
 # build tree in object directory if source path is different from current one
diff --git a/vl.c b/vl.c
index 5dcf46e..9efb4f7 100644
--- a/vl.c
+++ b/vl.c
@@ -72,6 +72,11 @@
 #endif
 #endif /* CONFIG_SDL */
 
+#ifdef CONFIG_COCOA
+#undef main
+#define main qemu_main
+#endif /* CONFIG_COCOA */
+
 #include "disas.h"
 
 #include "exec-all.h"
@@ -3554,8 +3559,10 @@
     if (nographic) {
         dumb_display_init(ds);
     } else {
-#ifdef CONFIG_SDL
+#if defined(CONFIG_SDL)
         sdl_display_init(ds, full_screen);
+#elif defined(CONFIG_COCOA)
+        cocoa_display_init(ds, full_screen);
 #else
         dumb_display_init(ds);
 #endif