1///////////////////////////////////////////////////////////////////////////// 2// Name: src/osx/cocoa/utils.mm 3// Purpose: various cocoa utility functions 4// Author: Stefan Csomor 5// Modified by: 6// Created: 1998-01-01 7// Copyright: (c) Stefan Csomor 8// Licence: wxWindows licence 9///////////////////////////////////////////////////////////////////////////// 10 11#include "wx/wxprec.h" 12 13#include "wx/utils.h" 14#include "wx/platinfo.h" 15 16#ifndef WX_PRECOMP 17 #include "wx/intl.h" 18 #include "wx/app.h" 19 #if wxUSE_GUI 20 #include "wx/dialog.h" 21 #include "wx/toplevel.h" 22 #include "wx/font.h" 23 #endif 24#endif 25 26#include "wx/apptrait.h" 27 28#include "wx/osx/private.h" 29#include "wx/osx/private/available.h" 30 31#if wxUSE_GUI 32#if wxOSX_USE_COCOA_OR_CARBON 33 #include <CoreServices/CoreServices.h> 34 #include "wx/osx/dcclient.h" 35 #include "wx/osx/private/timer.h" 36#endif 37#endif // wxUSE_GUI 38 39#if wxUSE_GUI 40 41// Emit a beeeeeep 42void wxBell() 43{ 44 NSBeep(); 45} 46 47@implementation wxNSAppController 48 49- (void)applicationWillFinishLaunching:(NSNotification *)application 50{ 51 wxUnusedVar(application); 52 53 // we must install our handlers later than setting the app delegate, because otherwise our handlers 54 // get overwritten in the meantime 55 56 NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; 57 58 [appleEventManager setEventHandler:self andSelector:@selector(handleGetURLEvent:withReplyEvent:) 59 forEventClass:kInternetEventClass andEventID:kAEGetURL]; 60 61 [appleEventManager setEventHandler:self andSelector:@selector(handleOpenAppEvent:withReplyEvent:) 62 forEventClass:kCoreEventClass andEventID:kAEOpenApplication]; 63 64 [appleEventManager setEventHandler:self andSelector:@selector(handleQuitAppEvent:withReplyEvent:) 65 forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; 66 67 wxTheApp->OSXOnWillFinishLaunching(); 68} 69 70- (void)applicationDidFinishLaunching:(NSNotification *)notification 71{ 72 wxUnusedVar(notification); 73 [NSApp stop:nil]; 74 wxTheApp->OSXOnDidFinishLaunching(); 75 76 // We need to activate the application manually if it's not part of a 77 // bundle, otherwise not only it won't come to the foreground, but under 78 // recent macOS versions (10.15+), its menus simply won't work at all. 79 // 80 // Note that we have not one but two methods to opt out from this behaviour 81 // for compatibility. 82 if ( !wxApp::sm_isEmbedded && wxTheApp && wxTheApp->OSXIsGUIApplication() ) 83 { 84 CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle() ) ; 85 CFStringRef path = CFURLCopyFileSystemPath ( url , kCFURLPOSIXPathStyle ) ; 86 CFRelease( url ) ; 87 wxString app = wxCFStringRef(path).AsString(wxLocale::GetSystemEncoding()); 88 if ( !app.EndsWith(".app") ) 89 { 90 [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; 91 [NSApp activateIgnoringOtherApps: YES]; 92 } 93 } 94} 95 96- (void)application:(NSApplication *)sender openFiles:(NSArray *)fileNames 97{ 98 wxUnusedVar(sender); 99 wxArrayString fileList; 100 size_t i; 101 const size_t count = [fileNames count]; 102 for (i = 0; i < count; i++) 103 { 104 fileList.Add( wxCFStringRef::AsStringWithNormalizationFormC([fileNames objectAtIndex:i]) ); 105 } 106 107 if ( wxTheApp->OSXInitWasCalled() ) 108 wxTheApp->MacOpenFiles(fileList); 109 else 110 wxTheApp->OSXStoreOpenFiles(fileList); 111} 112 113- (NSApplicationPrintReply)application:(NSApplication *)sender printFiles:(NSArray *)fileNames withSettings:(NSDictionary *)printSettings showPrintPanels:(BOOL)showPrintPanels 114{ 115 wxUnusedVar(sender); 116 wxUnusedVar(printSettings); 117 wxUnusedVar(showPrintPanels); 118 119 wxArrayString fileList; 120 size_t i; 121 const size_t count = [fileNames count]; 122 for (i = 0; i < count; i++) 123 { 124 fileList.Add( wxCFStringRef::AsStringWithNormalizationFormC([fileNames objectAtIndex:i]) ); 125 } 126 127 if ( wxTheApp->OSXInitWasCalled() ) 128 wxTheApp->MacPrintFiles(fileList); 129 else 130 wxTheApp->OSXStorePrintFiles(fileList); 131 132 return NSPrintingSuccess; 133} 134 135- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag 136{ 137 wxUnusedVar(flag); 138 wxUnusedVar(sender); 139 if ( wxTheApp->OSXInitWasCalled() ) 140 wxTheApp->MacReopenApp(); 141 // else: It's possible that this function was called as the first thing. 142 // This can happen when OS X restores running apps when starting a new 143 // user session. Apps that were hidden (dock only) when the previous 144 // session terminated are only restored in a limited, resources-saving 145 // way. When the user clicks the icon, applicationShouldHandleReopen: 146 // is called, but we didn't call OnInit() yet. In this case, we 147 // shouldn't call MacReopenApp(), but should proceed with normal 148 // initialization. 149 return NO; 150} 151 152- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event 153 withReplyEvent:(NSAppleEventDescriptor *)replyEvent 154{ 155 wxUnusedVar(replyEvent); 156 NSString* url = [[event descriptorAtIndex:1] stringValue]; 157 wxCFStringRef cf(wxCFRetain(url)); 158 if ( wxTheApp->OSXInitWasCalled() ) 159 wxTheApp->MacOpenURL(cf.AsString()) ; 160 else 161 wxTheApp->OSXStoreOpenURL(cf.AsString()); 162} 163 164- (void)handleQuitAppEvent:(NSAppleEventDescriptor *)event 165 withReplyEvent:(NSAppleEventDescriptor *)replyEvent 166{ 167 wxUnusedVar(event); 168 wxUnusedVar(replyEvent); 169 if ( wxTheApp->OSXOnShouldTerminate() ) 170 { 171 wxTheApp->OSXOnWillTerminate(); 172 wxTheApp->ExitMainLoop(); 173 } 174} 175 176- (void)handleOpenAppEvent:(NSAppleEventDescriptor *)event 177 withReplyEvent:(NSAppleEventDescriptor *)replyEvent 178{ 179 wxUnusedVar(event); 180 wxUnusedVar(replyEvent); 181} 182 183/* 184 Allowable return values are: 185 NSTerminateNow - it is ok to proceed with termination 186 NSTerminateCancel - the application should not be terminated 187 NSTerminateLater - it may be ok to proceed with termination later. The application must call -replyToApplicationShouldTerminate: with YES or NO once the answer is known 188 this return value is for delegates who need to provide document modal alerts (sheets) in order to decide whether to quit. 189*/ 190- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 191{ 192 wxUnusedVar(sender); 193 if ( !wxTheApp->OSXOnShouldTerminate() ) 194 return NSTerminateCancel; 195 196 return NSTerminateNow; 197} 198 199- (void)applicationWillTerminate:(NSNotification *)application { 200 wxUnusedVar(application); 201 wxTheApp->OSXOnWillTerminate(); 202} 203 204- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 205{ 206 wxUnusedVar(sender); 207 // let wx do this, not cocoa 208 return NO; 209} 210 211- (void)applicationDidBecomeActive:(NSNotification *)notification 212{ 213 wxUnusedVar(notification); 214 215 for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(), 216 end = wxTopLevelWindows.end(); 217 i != end; 218 ++i ) 219 { 220 wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i); 221 wxNonOwnedWindowImpl* winimpl = win ? win->GetNonOwnedPeer() : NULL; 222 WXWindow nswindow = win ? win->GetWXWindow() : nil; 223 224 if ( nswindow && [nswindow hidesOnDeactivate] == NO && winimpl) 225 winimpl->RestoreWindowLevel(); 226 } 227 if ( wxTheApp ) 228 wxTheApp->SetActive( true , NULL ) ; 229} 230 231- (void)applicationWillResignActive:(NSNotification *)notification 232{ 233 wxUnusedVar(notification); 234 for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(), 235 end = wxTopLevelWindows.end(); 236 i != end; 237 ++i ) 238 { 239 wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i); 240 WXWindow nswindow = win ? win->GetWXWindow() : nil; 241 242 if ( nswindow && [nswindow level] == kCGFloatingWindowLevel && [nswindow hidesOnDeactivate] == NO ) 243 [nswindow setLevel:kCGNormalWindowLevel]; 244 } 245} 246 247- (void)applicationDidResignActive:(NSNotification *)notification 248{ 249 wxUnusedVar(notification); 250 if ( wxTheApp ) 251 wxTheApp->SetActive( false , NULL ) ; 252} 253 254@end 255 256/* 257 allows ShowModal to work when using sheets. 258 see include/wx/osx/cocoa/private.h for more info 259*/ 260@implementation ModalDialogDelegate 261- (id)init 262{ 263 if ( self = [super init] ) 264 { 265 sheetFinished = NO; 266 resultCode = -1; 267 impl = 0; 268 } 269 return self; 270} 271 272- (void)setImplementation: (wxDialog *)dialog 273{ 274 impl = dialog; 275} 276 277- (BOOL)finished 278{ 279 return sheetFinished; 280} 281 282- (int)code 283{ 284 return resultCode; 285} 286 287- (void)waitForSheetToFinish 288{ 289 while (!sheetFinished) 290 { 291 wxSafeYield(); 292 } 293} 294 295- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 296{ 297 wxUnusedVar(contextInfo); 298 resultCode = returnCode; 299 sheetFinished = YES; 300 // NSAlerts don't need nor respond to orderOut 301 if ([sheet respondsToSelector:@selector(orderOut:)]) 302 [sheet orderOut: self]; 303 304 if (impl) 305 impl->ModalFinishedCallback(sheet, returnCode); 306} 307@end 308 309// here we subclass NSApplication, for the purpose of being able to override sendEvent. 310@interface wxNSApplication : NSApplication 311{ 312} 313 314- (id)init; 315 316- (void)sendEvent:(NSEvent *)anEvent; 317 318@end 319 320@implementation wxNSApplication 321 322- (id)init 323{ 324 if ( self = [super init] ) 325 { 326 // further init 327 } 328 return self; 329} 330 331/* This is needed because otherwise we don't receive any key-up events for command-key 332 combinations (an AppKit bug, apparently) */ 333- (void)sendEvent:(NSEvent *)anEvent 334{ 335 if ([anEvent type] == NSKeyUp && ([anEvent modifierFlags] & NSCommandKeyMask)) 336 [[self keyWindow] sendEvent:anEvent]; 337 else 338 [super sendEvent:anEvent]; 339} 340 341@end 342 343WX_NSObject appcontroller = nil; 344 345NSLayoutManager* gNSLayoutManager = nil; 346 347WX_NSObject wxApp::OSXCreateAppController() 348{ 349 return [[wxNSAppController alloc] init]; 350} 351 352bool wxApp::DoInitGui() 353{ 354 wxMacAutoreleasePool pool; 355 356 if (!sm_isEmbedded) 357 { 358 [wxNSApplication sharedApplication]; 359 360 appcontroller = OSXCreateAppController(); 361 [[NSApplication sharedApplication] setDelegate:(id <NSApplicationDelegate>)appcontroller]; 362 [NSColor setIgnoresAlpha:NO]; 363 } 364 gNSLayoutManager = [[NSLayoutManager alloc] init]; 365 366 // This call makes it so that appplication:openFile: doesn't get bogus calls 367 // from Cocoa doing its own parsing of the argument string. And yes, we need 368 // to use a string with a boolean value in it. That's just how it works. 369 [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"]; 370 371 return true; 372} 373 374bool wxApp::CallOnInit() 375{ 376 wxMacAutoreleasePool autoreleasepool; 377 m_onInitResult = false; 378 m_inited = false; 379 380 if ( !sm_isEmbedded ) 381 { 382 // Feed the upcoming event loop with a dummy event. Without this, 383 // [NSApp run] below wouldn't return, as we expect it to, if the 384 // application was launched without being activated and would block 385 // until the dock icon was clicked - delaying OnInit() call too. 386 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined 387 location:NSMakePoint(0.0, 0.0) 388 modifierFlags:0 389 timestamp:0 390 windowNumber:0 391 context:nil 392 subtype:0 data1:0 data2:0]; 393 [NSApp postEvent:event atStart:FALSE]; 394 [NSApp run]; 395 } 396 397 m_onInitResult = OnInit(); 398 m_inited = true; 399 if ( !sm_isEmbedded && m_onInitResult ) 400 { 401 if ( m_openFiles.GetCount() > 0 ) 402 MacOpenFiles(m_openFiles); 403 else if ( m_printFiles.GetCount() > 0 ) 404 MacPrintFiles(m_printFiles); 405 else if ( m_getURL.Len() > 0 ) 406 MacOpenURL(m_getURL); 407 else 408 MacNewFile(); 409 } 410 return m_onInitResult; 411} 412 413void wxApp::DoCleanUp() 414{ 415 if ( appcontroller != nil ) 416 { 417 [NSApp setDelegate:nil]; 418 [appcontroller release]; 419 appcontroller = nil; 420 } 421 if ( gNSLayoutManager != nil ) 422 { 423 [gNSLayoutManager release]; 424 gNSLayoutManager = nil; 425 } 426} 427 428void wxApp::OSXEnableAutomaticTabbing(bool enable) 429{ 430 // Automatic tabbing was first introduced in 10.12 431#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 432 if ( WX_IS_MACOS_AVAILABLE(10, 12) ) 433 { 434 [NSWindow setAllowsAutomaticWindowTabbing:enable]; 435 } 436#endif // macOS 10.12+ 437} 438 439extern // used from src/osx/core/display.cpp 440wxRect wxOSXGetMainDisplayClientArea() 441{ 442 NSRect displayRect = [wxOSXGetMenuScreen() visibleFrame]; 443 return wxFromNSRect( NULL, displayRect ); 444} 445 446static NSScreen* wxOSXGetScreenFromDisplay( CGDirectDisplayID ID) 447{ 448 for (NSScreen* screen in [NSScreen screens]) 449 { 450 CGDirectDisplayID displayID = [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; 451 if ( displayID == ID ) 452 return screen; 453 } 454 return NULL; 455} 456 457extern // used from src/osx/core/display.cpp 458wxRect wxOSXGetDisplayClientArea(CGDirectDisplayID ID) 459{ 460 NSRect displayRect = [wxOSXGetScreenFromDisplay(ID) visibleFrame]; 461 return wxFromNSRect( NULL, displayRect ); 462} 463 464void wxGetMousePosition( int* x, int* y ) 465{ 466 wxPoint pt = wxFromNSPoint(NULL, [NSEvent mouseLocation]); 467 if ( x ) 468 *x = pt.x; 469 if ( y ) 470 *y = pt.y; 471}; 472 473wxMouseState wxGetMouseState() 474{ 475 wxMouseState ms; 476 477 wxPoint pt = wxGetMousePosition(); 478 ms.SetX(pt.x); 479 ms.SetY(pt.y); 480 481 NSUInteger modifiers = [NSEvent modifierFlags]; 482 NSUInteger buttons = [NSEvent pressedMouseButtons]; 483 484 ms.SetLeftDown( (buttons & 0x01) != 0 ); 485 ms.SetMiddleDown( (buttons & 0x04) != 0 ); 486 ms.SetRightDown( (buttons & 0x02) != 0 ); 487 488 ms.SetRawControlDown(modifiers & NSControlKeyMask); 489 ms.SetShiftDown(modifiers & NSShiftKeyMask); 490 ms.SetAltDown(modifiers & NSAlternateKeyMask); 491 ms.SetControlDown(modifiers & NSCommandKeyMask); 492 493 return ms; 494} 495 496wxTimerImpl* wxGUIAppTraits::CreateTimerImpl(wxTimer *timer) 497{ 498 return new wxOSXTimerImpl(timer); 499} 500 501int gs_wxBusyCursorCount = 0; 502extern wxCursor gMacCurrentCursor; 503wxCursor gMacStoredActiveCursor; 504 505// Set the cursor to the busy cursor for all windows 506void wxBeginBusyCursor(const wxCursor *cursor) 507{ 508 if (gs_wxBusyCursorCount++ == 0) 509 { 510 NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator]; 511 id object; 512 513 while ((object = [enumerator nextObject])) { 514 [(NSWindow*) object disableCursorRects]; 515 } 516 517 gMacStoredActiveCursor = gMacCurrentCursor; 518 cursor->MacInstall(); 519 520 wxSetCursor(*cursor); 521 } 522 //else: nothing to do, already set 523} 524 525// Restore cursor to normal 526void wxEndBusyCursor() 527{ 528 wxCHECK_RET( gs_wxBusyCursorCount > 0, 529 wxT("no matching wxBeginBusyCursor() for wxEndBusyCursor()") ); 530 531 if (--gs_wxBusyCursorCount == 0) 532 { 533 NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator]; 534 id object; 535 536 while ((object = [enumerator nextObject])) { 537 [(NSWindow*) object enableCursorRects]; 538 } 539 540 wxSetCursor(wxNullCursor); 541 542 gMacStoredActiveCursor.MacInstall(); 543 gMacStoredActiveCursor = wxNullCursor; 544 } 545} 546 547// true if we're between the above two calls 548bool wxIsBusy() 549{ 550 return (gs_wxBusyCursorCount > 0); 551} 552 553wxBitmap wxWindowDCImpl::DoGetAsBitmap(const wxRect *subrect) const 554{ 555 // wxScreenDC is derived from wxWindowDC, so a screen dc will 556 // call this method when a Blit is performed with it as a source. 557 if (!m_window) 558 return wxNullBitmap; 559 560 const wxSize bitmapSize(subrect ? subrect->GetSize() : m_window->GetSize()); 561 wxBitmap bitmap; 562 bitmap.CreateScaled(bitmapSize.x, bitmapSize.y, -1, m_contentScaleFactor); 563 564 NSView* view = (NSView*) m_window->GetHandle(); 565 if ( [view isHiddenOrHasHiddenAncestor] == NO ) 566 { 567 // the old implementaiton is not working under 10.15, the new one should work for older systems as well 568 // however the new implementation does not take into account the backgroundViews, and I'm not sure about 569 // until we're 570 // sure the replacement is always better 571 572 bool useOldImplementation = false; 573 NSBitmapImageRep *rep = nil; 574 575 if ( useOldImplementation ) 576 { 577 [view lockFocus]; 578 // we use this method as other methods force a repaint, and this method can be 579 // called from OnPaint, even with the window's paint dc as source (see wxHTMLWindow) 580 rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: [view bounds]]; 581 [view unlockFocus]; 582 583 } 584 else 585 { 586 rep = [view bitmapImageRepForCachingDisplayInRect:[view bounds]]; 587 [view cacheDisplayInRect:[view bounds] toBitmapImageRep:rep]; 588 } 589 590 CGImageRef cgImageRef = (CGImageRef)[rep CGImage]; 591 592 CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); 593 594 // The bitmap created by wxBitmap::CreateScaled() above is scaled, 595 // so we need to adjust the coordinates for it. 596 r.size.width /= m_contentScaleFactor; 597 r.size.height /= m_contentScaleFactor; 598 599 // since our context is upside down we dont use CGContextDrawImage 600 wxMacDrawCGImage( (CGContextRef) bitmap.GetHBITMAP() , &r, cgImageRef ) ; 601 602 if ( useOldImplementation ) 603 [rep release]; 604 } 605 606 return bitmap; 607} 608 609#endif // wxUSE_GUI 610 611