DOSBox-X
|
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