DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/menu_osx.mm
00001 /* Mac OS X portion of menu.cpp */
00002 
00003 #include "config.h"
00004 #include "menu.h"
00005 
00006 #include "sdlmain.h"
00007 #include "SDL.h"
00008 #include "SDL_version.h"
00009 #include "SDL_syswm.h"
00010 
00011 #if DOSBOXMENU_TYPE == DOSBOXMENU_NSMENU /* Mac OS X NSMenu / NSMenuItem handle */
00012 # include <MacTypes.h>
00013 # include <Cocoa/Cocoa.h>
00014 # include <Foundation/NSString.h>
00015 # include <ApplicationServices/ApplicationServices.h>
00016 # include <IOKit/pwr_mgt/IOPMLib.h>
00017 # include <Cocoa/Cocoa.h>
00018 
00019 @interface NSApplication (DOSBoxX)
00020 @end
00021 
00022 #if !defined(C_SDL2)
00023 extern "C" void* sdl1_hax_stock_osx_menu(void);
00024 extern "C" void sdl1_hax_stock_osx_menu_additem(NSMenu *modme);
00025 extern "C" NSWindow *sdl1_hax_get_window(void);
00026 #endif
00027 
00028 void *sdl_hax_nsMenuItemFromTag(void *nsMenu, unsigned int tag) {
00029         NSMenuItem *ns_item = [((NSMenu*)nsMenu) itemWithTag: tag];
00030         return (ns_item != nil) ? ns_item : NULL;
00031 }
00032 
00033 void sdl_hax_nsMenuItemUpdateFromItem(void *nsMenuItem, DOSBoxMenu::item &item) {
00034         if (item.has_changed()) {
00035                 NSMenuItem *ns_item = (NSMenuItem*)nsMenuItem;
00036 
00037                 [ns_item setEnabled:(item.is_enabled() ? YES : NO)];
00038                 [ns_item setState:(item.is_checked() ? NSOnState : NSOffState)];
00039 
00040                 const std::string &it = item.get_text();
00041                 const std::string &st = item.get_shortcut_text();
00042                 std::string ft = it;
00043 
00044                 /* TODO: Figure out how to put the shortcut text right-aligned while leaving the main text left-aligned */
00045                 if (!st.empty()) {
00046                         ft += " [";
00047                         ft += st;
00048                         ft += "]";
00049                 }
00050 
00051                 {
00052                         NSString *title = [[NSString alloc] initWithUTF8String:ft.c_str()];
00053                         [ns_item setTitle:title];
00054                         [title release];
00055                 }
00056 
00057                 item.clear_changed();
00058         }
00059 }
00060 
00061 void* sdl_hax_nsMenuAlloc(const char *initWithText) {
00062         NSString *title = [[NSString alloc] initWithUTF8String:initWithText];
00063         NSMenu *menu = [[NSMenu alloc] initWithTitle: title];
00064         [title release];
00065         [menu setAutoenablesItems:NO];
00066         return (void*)menu;
00067 }
00068 
00069 void sdl_hax_nsMenuRelease(void *nsMenu) {
00070         [((NSMenu*)nsMenu) release];
00071 }
00072 
00073 void sdl_hax_macosx_setmenu(void *nsMenu) {
00074         if (nsMenu != NULL) {
00075         /* switch to the menu object given */
00076                 [NSApp setMainMenu:((NSMenu*)nsMenu)];
00077         }
00078         else {
00079 #if !defined(C_SDL2)
00080                 /* switch back to the menu SDL 1.x made */
00081                 [NSApp setMainMenu:((NSMenu*)sdl1_hax_stock_osx_menu())];
00082 #endif
00083         }
00084 }
00085 
00086 void sdl_hax_nsMenuItemSetTag(void *nsMenuItem, unsigned int new_id) {
00087         [((NSMenuItem*)nsMenuItem) setTag:new_id];
00088 }
00089 
00090 void sdl_hax_nsMenuItemSetSubmenu(void *nsMenuItem,void *nsMenu) {
00091         [((NSMenuItem*)nsMenuItem) setSubmenu:((NSMenu*)nsMenu)];
00092 }
00093 
00094 void* sdl_hax_nsMenuItemAlloc(const char *initWithText) {
00095         NSString *title = [[NSString alloc] initWithUTF8String:initWithText];
00096         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle: title action:@selector(DOSBoxXMenuAction:) keyEquivalent:@""];
00097         [title release];
00098         return (void*)item;
00099 }
00100 
00101 void sdl_hax_nsMenuAddItem(void *nsMenu,void *nsMenuItem) {
00102         [((NSMenu*)nsMenu) addItem:((NSMenuItem*)nsMenuItem)];
00103 }
00104 
00105 void* sdl_hax_nsMenuAllocSeparator(void) {
00106         return (void*)([NSMenuItem separatorItem]);
00107 }
00108 
00109 void sdl_hax_nsMenuItemRelease(void *nsMenuItem) {
00110         [((NSMenuItem*)nsMenuItem) release];
00111 }
00112 
00113 void sdl_hax_nsMenuAddApplicationMenu(void *nsMenu) {
00114 #if defined(C_SDL2)
00115         /* make up an Application menu and stick it in first.
00116            the caller should have passed us an empty menu */
00117         NSMenu *appMenu;
00118         NSMenuItem *appMenuItem;
00119 
00120         appMenu = [[NSMenu alloc] initWithTitle:@""];
00121         [appMenu addItemWithTitle:@"About DOSBox-X" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
00122 
00123         appMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
00124         [appMenuItem setSubmenu:appMenu];
00125         [((NSMenu*)nsMenu) addItem:appMenuItem];
00126         [appMenuItem release];
00127         [appMenu release];
00128 #else
00129     /* Re-use the application menu from SDL1 */
00130     sdl1_hax_stock_osx_menu_additem((NSMenu*)nsMenu);
00131 #endif
00132 }
00133 
00134 extern int pause_menu_item_tag;
00135 extern bool is_paused;
00136 extern void PushDummySDL(void);
00137 extern bool MAPPER_IsRunning(void);
00138 extern bool GUI_IsRunning(void);
00139 
00140 static DOSBoxMenu *altMenu = NULL;
00141 
00142 void menu_osx_set_menuobj(DOSBoxMenu *new_altMenu) {
00143     if (new_altMenu != NULL && new_altMenu != &mainMenu)
00144         altMenu = new_altMenu;
00145     else
00146         altMenu = NULL;
00147 }
00148 
00149 @implementation NSApplication (DOSBoxX)
00150 - (void)DOSBoxXMenuAction:(id)sender
00151 {
00152     if (altMenu != NULL) {
00153         altMenu->mainMenuAction([sender tag]);
00154     }
00155     else {
00156         if ((is_paused && pause_menu_item_tag != [sender tag]) || MAPPER_IsRunning() || GUI_IsRunning()) return;
00157         /* sorry! */
00158         mainMenu.mainMenuAction([sender tag]);
00159     }
00160 }
00161 
00162 - (void)DOSBoxXMenuActionMapper:(id)sender
00163 {
00164     (void)sender;
00165     if (is_paused || MAPPER_IsRunning() || GUI_IsRunning()) return;
00166     extern void MAPPER_Run(bool pressed);
00167     MAPPER_Run(false);
00168 }
00169 
00170 - (void)DOSBoxXMenuActionCapMouse:(id)sender
00171 {
00172     (void)sender;
00173     if (is_paused || MAPPER_IsRunning() || GUI_IsRunning()) return;
00174     extern void MapperCapCursorToggle(void);
00175     MapperCapCursorToggle();
00176 }
00177 
00178 - (void)DOSBoxXMenuActionCfgGUI:(id)sender
00179 {
00180     (void)sender;
00181     if (is_paused || MAPPER_IsRunning() || GUI_IsRunning()) return;
00182     extern void GUI_Run(bool pressed);
00183     GUI_Run(false);
00184 }
00185 
00186 - (void)DOSBoxXMenuActionPause:(id)sender
00187 {
00188     (void)sender;
00189     extern bool unpause_now;
00190     extern void PauseDOSBox(bool pressed);
00191 
00192     if (MAPPER_IsRunning() || GUI_IsRunning()) return;
00193 
00194     if (is_paused) {
00195         PushDummySDL();
00196         unpause_now = true;
00197     }
00198     else {
00199         PauseDOSBox(true);
00200     }
00201 }
00202 @end
00203 
00204 bool has_touch_bar_support = false;
00205 
00206 bool osx_detect_nstouchbar(void) {
00207 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00208     return (has_touch_bar_support = (NSClassFromString(@"NSTouchBar") != nil));
00209 #else
00210     return false;
00211 #endif
00212 }
00213 
00214 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00215 # if !defined(C_SDL2)
00216 extern "C" void sdl1_hax_make_touch_bar_set_callback(NSTouchBar* (*newcb)(NSWindow*));
00217 # endif
00218 
00219 static NSTouchBarItemIdentifier TouchBarCustomIdentifier = @"com.dosbox-x.touchbar.custom";
00220 static NSTouchBarItemIdentifier TouchBarMapperIdentifier = @"com.dosbox-x.touchbar.mapper";
00221 static NSTouchBarItemIdentifier TouchBarCFGGUIIdentifier = @"com.dosbox-x.touchbar.cfggui";
00222 static NSTouchBarItemIdentifier TouchBarHostKeyIdentifier = @"com.dosbox-x.touchbar.hostkey";
00223 static NSTouchBarItemIdentifier TouchBarPauseIdentifier = @"com.dosbox-x.touchbar.pause";
00224 static NSTouchBarItemIdentifier TouchBarCursorCaptureIdentifier = @"com.dosbox-x.touchbar.capcursor";
00225 
00226 @interface DOSBoxXTouchBarDelegate : NSViewController
00227 @end
00228 
00229 @interface DOSBoxXTouchBarDelegate () <NSTouchBarDelegate,NSTextViewDelegate>
00230 @end
00231 
00232 @interface DOSBoxHostButton : NSButton
00233 @end
00234 #endif
00235 
00236 extern void ext_signal_host_key(bool enable);
00237 
00238 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00239 @implementation DOSBoxHostButton
00240 - (void)touchesBeganWithEvent:(NSEvent*)event
00241 {
00242     fprintf(stderr,"Host key down\n");
00243     ext_signal_host_key(true);
00244     [super touchesBeganWithEvent:event];
00245 }
00246 
00247 - (void)touchesEndedWithEvent:(NSEvent*)event
00248 {
00249     fprintf(stderr,"Host key up\n");
00250     ext_signal_host_key(false);
00251     [super touchesEndedWithEvent:event];
00252 }
00253 
00254 - (void)touchesCancelledWithEvent:(NSEvent*)event
00255 {
00256     fprintf(stderr,"Host key cancelled\n");
00257     ext_signal_host_key(false);
00258     [super touchesEndedWithEvent:event];
00259 }
00260 @end
00261 #endif
00262 
00263 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00264 @implementation DOSBoxXTouchBarDelegate
00265 - (void)onHostKey:(id)sender
00266 {
00267     (void)sender;
00268     fprintf(stderr,"HostKey\n");
00269 }
00270 
00271 - (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
00272     (void)touchBar;
00273 
00274     if ([identifier isEqualToString:TouchBarMapperIdentifier]) {
00275         NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:TouchBarMapperIdentifier];
00276 
00277         item.view = [NSButton buttonWithTitle:@"Mapper" target:NSApp action:@selector(DOSBoxXMenuActionMapper:)];
00278         item.customizationLabel = TouchBarCustomIdentifier;
00279 
00280         return item;
00281     }
00282     else if ([identifier isEqualToString:TouchBarHostKeyIdentifier]) {
00283         NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:TouchBarHostKeyIdentifier];
00284 
00285         item.view = [DOSBoxHostButton buttonWithTitle:@"Host Key" target:self action:@selector(onHostKey:)];
00286         item.customizationLabel = TouchBarCustomIdentifier;
00287 
00288         return item;
00289     }
00290     else if ([identifier isEqualToString:TouchBarCFGGUIIdentifier]) {
00291         NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:TouchBarCFGGUIIdentifier];
00292 
00293         item.view = [NSButton buttonWithTitle:@"Cfg GUI" target:NSApp action:@selector(DOSBoxXMenuActionCfgGUI:)];
00294         item.customizationLabel = TouchBarCustomIdentifier;
00295 
00296         return item;
00297     }
00298     else if ([identifier isEqualToString:TouchBarPauseIdentifier]) {
00299         NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:TouchBarPauseIdentifier];
00300 
00301         item.view = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:NSApp action:@selector(DOSBoxXMenuActionPause:)];
00302         item.customizationLabel = TouchBarCustomIdentifier;
00303 
00304         return item;
00305     }
00306     else if ([identifier isEqualToString:TouchBarCursorCaptureIdentifier]) {
00307         NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:TouchBarCursorCaptureIdentifier];
00308 
00309         item.view = [NSButton buttonWithTitle:@"CapMouse" target:NSApp action:@selector(DOSBoxXMenuActionCapMouse:)];
00310         item.customizationLabel = TouchBarCustomIdentifier;
00311 
00312         return item;
00313     }
00314     else {
00315         fprintf(stderr,"Touch bar warning, unknown item '%s'\n",[identifier UTF8String]);
00316     }
00317 
00318     return nil;
00319 }
00320 @end
00321 #endif
00322 
00323 void osx_reload_touchbar(void) {
00324 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00325     NSWindow *wnd = nil;
00326 
00327 # if !defined(C_SDL2)
00328     wnd = sdl1_hax_get_window();
00329 # endif
00330 
00331     if (wnd != nil) {
00332         [wnd setTouchBar:nil];
00333     }
00334 #endif
00335 }
00336 
00337 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00338 NSTouchBar* osx_on_make_touch_bar(NSWindow *wnd) {
00339     (void)wnd;
00340 
00341     NSTouchBar* touchBar = [[NSTouchBar alloc] init];
00342     touchBar.delegate = [DOSBoxXTouchBarDelegate alloc];
00343 
00344     touchBar.customizationIdentifier = TouchBarCustomIdentifier;
00345     if (GUI_IsRunning()) {
00346         touchBar.defaultItemIdentifiers = @[
00347             NSTouchBarItemIdentifierOtherItemsProxy
00348         ];
00349     }
00350     else if (MAPPER_IsRunning()) {
00351         touchBar.defaultItemIdentifiers = @// try to keep the user from hitting the ESC button accidentally when reaching for Host Key[
00352             NSTouchBarItemIdentifierFixedSpaceLarge, 
00353             TouchBarHostKeyIdentifier,
00354             NSTouchBarItemIdentifierFixedSpaceLarge,
00355             NSTouchBarItemIdentifierOtherItemsProxy
00356         ];
00357     }
00358     else {
00359         touchBar.defaultItemIdentifiers = @// try to keep the user from hitting the ESC button accidentally when reaching for Host Key[
00360             NSTouchBarItemIdentifierFixedSpaceLarge, 
00361             TouchBarHostKeyIdentifier,
00362             NSTouchBarItemIdentifierFixedSpaceLarge,
00363             TouchBarPauseIdentifier,
00364             NSTouchBarItemIdentifierFixedSpaceLarge,
00365             TouchBarCursorCaptureIdentifier,
00366             NSTouchBarItemIdentifierFixedSpaceLarge,
00367             TouchBarMapperIdentifier,
00368             TouchBarCFGGUIIdentifier,
00369             NSTouchBarItemIdentifierOtherItemsProxy
00370         ];
00371     }
00372 
00373     touchBar.customizationAllowedItemIdentifiers = @[
00374         TouchBarHostKeyIdentifier,
00375         TouchBarMapperIdentifier,
00376         TouchBarCFGGUIIdentifier,
00377         TouchBarCursorCaptureIdentifier,
00378         TouchBarPauseIdentifier
00379     ];
00380 
00381 // Do not mark as principal, it just makes the button centered in the touch bar
00382 //    touchBar.principalItemIdentifier = TouchBarMapperIdentifier;
00383 
00384     return touchBar;
00385 }
00386 #endif
00387 
00388 void osx_init_touchbar(void) {
00389 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202/* touch bar interface appeared in 10.12.2+ according to Apple */
00390 # if !defined(C_SDL2)
00391     if (has_touch_bar_support)
00392         sdl1_hax_make_touch_bar_set_callback(osx_on_make_touch_bar);
00393 # endif
00394 #endif
00395 }
00396 
00397 #if !defined(C_SDL2)
00398 extern "C" void sdl1_hax_set_dock_menu(NSMenu *menu);
00399 #endif
00400 
00401 void osx_init_dock_menu(void) {
00402 #if !defined(C_SDL2)
00403     NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
00404 
00405     {
00406         NSString *title = [[NSString alloc] initWithUTF8String: "Mapper"];
00407         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(DOSBoxXMenuActionMapper:) keyEquivalent:@""];
00408         [menu addItem:item];
00409         [title release];
00410         [item release];
00411     }
00412 
00413     {
00414         NSString *title = [[NSString alloc] initWithUTF8String: "Configuration GUI"];
00415         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(DOSBoxXMenuActionCfgGUI:) keyEquivalent:@""];
00416         [menu addItem:item];
00417         [title release];
00418         [item release];
00419     }
00420 
00421     {
00422             NSMenuItem *item = [NSMenuItem separatorItem];
00423         [menu addItem:item];
00424         [item release];
00425     }
00426 
00427     {
00428         NSString *title = [[NSString alloc] initWithUTF8String: "Pause"];
00429         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(DOSBoxXMenuActionPause:) keyEquivalent:@""];
00430         [menu addItem:item];
00431         [title release];
00432         [item release];
00433     }
00434 
00435     sdl1_hax_set_dock_menu(menu);
00436 
00437     [menu release];
00438 #endif
00439 }
00440 #endif
00441 
00442 #if !defined(C_SDL2)
00443 extern "C" int sdl1_hax_macosx_window_to_monitor_and_update(CGDirectDisplayID *did);
00444 #endif
00445 
00446 int my_quartz_match_window_to_monitor(CGDirectDisplayID *new_id,NSWindow *wnd);
00447 
00448 void MacOSX_GetWindowDPI(ScreenSizeInfo &info) {
00449     NSWindow *wnd = nil;
00450 
00451     info.clear();
00452 
00453 #if !defined(C_SDL2)
00454     wnd = sdl1_hax_get_window();
00455 #else
00456     SDL_Window* GFX_GetSDLWindow(void);
00457 
00458     SDL_SysWMinfo wminfo;
00459     memset(&wminfo,0,sizeof(wminfo));
00460     SDL_VERSION(&wminfo.version);
00461 
00462     if (SDL_GetWindowWMInfo(GFX_GetSDLWindow(),&wminfo) >= 0) {
00463         if (wminfo.subsystem == SDL_SYSWM_COCOA && wminfo.info.cocoa.window != NULL) {
00464             wnd = wminfo.info.cocoa.window;
00465         }
00466     }
00467 #endif
00468 
00469     if (wnd != nil) {
00470         CGDirectDisplayID did = 0;
00471 
00472         if (my_quartz_match_window_to_monitor(&did,wnd) >= 0) {
00473             CGRect drct = CGDisplayBounds(did);
00474             CGSize dsz = CGDisplayScreenSize(did);
00475 
00476             info.method = METHOD_COREGRAPHICS;
00477 
00478             info.screen_position_pixels.x        = drct.origin.x;
00479             info.screen_position_pixels.y        = drct.origin.y;
00480 
00481             info.screen_dimensions_pixels.width  = drct.size.width;
00482             info.screen_dimensions_pixels.height = drct.size.height;
00483 
00484             /* According to Apple documentation, this function CAN return zero */
00485             if (dsz.width > 0 && dsz.height > 0) {
00486                 info.screen_dimensions_mm.width      = dsz.width;
00487                 info.screen_dimensions_mm.height     = dsz.height;
00488 
00489                 if (info.screen_dimensions_mm.width > 0)
00490                     info.screen_dpi.width =
00491                         ((((double)info.screen_dimensions_pixels.width) * 25.4) /
00492                          ((double)info.screen_dimensions_mm.width));
00493 
00494                 if (info.screen_dimensions_mm.height > 0)
00495                     info.screen_dpi.height =
00496                         ((((double)info.screen_dimensions_pixels.height) * 25.4) /
00497                          ((double)info.screen_dimensions_mm.height));
00498             }
00499         }
00500     }
00501 }
00502 
00503 int my_quartz_match_window_to_monitor(CGDirectDisplayID *new_id,NSWindow *wnd) {
00504     if (wnd != nil) {
00505         CGError err;
00506         uint32_t cnt = 1;
00507         CGDirectDisplayID did = 0;
00508         NSRect rct = [wnd frame];
00509 // NTS: This did not appear until Mojave, and some followers on Github prefer to compile for somewhat older versions of OS X
00510 //      NSPoint pt = [wnd convertPointToScreen:NSMakePoint(rct.size.width / 2, rct.size.height / 2)];
00511 // NTS: convertRectToScreen however is documented to exist since 10.7, unless Apple got that wrong too...
00512         NSPoint pt = [wnd convertRectToScreen:NSMakeRect(rct.size.width / 2, rct.size.height / 2, 0, 0)].origin; /* x,y,w,h */
00513 
00514         {
00515             /* Eugh this ugliness wouldn't be necessary if we didn't have to fudge relative to primary display. */
00516             CGRect prct = CGDisplayBounds(CGMainDisplayID());
00517             pt.y = (prct.origin.y + prct.size.height) - pt.y;
00518         }
00519 
00520         err = CGGetDisplaysWithPoint(pt,1,&did,&cnt);
00521 
00522         /* This might happen if our window is so far off the screen that the center point does not match any monitor */
00523         if (err != kCGErrorSuccess) {
00524             err = kCGErrorSuccess;
00525             did = CGMainDisplayID(); /* Can't fail, eh, Apple? OK then. */
00526         }
00527 
00528         if (err == kCGErrorSuccess) {
00529             *new_id = did;
00530             return 0;
00531         }
00532     }
00533 
00534     return -1;
00535 }
00536 
00537 #if !defined(C_SDL2)
00538 extern "C" int (*sdl1_hax_quartz_match_window_to_monitor)(CGDirectDisplayID *new_id,NSWindow *wnd);
00539 #endif
00540 
00541 void qz_set_match_monitor_cb(void) {
00542 #if !defined(C_SDL2)
00543     sdl1_hax_quartz_match_window_to_monitor = my_quartz_match_window_to_monitor;
00544 #endif
00545 }
00546 
00547