@@ -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
@@ -65,6 +67,14 @@ 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;
+int floppy_count = 0;
+int cd_count = 0;
+NSMutableDictionary * device_dict;
+int device_id = 0;
+
// keymap conversion
int keymap[] =
{
@@ -239,9 +249,330 @@ 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;
+ }
+}
+
+/* Determines if the given device is a floppy drive */
+static bool isFloppyDevice(BlockInfo * current_device)
+{
+ if(strstr(current_device->device, "floppy")) {
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+/* Determines if the given device is a CD drive */
+static bool isCdromDevice(BlockInfo * current_device)
+{
+ if(strstr(current_device->device, "-cd")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/* Returns a floppy device */
+static NSString * getFloppyDevice(int index)
+{
+ int count = 0;
+ BlockInfoList *current_device;
+ current_device = qmp_query_block(false);
+ if(current_device == NULL) {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"Could not query block devices!", @"OK", nil, nil);
+ printf("Error: could not query block devices!\nFunction: getFloppyDevice()\n");
+ return @"FAILED TO QUERY FOR FLOPPY DRIVES";
+ }
+
+ // look thru all the devices
+ while (current_device) {
+ if(isFloppyDevice(current_device->value)) { /* If found a floppy drive */
+ if(count == index) { /* The drive we want */
+ return [NSString stringWithFormat: @"%s", current_device->value->device];
+ }
+ count++;
+ }
+ current_device = current_device->next;
+ }
+
+ /* If failed to find the drive */
+ NSRunAlertPanel(@"Alert", @"Could not find floppy drive.", @"OK", nil, nil);
+ printf("Error: No floppy drive found at index %d\n\a", index);
+ return [NSString stringWithFormat: @"NO FLOPPY DRIVE FOUND AT INDEX %d", index ];
+}
+
+/* Returns a cdrom device */
+static NSString * getCdromDevice(int index)
+{
+ int count = 0;
+ BlockInfoList *current_device;
+ current_device = qmp_query_block(false);
+ if(current_device == NULL) {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"Could not query block devices!", @"OK", nil, nil);
+ printf("Error: could not query block devices!\nFunction: getCdromDevice()");
+ return @"FAILED TO QUERY FOR CDROM DRIVES";
+ }
+
+ // look thru all the devices
+ while (current_device) {
+ if(isCdromDevice(current_device->value)) { /* If found a cd drive */
+ if(count == index) { /* The drive we want */
+ return [NSString stringWithFormat: @"%s", current_device->value->device];
+ }
+ count++;
+ }
+ current_device = current_device->next;
+ }
+
+ /* If failed to find the drive */
+ NSRunAlertPanel(@"Alert", @"Could not find cdrom.", @"OK", nil, nil);
+ printf("Error: could not find cdrom drive.\n");
+ return [NSString stringWithFormat: @"NO CDROM DRIVE FOUND AT INDEX %d", index ];
+}
+
+
+/* Counts the number of floppy and cdrom drives */
+static void countDevices()
+{
+ BlockInfoList *current_device;
+ current_device = qmp_query_block(false);
+ if(current_device == NULL) {
+ NSBeep();
+ NSRunAlertPanel(@"Failure", @"Could not query block devices!", @"OK", nil, nil);
+ return;
+ }
+
+ /* count the number of each device we have */
+ while (current_device) {
+ if(current_device->value->removable == true) {
+ if(isFloppyDevice(current_device->value))
+ floppy_count++;
+ if(isCdromDevice(current_device->value))
+ cd_count++;
+ }
+ current_device = current_device->next;
+ }
+}
+
+
+/* Create all the menu items for all floppy drives */
+static void makeFloppyDeviceMenus(NSMenu * menu)
+{
+ NSMenuItem * menu_item;
+
+ /* No floppies - no reason to continue */
+ if(floppy_count == 0) {
+ return;
+ }
+
+ /* If there is only one floppy device - don't append any number of menu item title */
+ else if(floppy_count == 1) {
+ menu_item = [[NSMenuItem alloc] initWithTitle: @"Change floppy..." action: @selector(changeDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ menu_item = [[NSMenuItem alloc] initWithTitle: @"Eject floppy" action: @selector(ejectDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ [device_dict setValue: getFloppyDevice(0) forKey: [[NSNumber numberWithInt: device_id] stringValue]];
+ device_id++;
+ }
+
+ /* If there is more than one floppy drive */
+ else if(floppy_count > 1) {
+ int i;
+ for(i = 0; i < floppy_count; i++) {
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change floppy %d...", i+1] action: @selector(changeDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject floppy %d", i+1] action: @selector(ejectDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ [device_dict setValue: getFloppyDevice(i) forKey: [[NSNumber numberWithInt: device_id] stringValue]];
+ device_id++;
+ }
+ }
+}
+
+
+/* Makes all the CDROM menu items */
+static void makeCDROMDeviceMenus(NSMenu * menu)
+{
+ NSMenuItem * menu_item;
+
+ /* No CDROM drives for QEMU - no reason to continue */
+ if(cd_count == 0) {
+ return;
+ }
+
+ /* So we don't add a number to a menu item when there is only one of them */
+ else if(cd_count == 1) {
+ menu_item = [[NSMenuItem alloc] initWithTitle: @"Change cdrom..." action: @selector(changeDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ menu_item = [[NSMenuItem alloc] initWithTitle: @"Eject cdrom" action: @selector(ejectDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ [device_dict setValue: getCdromDevice(0) forKey: [[NSNumber numberWithInt: device_id] stringValue]];
+ device_id++;
+ }
+
+ else if(cd_count > 1) {
+ int i;
+ for(i = 0; i < cd_count; i++) {
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change cdrom (%@)...", getCdromDevice(i)] action: @selector(changeDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject cdrom (%@)", getCdromDevice(i)] action: @selector(ejectDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ [device_dict setValue: getCdromDevice(i) forKey: [[NSNumber numberWithInt: device_id] stringValue]];
+ device_id++;
+ }
+ }
+
+ /* This menu item should always be available. The user
+ can plug in an external optical drive and use it -
+ even after QEMU has started up. */
+ menu_item = [[NSMenuItem alloc] initWithTitle: @"Use real cdrom drive" action: @selector(useRealCdrom:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item autorelease];
+}
+
+/* Make menus for other removable devices that are not floppy or cdrom */
+static void makeOtherDeviceMenus(NSMenu * menu)
+{
+ NSMenuItem * menu_item;
+ BlockInfoList * current_device;
+ current_device = qmp_query_block(false);
+ if(current_device == NULL) {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"Failed to query for block devices!", @"OK", nil, nil);
+ return;
+ }
+
+ /* Loop thru all the block devices in the emulator */
+ while (current_device) {
+ if(current_device->value->removable == true) {
+ if(!isCdromDevice(current_device->value) && !isFloppyDevice(current_device->value)) {
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", current_device->value->device] action: @selector(changeDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ menu_item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", current_device->value->device] action: @selector(ejectDeviceMedia:) keyEquivalent: @""];
+ [menu addItem: menu_item];
+ [menu_item setTag: device_id];
+ [menu_item autorelease];
+
+ [device_dict setValue: [NSString stringWithFormat: @"%s", current_device->value->device] forKey: [[NSNumber numberWithInt: device_id] stringValue]];
+ device_id++;
+ }
+ }
+ current_device = current_device->next;
+ }
+}
+
+/* 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]];
+ }
+ makeFloppyDeviceMenus(menu);
+ makeCDROMDeviceMenus(menu);
+ makeOtherDeviceMenus(menu);
+ [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];
+}
+
+/*
------------------------------------------------------
QemuCocoaView
------------------------------------------------------
@@ -801,6 +1132,15 @@ QemuCocoaView *cocoaView;
- (void)toggleFullScreen:(id)sender;
- (void)showQEMUDoc:(id)sender;
- (void)showQEMUTec:(id)sender;
+- (void)pauseQemu:(id)sender;
+- (void)ejectDeviceMedia:(id)sender;
+- (void)changeDeviceMedia:(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 +1173,24 @@ QemuCocoaView *cocoaView;
[normalWindow makeKeyAndOrderFront:self];
[normalWindow center];
+ /* Used for displaying pause on the screen */
+ pause_label = [NSTextField new];
+ [pause_label setBezeled:YES];
+ [pause_label setDrawsBackground:YES];
+ [pause_label setBackgroundColor: [NSColor whiteColor]];
+ [pause_label setEditable:NO];
+ [pause_label setSelectable:NO];
+ [pause_label setStringValue: @"Paused"];
+ [pause_label setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
+ [pause_label setTextColor: [NSColor blackColor]];
+ [pause_label sizeToFit];
+
+ /* Verify with the user before quitting QEMU */
+ NSButton *closeButton = [normalWindow standardWindowButton:NSWindowCloseButton];
+ [closeButton setTarget: self];
+ [closeButton setAction: @selector(verifyQuit:)];
+
+ device_dict = [NSMutableDictionary new];
}
return self;
}
@@ -943,6 +1301,127 @@ 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];
+}
+
+
+// Ejects the media
+// Uses sender's tag to figure out device to eject
+- (void)ejectDeviceMedia:(id)sender
+{
+ NSString * drive;
+ drive = [device_dict valueForKey: [[NSNumber numberWithInt: [sender tag]] stringValue]];
+ if(drive == nil) {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"Failed to find drive to eject!", @"OK", nil, nil);
+ printf("Error: Failed to find drive to eject!\n");
+ NSLog(@"device dictionary: %@", [device_dict description]);
+ return;
+ }
+
+ Error *err = NULL;
+ qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding], false, false, &err);
+ handleAnyDeviceErrors(err);
+}
+
+/* Displays a dialog box asking the user to select an image file to load.
+ Uses sender's tag value to figure out which drive to use. */
+- (void)changeDeviceMedia:(id)sender
+{
+ /* Find the drive name */
+ NSString * drive;
+ drive = [device_dict valueForKey: [[NSNumber numberWithInt: [sender tag]] stringValue]];
+ if(drive == nil) {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"Could not find drive!", @"OK", nil, nil);
+ printf("Error: Could not find drive!\n");
+ NSLog(@"device dictionary: %@", [device_dict description]);
+ return;
+ }
+
+ /* Display the file open dialog */
+ 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([drive cStringUsingEncoding: NSASCIIStringEncoding], [file cStringUsingEncoding: NSASCIIStringEncoding], "raw", &err);
+ handleAnyDeviceErrors(err);
+ }
+}
+
+/* Restarts QEMU */
+- (void)restartQemu:(id)sender
+{
+ qemu_system_reset_request();
+}
+
+/* Has QEMU use a real cdrom disc */
+- (void)useRealCdrom:(id)sender
+{
+ char *cdrom_drive_name;
+ if (emulatorHasDevice("cd", cdrom_drive_name)) {
+ Error *err = NULL;
+ qmp_change_blockdev(cdrom_drive_name, "/dev/cdrom", "raw", &err);
+ handleAnyDeviceErrors(err);
+ } else {
+ NSBeep();
+ NSRunAlertPanel(@"Alert", @"No real cdrom found.", @"OK", nil, nil);
+ }
+}
+
+/* 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 +1476,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 +1607,9 @@ void cocoa_display_init(DisplayState *ds, int full_screen)
// register cleanup function
atexit(cocoa_cleanup);
+
+ countDevices();
+
+ /* Creates and adds the Machine menu to the menubar */
+ createMachineMenu();
}
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. Pause and resume menu items with the word pause displayed on the window. Signed-off-by: John Arbuckle <programmingkidx@gmail.com> --- Other devices like SD are also given menu items. Menu items now support having more than one cdrom and floppy drives. ui/cocoa.m | 486 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 485 insertions(+), 1 deletions(-)