diff mbox

[v3] ui/cocoa.m: Machine menu patch for Mac OS X

Message ID E7D79317-28DC-4B16-AFCC-055D3C7C9C3D@gmail.com
State New
Headers show

Commit Message

Programmingkid Feb. 14, 2015, 1:43 a.m. UTC
Added features:
Menu items to switch floppy and CD image files.
Menu items to eject floppy and CD image files.
Menu item to use /dev/cdrom.
Verifies with the user before quitting QEMU by displaying a dialog box.

Signed-off-by: John Arbuckle <programmingkidx@gmail.com>

---
Added yellow background to the pause label.
Removed all depreciated methods.
Using strncpy() in place of strcpy() in emulatorHasDevice() .
Eliminated all warnings when compiling.

 ui/cocoa.m |  238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 237 insertions(+), 1 deletions(-)

Comments

Peter Maydell Feb. 14, 2015, 2:28 a.m. UTC | #1
On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
> Added features:
> Menu items to switch floppy and CD image files.
> Menu items to eject floppy and CD image files.
> Menu item to use /dev/cdrom.
> Verifies with the user before quitting QEMU by displaying a dialog box.
>
> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>

Stefan, Kevin -- could you review the bits of this patch
which determine whether the machine has a floppy/cdrom
drive and if so let the user insert/inject it, please?
(that's the emulatorHasDevice and ejectFloppy/changeFloppy
functions, mostly). I don't know the block layer APIs so
I can't really say if this patch is doing it in the best/
non-deprecated/etc way or not...

thanks
-- PMM

> ---
> Added yellow background to the pause label.
> Removed all depreciated methods.
> Using strncpy() in place of strcpy() in emulatorHasDevice() .
> Eliminated all warnings when compiling.
>
>  ui/cocoa.m |  238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 files changed, 237 insertions(+), 1 deletions(-)
>
> diff --git a/ui/cocoa.m b/ui/cocoa.m
> index d37c29b..0e4a327 100644
> --- a/ui/cocoa.m
> +++ b/ui/cocoa.m
> @@ -29,6 +29,8 @@
>  #include "ui/console.h"
>  #include "ui/input.h"
>  #include "sysemu/sysemu.h"
> +#include "qmp-commands.h"
> +#include "sysemu/blockdev.h"
>
>  #ifndef MAC_OS_X_VERSION_10_4
>  #define MAC_OS_X_VERSION_10_4 1040
> @@ -64,6 +66,9 @@ static int last_buttons;
>
>  int gArgc;
>  char **gArgv;
> +#define MAX_DEVICE_NAME_SIZE 10
> +char floppy_drive_name[MAX_DEVICE_NAME_SIZE], cdrom_drive_name[MAX_DEVICE_NAME_SIZE];
> +NSTextField * pause_label;
>
>  // keymap conversion
>  int keymap[] =
> @@ -239,7 +244,95 @@ static int cocoa_keycode_to_qemu(int keycode)
>      return keymap[keycode];
>  }
>
> +/* Handles any errors that happen with a device transaction */
> +static void handleAnyDeviceErrors(Error * err)
> +{
> +    if (err) {
> +        NSRunAlertPanel(@"Alert", [NSString stringWithCString: error_get_pretty(err) encoding: NSASCIIStringEncoding], @"OK", nil, nil);
> +        error_free(err);
> +    }
> +}
> +
> +/*
> +Determine if the current emulator has the specified device.
> +device_name: the name of the device you want: floppy, cd
> +official_name: QEMU's name for the device: floppy0, ide-cd0
> +*/
> +static bool emulatorHasDevice(const char * device_name, char * official_name)
> +{
> +    BlockInfoList * block_device_data;
> +    block_device_data = qmp_query_block(false);
> +    if(block_device_data == NULL) {
> +        return false;
> +    }
> +    while(block_device_data->next != NULL) {
> +        /* If we found the device */
> +        if (strstr(block_device_data->value->device, device_name)) {
> +            strncpy(official_name, block_device_data->value->device, MAX_DEVICE_NAME_SIZE);
> +            qapi_free_BlockInfoList(block_device_data);
> +            return true;
> +        }
> +        block_device_data = block_device_data->next;
> +    }
> +    return false;
> +}
> +
> +/* Determine if the current emulator has a floppy drive */
> +static bool emulatorHasFloppy()
> +{
> +    if (emulatorHasDevice("floppy", floppy_drive_name)) {
> +        return true;
> +    } else {
> +        return false;
> +    }
> +}
> +
> +/* Determine if the current emulator has a CDROM drive */
> +static bool emulatorHasCDROM()
> +{
> +    if (emulatorHasDevice("cd", cdrom_drive_name)) {
> +        return true;
> +    } else {
> +        return false;
> +    }
> +}
> +
> +/* Adds the Machine menu to the menu bar. */
> +/* Has to be added separately because QEMU needs
> +   to be running to determine used devices.
> +*/
> +static void createMachineMenu()
> +{
> +    NSMenu * menu;
> +    NSMenuItem * menuItem;
> +
> +    // Machine menu
> +     menu = [[NSMenu alloc] initWithTitle: @"Machine"];
> +    [menu setAutoenablesItems: NO];
> +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQemu:) keyEquivalent: @""] autorelease]];
> +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQemu:) keyEquivalent: @""] autorelease]];
> +
> +    if(emulatorHasFloppy() || emulatorHasCDROM()) {
> +        [menu addItem: [NSMenuItem separatorItem]];
> +    }
>
> +    if (emulatorHasFloppy()) {
> +        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Eject Floppy" action: @selector(ejectFloppy:) keyEquivalent: @""] autorelease]];
> +        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Change Floppy..." action: @selector(changeFloppy:) keyEquivalent: @""] autorelease]];
> +    }
> +    if (emulatorHasCDROM()) {
> +        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Eject cdrom" action: @selector(ejectCdrom:) keyEquivalent: @""] autorelease]];
> +        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Use cdrom image..." action: @selector(changeCdrom:) keyEquivalent: @""] autorelease]];
> +        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Use real cdrom drive" action: @selector(useRealCdrom:) keyEquivalent: @""] autorelease]];
> +    }
> +    [menu addItem: [NSMenuItem separatorItem]];
> +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQemu:) keyEquivalent: @""] autorelease]];
> +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDown:) keyEquivalent: @""] autorelease]];
> +    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
> +    [menuItem setSubmenu:menu];
> +    [[NSApp mainMenu] insertItem: menuItem atIndex: 2]; /* Insert after View menu */
> +    [[menu itemWithTitle: @"Resume"] setEnabled: NO];
> +}
>
>  /*
>   ------------------------------------------------------
> @@ -801,6 +894,17 @@ QemuCocoaView *cocoaView;
>  - (void)toggleFullScreen:(id)sender;
>  - (void)showQEMUDoc:(id)sender;
>  - (void)showQEMUTec:(id)sender;
> +- (void)pauseQemu:(id)sender;
> +- (void)ejectFloppy:(id)sender;
> +- (void)ejectCdrom:(id)sender;
> +- (void)changeCdrom:(id)sender;
> +- (void)changeFloppy:(id)sender;
> +- (void)restartQemu:(id)sender;
> +- (void)useRealCdrom:(id)sender;
> +- (void)verifyQuit:(id)sender;
> +- (void)powerDown:(id)sender;
> +- (void)displayPause;
> +- (void)removePause;
>  @end
>
>  @implementation QemuCocoaAppController
> @@ -833,6 +937,22 @@ QemuCocoaView *cocoaView;
>          [normalWindow makeKeyAndOrderFront:self];
>          [normalWindow center];
>
> +        /* Used for displaying pause on the screen */
> +        pause_label = [NSTextField new];
> +        [pause_label setBezeled:NO];
> +        [pause_label setDrawsBackground:YES];
> +        [pause_label setBackgroundColor: [NSColor yellowColor]];
> +        [pause_label setEditable:NO];
> +        [pause_label setSelectable:NO];
> +        [pause_label setStringValue: @"Paused"];
> +        [pause_label setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
> +        [pause_label setTextColor: [NSColor redColor]];
> +        [pause_label sizeToFit];
> +
> +        /* Verify with the user before quitting QEMU */
> +        NSButton *closeButton = [normalWindow standardWindowButton:NSWindowCloseButton];
> +        [closeButton setTarget: self];
> +        [closeButton setAction: @selector(verifyQuit:)];
>      }
>      return self;
>  }
> @@ -943,6 +1063,119 @@ QemuCocoaView *cocoaView;
>      [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html",
>          [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
>  }
> +
> +/* Pause the guest */
> +- (void)pauseQemu:(id)sender
> +{
> +    qmp_stop(NULL);
> +    [sender setEnabled: NO];
> +    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
> +    [self displayPause];
> +}
> +
> +/* Resume running the guest operating system */
> +- (void)resumeQemu: (id) sender
> +{
> +    qmp_cont(NULL);
> +    [sender setEnabled: NO];
> +    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
> +    [self removePause];
> +}
> +
> +/* Eject the floppy0 disk */
> +- (void)ejectFloppy:(id)sender
> +{
> +    Error *err = NULL;
> +    qmp_eject(floppy_drive_name, false, false, &err);
> +    handleAnyDeviceErrors(err);
> +}
> +
> +/* Displays a dialog box asking the user to select a floppy image to load */
> +- (void)changeFloppy:(id)sender
> +{
> +    NSOpenPanel * open_panel;
> +    open_panel = [NSOpenPanel openPanel];
> +    [open_panel setCanChooseFiles: YES];
> +    [open_panel setAllowsMultipleSelection: NO];
> +    if([open_panel runModalForDirectory: nil file: nil] == NSOKButton) {
> +        Error *err = NULL;
> +        NSString * file = [[open_panel filenames] objectAtIndex: 0];
> +        qmp_change_blockdev(floppy_drive_name, [file cStringUsingEncoding: NSASCIIStringEncoding], "raw", &err);
> +        handleAnyDeviceErrors(err);
> +    }
> +}
> +
> +// Ejects the cdrom
> +- (void)ejectCdrom:(id)sender
> +{
> +    Error *err = NULL;
> +    qmp_eject(cdrom_drive_name, false, false, &err);
> +    handleAnyDeviceErrors(err);
> +}
> +
> +/* Displays a dialog box asking the user to select a CD image to load */
> +- (void)changeCdrom:(id)sender
> +{
> +    NSOpenPanel * open_panel;
> +    open_panel = [NSOpenPanel openPanel];
> +    [open_panel setCanChooseFiles: YES];
> +    [open_panel setAllowsMultipleSelection: NO];
> +    if([open_panel runModalForDirectory: nil file: nil] == NSOKButton) {
> +        NSString * file = [[open_panel filenames] objectAtIndex: 0];
> +        Error *err = NULL;
> +        qmp_change_blockdev(cdrom_drive_name, [file cStringUsingEncoding: NSASCIIStringEncoding], "raw", &err);
> +        handleAnyDeviceErrors(err);
> +    }
> +}
> +
> +/* Restarts QEMU */
> +- (void)restartQemu:(id)sender
> +{
> +    qemu_system_reset_request();
> +}
> +
> +/* Switches QEMU to use the real cdrom drive */
> +- (void)useRealCdrom:(id)sender
> +{
> +    Error *err = NULL;
> +    qmp_change_blockdev(cdrom_drive_name, "/dev/cdrom", "raw", &err);
> +    handleAnyDeviceErrors(err);
> +}
> +
> +/* Verifies if the user really wants to quit */
> +- (void)verifyQuit:(id)sender
> +{
> +    NSInteger response;
> +    response = NSRunAlertPanel(@"Quit?", @"Are you sure you want to quit?", @"Cancel", @"Quit", nil);
> +    if(response == NSAlertAlternateReturn)
> +        qmp_quit(NULL);
> +}
> +
> +/* Powers down the emulator */
> +- (void)powerDown:(id)sender
> +{
> +    qmp_system_powerdown(NULL);
> +}
> +
> +/* Displays the word pause on the screen */
> +- (void)displayPause
> +{
> +    /* Coordinates have to be calculated each time because the window can change its size */
> +    int xCoord, yCoord, width, height;
> +    xCoord = ([normalWindow frame].size.width - [pause_label frame].size.width)/2;
> +    yCoord = [normalWindow frame].size.height - [pause_label frame].size.height - ([pause_label frame].size.height * .5);
> +    width = [pause_label frame].size.width;
> +    height = [pause_label frame].size.height;
> +    [pause_label setFrame: NSMakeRect(xCoord, yCoord, width, height)];
> +    [cocoaView addSubview: pause_label];
> +}
> +
> +/* Removes the word pause from the screen */
> +- (void)removePause
> +{
> +    [pause_label removeFromSuperview];
> +}
> +
>  @end
>
>
> @@ -997,7 +1230,7 @@ int main (int argc, const char * argv[]) {
>      [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
>      [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
>      [menu addItem:[NSMenuItem separatorItem]]; //Separator
> -    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
> +    [menu addItemWithTitle:@"Quit QEMU" action:@selector(verifyQuit:) keyEquivalent:@"q"];
>      menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
>      [menuItem setSubmenu:menu];
>      [[NSApp mainMenu] addItem:menuItem];
> @@ -1128,4 +1361,7 @@ void cocoa_display_init(DisplayState *ds, int full_screen)
>
>      // register cleanup function
>      atexit(cocoa_cleanup);
> +
> +    /* Creates and adds the Machine menu to the menubar */
> +    createMachineMenu();
>  }
> --
> 1.7.5.4
>
Kevin Wolf Feb. 16, 2015, 10:08 a.m. UTC | #2
Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
> On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
> > Added features:
> > Menu items to switch floppy and CD image files.
> > Menu items to eject floppy and CD image files.
> > Menu item to use /dev/cdrom.
> > Verifies with the user before quitting QEMU by displaying a dialog box.
> >
> > Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
> 
> Stefan, Kevin -- could you review the bits of this patch
> which determine whether the machine has a floppy/cdrom
> drive and if so let the user insert/inject it, please?
> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
> functions, mostly). I don't know the block layer APIs so
> I can't really say if this patch is doing it in the best/
> non-deprecated/etc way or not...

Well, it's trying to detect the floppy/cdrom device by comparing string
with default IDs that can be overridden by the user, so no, that's
probably far from the best way to do it. The code also doesn't consider
that you could have more than one floppy or cdrom drive.

The correct way is probably to just display any removable block device,
and ideally also to implement some notifiers to deal with hotplug.

Kevin
Programmingkid Feb. 16, 2015, 4:12 p.m. UTC | #3
On Feb 16, 2015, at 10:42 AM, Kevin Wolf wrote:

> Am 16.02.2015 um 16:31 hat Programmingkid geschrieben:
>> 
>> On Feb 16, 2015, at 5:08 AM, Kevin Wolf wrote:
>> 
>>> Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
>>>> On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
>>>>> Added features:
>>>>> Menu items to switch floppy and CD image files.
>>>>> Menu items to eject floppy and CD image files.
>>>>> Menu item to use /dev/cdrom.
>>>>> Verifies with the user before quitting QEMU by displaying a dialog box.
>>>>> 
>>>>> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
>>>> 
>>>> Stefan, Kevin -- could you review the bits of this patch
>>>> which determine whether the machine has a floppy/cdrom
>>>> drive and if so let the user insert/inject it, please?
>>>> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
>>>> functions, mostly). I don't know the block layer APIs so
>>>> I can't really say if this patch is doing it in the best/
>>>> non-deprecated/etc way or not...
>>> 
>>> Well, it's trying to detect the floppy/cdrom device by comparing string
>>> with default IDs that can be overridden by the user, so no, that's
>>> probably far from the best way to do it. The code also doesn't consider
>>> that you could have more than one floppy or cdrom drive.
>>> 
>>> The correct way is probably to just display any removable block device,
>>> and ideally also to implement some notifiers to deal with hotplug.
>> 
>> Could you provide examples?
> 
> You already use qmp_query_block(), so you get all existing devices.
> Currently you filter for everything that has a name that starts with
> either 'floppy' or 'cdrom'. You could filter for info->removable == true
> instead.


> 
> Of course, you'd have to do this while building up the menu, so that the
> menu will contain dynamically generated entries for every device.
> 
> Hotplug is a bit trickier, I guess. If you can make sure that qemu
> doesn't crash if the device for a menu entry has gone away, that would
> probably be acceptable for the start.

So what you want me to do is loop thru each entry in the BlockInfoList (returned by qmp_query_block() ) and see if it is removable. Then just add a menu item for the device name. If I did that we would have menu items like "ide1-cd0" and "floppy0". The menu items would not have intuitive names that the user would be able to understand easily. Sorry but your idea is not user friendly. I did look at the type field of the BlockInfoList structure and it is only set to "unknown". Maybe a compromise would be the solution. We set the type field to the common name of the device. "ide1-cd0" would have a type field set to "cdrom". Then set the menu item to this type field's value.
Kevin Wolf Feb. 16, 2015, 4:22 p.m. UTC | #4
Am 16.02.2015 um 17:12 hat Programmingkid geschrieben:
> 
> On Feb 16, 2015, at 10:42 AM, Kevin Wolf wrote:
> 
> > Am 16.02.2015 um 16:31 hat Programmingkid geschrieben:
> >> 
> >> On Feb 16, 2015, at 5:08 AM, Kevin Wolf wrote:
> >> 
> >>> Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
> >>>> On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
> >>>>> Added features:
> >>>>> Menu items to switch floppy and CD image files.
> >>>>> Menu items to eject floppy and CD image files.
> >>>>> Menu item to use /dev/cdrom.
> >>>>> Verifies with the user before quitting QEMU by displaying a dialog box.
> >>>>> 
> >>>>> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
> >>>> 
> >>>> Stefan, Kevin -- could you review the bits of this patch
> >>>> which determine whether the machine has a floppy/cdrom
> >>>> drive and if so let the user insert/inject it, please?
> >>>> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
> >>>> functions, mostly). I don't know the block layer APIs so
> >>>> I can't really say if this patch is doing it in the best/
> >>>> non-deprecated/etc way or not...
> >>> 
> >>> Well, it's trying to detect the floppy/cdrom device by comparing string
> >>> with default IDs that can be overridden by the user, so no, that's
> >>> probably far from the best way to do it. The code also doesn't consider
> >>> that you could have more than one floppy or cdrom drive.
> >>> 
> >>> The correct way is probably to just display any removable block device,
> >>> and ideally also to implement some notifiers to deal with hotplug.
> >> 
> >> Could you provide examples?
> > 
> > You already use qmp_query_block(), so you get all existing devices.
> > Currently you filter for everything that has a name that starts with
> > either 'floppy' or 'cdrom'. You could filter for info->removable == true
> > instead.
> 
> 
> > 
> > Of course, you'd have to do this while building up the menu, so that the
> > menu will contain dynamically generated entries for every device.
> > 
> > Hotplug is a bit trickier, I guess. If you can make sure that qemu
> > doesn't crash if the device for a menu entry has gone away, that would
> > probably be acceptable for the start.
> 
> So what you want me to do is loop thru each entry in the BlockInfoList (returned by qmp_query_block() ) and see if it is removable. Then just add a menu item for the device name. If I did that we would have menu items like "ide1-cd0" and "floppy0". The menu items would not have intuitive names that the user would be able to understand easily. Sorry but your idea is not user friendly. I did look at the type field of the BlockInfoList structure and it is only set to "unknown". Maybe a compromise would be the solution. We set the type field to the common name of the device. "ide1-cd0" would have a type field set to "cdrom". Then set the menu item to this type field's value. 

You could still apply some translation table to the menu entry string,
like:

floppy0     => Floppy drive A
floppy1     => Floppy drive B
ide0-cd0    => IDE CD-ROM (Primary Master)
ide0-cd1    => IDE CD-ROM (Primary Slave)
ide1-cd0    => IDE CD-ROM (Secondary Master)
ide1-cd1    => IDE CD-ROM (Secondary Slave)

And everything else just gets the block device ID in the menu name. Then
you get user friendly menu entry names where we have an idea what the
device might be, but still let the device show up with an identifiable
name when we don't.

Because having a CD-ROM drive not show up at all is definitely even less
user friendly than having a cryptic name for it.

Kevin
Programmingkid Feb. 16, 2015, 5 p.m. UTC | #5
On Feb 16, 2015, at 11:22 AM, Kevin Wolf wrote:

> Am 16.02.2015 um 17:12 hat Programmingkid geschrieben:
>> 
>> On Feb 16, 2015, at 10:42 AM, Kevin Wolf wrote:
>> 
>>> Am 16.02.2015 um 16:31 hat Programmingkid geschrieben:
>>>> 
>>>> On Feb 16, 2015, at 5:08 AM, Kevin Wolf wrote:
>>>> 
>>>>> Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
>>>>>> On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
>>>>>>> Added features:
>>>>>>> Menu items to switch floppy and CD image files.
>>>>>>> Menu items to eject floppy and CD image files.
>>>>>>> Menu item to use /dev/cdrom.
>>>>>>> Verifies with the user before quitting QEMU by displaying a dialog box.
>>>>>>> 
>>>>>>> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
>>>>>> 
>>>>>> Stefan, Kevin -- could you review the bits of this patch
>>>>>> which determine whether the machine has a floppy/cdrom
>>>>>> drive and if so let the user insert/inject it, please?
>>>>>> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
>>>>>> functions, mostly). I don't know the block layer APIs so
>>>>>> I can't really say if this patch is doing it in the best/
>>>>>> non-deprecated/etc way or not...
>>>>> 
>>>>> Well, it's trying to detect the floppy/cdrom device by comparing string
>>>>> with default IDs that can be overridden by the user, so no, that's
>>>>> probably far from the best way to do it. The code also doesn't consider
>>>>> that you could have more than one floppy or cdrom drive.
>>>>> 
>>>>> The correct way is probably to just display any removable block device,
>>>>> and ideally also to implement some notifiers to deal with hotplug.
>>>> 
>>>> Could you provide examples?
>>> 
>>> You already use qmp_query_block(), so you get all existing devices.
>>> Currently you filter for everything that has a name that starts with
>>> either 'floppy' or 'cdrom'. You could filter for info->removable == true
>>> instead.
>> 
>> 
>>> 
>>> Of course, you'd have to do this while building up the menu, so that the
>>> menu will contain dynamically generated entries for every device.
>>> 
>>> Hotplug is a bit trickier, I guess. If you can make sure that qemu
>>> doesn't crash if the device for a menu entry has gone away, that would
>>> probably be acceptable for the start.
>> 
>> So what you want me to do is loop thru each entry in the BlockInfoList (returned by qmp_query_block() ) and see if it is removable. Then just add a menu item for the device name. If I did that we would have menu items like "ide1-cd0" and "floppy0". The menu items would not have intuitive names that the user would be able to understand easily. Sorry but your idea is not user friendly. I did look at the type field of the BlockInfoList structure and it is only set to "unknown". Maybe a compromise would be the solution. We set the type field to the common name of the device. "ide1-cd0" would have a type field set to "cdrom". Then set the menu item to this type field's value. 
> 
> You could still apply some translation table to the menu entry string,
> like:
> 
> floppy0     => Floppy drive A
> floppy1     => Floppy drive B
> ide0-cd0    => IDE CD-ROM (Primary Master)
> ide0-cd1    => IDE CD-ROM (Primary Slave)
> ide1-cd0    => IDE CD-ROM (Secondary Master)
> ide1-cd1    => IDE CD-ROM (Secondary Slave)
> 
> And everything else just gets the block device ID in the menu name. Then
> you get user friendly menu entry names where we have an idea what the
> device might be, but still let the device show up with an identifiable
> name when we don't.
> 
> Because having a CD-ROM drive not show up at all is definitely even less
> user friendly than having a cryptic name for it.

This is a good start, but still needs more work. Is it safe to assume all cdrom drives in QEMU will have "cd" in its name? scsi0-cd0, ide0-cd0,...

I will still give every drive that has its removable property set to true a menu item. Is it ok to translate anything that has "-cd#" in its name as cdrom instead of using a translation table? A search algorithm I had in mind is look for anything that has a "cd" after a hyphen. If it is found, translate it to "cdrom". If there are more than one of these, then append a number to cdrom like "cdrom 1". Does that sound good?
Peter Maydell Feb. 16, 2015, 11:41 p.m. UTC | #6
On 17 February 2015 at 01:22, Kevin Wolf <kwolf@redhat.com> wrote:
> You could still apply some translation table to the menu entry string,
> like:
>
> floppy0     => Floppy drive A
> floppy1     => Floppy drive B
> ide0-cd0    => IDE CD-ROM (Primary Master)
> ide0-cd1    => IDE CD-ROM (Primary Slave)
> ide1-cd0    => IDE CD-ROM (Secondary Master)
> ide1-cd1    => IDE CD-ROM (Secondary Slave)

It would be nice if every UI frontend didn't have to duplicate
this code...

-- PMM
Programmingkid Feb. 17, 2015, 1:55 a.m. UTC | #7
On Feb 16, 2015, at 6:41 PM, Peter Maydell wrote:

> On 17 February 2015 at 01:22, Kevin Wolf <kwolf@redhat.com> wrote:
>> You could still apply some translation table to the menu entry string,
>> like:
>> 
>> floppy0     => Floppy drive A
>> floppy1     => Floppy drive B
>> ide0-cd0    => IDE CD-ROM (Primary Master)
>> ide0-cd1    => IDE CD-ROM (Primary Slave)
>> ide1-cd0    => IDE CD-ROM (Secondary Master)
>> ide1-cd1    => IDE CD-ROM (Secondary Slave)
> 
> It would be nice if every UI frontend didn't have to duplicate
> this code...

I told Kevin that the type field in the BlockInfo structure (qapi-types.h) looks unused. That could be used to store the above data.
Kevin Wolf Feb. 17, 2015, 9:07 a.m. UTC | #8
Am 16.02.2015 um 18:00 hat Programmingkid geschrieben:
> 
> On Feb 16, 2015, at 11:22 AM, Kevin Wolf wrote:
> 
> > Am 16.02.2015 um 17:12 hat Programmingkid geschrieben:
> >> 
> >> On Feb 16, 2015, at 10:42 AM, Kevin Wolf wrote:
> >> 
> >>> Am 16.02.2015 um 16:31 hat Programmingkid geschrieben:
> >>>> 
> >>>> On Feb 16, 2015, at 5:08 AM, Kevin Wolf wrote:
> >>>> 
> >>>>> Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
> >>>>>> On 14 February 2015 at 01:43, Programmingkid <programmingkidx@gmail.com> wrote:
> >>>>>>> Added features:
> >>>>>>> Menu items to switch floppy and CD image files.
> >>>>>>> Menu items to eject floppy and CD image files.
> >>>>>>> Menu item to use /dev/cdrom.
> >>>>>>> Verifies with the user before quitting QEMU by displaying a dialog box.
> >>>>>>> 
> >>>>>>> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
> >>>>>> 
> >>>>>> Stefan, Kevin -- could you review the bits of this patch
> >>>>>> which determine whether the machine has a floppy/cdrom
> >>>>>> drive and if so let the user insert/inject it, please?
> >>>>>> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
> >>>>>> functions, mostly). I don't know the block layer APIs so
> >>>>>> I can't really say if this patch is doing it in the best/
> >>>>>> non-deprecated/etc way or not...
> >>>>> 
> >>>>> Well, it's trying to detect the floppy/cdrom device by comparing string
> >>>>> with default IDs that can be overridden by the user, so no, that's
> >>>>> probably far from the best way to do it. The code also doesn't consider
> >>>>> that you could have more than one floppy or cdrom drive.
> >>>>> 
> >>>>> The correct way is probably to just display any removable block device,
> >>>>> and ideally also to implement some notifiers to deal with hotplug.
> >>>> 
> >>>> Could you provide examples?
> >>> 
> >>> You already use qmp_query_block(), so you get all existing devices.
> >>> Currently you filter for everything that has a name that starts with
> >>> either 'floppy' or 'cdrom'. You could filter for info->removable == true
> >>> instead.
> >> 
> >> 
> >>> 
> >>> Of course, you'd have to do this while building up the menu, so that the
> >>> menu will contain dynamically generated entries for every device.
> >>> 
> >>> Hotplug is a bit trickier, I guess. If you can make sure that qemu
> >>> doesn't crash if the device for a menu entry has gone away, that would
> >>> probably be acceptable for the start.
> >> 
> >> So what you want me to do is loop thru each entry in the BlockInfoList (returned by qmp_query_block() ) and see if it is removable. Then just add a menu item for the device name. If I did that we would have menu items like "ide1-cd0" and "floppy0". The menu items would not have intuitive names that the user would be able to understand easily. Sorry but your idea is not user friendly. I did look at the type field of the BlockInfoList structure and it is only set to "unknown". Maybe a compromise would be the solution. We set the type field to the common name of the device. "ide1-cd0" would have a type field set to "cdrom". Then set the menu item to this type field's value. 
> > 
> > You could still apply some translation table to the menu entry string,
> > like:
> > 
> > floppy0     => Floppy drive A
> > floppy1     => Floppy drive B
> > ide0-cd0    => IDE CD-ROM (Primary Master)
> > ide0-cd1    => IDE CD-ROM (Primary Slave)
> > ide1-cd0    => IDE CD-ROM (Secondary Master)
> > ide1-cd1    => IDE CD-ROM (Secondary Slave)
> > 
> > And everything else just gets the block device ID in the menu name. Then
> > you get user friendly menu entry names where we have an idea what the
> > device might be, but still let the device show up with an identifiable
> > name when we don't.
> > 
> > Because having a CD-ROM drive not show up at all is definitely even less
> > user friendly than having a cryptic name for it.
> 
> This is a good start, but still needs more work. Is it safe to assume all cdrom drives in QEMU will have "cd" in its name? scsi0-cd0, ide0-cd0,...

The ID is user-defined, so no, no assumption about it is safe.  If you
like, you can name your floppy drive 'ide0-cd1', your virtio harddisk
'floppy1' and your ATAPI CD-ROM drive 'virtio0'. But if you do that,
I think it's reasonable to argue that it's your own fault that you get
misleading menu entries. The default would yield the right results
anyway.

The only other option would be to start at the device tree, look for
all known block devices with removable media, and then get the
associated block backend from there. That would end up a lot more
complicated, though.

> I will still give every drive that has its removable property set to true a menu item. Is it ok to translate anything that has "-cd#" in its name as cdrom instead of using a translation table? A search algorithm I had in mind is look for anything that has a "cd" after a hyphen. If it is found, translate it to "cdrom". If there are more than one of these, then append a number to cdrom like "cdrom 1". Does that sound good?

No, because a number doesn't really help me identify the drive. There's
a reason why I didn't just say "Floppy", but "Floppy drive A".

Kevin
Peter Maydell Feb. 17, 2015, 9:41 a.m. UTC | #9
On 17 February 2015 at 18:07, Kevin Wolf <kwolf@redhat.com> wrote:
> The ID is user-defined, so no, no assumption about it is safe.  If you
> like, you can name your floppy drive 'ide0-cd1', your virtio harddisk
> 'floppy1' and your ATAPI CD-ROM drive 'virtio0'. But if you do that,
> I think it's reasonable to argue that it's your own fault that you get
> misleading menu entries. The default would yield the right results
> anyway.

Probably wise to have the menu entry include the id string as
well as any guessed friendly name (or just the id string in cases
where we can't come up with a guess).

-- PMM
Kevin Wolf Feb. 17, 2015, 9:57 a.m. UTC | #10
Am 17.02.2015 um 10:41 hat Peter Maydell geschrieben:
> On 17 February 2015 at 18:07, Kevin Wolf <kwolf@redhat.com> wrote:
> > The ID is user-defined, so no, no assumption about it is safe.  If you
> > like, you can name your floppy drive 'ide0-cd1', your virtio harddisk
> > 'floppy1' and your ATAPI CD-ROM drive 'virtio0'. But if you do that,
> > I think it's reasonable to argue that it's your own fault that you get
> > misleading menu entries. The default would yield the right results
> > anyway.
> 
> Probably wise to have the menu entry include the id string as
> well as any guessed friendly name (or just the id string in cases
> where we can't come up with a guess).

Yes, that's a good suggestion.

Kevin
Markus Armbruster Feb. 17, 2015, 12:41 p.m. UTC | #11
Kevin Wolf <kwolf@redhat.com> writes:

> Am 16.02.2015 um 18:00 hat Programmingkid geschrieben:
>> 
>> On Feb 16, 2015, at 11:22 AM, Kevin Wolf wrote:
>> 
>> > Am 16.02.2015 um 17:12 hat Programmingkid geschrieben:
>> >> 
>> >> On Feb 16, 2015, at 10:42 AM, Kevin Wolf wrote:
>> >> 
>> >>> Am 16.02.2015 um 16:31 hat Programmingkid geschrieben:
>> >>>> 
>> >>>> On Feb 16, 2015, at 5:08 AM, Kevin Wolf wrote:
>> >>>> 
>> >>>>> Am 14.02.2015 um 03:28 hat Peter Maydell geschrieben:
>> >>>>>> On 14 February 2015 at 01:43, Programmingkid
>> >>>>>> <programmingkidx@gmail.com> wrote:
>> >>>>>>> Added features:
>> >>>>>>> Menu items to switch floppy and CD image files.
>> >>>>>>> Menu items to eject floppy and CD image files.
>> >>>>>>> Menu item to use /dev/cdrom.
>> >>>>>>> Verifies with the user before quitting QEMU by displaying a
>> >>>>>>> dialog box.
>> >>>>>>> 
>> >>>>>>> Signed-off-by: John Arbuckle <programmingkidx@gmail.com>
>> >>>>>> 
>> >>>>>> Stefan, Kevin -- could you review the bits of this patch
>> >>>>>> which determine whether the machine has a floppy/cdrom
>> >>>>>> drive and if so let the user insert/inject it, please?
>> >>>>>> (that's the emulatorHasDevice and ejectFloppy/changeFloppy
>> >>>>>> functions, mostly). I don't know the block layer APIs so
>> >>>>>> I can't really say if this patch is doing it in the best/
>> >>>>>> non-deprecated/etc way or not...
>> >>>>> 
>> >>>>> Well, it's trying to detect the floppy/cdrom device by comparing string
>> >>>>> with default IDs that can be overridden by the user, so no, that's
>> >>>>> probably far from the best way to do it. The code also doesn't consider
>> >>>>> that you could have more than one floppy or cdrom drive.
>> >>>>> 
>> >>>>> The correct way is probably to just display any removable block device,
>> >>>>> and ideally also to implement some notifiers to deal with hotplug.
>> >>>> 
>> >>>> Could you provide examples?
>> >>> 
>> >>> You already use qmp_query_block(), so you get all existing devices.
>> >>> Currently you filter for everything that has a name that starts with
>> >>> either 'floppy' or 'cdrom'. You could filter for info->removable == true
>> >>> instead.
>> >> 
>> >> 
>> >>> 
>> >>> Of course, you'd have to do this while building up the menu, so that the
>> >>> menu will contain dynamically generated entries for every device.
>> >>> 
>> >>> Hotplug is a bit trickier, I guess. If you can make sure that qemu
>> >>> doesn't crash if the device for a menu entry has gone away, that would
>> >>> probably be acceptable for the start.
>> >> 
>> >> So what you want me to do is loop thru each entry in the
>> >> BlockInfoList (returned by qmp_query_block() ) and see if it is
>> >> removable. Then just add a menu item for the device name. If I
>> >> did that we would have menu items like "ide1-cd0" and
>> >> "floppy0". The menu items would not have intuitive names that the
>> >> user would be able to understand easily. Sorry but your idea is
>> >> not user friendly. I did look at the type field of the
>> >> BlockInfoList structure and it is only set to "unknown". Maybe a
>> >> compromise would be the solution. We set the type field to the
>> >> common name of the device. "ide1-cd0" would have a type field set
>> >> to "cdrom". Then set the menu item to this type field's value.
>> > 
>> > You could still apply some translation table to the menu entry string,
>> > like:
>> > 
>> > floppy0     => Floppy drive A
>> > floppy1     => Floppy drive B
>> > ide0-cd0    => IDE CD-ROM (Primary Master)
>> > ide0-cd1    => IDE CD-ROM (Primary Slave)
>> > ide1-cd0    => IDE CD-ROM (Secondary Master)
>> > ide1-cd1    => IDE CD-ROM (Secondary Slave)
>> > 
>> > And everything else just gets the block device ID in the menu name. Then
>> > you get user friendly menu entry names where we have an idea what the
>> > device might be, but still let the device show up with an identifiable
>> > name when we don't.
>> > 
>> > Because having a CD-ROM drive not show up at all is definitely even less
>> > user friendly than having a cryptic name for it.
>> 
>> This is a good start, but still needs more work. Is it safe to
>> assume all cdrom drives in QEMU will have "cd" in its name?
>> scsi0-cd0, ide0-cd0,...
>
> The ID is user-defined, so no, no assumption about it is safe.  If you
> like, you can name your floppy drive 'ide0-cd1', your virtio harddisk
> 'floppy1' and your ATAPI CD-ROM drive 'virtio0'. But if you do that,
> I think it's reasonable to argue that it's your own fault that you get
> misleading menu entries. The default would yield the right results
> anyway.
>
> The only other option would be to start at the device tree, look for
> all known block devices with removable media, and then get the
> associated block backend from there. That would end up a lot more
> complicated, though.

The backend has a pointer to the frontend.  We could include the
frontend's canonical QOM path in some backend query- output.  Backends
that still haven't qdevified after all these years would make it warty,
however.

With the QOM path, you can use qom-get to find the device model name and
other frontend properties.  Example:

    { "execute": "qom-get", "arguments": { "path": "/machine/peripheral/ide2",  "property": "type" } }
    {"return": "ide-cd"}
    { "execute": "qom-get", "arguments": { "path": "/machine/peripheral/ide2",  "property": "parent_bus" } }
    {"return": "/machine/unattached/device[16]/ide.1"}
    { "execute": "qom-get", "arguments": { "path": "/machine/peripheral/ide2",  "property": "unit" } }
    {"return": 0}

From within QEMU, using the interfaces directly could be easier than
going through the QMP functions.  blk_get_attached_dev() returns the
backend.  Unfortunately, it returns void * rather than DeviceState *,
because block device qdevification is still incomplete (and has seen no
further progress in years).

[...]
Programmingkid Feb. 17, 2015, 3:52 p.m. UTC | #12
On Feb 17, 2015, at 4:57 AM, Kevin Wolf wrote:

> Am 17.02.2015 um 10:41 hat Peter Maydell geschrieben:
>> On 17 February 2015 at 18:07, Kevin Wolf <kwolf@redhat.com> wrote:
>>> The ID is user-defined, so no, no assumption about it is safe.  If you
>>> like, you can name your floppy drive 'ide0-cd1', your virtio harddisk
>>> 'floppy1' and your ATAPI CD-ROM drive 'virtio0'. But if you do that,
>>> I think it's reasonable to argue that it's your own fault that you get
>>> misleading menu entries. The default would yield the right results
>>> anyway.
>> 
>> Probably wise to have the menu entry include the id string as
>> well as any guessed friendly name (or just the id string in cases
>> where we can't come up with a guess).
> 
> Yes, that's a good suggestion.

That is exactly what my patch does :)
diff mbox

Patch

diff --git a/ui/cocoa.m b/ui/cocoa.m
index d37c29b..0e4a327 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -29,6 +29,8 @@ 
 #include "ui/console.h"
 #include "ui/input.h"
 #include "sysemu/sysemu.h"
+#include "qmp-commands.h"
+#include "sysemu/blockdev.h"
 
 #ifndef MAC_OS_X_VERSION_10_4
 #define MAC_OS_X_VERSION_10_4 1040
@@ -64,6 +66,9 @@  static int last_buttons;
 
 int gArgc;
 char **gArgv;
+#define MAX_DEVICE_NAME_SIZE 10
+char floppy_drive_name[MAX_DEVICE_NAME_SIZE], cdrom_drive_name[MAX_DEVICE_NAME_SIZE];
+NSTextField * pause_label;
 
 // keymap conversion
 int keymap[] =
@@ -239,7 +244,95 @@  static int cocoa_keycode_to_qemu(int keycode)
     return keymap[keycode];
 }
 
+/* Handles any errors that happen with a device transaction */
+static void handleAnyDeviceErrors(Error * err)
+{
+    if (err) {
+        NSRunAlertPanel(@"Alert", [NSString stringWithCString: error_get_pretty(err) encoding: NSASCIIStringEncoding], @"OK", nil, nil);
+        error_free(err);
+    }
+}
+
+/*
+Determine if the current emulator has the specified device.
+device_name: the name of the device you want: floppy, cd
+official_name: QEMU's name for the device: floppy0, ide-cd0
+*/
+static bool emulatorHasDevice(const char * device_name, char * official_name)
+{
+    BlockInfoList * block_device_data;
+    block_device_data = qmp_query_block(false);
+    if(block_device_data == NULL) {
+        return false;
+    }
+    while(block_device_data->next != NULL) {
+        /* If we found the device */
+        if (strstr(block_device_data->value->device, device_name)) {
+            strncpy(official_name, block_device_data->value->device, MAX_DEVICE_NAME_SIZE);
+            qapi_free_BlockInfoList(block_device_data);
+            return true;
+        }
+        block_device_data = block_device_data->next;
+    }
+    return false;
+}
+
+/* Determine if the current emulator has a floppy drive */
+static bool emulatorHasFloppy()
+{
+    if (emulatorHasDevice("floppy", floppy_drive_name)) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Determine if the current emulator has a CDROM drive */
+static bool emulatorHasCDROM()
+{
+    if (emulatorHasDevice("cd", cdrom_drive_name)) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Adds the Machine menu to the menu bar. */
+/* Has to be added separately because QEMU needs
+   to be running to determine used devices.
+*/
+static void createMachineMenu()
+{
+    NSMenu * menu;
+    NSMenuItem * menuItem;
+
+    // Machine menu
+     menu = [[NSMenu alloc] initWithTitle: @"Machine"];
+    [menu setAutoenablesItems: NO];
+    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQemu:) keyEquivalent: @""] autorelease]];
+    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQemu:) keyEquivalent: @""] autorelease]];
+
+    if(emulatorHasFloppy() || emulatorHasCDROM()) {
+        [menu addItem: [NSMenuItem separatorItem]];
+    }
 
+    if (emulatorHasFloppy()) {
+        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Eject Floppy" action: @selector(ejectFloppy:) keyEquivalent: @""] autorelease]];
+        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Change Floppy..." action: @selector(changeFloppy:) keyEquivalent: @""] autorelease]];
+    }
+    if (emulatorHasCDROM()) {
+        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Eject cdrom" action: @selector(ejectCdrom:) keyEquivalent: @""] autorelease]];
+        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Use cdrom image..." action: @selector(changeCdrom:) keyEquivalent: @""] autorelease]];
+        [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Use real cdrom drive" action: @selector(useRealCdrom:) keyEquivalent: @""] autorelease]];
+    }
+    [menu addItem: [NSMenuItem separatorItem]];
+    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQemu:) keyEquivalent: @""] autorelease]];
+    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDown:) keyEquivalent: @""] autorelease]];
+    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
+    [menuItem setSubmenu:menu];
+    [[NSApp mainMenu] insertItem: menuItem atIndex: 2]; /* Insert after View menu */
+    [[menu itemWithTitle: @"Resume"] setEnabled: NO];
+}
 
 /*
  ------------------------------------------------------
@@ -801,6 +894,17 @@  QemuCocoaView *cocoaView;
 - (void)toggleFullScreen:(id)sender;
 - (void)showQEMUDoc:(id)sender;
 - (void)showQEMUTec:(id)sender;
+- (void)pauseQemu:(id)sender;
+- (void)ejectFloppy:(id)sender;
+- (void)ejectCdrom:(id)sender;
+- (void)changeCdrom:(id)sender;
+- (void)changeFloppy:(id)sender;
+- (void)restartQemu:(id)sender;
+- (void)useRealCdrom:(id)sender;
+- (void)verifyQuit:(id)sender;
+- (void)powerDown:(id)sender;
+- (void)displayPause;
+- (void)removePause;
 @end
 
 @implementation QemuCocoaAppController
@@ -833,6 +937,22 @@  QemuCocoaView *cocoaView;
         [normalWindow makeKeyAndOrderFront:self];
         [normalWindow center];
 
+        /* Used for displaying pause on the screen */
+        pause_label = [NSTextField new];
+        [pause_label setBezeled:NO];
+        [pause_label setDrawsBackground:YES];
+        [pause_label setBackgroundColor: [NSColor yellowColor]];
+        [pause_label setEditable:NO];
+        [pause_label setSelectable:NO];
+        [pause_label setStringValue: @"Paused"];
+        [pause_label setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
+        [pause_label setTextColor: [NSColor redColor]];
+        [pause_label sizeToFit];
+
+        /* Verify with the user before quitting QEMU */
+        NSButton *closeButton = [normalWindow standardWindowButton:NSWindowCloseButton];
+        [closeButton setTarget: self];
+        [closeButton setAction: @selector(verifyQuit:)];
     }
     return self;
 }
@@ -943,6 +1063,119 @@  QemuCocoaView *cocoaView;
     [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html",
         [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
 }
+
+/* Pause the guest */
+- (void)pauseQemu:(id)sender
+{
+    qmp_stop(NULL);
+    [sender setEnabled: NO];
+    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
+    [self displayPause];
+}
+
+/* Resume running the guest operating system */
+- (void)resumeQemu: (id) sender
+{
+    qmp_cont(NULL);
+    [sender setEnabled: NO];
+    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
+    [self removePause];
+}
+
+/* Eject the floppy0 disk */
+- (void)ejectFloppy:(id)sender
+{
+    Error *err = NULL;
+    qmp_eject(floppy_drive_name, false, false, &err);
+    handleAnyDeviceErrors(err);
+}
+
+/* Displays a dialog box asking the user to select a floppy image to load */
+- (void)changeFloppy:(id)sender
+{
+    NSOpenPanel * open_panel;
+    open_panel = [NSOpenPanel openPanel];
+    [open_panel setCanChooseFiles: YES];
+    [open_panel setAllowsMultipleSelection: NO];
+    if([open_panel runModalForDirectory: nil file: nil] == NSOKButton) {
+        Error *err = NULL;
+        NSString * file = [[open_panel filenames] objectAtIndex: 0];
+        qmp_change_blockdev(floppy_drive_name, [file cStringUsingEncoding: NSASCIIStringEncoding], "raw", &err);
+        handleAnyDeviceErrors(err);
+    }
+}
+
+// Ejects the cdrom
+- (void)ejectCdrom:(id)sender
+{
+    Error *err = NULL;
+    qmp_eject(cdrom_drive_name, false, false, &err);
+    handleAnyDeviceErrors(err);
+}
+
+/* Displays a dialog box asking the user to select a CD image to load */
+- (void)changeCdrom:(id)sender
+{
+    NSOpenPanel * open_panel;
+    open_panel = [NSOpenPanel openPanel];
+    [open_panel setCanChooseFiles: YES];
+    [open_panel setAllowsMultipleSelection: NO];
+    if([open_panel runModalForDirectory: nil file: nil] == NSOKButton) {
+        NSString * file = [[open_panel filenames] objectAtIndex: 0];
+        Error *err = NULL;
+        qmp_change_blockdev(cdrom_drive_name, [file cStringUsingEncoding: NSASCIIStringEncoding], "raw", &err);
+        handleAnyDeviceErrors(err);
+    }
+}
+
+/* Restarts QEMU */
+- (void)restartQemu:(id)sender
+{
+    qemu_system_reset_request();
+}
+
+/* Switches QEMU to use the real cdrom drive */
+- (void)useRealCdrom:(id)sender
+{
+    Error *err = NULL;
+    qmp_change_blockdev(cdrom_drive_name, "/dev/cdrom", "raw", &err);
+    handleAnyDeviceErrors(err);
+}
+
+/* Verifies if the user really wants to quit */
+- (void)verifyQuit:(id)sender
+{
+    NSInteger response;
+    response = NSRunAlertPanel(@"Quit?", @"Are you sure you want to quit?", @"Cancel", @"Quit", nil);
+    if(response == NSAlertAlternateReturn)
+        qmp_quit(NULL);
+}
+
+/* Powers down the emulator */
+- (void)powerDown:(id)sender
+{
+    qmp_system_powerdown(NULL);
+}
+
+/* Displays the word pause on the screen */
+- (void)displayPause
+{
+    /* Coordinates have to be calculated each time because the window can change its size */
+    int xCoord, yCoord, width, height;
+    xCoord = ([normalWindow frame].size.width - [pause_label frame].size.width)/2;
+    yCoord = [normalWindow frame].size.height - [pause_label frame].size.height - ([pause_label frame].size.height * .5);
+    width = [pause_label frame].size.width;
+    height = [pause_label frame].size.height;
+    [pause_label setFrame: NSMakeRect(xCoord, yCoord, width, height)];
+    [cocoaView addSubview: pause_label];
+}
+
+/* Removes the word pause from the screen */
+- (void)removePause
+{
+    [pause_label removeFromSuperview];
+}
+
 @end
 
 
@@ -997,7 +1230,7 @@  int main (int argc, const char * argv[]) {
     [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
     [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
     [menu addItem:[NSMenuItem separatorItem]]; //Separator
-    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
+    [menu addItemWithTitle:@"Quit QEMU" action:@selector(verifyQuit:) keyEquivalent:@"q"];
     menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
     [menuItem setSubmenu:menu];
     [[NSApp mainMenu] addItem:menuItem];
@@ -1128,4 +1361,7 @@  void cocoa_display_init(DisplayState *ds, int full_screen)
 
     // register cleanup function
     atexit(cocoa_cleanup);
+
+    /* Creates and adds the Machine menu to the menubar */
+    createMachineMenu();
 }