From 8524f1c79e614552c0165db9cc75a8a6bd8607a5 Mon Sep 17 00:00:00 2001 From: John Arbuckle Date: Fri, 19 Jun 2015 10:53:27 +0100 Subject: [PATCH 1/3] ui/cocoa.m: Add Machine menu with pause and resume menu items Add Machine menu to the Macintosh interface with pause and resume menu items. These items can either pause or resume execution of the guest operating system. Signed-off-by: John Arbuckle Message-id: 6D7AE6AA-0595-4FAD-AACF-9DFAB87248F0@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- ui/cocoa.m | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/ui/cocoa.m b/ui/cocoa.m index 85cb24c78d..d28140b2ab 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -29,6 +29,7 @@ #include "ui/console.h" #include "ui/input.h" #include "sysemu/sysemu.h" +#include "qmp-commands.h" #ifndef MAC_OS_X_VERSION_10_5 #define MAC_OS_X_VERSION_10_5 1050 @@ -65,6 +66,7 @@ int gArgc; char **gArgv; bool stretch_video; +NSTextField *pauseLabel; // keymap conversion int keymap[] = @@ -800,6 +802,10 @@ - (void)showQEMUDoc:(id)sender; - (void)showQEMUTec:(id)sender; - (void)zoomToFit:(id) sender; - (void)displayConsole:(id)sender; +- (void)pauseQEMU:(id)sender; +- (void)resumeQEMU:(id)sender; +- (void)displayPause; +- (void)removePause; @end @implementation QemuCocoaAppController @@ -834,6 +840,18 @@ - (id) init [normalWindow makeKeyAndOrderFront:self]; [normalWindow center]; stretch_video = false; + + /* Used for displaying pause on the screen */ + pauseLabel = [NSTextField new]; + [pauseLabel setBezeled:YES]; + [pauseLabel setDrawsBackground:YES]; + [pauseLabel setBackgroundColor: [NSColor whiteColor]]; + [pauseLabel setEditable:NO]; + [pauseLabel setSelectable:NO]; + [pauseLabel setStringValue: @"Paused"]; + [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; + [pauseLabel setTextColor: [NSColor blackColor]]; + [pauseLabel sizeToFit]; } return self; } @@ -977,6 +995,44 @@ - (void)displayConsole:(id)sender { console_select([sender tag]); } + +/* 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]; +} + +/* 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 - [pauseLabel frame].size.width)/2; + yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); + width = [pauseLabel frame].size.width; + height = [pauseLabel frame].size.height; + [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; + [cocoaView addSubview: pauseLabel]; +} + +/* Removes the word pause from the screen */ +- (void)removePause +{ + [pauseLabel removeFromSuperview]; +} + @end @@ -1036,6 +1092,17 @@ int main (int argc, const char * argv[]) { [[NSApp mainMenu] addItem:menuItem]; [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) + // Machine menu + menu = [[NSMenu alloc] initWithTitle: @"Machine"]; + [menu setAutoenablesItems: NO]; + [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; + menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; + [menu addItem: menuItem]; + [menuItem setEnabled: NO]; + menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; + [menuItem setSubmenu:menu]; + [[NSApp mainMenu] addItem:menuItem]; + // View menu menu = [[NSMenu alloc] initWithTitle:@"View"]; [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen From 270746142c3c96549ecd82c6097a6d85a35f27cf Mon Sep 17 00:00:00 2001 From: John Arbuckle Date: Fri, 19 Jun 2015 10:53:27 +0100 Subject: [PATCH 2/3] ui/cocoa.m: Add Reset and Power Down menu items to Machine menu Add "Reset" and "Power Down" menu items to Machine menu. Signed-off-by: John Arbuckle Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- ui/cocoa.m | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ui/cocoa.m b/ui/cocoa.m index d28140b2ab..559058ba39 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -806,6 +806,8 @@ - (void)pauseQEMU:(id)sender; - (void)resumeQEMU:(id)sender; - (void)displayPause; - (void)removePause; +- (void)restartQEMU:(id)sender; +- (void)powerDownQEMU:(id)sender; @end @implementation QemuCocoaAppController @@ -1033,6 +1035,18 @@ - (void)removePause [pauseLabel removeFromSuperview]; } +/* Restarts QEMU */ +- (void)restartQEMU:(id)sender +{ + qmp_system_reset(NULL); +} + +/* Powers down QEMU */ +- (void)powerDownQEMU:(id)sender +{ + qmp_system_powerdown(NULL); +} + @end @@ -1099,6 +1113,9 @@ int main (int argc, const char * argv[]) { menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; [menu addItem: menuItem]; [menuItem setEnabled: NO]; + [menu addItem: [NSMenuItem separatorItem]]; + [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; + [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; [menuItem setSubmenu:menu]; [[NSApp mainMenu] addItem:menuItem]; From 693a3e01af8082f855094061650311fcaf3e1269 Mon Sep 17 00:00:00 2001 From: John Arbuckle Date: Fri, 19 Jun 2015 10:53:27 +0100 Subject: [PATCH 3/3] ui/cocoa.m: Add machine menu items to change and eject removable drive media Adds all removable devices to the Machine menu as a Change and Eject menu item pair. ide-cd0 would have a "Change ide-cd0..." and "Eject ide-cd0" menu items. Signed-off-by: John Arbuckle Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- ui/cocoa.m | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 3 deletions(-) diff --git a/ui/cocoa.m b/ui/cocoa.m index 559058ba39..334e6f6667 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -30,6 +30,7 @@ #include "ui/input.h" #include "sysemu/sysemu.h" #include "qmp-commands.h" +#include "sysemu/blockdev.h" #ifndef MAC_OS_X_VERSION_10_5 #define MAC_OS_X_VERSION_10_5 1050 @@ -67,6 +68,7 @@ char **gArgv; bool stretch_video; NSTextField *pauseLabel; +NSArray * supportedImageFileTypes; // keymap conversion int keymap[] = @@ -242,7 +244,24 @@ static int cocoa_keycode_to_qemu(int keycode) return keymap[keycode]; } +/* Displays an alert dialog box with the specified message */ +static void QEMU_Alert(NSString *message) +{ + NSAlert *alert; + alert = [NSAlert new]; + [alert setMessageText: message]; + [alert runModal]; +} +/* Handles any errors that happen with a device transaction */ +static void handleAnyDeviceErrors(Error * err) +{ + if (err) { + QEMU_Alert([NSString stringWithCString: error_get_pretty(err) + encoding: NSASCIIStringEncoding]); + error_free(err); + } +} /* ------------------------------------------------------ @@ -808,6 +827,8 @@ - (void)displayPause; - (void)removePause; - (void)restartQEMU:(id)sender; - (void)powerDownQEMU:(id)sender; +- (void)ejectDeviceMedia:(id)sender; +- (void)changeDeviceMedia:(id)sender; @end @implementation QemuCocoaAppController @@ -854,6 +875,10 @@ - (id) init [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; [pauseLabel setTextColor: [NSColor blackColor]]; [pauseLabel sizeToFit]; + + // set the supported image file types that can be opened + supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", + @"qcow", @"qcow2", @"cloop", @"vmdk", nil]; } return self; } @@ -877,10 +902,8 @@ - (void)applicationDidFinishLaunching: (NSNotification *) note NSOpenPanel *op = [[NSOpenPanel alloc] init]; [op setPrompt:@"Boot image"]; [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; - NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg", - @"qcow", @"qcow2", @"cloop", @"vmdk", nil]; #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) - [op setAllowedFileTypes:filetypes]; + [op setAllowedFileTypes:supportedImageFileTypes]; [op beginSheetModalForWindow:normalWindow completionHandler:^(NSInteger returnCode) { [self openPanelDidEnd:op @@ -1047,6 +1070,61 @@ - (void)powerDownQEMU:(id)sender qmp_system_powerdown(NULL); } +/* Ejects the media. + * Uses sender's tag to figure out the device to eject. + */ +- (void)ejectDeviceMedia:(id)sender +{ + NSString * drive; + drive = [sender representedObject]; + if(drive == nil) { + NSBeep(); + QEMU_Alert(@"Failed to find drive to eject!"); + 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 represented object value to figure out which drive to use. + */ +- (void)changeDeviceMedia:(id)sender +{ + /* Find the drive name */ + NSString * drive; + drive = [sender representedObject]; + if(drive == nil) { + NSBeep(); + QEMU_Alert(@"Could not find drive!"); + return; + } + + /* Display the file open dialog */ + NSOpenPanel * openPanel; + openPanel = [NSOpenPanel openPanel]; + [openPanel setCanChooseFiles: YES]; + [openPanel setAllowsMultipleSelection: NO]; + [openPanel setAllowedFileTypes: supportedImageFileTypes]; + if([openPanel runModal] == NSFileHandlingPanelOKButton) { + NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; + if(file == nil) { + NSBeep(); + QEMU_Alert(@"Failed to convert URL to file path!"); + return; + } + + Error *err = NULL; + qmp_change_blockdev([drive cStringUsingEncoding: NSASCIIStringEncoding], + [file cStringUsingEncoding: NSASCIIStringEncoding], + "raw", + &err); + handleAnyDeviceErrors(err); + } +} + @end @@ -1260,6 +1338,72 @@ static void add_console_menu_entries(void) } } +/* Make menu items for all removable devices. + * Each device is given an 'Eject' and 'Change' menu item. + */ +static void addRemovableDevicesMenuItems() +{ + NSMenu *menu; + NSMenuItem *menuItem; + BlockInfoList *currentDevice, *pointerToFree; + NSString *deviceName; + + currentDevice = qmp_query_block(NULL); + pointerToFree = currentDevice; + if(currentDevice == NULL) { + NSBeep(); + QEMU_Alert(@"Failed to query for block devices!"); + return; + } + + menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; + + // Add a separator between related groups of menu items + [menu addItem:[NSMenuItem separatorItem]]; + + // Set the attributes to the "Removable Media" menu item + NSString *titleString = @"Removable Media"; + NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; + NSColor *newColor = [NSColor blackColor]; + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + NSFont *font = [fontManager fontWithFamily:@"Helvetica" + traits:NSBoldFontMask|NSItalicFontMask + weight:0 + size:14]; + [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; + [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; + [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; + + // Add the "Removable Media" menu item + menuItem = [NSMenuItem new]; + [menuItem setAttributedTitle: attString]; + [menuItem setEnabled: NO]; + [menu addItem: menuItem]; + + /* Loop thru all the block devices in the emulator */ + while (currentDevice) { + deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; + + if(currentDevice->value->removable) { + menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] + action: @selector(changeDeviceMedia:) + keyEquivalent: @""]; + [menu addItem: menuItem]; + [menuItem setRepresentedObject: deviceName]; + [menuItem autorelease]; + + menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] + action: @selector(ejectDeviceMedia:) + keyEquivalent: @""]; + [menu addItem: menuItem]; + [menuItem setRepresentedObject: deviceName]; + [menuItem autorelease]; + } + currentDevice = currentDevice->next; + } + qapi_free_BlockInfoList(pointerToFree); +} + void cocoa_display_init(DisplayState *ds, int full_screen) { COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); @@ -1283,4 +1427,10 @@ void cocoa_display_init(DisplayState *ds, int full_screen) * menu entries for them. */ add_console_menu_entries(); + + /* Give all removable devices a menu item. + * Has to be called after QEMU has started to + * find out what removable devices it has. + */ + addRemovableDevicesMenuItems(); }