Blender V5.0
GHOST_SystemCocoa.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
10#include "GHOST_EventKey.hh"
11#include "GHOST_EventString.hh"
13#include "GHOST_EventWheel.hh"
14#include "GHOST_TimerManager.hh"
15#include "GHOST_TimerTask.hh"
16#include "GHOST_WindowCocoa.hh"
18
19/* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
20 * solvable in a short term. */
21#ifdef __clang__
22# pragma clang diagnostic ignored "-Wdeprecated-declarations"
23#endif
24
25#ifdef WITH_METAL_BACKEND
26# include "GHOST_ContextMTL.hh"
27#endif
28
29#ifdef WITH_VULKAN_BACKEND
30# include "GHOST_ContextVK.hh"
31#endif
32
33#ifdef WITH_INPUT_NDOF
35#endif
36
37#include "AssertMacros.h"
38
39#import <Cocoa/Cocoa.h>
40
41/* For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible) */
42#include <Carbon/Carbon.h>
43
44#include <sys/sysctl.h>
45#include <sys/time.h>
46#include <sys/types.h>
47
48#include <mach/mach_time.h>
49
50/* --------------------------------------------------------------------
51 * Keymaps, mouse converters.
52 */
53
54static GHOST_TButton convertButton(int button)
55{
56 switch (button) {
57 case 0:
59 case 1:
61 case 2:
63 case 3:
65 case 4:
67 case 5:
69 case 6:
71 default:
73 }
74}
75
83static GHOST_TKey convertKey(int rawCode, unichar recvChar)
84{
85 // printf("\nrecvchar %c 0x%x",recvChar,recvChar);
86 switch (rawCode) {
87 /* Physical key-codes: (not used due to map changes in international keyboards). */
88#if 0
89 case kVK_ANSI_A: return GHOST_kKeyA;
90 case kVK_ANSI_B: return GHOST_kKeyB;
91 case kVK_ANSI_C: return GHOST_kKeyC;
92 case kVK_ANSI_D: return GHOST_kKeyD;
93 case kVK_ANSI_E: return GHOST_kKeyE;
94 case kVK_ANSI_F: return GHOST_kKeyF;
95 case kVK_ANSI_G: return GHOST_kKeyG;
96 case kVK_ANSI_H: return GHOST_kKeyH;
97 case kVK_ANSI_I: return GHOST_kKeyI;
98 case kVK_ANSI_J: return GHOST_kKeyJ;
99 case kVK_ANSI_K: return GHOST_kKeyK;
100 case kVK_ANSI_L: return GHOST_kKeyL;
101 case kVK_ANSI_M: return GHOST_kKeyM;
102 case kVK_ANSI_N: return GHOST_kKeyN;
103 case kVK_ANSI_O: return GHOST_kKeyO;
104 case kVK_ANSI_P: return GHOST_kKeyP;
105 case kVK_ANSI_Q: return GHOST_kKeyQ;
106 case kVK_ANSI_R: return GHOST_kKeyR;
107 case kVK_ANSI_S: return GHOST_kKeyS;
108 case kVK_ANSI_T: return GHOST_kKeyT;
109 case kVK_ANSI_U: return GHOST_kKeyU;
110 case kVK_ANSI_V: return GHOST_kKeyV;
111 case kVK_ANSI_W: return GHOST_kKeyW;
112 case kVK_ANSI_X: return GHOST_kKeyX;
113 case kVK_ANSI_Y: return GHOST_kKeyY;
114 case kVK_ANSI_Z: return GHOST_kKeyZ;
115#endif
116 /* Numbers keys: mapped to handle some international keyboard (e.g. French). */
117 case kVK_ANSI_1:
118 return GHOST_kKey1;
119 case kVK_ANSI_2:
120 return GHOST_kKey2;
121 case kVK_ANSI_3:
122 return GHOST_kKey3;
123 case kVK_ANSI_4:
124 return GHOST_kKey4;
125 case kVK_ANSI_5:
126 return GHOST_kKey5;
127 case kVK_ANSI_6:
128 return GHOST_kKey6;
129 case kVK_ANSI_7:
130 return GHOST_kKey7;
131 case kVK_ANSI_8:
132 return GHOST_kKey8;
133 case kVK_ANSI_9:
134 return GHOST_kKey9;
135 case kVK_ANSI_0:
136 return GHOST_kKey0;
137
138 case kVK_ANSI_Keypad0:
139 return GHOST_kKeyNumpad0;
140 case kVK_ANSI_Keypad1:
141 return GHOST_kKeyNumpad1;
142 case kVK_ANSI_Keypad2:
143 return GHOST_kKeyNumpad2;
144 case kVK_ANSI_Keypad3:
145 return GHOST_kKeyNumpad3;
146 case kVK_ANSI_Keypad4:
147 return GHOST_kKeyNumpad4;
148 case kVK_ANSI_Keypad5:
149 return GHOST_kKeyNumpad5;
150 case kVK_ANSI_Keypad6:
151 return GHOST_kKeyNumpad6;
152 case kVK_ANSI_Keypad7:
153 return GHOST_kKeyNumpad7;
154 case kVK_ANSI_Keypad8:
155 return GHOST_kKeyNumpad8;
156 case kVK_ANSI_Keypad9:
157 return GHOST_kKeyNumpad9;
158 case kVK_ANSI_KeypadDecimal:
160 case kVK_ANSI_KeypadEnter:
162 case kVK_ANSI_KeypadPlus:
164 case kVK_ANSI_KeypadMinus:
166 case kVK_ANSI_KeypadMultiply:
168 case kVK_ANSI_KeypadDivide:
170 case kVK_ANSI_KeypadClear:
171 return GHOST_kKeyUnknown;
172
173 case kVK_F1:
174 return GHOST_kKeyF1;
175 case kVK_F2:
176 return GHOST_kKeyF2;
177 case kVK_F3:
178 return GHOST_kKeyF3;
179 case kVK_F4:
180 return GHOST_kKeyF4;
181 case kVK_F5:
182 return GHOST_kKeyF5;
183 case kVK_F6:
184 return GHOST_kKeyF6;
185 case kVK_F7:
186 return GHOST_kKeyF7;
187 case kVK_F8:
188 return GHOST_kKeyF8;
189 case kVK_F9:
190 return GHOST_kKeyF9;
191 case kVK_F10:
192 return GHOST_kKeyF10;
193 case kVK_F11:
194 return GHOST_kKeyF11;
195 case kVK_F12:
196 return GHOST_kKeyF12;
197 case kVK_F13:
198 return GHOST_kKeyF13;
199 case kVK_F14:
200 return GHOST_kKeyF14;
201 case kVK_F15:
202 return GHOST_kKeyF15;
203 case kVK_F16:
204 return GHOST_kKeyF16;
205 case kVK_F17:
206 return GHOST_kKeyF17;
207 case kVK_F18:
208 return GHOST_kKeyF18;
209 case kVK_F19:
210 return GHOST_kKeyF19;
211 case kVK_F20:
212 return GHOST_kKeyF20;
213
214 case kVK_UpArrow:
215 return GHOST_kKeyUpArrow;
216 case kVK_DownArrow:
217 return GHOST_kKeyDownArrow;
218 case kVK_LeftArrow:
219 return GHOST_kKeyLeftArrow;
220 case kVK_RightArrow:
222
223 case kVK_Return:
224 return GHOST_kKeyEnter;
225 case kVK_Delete:
226 return GHOST_kKeyBackSpace;
227 case kVK_ForwardDelete:
228 return GHOST_kKeyDelete;
229 case kVK_Escape:
230 return GHOST_kKeyEsc;
231 case kVK_Tab:
232 return GHOST_kKeyTab;
233 case kVK_Space:
234 return GHOST_kKeySpace;
235
236 case kVK_Home:
237 return GHOST_kKeyHome;
238 case kVK_End:
239 return GHOST_kKeyEnd;
240 case kVK_PageUp:
241 return GHOST_kKeyUpPage;
242 case kVK_PageDown:
243 return GHOST_kKeyDownPage;
244#if 0
245 /* These constants with "ANSI" in the name are labeled according to the key position on an
246 * ANSI-standard US keyboard. Therefore they may not match the physical key label on other
247 * keyboard layouts. */
248 case kVK_ANSI_Minus: return GHOST_kKeyMinus;
249 case kVK_ANSI_Equal: return GHOST_kKeyEqual;
250 case kVK_ANSI_Comma: return GHOST_kKeyComma;
251 case kVK_ANSI_Period: return GHOST_kKeyPeriod;
252 case kVK_ANSI_Slash: return GHOST_kKeySlash;
253 case kVK_ANSI_Semicolon: return GHOST_kKeySemicolon;
254 case kVK_ANSI_Quote: return GHOST_kKeyQuote;
255 case kVK_ANSI_Backslash: return GHOST_kKeyBackslash;
256 case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
257 case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
258 case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;
259 case kVK_ISO_Section: return GHOST_kKeyUnknown;
260#endif
261 case kVK_VolumeUp:
262 case kVK_VolumeDown:
263 case kVK_Mute:
264 return GHOST_kKeyUnknown;
265
266 default: {
267 /* Alphanumerical or punctuation key that is remappable in international keyboards. */
268 if ((recvChar >= 'A') && (recvChar <= 'Z')) {
269 return (GHOST_TKey)(recvChar - 'A' + GHOST_kKeyA);
270 }
271
272 if ((recvChar >= 'a') && (recvChar <= 'z')) {
273 return (GHOST_TKey)(recvChar - 'a' + GHOST_kKeyA);
274 }
275 else {
276 /* Leopard and Snow Leopard 64bit compatible API. */
277 const TISInputSourceRef kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
278 /* The keyboard layout. */
279 const CFDataRef uchrHandle = static_cast<CFDataRef>(
280 TISGetInputSourceProperty(kbdTISHandle, kTISPropertyUnicodeKeyLayoutData));
281 CFRelease(kbdTISHandle);
282
283 /* Get actual character value of the "remappable" keys in international keyboards,
284 * if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
285 * then fall back on using the received #charactersIgnoringModifiers. */
286 if (uchrHandle) {
287 UInt32 deadKeyState = 0;
288 UniCharCount actualStrLength = 0;
289
290 UCKeyTranslate((UCKeyboardLayout *)CFDataGetBytePtr(uchrHandle),
291 rawCode,
292 kUCKeyActionDown,
293 0,
294 LMGetKbdType(),
295 kUCKeyTranslateNoDeadKeysMask,
296 &deadKeyState,
297 1,
298 &actualStrLength,
299 &recvChar);
300 }
301
302 switch (recvChar) {
303 case '-':
304 return GHOST_kKeyMinus;
305 case '+':
306 return GHOST_kKeyPlus;
307 case '=':
308 return GHOST_kKeyEqual;
309 case ',':
310 return GHOST_kKeyComma;
311 case '.':
312 return GHOST_kKeyPeriod;
313 case '/':
314 return GHOST_kKeySlash;
315 case ';':
316 return GHOST_kKeySemicolon;
317 case '\'':
318 return GHOST_kKeyQuote;
319 case '\\':
320 return GHOST_kKeyBackslash;
321 case '[':
323 case ']':
325 case '`':
326 case '<': /* The position of '`' is equivalent to this symbol in the French layout. */
328 default:
329 return GHOST_kKeyUnknown;
330 }
331 }
332 }
333 }
334 return GHOST_kKeyUnknown;
335}
336
337/* --------------------------------------------------------------------
338 * Utility functions.
339 */
340
341#define FIRSTFILEBUFLG 512
342static bool g_hasFirstFile = false;
344
345/* TODO: Need to investigate this.
346 * Function called too early in creator.c to have g_hasFirstFile == true */
348{
349 if (g_hasFirstFile) {
350 memcpy(buf, g_firstFileBuf, FIRSTFILEBUFLG);
351 buf[FIRSTFILEBUFLG - 1] = '\0';
352 return 1;
353 }
354 return 0;
355}
356
357/* --------------------------------------------------------------------
358 * Cocoa objects.
359 */
360
365@interface CocoaAppDelegate : NSObject <NSApplicationDelegate>
366
367@property(nonatomic, readonly, assign) GHOST_SystemCocoa *systemCocoa;
368
369- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)systemCocoa;
370- (void)dealloc;
371- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
372- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
373- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
374- (void)applicationWillTerminate:(NSNotification *)aNotification;
375- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
376- (void)toggleFullScreen:(NSNotification *)notification;
377- (void)windowWillClose:(NSNotification *)notification;
378
379- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app;
380
381@end
382
383@implementation CocoaAppDelegate : NSObject
384
385@synthesize systemCocoa = system_cocoa_;
386
387- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)systemCocoa
388{
389 self = [super init];
390
391 if (self) {
392 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
393 [center addObserver:self
394 selector:@selector(windowWillClose:)
395 name:NSWindowWillCloseNotification
396 object:nil];
397 system_cocoa_ = systemCocoa;
398 }
399
400 return self;
401}
402
403- (void)dealloc
404{
405 @autoreleasepool {
406 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
407 [center removeObserver:self name:NSWindowWillCloseNotification object:nil];
408 [super dealloc];
409 }
410}
411
412- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
413{
414 if (system_cocoa_->window_focus_) {
415 /* Raise application to front, convenient when starting from the terminal
416 * and important for launching the animation player. we call this after the
417 * application finishes launching, as doing it earlier can make us end up
418 * with a front-most window but an inactive application. */
419 [NSApp activateIgnoringOtherApps:YES];
420 }
421
422 [NSEvent setMouseCoalescingEnabled:NO];
423}
424
425- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
426{
427 return system_cocoa_->handleOpenDocumentRequest(filename);
428}
429
430- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
431{
432 /* TODO: implement graceful termination through Cocoa mechanism
433 * to avoid session log off to be canceled. */
434 /* Note that Command-Q is already handled by key-handler. */
435 system_cocoa_->handleQuitRequest();
436 return NSTerminateCancel;
437}
438
439/* To avoid canceling a log off process, we must use Cocoa termination process
440 * And this function is the only chance to perform clean up
441 * So WM_exit needs to be called directly, as the event loop will never run before termination. */
442- (void)applicationWillTerminate:(NSNotification *)aNotification
443{
444#if 0
445 WM_exit(C, EXIT_SUCCESS);
446#endif
447}
448
449- (void)applicationWillBecomeActive:(NSNotification *)aNotification
450{
451 system_cocoa_->handleApplicationBecomeActiveEvent();
452}
453
454- (void)toggleFullScreen:(NSNotification *)notification
455{
456}
457
458/* The purpose of this function is to make sure closing "About" window does not
459 * leave Blender with no key windows. This is needed due to a custom event loop
460 * nature of the application: for some reason only using [NSApp run] will ensure
461 * correct behavior in this case.
462 *
463 * This is similar to an issue solved in SDL:
464 * https://bugzilla.libsdl.org/show_bug.cgi?id=1825
465 *
466 * Our solution is different, since we want Blender to keep track of what is
467 * the key window during normal operation. In order to do so we exploit the
468 * fact that "About" window is never in the orderedWindows array: we only force
469 * key window from here if the closing one is not in the orderedWindows. This
470 * saves lack of key windows when closing "About", but does not interfere with
471 * Blender's window manager when closing Blender's windows.
472 *
473 * NOTE: It also receives notifiers when menus are closed on macOS 14.
474 * Presumably it considers menus to be windows. */
475- (void)windowWillClose:(NSNotification *)notification
476{
477 @autoreleasepool {
478 NSWindow *closing_window = (NSWindow *)[notification object];
479
480 if (![closing_window isKeyWindow]) {
481 /* If the window wasn't key then its either none of the windows are key or another window
482 * is a key. The former situation is a bit strange, but probably forcing a key window is not
483 * something desirable. The latter situation is when we definitely do not want to change the
484 * key window.
485 *
486 * Ignoring non-key windows also avoids the code which ensures ordering below from running
487 * when the notifier is received for menus on macOS 14. */
488 return;
489 }
490
491 const NSInteger index = [[NSApp orderedWindows] indexOfObject:closing_window];
492 if (index != NSNotFound) {
493 return;
494 }
495 /* Find first suitable window from the current space. */
496 for (NSWindow *current_window in [NSApp orderedWindows]) {
497 if (current_window == closing_window) {
498 continue;
499 }
500 if (current_window.isOnActiveSpace && current_window.canBecomeKeyWindow) {
501 [current_window makeKeyAndOrderFront:nil];
502 return;
503 }
504 }
505 /* If that didn't find any windows, we try to find any suitable window of the application. */
506 for (NSNumber *window_number in [NSWindow windowNumbersWithOptions:0]) {
507 NSWindow *current_window = [NSApp windowWithWindowNumber:[window_number integerValue]];
508 if (current_window == closing_window) {
509 continue;
510 }
511 if ([current_window canBecomeKeyWindow]) {
512 [current_window makeKeyAndOrderFront:nil];
513 return;
514 }
515 }
516 }
517}
518
519/* Explicitly opt-in to the secure coding for the restorable state.
520 *
521 * This is something that only has affect on macOS 12+, and is implicitly
522 * enabled on macOS 14.
523 *
524 * For the details see
525 * https://sector7.computest.nl/post/2022-08-process-injection-breaking-all-macos-security-layers-with-a-single-vulnerability/
526 */
527- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app
528{
529 return YES;
530}
531
532@end
533
534/* --------------------------------------------------------------------
535 * Initialization / Finalization.
536 */
537
549
551{
552 /* The application delegate integrates the Cocoa application with the GHOST system.
553 *
554 * Since the GHOST system is about to be fully destroyed release the application delegate as
555 * well, so it does not point back to a freed system, forcing the delegate to be created with the
556 * new GHOST system in init(). */
557 @autoreleasepool {
558 CocoaAppDelegate *appDelegate = (CocoaAppDelegate *)[NSApp delegate];
559 if (appDelegate) {
560 [NSApp setDelegate:nil];
561 [appDelegate release];
562 }
563 }
564}
565
567{
569 if (success) {
570
571#ifdef WITH_INPUT_NDOF
572 ndof_manager_ = new GHOST_NDOFManagerCocoa(*this);
573#endif
574
575 // ProcessSerialNumber psn;
576
577 /* Carbon stuff to move window & menu to foreground. */
578#if 0
579 if (!GetCurrentProcess(&psn)) {
580 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
581 SetFrontProcess(&psn);
582 }
583#endif
584
585 @autoreleasepool {
586 [NSApplication sharedApplication]; /* initializes `NSApp`. */
587
588 if ([NSApp mainMenu] == nil) {
589 NSMenu *mainMenubar = [[NSMenu alloc] init];
590 NSMenuItem *menuItem;
591 NSMenu *windowMenu;
592 NSMenu *appMenu;
593
594 /* Create the application menu. */
595 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
596
597 [appMenu addItemWithTitle:@"About Blender"
598 action:@selector(orderFrontStandardAboutPanel:)
599 keyEquivalent:@""];
600 [appMenu addItem:[NSMenuItem separatorItem]];
601
602 menuItem = [appMenu addItemWithTitle:@"Hide Blender"
603 action:@selector(hide:)
604 keyEquivalent:@"h"];
605 menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
606
607 menuItem = [appMenu addItemWithTitle:@"Hide Others"
608 action:@selector(hideOtherApplications:)
609 keyEquivalent:@"h"];
610 menuItem.keyEquivalentModifierMask = (NSEventModifierFlagOption |
611 NSEventModifierFlagCommand);
612
613 [appMenu addItemWithTitle:@"Show All"
614 action:@selector(unhideAllApplications:)
615 keyEquivalent:@""];
616
617 menuItem = [appMenu addItemWithTitle:@"Quit Blender"
618 action:@selector(terminate:)
619 keyEquivalent:@"q"];
620 menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
621
622 menuItem = [[NSMenuItem alloc] init];
623 menuItem.submenu = appMenu;
624
625 [mainMenubar addItem:menuItem];
626 [menuItem release];
627 [appMenu release];
628
629 /* Create the window menu. */
630 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
631
632 menuItem = [windowMenu addItemWithTitle:@"Minimize"
633 action:@selector(performMiniaturize:)
634 keyEquivalent:@"m"];
635 menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
636
637 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
638
639 menuItem = [windowMenu addItemWithTitle:@"Enter Full Screen"
640 action:@selector(toggleFullScreen:)
641 keyEquivalent:@"f"];
642 menuItem.keyEquivalentModifierMask = NSEventModifierFlagControl |
643 NSEventModifierFlagCommand;
644
645 menuItem = [windowMenu addItemWithTitle:@"Close"
646 action:@selector(performClose:)
647 keyEquivalent:@"w"];
648 menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
649
650 menuItem = [[NSMenuItem alloc] init];
651 menuItem.submenu = windowMenu;
652
653 [mainMenubar addItem:menuItem];
654 [menuItem release];
655
656 [NSApp setMainMenu:mainMenubar];
657 [NSApp setWindowsMenu:windowMenu];
658 [windowMenu release];
659 }
660
661 if ([NSApp delegate] == nil) {
662 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] initWithSystemCocoa:this];
663 [NSApp setDelegate:appDelegate];
664 }
665
666 /* AppKit provides automatic window tabbing. Blender is a single-tabbed
667 * application without a macOS tab bar, and should explicitly opt-out of this.
668 * This is also controlled by the macOS user default #NSWindowTabbingEnabled. */
669 NSWindow.allowsAutomaticWindowTabbing = NO;
670
671 [NSApp finishLaunching];
672 }
673 }
674 return success;
675}
676
677/* --------------------------------------------------------------------
678 * Window management.
679 */
680
682{
683 /* For comparing to NSEvent timestamp, this particular API function matches. */
684 return (uint64_t)([[NSProcessInfo processInfo] systemUptime] * 1000);
685}
686
688{
689 @autoreleasepool {
690 return [[NSScreen screens] count];
691 }
692}
693
694void GHOST_SystemCocoa::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
695{
696 @autoreleasepool {
697 /* Get visible frame, that is frame excluding dock and top menu bar. */
698 const NSRect frame = [GHOST_WindowCocoa::getPrimaryScreen() visibleFrame];
699
700 /* Returns max window contents (excluding title bar...). */
701 const NSRect contentRect = [NSWindow
702 contentRectForFrameRect:frame
703 styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
704 NSWindowStyleMaskMiniaturizable)];
705
706 width = contentRect.size.width;
707 height = contentRect.size.height;
708 }
709}
710
711void GHOST_SystemCocoa::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
712{
713 /* TODO! */
714 getMainDisplayDimensions(width, height);
715}
716
719 int32_t top,
720 uint32_t width,
721 uint32_t height,
723 GHOST_GPUSettings gpu_settings,
724 const bool /*exclusive*/,
725 const bool is_dialog,
726 const GHOST_IWindow *parent_window)
727{
728 const GHOST_ContextParams context_params = GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(gpu_settings);
729 GHOST_IWindow *window = nullptr;
730 @autoreleasepool {
731 /* Get the available rect for including window contents. */
732 const NSRect primaryScreenFrame = [GHOST_WindowCocoa::getPrimaryScreen() visibleFrame];
733 const NSRect primaryScreenContentRect = [NSWindow
734 contentRectForFrameRect:primaryScreenFrame
735 styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
736 NSWindowStyleMaskMiniaturizable)];
737
738 const int32_t bottom = primaryScreenContentRect.size.height - top - height;
739
740 window = new GHOST_WindowCocoa(this,
741 title,
742 left,
743 bottom,
744 width,
745 height,
746 state,
747 gpu_settings.context_type,
748 context_params,
749 is_dialog,
750 (GHOST_WindowCocoa *)parent_window,
751 gpu_settings.preferred_device);
752
753 if (window->getValid()) {
754 /* Store the pointer to the window. */
755 GHOST_ASSERT(window_manager_, "window_manager_ not initialized");
756 window_manager_->addWindow(window);
757 window_manager_->setActiveWindow(window);
758 /* Need to tell window manager the new window is the active one
759 * (Cocoa does not send the event activate upon window creation). */
762 }
763 else {
764 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
765 delete window;
766 window = nullptr;
767 }
768 }
769 return window;
770}
771
778{
779 const GHOST_ContextParams context_params_offscreen =
781
782 switch (gpu_settings.context_type) {
783#ifdef WITH_VULKAN_BACKEND
784 case GHOST_kDrawingContextTypeVulkan: {
785 GHOST_Context *context = new GHOST_ContextVK(
786 context_params_offscreen, nullptr, 1, 2, gpu_settings.preferred_device);
787 if (context->initializeDrawingContext()) {
788 return context;
789 }
790 delete context;
791 return nullptr;
792 }
793#endif
794
795#ifdef WITH_METAL_BACKEND
796 case GHOST_kDrawingContextTypeMetal: {
797 GHOST_Context *context = new GHOST_ContextMTL(context_params_offscreen, nullptr, nullptr);
798 if (context->initializeDrawingContext()) {
799 return context;
800 }
801 delete context;
802 return nullptr;
803 }
804#endif
805
806 default:
807 /* Unsupported backend. */
808 return nullptr;
809 }
810}
811
818{
819 delete context;
820
821 return GHOST_kSuccess;
822}
823
825{
826 const NSPoint scr_co = NSMakePoint(x, y);
827
828 @autoreleasepool {
829 const int windowNumberAtPoint = [NSWindow windowNumberAtPoint:scr_co
830 belowWindowWithWindowNumber:0];
831 NSWindow *nswindow = [NSApp windowWithWindowNumber:windowNumberAtPoint];
832
833 if (nswindow == nil) {
834 return nil;
835 }
836
837 return window_manager_->getWindowAssociatedWithOSWindow((const void *)nswindow);
838 }
839}
840
845{
846 const NSPoint mouseLoc = [NSEvent mouseLocation];
847
848 /* Returns the mouse location in screen coordinates. */
849 x = int32_t(mouseLoc.x);
850 y = int32_t(mouseLoc.y);
851 return GHOST_kSuccess;
852}
853
858{
859 GHOST_WindowCocoa *window = (GHOST_WindowCocoa *)window_manager_->getActiveWindow();
860 if (!window) {
861 return GHOST_kFailure;
862 }
863
864 /* Cursor and mouse dissociation placed here not to interfere with continuous grab
865 * (in cont. grab setMouseCursorPosition is directly called). */
866 CGAssociateMouseAndMouseCursorPosition(false);
868 CGAssociateMouseAndMouseCursorPosition(true);
869
870 /* Force mouse move event (not pushed by Cocoa). */
874
875 return GHOST_kSuccess;
876}
877
879{
880 @autoreleasepool {
881 NSColorSampler *sampler = [[NSColorSampler alloc] init];
882 __block BOOL selectCompleted = NO;
883 __block BOOL samplingSucceeded = NO;
884
885 [sampler showSamplerWithSelectionHandler:^(NSColor *selectedColor) {
886 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
887 dispatch_get_main_queue(),
888 ^{
889 if (selectedColor != nil) {
890 NSColor *rgbColor = [selectedColor
891 colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
892 if (rgbColor) {
893 r_color[0] = [rgbColor redComponent];
894 r_color[1] = [rgbColor greenComponent];
895 r_color[2] = [rgbColor blueComponent];
896 }
897 samplingSucceeded = YES;
898 }
899 selectCompleted = YES;
900 });
901 }];
902
903 while (!selectCompleted) {
904 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
905 beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
906 }
907
908 return samplingSucceeded ? GHOST_kSuccess : GHOST_kFailure;
909 }
910}
911
913{
914 float xf = float(x), yf = float(y);
915 GHOST_WindowCocoa *window = (GHOST_WindowCocoa *)window_manager_->getActiveWindow();
916 if (!window) {
917 return GHOST_kFailure;
918 }
919
920 @autoreleasepool {
921 NSScreen *windowScreen = window->getScreen();
922 const NSRect screenRect = windowScreen.frame;
923
924 /* Set position relative to current screen. */
925 xf -= screenRect.origin.x;
926 yf -= screenRect.origin.y;
927
928 /* Quartz Display Services uses the old coordinates (top left origin). */
929 yf = screenRect.size.height - yf;
930
931 CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription]
932 objectForKey:@"NSScreenNumber"] unsignedIntValue],
933 CGPointMake(xf, yf));
934
935 /* See https://stackoverflow.com/a/17559012. By default, hardware events
936 * will be suppressed for 500ms after a synthetic mouse event. For unknown
937 * reasons CGEventSourceSetLocalEventsSuppressionInterval does not work,
938 * however calling CGAssociateMouseAndMouseCursorPosition also removes the
939 * delay, even if this is undocumented. */
940 CGAssociateMouseAndMouseCursorPosition(true);
941 }
942 return GHOST_kSuccess;
943}
944
946{
947 keys.set(GHOST_kModifierKeyLeftOS, (modifier_mask_ & NSEventModifierFlagCommand) ? true : false);
948 keys.set(GHOST_kModifierKeyLeftAlt, (modifier_mask_ & NSEventModifierFlagOption) ? true : false);
950 (modifier_mask_ & NSEventModifierFlagShift) ? true : false);
952 (modifier_mask_ & NSEventModifierFlagControl) ? true : false);
953
954 return GHOST_kSuccess;
955}
956
958{
959 const UInt32 button_state = GetCurrentEventButtonState();
960
961 buttons.clear();
962 buttons.set(GHOST_kButtonMaskLeft, button_state & (1 << 0));
963 buttons.set(GHOST_kButtonMaskRight, button_state & (1 << 1));
964 buttons.set(GHOST_kButtonMaskMiddle, button_state & (1 << 2));
965 buttons.set(GHOST_kButtonMaskButton4, button_state & (1 << 3));
966 buttons.set(GHOST_kButtonMaskButton5, button_state & (1 << 4));
967 return GHOST_kSuccess;
968}
969
971{
974 /* NOTE: order the following flags as they they're declared in the source. */
975 ~(
976 /* Cocoa has no support for a primary selection clipboard. */
978 /* Cocoa doesn't define a Hyper modifier key,
979 * it's possible another modifier could be optionally used in it's place. */
981 /* No support yet for RGBA mouse cursors. */
983 /* No support yet for dynamic cursor generation. */
985}
986
987/* --------------------------------------------------------------------
988 * Event handlers.
989 */
990
994bool GHOST_SystemCocoa::processEvents(bool /*waitForEvent*/)
995{
996 bool anyProcessed = false;
997 NSEvent *event;
998
999 /* TODO: implement timer? */
1000#if 0
1001 do {
1002 GHOST_TimerManager* timerMgr = getTimerManager();
1003
1004 if (waitForEvent) {
1005 uint64_t next = timerMgr->nextFireTime();
1006 double timeOut;
1007
1008 if (next == GHOST_kFireTimeNever) {
1009 timeOut = kEventDurationForever;
1010 }
1011 else {
1012 timeOut = (double)(next - getMilliSeconds())/1000.0;
1013 if (timeOut < 0.0) {
1014 timeOut = 0.0;
1015 }
1016 }
1017
1018 ::ReceiveNextEvent(0, nullptr, timeOut, false, &event);
1019 }
1020
1021 if (timerMgr->fireTimers(getMilliSeconds())) {
1022 anyProcessed = true;
1023 }
1024#endif
1025 do {
1026 @autoreleasepool {
1027 event = [NSApp nextEventMatchingMask:NSEventMaskAny
1028 untilDate:[NSDate distantPast]
1029 inMode:NSDefaultRunLoopMode
1030 dequeue:YES];
1031 if (event == nil) {
1032 break;
1033 }
1034
1035 anyProcessed = true;
1036
1037 /* Send event to NSApp to ensure Mac wide events are handled,
1038 * this will send events to BlenderWindow which will call back
1039 * to handleKeyEvent, handleMouseEvent and handleTabletEvent. */
1040
1041 /* There is on special exception for Control+(Shift)+Tab.
1042 * We do not get keyDown events delivered to the view because they are
1043 * special hotkeys to switch between views, so override directly */
1044
1045 if (event.type == NSEventTypeKeyDown && event.keyCode == kVK_Tab &&
1046 (event.modifierFlags & NSEventModifierFlagControl))
1047 {
1048 handleKeyEvent(event);
1049 }
1050 else {
1051 /* For some reason NSApp is swallowing the key up events when modifier
1052 * key is pressed, even if there seems to be no apparent reason to do
1053 * so, as a workaround we always handle these up events. */
1054 if (event.type == NSEventTypeKeyUp &&
1055 (event.modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagOption)))
1056 {
1057 handleKeyEvent(event);
1058 }
1059
1060 [NSApp sendEvent:event];
1061 }
1062 }
1063 } while (event != nil);
1064#if 0
1065 } while (waitForEvent && !anyProcessed); /* Needed only for timer implementation. */
1066#endif
1067
1070 }
1071
1074 return true;
1075 }
1076
1078
1079 return anyProcessed;
1080}
1081
1082/* NOTE: called from #NSApplication delegate. */
1084{
1085 @autoreleasepool {
1086 for (GHOST_IWindow *iwindow : window_manager_->getWindows()) {
1087 GHOST_WindowCocoa *window = (GHOST_WindowCocoa *)iwindow;
1088 if (window->isDialog()) {
1089 [window->getViewWindow() makeKeyAndOrderFront:nil];
1090 }
1091 }
1092
1093 /* Update the modifiers key mask, as its status may have changed when the application
1094 * was not active (that is when update events are sent to another application). */
1095 GHOST_IWindow *window = window_manager_->getActiveWindow();
1096
1097 if (!window) {
1099 return GHOST_kFailure;
1100 }
1101
1103
1104 const unsigned int modifiers = [[[NSApplication sharedApplication] currentEvent]
1105 modifierFlags];
1106
1107 if ((modifiers & NSEventModifierFlagShift) != (modifier_mask_ & NSEventModifierFlagShift)) {
1109 (modifiers & NSEventModifierFlagShift) ? GHOST_kEventKeyDown :
1111 window,
1113 false));
1114 }
1115 if ((modifiers & NSEventModifierFlagControl) != (modifier_mask_ & NSEventModifierFlagControl))
1116 {
1118 (modifiers & NSEventModifierFlagControl) ? GHOST_kEventKeyDown :
1120 window,
1122 false));
1123 }
1124 if ((modifiers & NSEventModifierFlagOption) != (modifier_mask_ & NSEventModifierFlagOption)) {
1126 (modifiers & NSEventModifierFlagOption) ? GHOST_kEventKeyDown :
1128 window,
1130 false));
1131 }
1132 if ((modifiers & NSEventModifierFlagCommand) != (modifier_mask_ & NSEventModifierFlagCommand))
1133 {
1135 (modifiers & NSEventModifierFlagCommand) ? GHOST_kEventKeyDown :
1137 window,
1139 false));
1140 }
1141
1142 modifier_mask_ = modifiers;
1143
1145 }
1146 return GHOST_kSuccess;
1147}
1148
1150{
1151 for (GHOST_IWindow *iwindow : window_manager_->getWindows()) {
1152 GHOST_WindowCocoa *window = (GHOST_WindowCocoa *)iwindow;
1153 if (window->isDialog()) {
1154 return true;
1155 }
1156 }
1157 return false;
1158}
1159
1164
1165/* NOTE: called from #NSWindow delegate. */
1167 GHOST_WindowCocoa *window)
1168{
1169 if (!validWindow(window)) {
1170 return GHOST_kFailure;
1171 }
1172 switch (eventType) {
1175 break;
1177 window_manager_->setActiveWindow(window);
1178 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1180 break;
1182 window_manager_->setWindowInactive(window);
1184 break;
1186 if (native_pixel_) {
1187 window->setNativePixelSize();
1189 }
1191 break;
1194 break;
1197 /* Enforce only one resize message per event loop
1198 * (coalescing all the live resize messages). */
1199 window->updateDrawingContext();
1201 /* Mouse up event is trapped by the resizing event loop,
1202 * so send it anyway to the window manager. */
1205 window,
1208 // ignore_window_sized_messages_ = true;
1209 }
1210 break;
1212
1213 if (native_pixel_) {
1214 window->setNativePixelSize();
1216 }
1217
1218 default:
1219 return GHOST_kFailure;
1220 break;
1221 }
1222
1224 return GHOST_kSuccess;
1225}
1226
1232static NSSize getNSImagePixelSize(NSImage *image)
1233{
1234 /* Assuming the NSImage instance only contains one single image. */
1235 @autoreleasepool {
1236 NSImageRep *imageRepresentation = [[image representations] firstObject];
1237 return NSMakeSize(imageRepresentation.pixelsWide, imageRepresentation.pixelsHigh);
1238 }
1239}
1240
1246static ImBuf *NSImageToImBuf(NSImage *image)
1247{
1248 const NSSize imageSize = getNSImagePixelSize(image);
1249 ImBuf *ibuf = IMB_allocImBuf(imageSize.width, imageSize.height, 32, IB_byte_data);
1250
1251 if (!ibuf) {
1252 return nullptr;
1253 }
1254
1255 @autoreleasepool {
1256 NSBitmapImageRep *bitmapImage = nil;
1257 for (NSImageRep *representation in [image representations]) {
1258 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1259 bitmapImage = (NSBitmapImageRep *)representation;
1260 break;
1261 }
1262 }
1263
1264 if (bitmapImage == nil || bitmapImage.bitsPerPixel != 32 || bitmapImage.isPlanar ||
1265 bitmapImage.bitmapFormat & (NSBitmapFormatAlphaFirst | NSBitmapFormatFloatingPointSamples))
1266 {
1267 return nullptr;
1268 }
1269
1270 uint8_t *ibuf_data = ibuf->byte_buffer.data;
1271 uint8_t *bmp_data = (uint8_t *)bitmapImage.bitmapData;
1272
1273 /* Vertical Flip. */
1274 for (int y = 0; y < imageSize.height; y++) {
1275 const int row_byte_count = 4 * imageSize.width;
1276 const int ibuf_off = (imageSize.height - y - 1) * row_byte_count;
1277 const int bmp_off = y * row_byte_count;
1278 memcpy(ibuf_data + ibuf_off, bmp_data + bmp_off, row_byte_count);
1279 }
1280 }
1281
1282 return ibuf;
1283}
1284
1285/* NOTE: called from #NSWindow subclass. */
1287 GHOST_TDragnDropTypes draggedObjectType,
1288 GHOST_WindowCocoa *window,
1289 int mouseX,
1290 int mouseY,
1291 void *data)
1292{
1293 if (!validWindow(window)) {
1294 return GHOST_kFailure;
1295 }
1296 switch (eventType) {
1300 window->clientToScreenIntern(mouseX, mouseY, mouseX, mouseY);
1302 getMilliSeconds(), eventType, draggedObjectType, window, mouseX, mouseY, nullptr));
1303 break;
1304
1306 if (!data) {
1307 return GHOST_kFailure;
1308 }
1309
1310 GHOST_TDragnDropDataPtr eventData;
1311 @autoreleasepool {
1312 switch (draggedObjectType) {
1314 NSArray *droppedArray = (NSArray *)data;
1315
1316 GHOST_TStringArray *strArray = (GHOST_TStringArray *)malloc(
1317 sizeof(GHOST_TStringArray));
1318 if (!strArray) {
1319 return GHOST_kFailure;
1320 }
1321
1322 strArray->count = droppedArray.count;
1323 if (strArray->count == 0) {
1324 free(strArray);
1325 return GHOST_kFailure;
1326 }
1327
1328 strArray->strings = (uint8_t **)malloc(strArray->count * sizeof(uint8_t *));
1329
1330 for (int i = 0; i < strArray->count; i++) {
1331 NSString *droppedStr = [droppedArray objectAtIndex:i];
1332 const size_t pastedTextSize = [droppedStr
1333 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1334 uint8_t *temp_buff = (uint8_t *)malloc(pastedTextSize + 1);
1335
1336 if (!temp_buff) {
1337 strArray->count = i;
1338 break;
1339 }
1340
1341 memcpy(temp_buff,
1342 [droppedStr cStringUsingEncoding:NSUTF8StringEncoding],
1343 pastedTextSize);
1344 temp_buff[pastedTextSize] = '\0';
1345
1346 strArray->strings[i] = temp_buff;
1347 }
1348
1349 eventData = static_cast<GHOST_TDragnDropDataPtr>(strArray);
1350 break;
1351 }
1353 NSString *droppedStr = (NSString *)data;
1354 const size_t pastedTextSize = [droppedStr
1355 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1356 uint8_t *temp_buff = (uint8_t *)malloc(pastedTextSize + 1);
1357
1358 if (temp_buff == nullptr) {
1359 return GHOST_kFailure;
1360 }
1361
1362 memcpy(
1363 temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1364 temp_buff[pastedTextSize] = '\0';
1365
1366 eventData = static_cast<GHOST_TDragnDropDataPtr>(temp_buff);
1367 break;
1368 }
1370 NSImage *droppedImg = static_cast<NSImage *>(data);
1371 ImBuf *ibuf = NSImageToImBuf(droppedImg);
1372
1373 eventData = static_cast<GHOST_TDragnDropDataPtr>(ibuf);
1374
1375 [droppedImg release];
1376 break;
1377 }
1378 default:
1379 return GHOST_kFailure;
1380 break;
1381 }
1382 }
1383
1384 window->clientToScreenIntern(mouseX, mouseY, mouseX, mouseY);
1386 getMilliSeconds(), eventType, draggedObjectType, window, mouseX, mouseY, eventData));
1387
1388 break;
1389 }
1390 default:
1391 return GHOST_kFailure;
1392 }
1394 return GHOST_kSuccess;
1395}
1396
1398{
1399 GHOST_Window *window = (GHOST_Window *)window_manager_->getActiveWindow();
1400
1401 /* Discard quit event if we are in cursor grab sequence. */
1402 if (window && window->getCursorGrabModeIsWarp()) {
1403 return;
1404 }
1405
1406 /* Push the event to Blender so it can open a dialog if needed. */
1409}
1410
1412{
1413 NSString *filepath = (NSString *)filepathStr;
1414
1415 /* Check for blender opened windows and make the front-most key.
1416 * In case blender is minimized, opened on another desktop space,
1417 * or in full-screen mode. */
1418 @autoreleasepool {
1419 NSArray *windowsList = [NSApp orderedWindows];
1420 if ([windowsList count]) {
1421 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1422 }
1423
1424 GHOST_Window *window = window_manager_->getWindows().empty() ?
1425 nullptr :
1426 (GHOST_Window *)window_manager_->getWindows().front();
1427
1428 if (!window) {
1429 return NO;
1430 }
1431
1432 /* Discard event if we are in cursor grab sequence,
1433 * it'll lead to "stuck cursor" situation if the alert panel is raised. */
1434 if (window && window->getCursorGrabModeIsWarp()) {
1435 return NO;
1436 }
1437
1438 const size_t filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1439 char *temp_buff = (char *)malloc(filenameTextSize + 1);
1440
1441 if (temp_buff == nullptr) {
1442 return GHOST_kFailure;
1443 }
1444
1445 memcpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize);
1446 temp_buff[filenameTextSize] = '\0';
1447
1450 window,
1451 static_cast<GHOST_TEventDataPtr>(temp_buff)));
1452 }
1453 return YES;
1454}
1455
1457{
1458 NSEvent *event = (NSEvent *)eventPtr;
1459
1460 GHOST_IWindow *window = window_manager_->getWindowAssociatedWithOSWindow(
1461 (const void *)event.window);
1462 if (!window) {
1463 // printf("\nW failure for event 0x%x",event.type);
1464 return GHOST_kFailure;
1465 }
1466
1467 GHOST_TabletData &ct = ((GHOST_WindowCocoa *)window)->GetCocoaTabletData();
1468
1469 switch (eventType) {
1470 case NSEventTypeTabletPoint:
1471 /* workaround 2 corner-cases:
1472 * 1. if event.isEnteringProximity was not triggered since program-start.
1473 * 2. device is not sending event.pointingDeviceType, due no eraser. */
1474 if (ct.Active == GHOST_kTabletModeNone) {
1476 }
1477
1478 ct.Pressure = event.pressure;
1479 /* Range: -1 (left) to 1 (right). */
1480 ct.Xtilt = event.tilt.x;
1481 /* On macOS, the y tilt behavior is inverted from what we expect: negative
1482 * meaning a tilt toward the user, positive meaning away from the user.
1483 * Convert to what Blender expects: -1.0 (away from user) to +1.0 (toward user). */
1484 ct.Ytilt = -event.tilt.y;
1485 break;
1486
1487 case NSEventTypeTabletProximity:
1488 /* Reset tablet data when device enters proximity or leaves. */
1490 if (event.isEnteringProximity) {
1491 /* Pointer is entering tablet area proximity. */
1492 switch (event.pointingDeviceType) {
1493 case NSPointingDeviceTypePen:
1495 break;
1496 case NSPointingDeviceTypeEraser:
1498 break;
1499 case NSPointingDeviceTypeCursor:
1500 case NSPointingDeviceTypeUnknown:
1501 default:
1502 break;
1503 }
1504 }
1505 break;
1506
1507 default:
1508 GHOST_ASSERT(FALSE, "GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1509 return GHOST_kFailure;
1510 break;
1511 }
1512 return GHOST_kSuccess;
1513}
1514
1516{
1517 NSEvent *event = (NSEvent *)eventPtr;
1518
1519 switch (event.subtype) {
1520 case NSEventSubtypeTabletPoint:
1521 handleTabletEvent(eventPtr, NSEventTypeTabletPoint);
1522 return true;
1523 case NSEventSubtypeTabletProximity:
1524 handleTabletEvent(eventPtr, NSEventTypeTabletProximity);
1525 return true;
1526 default:
1527 /* No tablet event included: do nothing. */
1528 return false;
1529 }
1530}
1531
1533{
1534 NSEvent *event = (NSEvent *)eventPtr;
1535
1536 /* event.window returns other windows if mouse-over, that's OSX input standard
1537 * however, if mouse exits window(s), the windows become inactive, until you click.
1538 * We then fall back to the active window from ghost. */
1540 ->getWindowAssociatedWithOSWindow((const void *)event.window);
1541 if (!window) {
1542 window = (GHOST_WindowCocoa *)window_manager_->getActiveWindow();
1543 if (!window) {
1544 // printf("\nW failure for event 0x%x", event.type);
1545 return GHOST_kFailure;
1546 }
1547 }
1548
1549 switch (event.type) {
1550 case NSEventTypeLeftMouseDown:
1551 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1552 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1554 window,
1556 window->GetCocoaTabletData()));
1557 break;
1558 case NSEventTypeRightMouseDown:
1559 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1560 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1562 window,
1564 window->GetCocoaTabletData()));
1565 break;
1566 case NSEventTypeOtherMouseDown:
1567 handleTabletEvent(event); /* Handle tablet events combined with mouse events. */
1568 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1570 window,
1571 convertButton(event.buttonNumber),
1572 window->GetCocoaTabletData()));
1573 break;
1574 case NSEventTypeLeftMouseUp:
1575 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1576 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1578 window,
1580 window->GetCocoaTabletData()));
1581 break;
1582 case NSEventTypeRightMouseUp:
1583 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1584 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1586 window,
1588 window->GetCocoaTabletData()));
1589 break;
1590 case NSEventTypeOtherMouseUp:
1591 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1592 pushEvent(new GHOST_EventButton(event.timestamp * 1000,
1594 window,
1595 convertButton(event.buttonNumber),
1596 window->GetCocoaTabletData()));
1597 break;
1598 case NSEventTypeLeftMouseDragged:
1599 case NSEventTypeRightMouseDragged:
1600 case NSEventTypeOtherMouseDragged:
1601 handleTabletEvent(event); /* Update window tablet state to be included in event. */
1602
1603 case NSEventTypeMouseMoved: {
1604 /* TODO: CHECK IF THIS IS A TABLET EVENT */
1605 bool is_tablet = false;
1606
1607 if (window->getCursorGrabModeIsWarp() && !is_tablet) {
1608 /* Wrap cursor at area/window boundaries. */
1610 if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) {
1611 /* Fall back to window bounds. */
1612 window->getClientBounds(bounds);
1613 }
1614
1615 GHOST_Rect corrected_bounds;
1616 /* Wrapping bounds are in window local coordinates, using GHOST (top-left) origin. */
1617 if (window->getCursorGrabMode() == GHOST_kGrabHide) {
1618 /* If the cursor is hidden, use the entire window. */
1619 corrected_bounds.t_ = 0;
1620 corrected_bounds.l_ = 0;
1621
1622 NSWindow *cocoa_window = (NSWindow *)window->getOSWindow();
1623 const NSSize frame_size = cocoa_window.frame.size;
1624
1625 corrected_bounds.b_ = frame_size.height;
1626 corrected_bounds.r_ = frame_size.width;
1627 }
1628 else {
1629 /* If the cursor is visible, use custom grab bounds provided in Cocoa bottom-left origin.
1630 * Flip them to use a GHOST top-left origin. */
1631 GHOST_Rect window_bounds;
1632 window->getClientBounds(window_bounds);
1633 window->screenToClient(bounds.l_, bounds.b_, corrected_bounds.l_, corrected_bounds.t_);
1634 window->screenToClient(bounds.r_, bounds.t_, corrected_bounds.r_, corrected_bounds.b_);
1635
1636 corrected_bounds.b_ = (window_bounds.b_ - window_bounds.t_) - corrected_bounds.b_;
1637 corrected_bounds.t_ = (window_bounds.b_ - window_bounds.t_) - corrected_bounds.t_;
1638 }
1639
1640 /* Clip the grabbing bounds to the current monitor bounds to prevent the cursor from
1641 * getting stuck at the edge of the screen. First compute the visible window frame: */
1642 NSWindow *cocoa_window = (NSWindow *)window->getOSWindow();
1643 NSRect screen_visible_frame = window->getScreen().visibleFrame;
1644 NSRect window_visible_frame = NSIntersectionRect(cocoa_window.frame, screen_visible_frame);
1645 NSRect local_visible_frame = [cocoa_window convertRectFromScreen:window_visible_frame];
1646
1647 GHOST_Rect visible_rect;
1648 visible_rect.l_ = local_visible_frame.origin.x;
1649 visible_rect.t_ = local_visible_frame.origin.y;
1650 visible_rect.r_ = local_visible_frame.origin.x + local_visible_frame.size.width;
1651 visible_rect.b_ = local_visible_frame.origin.y + local_visible_frame.size.height;
1652
1653 /* Then clip the corrected bound using the visible window rect. */
1654 visible_rect.clip(corrected_bounds);
1655
1656 /* Get accumulation from previous mouse warps. */
1657 int32_t x_accum, y_accum;
1658 window->getCursorGrabAccum(x_accum, y_accum);
1659
1660 /* Get the current software mouse pointer location, theoretically unaffected by pending
1661 * events that may still be referring to a location before warping. In practice extra
1662 * logic still need to be used to prevent interferences from stale events. */
1663 const NSPoint mousePos = event.window.mouseLocationOutsideOfEventStream;
1664 /* Casting. */
1665 const int32_t x_mouse = mousePos.x;
1666 const int32_t y_mouse = mousePos.y;
1667
1668 /* Warp mouse cursor if needed. */
1669 int32_t warped_x_mouse = x_mouse;
1670 int32_t warped_y_mouse = y_mouse;
1671 corrected_bounds.wrapPoint(warped_x_mouse, warped_y_mouse, 4, window->getCursorGrabAxis());
1672
1673 /* Set new cursor position. */
1674 if (x_mouse != warped_x_mouse || y_mouse != warped_y_mouse) {
1675 /* After warping, we can still receive unwrapped mouse that occured slightly before or
1676 * after the current event at close timestamps, causing the wrapping to be applied a
1677 * second time, leading to a visual jump. Ignore these events by returning early.
1678 * Using a small empirical future covering threshold, see PR #148158 for details. */
1679 const NSTimeInterval timestamp = event.timestamp;
1680 const NSTimeInterval stale_event_threshold = 0.003;
1681 if (timestamp < (last_warp_timestamp_ + stale_event_threshold)) {
1682 break;
1683 }
1684
1685 int32_t warped_x, warped_y;
1686 window->clientToScreenIntern(warped_x_mouse, warped_y_mouse, warped_x, warped_y);
1687 setMouseCursorPosition(warped_x, warped_y); /* wrap */
1688 window->setCursorGrabAccum(x_accum + (x_mouse - warped_x_mouse),
1689 y_accum + (y_mouse - warped_y_mouse));
1690
1691 /* This is the current time that matches NSEvent timestamp. */
1692 last_warp_timestamp_ = [[NSProcessInfo processInfo] systemUptime];
1693 }
1694
1695 /* Generate event. */
1696 int32_t x, y;
1697 window->clientToScreenIntern(x_mouse + x_accum, y_mouse + y_accum, x, y);
1698 pushEvent(new GHOST_EventCursor(event.timestamp * 1000,
1700 window,
1701 x,
1702 y,
1703 window->GetCocoaTabletData()));
1704 }
1705 else {
1706 /* Normal cursor operation: send mouse position in window. */
1707 const NSPoint mousePos = event.locationInWindow;
1708 int32_t x, y;
1709
1710 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1711 pushEvent(new GHOST_EventCursor(event.timestamp * 1000,
1713 window,
1714 x,
1715 y,
1716 window->GetCocoaTabletData()));
1717 }
1718 break;
1719 }
1720 case NSEventTypeScrollWheel: {
1721 const NSEventPhase momentumPhase = event.momentumPhase;
1722 const NSEventPhase phase = event.phase;
1723
1724 /* when pressing a key while momentum scrolling continues after
1725 * lifting fingers off the trackpad, the action can unexpectedly
1726 * change from e.g. scrolling to zooming. this works around the
1727 * issue by ignoring momentum scroll after a key press */
1728 if (momentumPhase) {
1730 break;
1731 }
1732 }
1733 else {
1735 }
1736
1737 /* we assume phases are only set for gestures from trackpad or magic
1738 * mouse events. note that using tablet at the same time may not work
1739 * since this is a static variable */
1740 if (phase == NSEventPhaseBegan && multitouch_gestures_) {
1741 multi_touch_scroll_ = true;
1742 }
1743 else if (phase == NSEventPhaseEnded) {
1744 multi_touch_scroll_ = false;
1745 }
1746
1747 /* Standard scroll-wheel case, if no swiping happened,
1748 * and no momentum (kinetic scroll) works. */
1749 if (!multi_touch_scroll_ && momentumPhase == NSEventPhaseNone) {
1750 /* Horizontal scrolling. */
1751 if (event.deltaX != 0.0) {
1752 const int32_t delta = event.deltaX > 0.0 ? 1 : -1;
1753 /* On macOS, shift + vertical scroll events will be transformed into shift + horizontal
1754 * events by the OS input layer. Counteract this behavior by transforming them back into
1755 * shift + vertical scroll event. See PR #148122 for more details. */
1756 const GHOST_TEventWheelAxis direction = modifier_mask_ & NSEventModifierFlagShift ?
1759
1760 pushEvent(new GHOST_EventWheel(event.timestamp * 1000, window, direction, delta));
1761 }
1762 /* Vertical scrolling. */
1763 if (event.deltaY != 0.0) {
1764 const int32_t delta = event.deltaY > 0.0 ? 1 : -1;
1766 event.timestamp * 1000, window, GHOST_kEventWheelAxisVertical, delta));
1767 }
1768 }
1769 else {
1770 const NSPoint mousePos = event.locationInWindow;
1771
1772 /* with 10.7 nice scrolling deltas are supported */
1773 double dx = event.scrollingDeltaX;
1774 double dy = event.scrollingDeltaY;
1775
1776 /* However, WACOM tablet (intuos5) needs old deltas,
1777 * it then has momentum and phase at zero. */
1778 if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) {
1779 dx = event.deltaX;
1780 dy = event.deltaY;
1781 }
1782
1783 int32_t x, y;
1784 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1785
1786 BlenderWindow *view_window = (BlenderWindow *)window->getOSWindow();
1787
1788 @autoreleasepool {
1789 const NSPoint delta = [[view_window contentView]
1790 convertPointToBacking:NSMakePoint(dx, dy)];
1791 pushEvent(new GHOST_EventTrackpad(event.timestamp * 1000,
1792 window,
1794 x,
1795 y,
1796 delta.x,
1797 delta.y,
1798 event.isDirectionInvertedFromDevice));
1799 }
1800 }
1801 break;
1802 }
1803 case NSEventTypeMagnify: {
1804 const NSPoint mousePos = event.locationInWindow;
1805 int32_t x, y;
1806 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1807 pushEvent(new GHOST_EventTrackpad(event.timestamp * 1000,
1808 window,
1810 x,
1811 y,
1812 event.magnification * 125.0 + 0.1,
1813 0,
1814 false));
1815 break;
1816 }
1817 case NSEventTypeSmartMagnify: {
1818 const NSPoint mousePos = event.locationInWindow;
1819 int32_t x, y;
1820 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1822 event.timestamp * 1000, window, GHOST_kTrackpadEventSmartMagnify, x, y, 0, 0, false));
1823 break;
1824 }
1825 case NSEventTypeRotate: {
1826 const NSPoint mousePos = event.locationInWindow;
1827 int32_t x, y;
1828 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1829 pushEvent(new GHOST_EventTrackpad(event.timestamp * 1000,
1830 window,
1832 x,
1833 y,
1834 event.rotation * -5.0,
1835 0,
1836 false));
1837 }
1838 default:
1839 return GHOST_kFailure;
1840 break;
1841 }
1842 return GHOST_kSuccess;
1843}
1844
1846{
1847 NSEvent *event = (NSEvent *)eventPtr;
1848 GHOST_IWindow *window = window_manager_->getWindowAssociatedWithOSWindow(
1849 (const void *)event.window);
1850
1851 if (!window) {
1852 // printf("\nW failure for event 0x%x",event.type);
1853 return GHOST_kFailure;
1854 }
1855
1856 switch (event.type) {
1857 case NSEventTypeKeyDown:
1858 case NSEventTypeKeyUp: {
1859 /* Returns an empty string for dead keys. */
1860 GHOST_TKey keyCode;
1861 char utf8_buf[6] = {'\0'};
1862
1863 @autoreleasepool {
1864 NSString *charsIgnoringModifiers = event.charactersIgnoringModifiers;
1865 if (charsIgnoringModifiers.length > 0) {
1866 keyCode = convertKey(event.keyCode, [charsIgnoringModifiers characterAtIndex:0]);
1867 }
1868 else {
1869 keyCode = convertKey(event.keyCode, 0);
1870 }
1871
1872 NSString *characters = event.characters;
1873 if ([characters length] > 0) {
1874 NSData *convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding];
1875
1876 for (int x = 0; x < convertedCharacters.length; x++) {
1877 utf8_buf[x] = ((char *)convertedCharacters.bytes)[x];
1878 }
1879 }
1880 }
1881
1882 /* Arrow keys should not have UTF8. */
1883 if ((keyCode >= GHOST_kKeyLeftArrow) && (keyCode <= GHOST_kKeyDownArrow)) {
1884 utf8_buf[0] = '\0';
1885 }
1886
1887 /* F-keys should not have UTF8. */
1888 if ((keyCode >= GHOST_kKeyF1) && (keyCode <= GHOST_kKeyF20)) {
1889 utf8_buf[0] = '\0';
1890 }
1891
1892 /* no text with command key pressed */
1893 if (modifier_mask_ & NSEventModifierFlagCommand) {
1894 utf8_buf[0] = '\0';
1895 }
1896
1897 if ((keyCode == GHOST_kKeyQ) && (modifier_mask_ & NSEventModifierFlagCommand)) {
1898 break; /* Command-Q is directly handled by Cocoa. */
1899 }
1900
1901 if (event.type == NSEventTypeKeyDown) {
1902 pushEvent(new GHOST_EventKey(event.timestamp * 1000,
1904 window,
1905 keyCode,
1906 event.isARepeat,
1907 utf8_buf));
1908#if 0
1909 printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u utf8=%s\n",
1910 event.keyCode,
1911 charsIgnoringModifiers.length > 0 ? [charsIgnoringModifiers characterAtIndex:0] :
1912 ' ',
1913 keyCode,
1914 utf8_buf);
1915#endif
1916 }
1917 else {
1919 event.timestamp * 1000, GHOST_kEventKeyUp, window, keyCode, false, nullptr));
1920#if 0
1921 printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u utf8=%s\n",
1922 event.keyCode,
1923 charsIgnoringModifiers.length > 0 ? [charsIgnoringModifiers characterAtIndex:0] :
1924 ' ',
1925 keyCode,
1926 utf8_buf);
1927#endif
1928 }
1930 break;
1931 }
1932 case NSEventTypeFlagsChanged: {
1933 const unsigned int modifiers = event.modifierFlags;
1934
1935 if ((modifiers & NSEventModifierFlagShift) != (modifier_mask_ & NSEventModifierFlagShift)) {
1936 pushEvent(new GHOST_EventKey(event.timestamp * 1000,
1937 (modifiers & NSEventModifierFlagShift) ? GHOST_kEventKeyDown :
1939 window,
1941 false));
1942 }
1943 if ((modifiers & NSEventModifierFlagControl) !=
1944 (modifier_mask_ & NSEventModifierFlagControl))
1945 {
1947 event.timestamp * 1000,
1948 (modifiers & NSEventModifierFlagControl) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp,
1949 window,
1951 false));
1952 }
1953 if ((modifiers & NSEventModifierFlagOption) != (modifier_mask_ & NSEventModifierFlagOption))
1954 {
1956 event.timestamp * 1000,
1957 (modifiers & NSEventModifierFlagOption) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp,
1958 window,
1960 false));
1961 }
1962 if ((modifiers & NSEventModifierFlagCommand) !=
1963 (modifier_mask_ & NSEventModifierFlagCommand))
1964 {
1966 event.timestamp * 1000,
1967 (modifiers & NSEventModifierFlagCommand) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp,
1968 window,
1970 false));
1971 }
1972
1973 modifier_mask_ = modifiers;
1975 break;
1976 }
1977
1978 default:
1979 return GHOST_kFailure;
1980 break;
1981 }
1982 return GHOST_kSuccess;
1983}
1984
1985/* --------------------------------------------------------------------
1986 * Clipboard get/set.
1987 */
1988
1989char *GHOST_SystemCocoa::getClipboard(bool /*selection*/) const
1990{
1991 @autoreleasepool {
1992 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1993 NSString *textPasted = [pasteBoard stringForType:NSPasteboardTypeString];
1994
1995 if (textPasted == nil) {
1996 return nullptr;
1997 }
1998
1999 const size_t pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2000
2001 char *temp_buff = (char *)malloc(pastedTextSize + 1);
2002
2003 if (temp_buff == nullptr) {
2004 return nullptr;
2005 }
2006
2007 memcpy(temp_buff, [textPasted cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
2008 temp_buff[pastedTextSize] = '\0';
2009
2010 if (temp_buff) {
2011 return temp_buff;
2012 }
2013 }
2014 return nullptr;
2015}
2016
2017void GHOST_SystemCocoa::putClipboard(const char *buffer, bool selection) const
2018{
2019 if (selection) {
2020 return; /* For copying the selection, used on X11. */
2021 }
2022
2023 @autoreleasepool {
2024 NSPasteboard *pasteBoard = NSPasteboard.generalPasteboard;
2025 [pasteBoard declareTypes:@[ NSPasteboardTypeString ] owner:nil];
2026
2027 NSString *textToCopy = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
2028 [pasteBoard setString:textToCopy forType:NSPasteboardTypeString];
2029 }
2030}
2031
2033{
2034 NSURL *pasteboardImageFile = nil;
2035
2036 @autoreleasepool {
2037 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
2038 NSDictionary *pasteboardFilteringOptions = @{
2039 NSPasteboardURLReadingFileURLsOnlyKey : @YES,
2040 NSPasteboardURLReadingContentsConformToTypesKey : [NSImage imageTypes]
2041 };
2042
2043 NSArray *pasteboardMatches = [pasteboard readObjectsForClasses:@[ [NSURL class] ]
2044 options:pasteboardFilteringOptions];
2045
2046 if (!pasteboardMatches || !pasteboardMatches.count) {
2047 return nil;
2048 }
2049
2050 pasteboardImageFile = [[pasteboardMatches firstObject] copy];
2051 }
2052
2053 return [pasteboardImageFile autorelease];
2054}
2055
2057{
2058 @autoreleasepool {
2059 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
2060 NSArray *supportedTypes = [NSArray
2061 arrayWithObjects:NSPasteboardTypeFileURL, NSPasteboardTypeTIFF, NSPasteboardTypePNG, nil];
2062
2063 NSPasteboardType availableType = [pasteboard availableTypeFromArray:supportedTypes];
2064
2065 if (!availableType) {
2066 return GHOST_kFailure;
2067 }
2068
2069 /* If we got a file, ensure it's an image file. */
2070 if ([pasteboard availableTypeFromArray:@[ NSPasteboardTypeFileURL ]] &&
2071 NSPasteboardGetImageFile() == nil)
2072 {
2073 return GHOST_kFailure;
2074 }
2075 }
2076
2077 return GHOST_kSuccess;
2078}
2079
2080uint *GHOST_SystemCocoa::getClipboardImage(int *r_width, int *r_height) const
2081{
2082 if (!hasClipboardImage()) {
2083 return nullptr;
2084 }
2085
2086 @autoreleasepool {
2087 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
2088
2089 NSImage *clipboardImage = nil;
2090 if (NSURL *pasteboardImageFile = NSPasteboardGetImageFile(); pasteboardImageFile != nil) {
2091 /* Image file. */
2092 clipboardImage = [[[NSImage alloc] initWithContentsOfURL:pasteboardImageFile] autorelease];
2093 }
2094 else {
2095 /* Raw image data. */
2096 clipboardImage = [[[NSImage alloc] initWithPasteboard:pasteboard] autorelease];
2097 }
2098
2099 if (!clipboardImage) {
2100 return nullptr;
2101 }
2102
2103 ImBuf *ibuf = NSImageToImBuf(clipboardImage);
2104 const NSSize clipboardImageSize = getNSImagePixelSize(clipboardImage);
2105
2106 if (ibuf) {
2107 const size_t byteCount = clipboardImageSize.width * clipboardImageSize.height * 4;
2108 uint *rgba = (uint *)malloc(byteCount);
2109
2110 if (!rgba) {
2111 IMB_freeImBuf(ibuf);
2112 return nullptr;
2113 }
2114
2115 memcpy(rgba, ibuf->byte_buffer.data, byteCount);
2116 IMB_freeImBuf(ibuf);
2117
2118 *r_width = clipboardImageSize.width;
2119 *r_height = clipboardImageSize.height;
2120
2121 return rgba;
2122 }
2123 }
2124
2125 return nullptr;
2126}
2127
2129{
2130 @autoreleasepool {
2131 const size_t rowByteCount = width * 4;
2132
2133 NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc]
2134 initWithBitmapDataPlanes:nil
2135 pixelsWide:width
2136 pixelsHigh:height
2137 bitsPerSample:8
2138 samplesPerPixel:4
2139 hasAlpha:YES
2140 isPlanar:NO
2141 colorSpaceName:NSDeviceRGBColorSpace
2142 bytesPerRow:rowByteCount
2143 bitsPerPixel:32];
2144
2145 /* Copy the source image data to imageRep, flipping it vertically. */
2146 uint8_t *srcBuffer = reinterpret_cast<uint8_t *>(rgba);
2147 uint8_t *dstBuffer = static_cast<uint8_t *>([imageRep bitmapData]);
2148
2149 for (int y = 0; y < height; y++) {
2150 const int dstOff = (height - y - 1) * rowByteCount;
2151 const int srcOff = y * rowByteCount;
2152 memcpy(dstBuffer + dstOff, srcBuffer + srcOff, rowByteCount);
2153 }
2154
2155 NSImage *image = [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] autorelease];
2156 [image addRepresentation:imageRep];
2157
2158 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
2159 [pasteboard clearContents];
2160
2161 BOOL pasteSuccess = [pasteboard writeObjects:@[ image ]];
2162
2163 if (!pasteSuccess) {
2164 return GHOST_kFailure;
2165 }
2166 }
2167 return GHOST_kSuccess;
2168}
2169
2171 const char *message,
2172 const char *help_label,
2173 const char *continue_label,
2174 const char *link,
2175 GHOST_DialogOptions dialog_options) const
2176{
2177 @autoreleasepool {
2178 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
2179 alert.accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 500, 0)] autorelease];
2180
2181 NSString *titleString = [NSString stringWithCString:title];
2182 NSString *messageString = [NSString stringWithCString:message];
2183 NSString *continueString = [NSString stringWithCString:continue_label];
2184 NSString *helpString = [NSString stringWithCString:help_label];
2185
2186 if (dialog_options & GHOST_DialogError) {
2187 alert.alertStyle = NSAlertStyleCritical;
2188 }
2189 else if (dialog_options & GHOST_DialogWarning) {
2190 alert.alertStyle = NSAlertStyleWarning;
2191 }
2192 else {
2193 alert.alertStyle = NSAlertStyleInformational;
2194 }
2195
2196 alert.messageText = titleString;
2197 alert.informativeText = messageString;
2198
2199 [alert addButtonWithTitle:continueString];
2200 if (link && strlen(link)) {
2201 [alert addButtonWithTitle:helpString];
2202 }
2203
2204 const NSModalResponse response = [alert runModal];
2205 if (response == NSAlertSecondButtonReturn) {
2206 NSString *linkString = [NSString stringWithCString:link];
2207 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:linkString]];
2208 }
2209 }
2210 return GHOST_kSuccess;
2211}
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
#define FALSE
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
static GHOST_TKey convertKey(int rawCode, unichar recvChar)
static ImBuf * NSImageToImBuf(NSImage *image)
#define FIRSTFILEBUFLG
int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG])
static char g_firstFileBuf[FIRSTFILEBUFLG]
static NSSize getNSImagePixelSize(NSImage *image)
static bool g_hasFirstFile
static GHOST_TButton convertButton(int button)
static NSURL * NSPasteboardGetImageFile()
#define pushEvent
GHOST_TEventWheelAxis
@ GHOST_kEventWheelAxisVertical
@ GHOST_kEventWheelAxisHorizontal
@ GHOST_kTrackpadEventMagnify
@ GHOST_kTrackpadEventSmartMagnify
@ GHOST_kTrackpadEventRotate
@ GHOST_kTrackpadEventScroll
GHOST_TWindowState
GHOST_TEventType
@ GHOST_kEventWindowClose
@ GHOST_kEventWindowMove
@ GHOST_kEventWindowSize
@ GHOST_kEventDraggingDropDone
@ GHOST_kEventDraggingExited
@ GHOST_kEventNativeResolutionChange
@ GHOST_kEventCursorMove
@ GHOST_kEventDraggingUpdated
@ GHOST_kEventOpenMainFile
@ GHOST_kEventDraggingEntered
@ GHOST_kEventButtonUp
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowUpdate
@ GHOST_kEventWindowDeactivate
@ GHOST_kEventButtonDown
@ GHOST_kEventKeyDown
@ GHOST_kEventKeyUp
@ GHOST_kEventQuitRequest
static const GHOST_TabletData GHOST_TABLET_DATA_NONE
@ GHOST_kTabletModeEraser
@ GHOST_kTabletModeStylus
@ GHOST_kTabletModeNone
GHOST_TCapabilityFlag
@ GHOST_kCapabilityClipboardPrimary
@ GHOST_kCapabilityKeyboardHyperKey
@ GHOST_kCapabilityCursorRGBA
@ GHOST_kCapabilityCursorGenerator
#define GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(gpu_settings)
void * GHOST_TDragnDropDataPtr
#define GHOST_CAPABILITY_FLAG_ALL
GHOST_TKey
@ GHOST_kKeyLeftOS
@ GHOST_kKeySemicolon
@ GHOST_kKey5
@ GHOST_kKeyZ
@ GHOST_kKeyQuote
@ GHOST_kKey4
@ GHOST_kKeyT
@ GHOST_kKeyNumpad3
@ GHOST_kKeyAccentGrave
@ GHOST_kKeyNumpad1
@ GHOST_kKeyW
@ GHOST_kKeyLeftAlt
@ GHOST_kKey3
@ GHOST_kKeyG
@ GHOST_kKeyF9
@ GHOST_kKeyC
@ GHOST_kKeyI
@ GHOST_kKeyEnter
@ GHOST_kKeyF20
@ GHOST_kKeyP
@ GHOST_kKeyJ
@ GHOST_kKeyNumpadSlash
@ GHOST_kKeyRightArrow
@ GHOST_kKeyF13
@ GHOST_kKeyF6
@ GHOST_kKeyNumpad4
@ GHOST_kKeyF11
@ GHOST_kKeyR
@ GHOST_kKeyN
@ GHOST_kKeyMinus
@ GHOST_kKeyO
@ GHOST_kKey6
@ GHOST_kKeyBackSpace
@ GHOST_kKey0
@ GHOST_kKeyF5
@ GHOST_kKeyF19
@ GHOST_kKeyDownPage
@ GHOST_kKeyDownArrow
@ GHOST_kKeyQ
@ GHOST_kKeyNumpadPeriod
@ GHOST_kKeyF12
@ GHOST_kKeyF1
@ GHOST_kKeyF
@ GHOST_kKeyU
@ GHOST_kKeyNumpadAsterisk
@ GHOST_kKeyB
@ GHOST_kKeyLeftControl
@ GHOST_kKeyLeftBracket
@ GHOST_kKey1
@ GHOST_kKeyM
@ GHOST_kKeyTab
@ GHOST_kKey8
@ GHOST_kKeyComma
@ GHOST_kKeyRightBracket
@ GHOST_kKeyBackslash
@ GHOST_kKeyNumpad2
@ GHOST_kKeyX
@ GHOST_kKeyL
@ GHOST_kKeyY
@ GHOST_kKeyPeriod
@ GHOST_kKeyNumpadPlus
@ GHOST_kKeyUpPage
@ GHOST_kKey9
@ GHOST_kKeyNumpad5
@ GHOST_kKeyLeftArrow
@ GHOST_kKeyF17
@ GHOST_kKeyD
@ GHOST_kKeyEqual
@ GHOST_kKey7
@ GHOST_kKeyS
@ GHOST_kKeyF8
@ GHOST_kKeyF18
@ GHOST_kKeyHome
@ GHOST_kKeyNumpad6
@ GHOST_kKeyF14
@ GHOST_kKeyNumpad8
@ GHOST_kKeyNumpad9
@ GHOST_kKeyF15
@ GHOST_kKeyEnd
@ GHOST_kKeyUpArrow
@ GHOST_kKeyH
@ GHOST_kKeyDelete
@ GHOST_kKeyF16
@ GHOST_kKeyNumpad0
@ GHOST_kKeyA
@ GHOST_kKey2
@ GHOST_kKeyK
@ GHOST_kKeyNumpad7
@ GHOST_kKeyEsc
@ GHOST_kKeyPlus
@ GHOST_kKeyUnknown
@ GHOST_kKeySlash
@ GHOST_kKeyV
@ GHOST_kKeyF7
@ GHOST_kKeyNumpadEnter
@ GHOST_kKeyNumpadMinus
@ GHOST_kKeyF10
@ GHOST_kKeyLeftShift
@ GHOST_kKeyF3
@ GHOST_kKeyF2
@ GHOST_kKeyF4
@ GHOST_kKeyE
@ GHOST_kKeySpace
const void * GHOST_TEventDataPtr
@ GHOST_kModifierKeyLeftControl
@ GHOST_kModifierKeyLeftAlt
@ GHOST_kModifierKeyLeftShift
@ GHOST_kModifierKeyLeftOS
GHOST_TSuccess
Definition GHOST_Types.h:57
@ GHOST_kFailure
Definition GHOST_Types.h:57
@ GHOST_kSuccess
Definition GHOST_Types.h:57
@ GHOST_kFireTimeNever
@ GHOST_kGrabHide
#define GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS_OFFSCREEN(gpu_settings)
GHOST_TDragnDropTypes
@ GHOST_kDragnDropTypeFilenames
@ GHOST_kDragnDropTypeBitmap
@ GHOST_kDragnDropTypeString
GHOST_TButton
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskButton4
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskButton7
@ GHOST_kButtonMaskButton6
@ GHOST_kButtonMaskButton5
@ GHOST_kButtonMaskMiddle
GHOST_DialogOptions
@ GHOST_DialogError
@ GHOST_DialogWarning
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
@ IB_byte_data
#define C
Definition RandGen.cpp:29
BMesh const char void * data
PyObject * self
unsigned long long int uint64_t
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
virtual bool getValid() const =0
int32_t t_
virtual bool clip(GHOST_Rect &r) const
Definition GHOST_Rect.cc:92
int32_t r_
virtual void wrapPoint(int32_t &x, int32_t &y, int32_t ofs, GHOST_TAxisFlag axis)
int32_t b_
int32_t l_
bool processEvents(bool waitForEvent) override
uint * getClipboardImage(int *r_width, int *r_height) const override
GHOST_TSuccess disposeContext(GHOST_IContext *context) override
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override
GHOST_TSuccess handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa *window)
GHOST_TSuccess handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType, GHOST_WindowCocoa *window, int mouseX, int mouseY, void *data)
GHOST_TSuccess handleApplicationBecomeActiveEvent()
GHOST_TSuccess hasClipboardImage() const override
GHOST_TSuccess handleMouseEvent(void *eventPtr)
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override
GHOST_TSuccess handleKeyEvent(void *eventPtr)
GHOST_TSuccess init() override
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override
uint64_t getMilliSeconds() const override
GHOST_TCapabilityFlag getCapabilities() const override
GHOST_TSuccess showMessageBox(const char *title, const char *message, const char *help_label, const char *continue_label, const char *link, GHOST_DialogOptions dialog_options) const override
uint8_t getNumDisplays() const override
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override
GHOST_TSuccess handleTabletEvent(void *eventPtr, short eventType)
bool handleOpenDocumentRequest(void *filepathStr)
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override
char * getClipboard(bool selection) const override
GHOST_TSuccess setMouseCursorPosition(int32_t x, int32_t y)
void putClipboard(const char *buffer, bool selection) const override
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override
GHOST_IWindow * getWindowUnderCursor(int32_t x, int32_t y) override
GHOST_IContext * createOffscreenContext(GHOST_GPUSettings gpu_settings) override
GHOST_IWindow * createWindow(const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, GHOST_GPUSettings gpu_settings, const bool exclusive=false, const bool is_dialog=false, const GHOST_IWindow *parent_window=nullptr) override
bool need_delayed_application_become_active_event_processing_
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override
GHOST_WindowManager * window_manager_
GHOST_TSuccess init() override
GHOST_TimerManager * getTimerManager() const
bool validWindow(GHOST_IWindow *window) override
bool multitouch_gestures_
bool fireTimers(uint64_t time)
bool isDialog() const override
NSScreen * getScreen() const
void clientToScreenIntern(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
void * getOSWindow() const override
GHOST_TabletData & GetCocoaTabletData()
void getClientBounds(GHOST_Rect &bounds) const override
void screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const override
void loadCursor(bool visible, GHOST_TStandardCursor cursor) const
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override
void setCursorGrabAccum(int32_t x, int32_t y)
GHOST_TAxisFlag getCursorGrabAxis() const
GHOST_TStandardCursor getCursorShape() const override
bool getCursorVisibility() const override
GHOST_TGrabCursorMode getCursorGrabMode() const
GHOST_TSuccess updateDrawingContext()
bool getCursorGrabModeIsWarp() const
void getCursorGrabAccum(int32_t &x, int32_t &y) const
nullptr float
uint top
#define in
#define printf(...)
float length(VecOp< float, D >) RET
IMG_TEMPLATE SizeVec imageSize(const T &) RET
GHOST_SystemCocoa * systemCocoa
int count
static ulong * next
static ulong state[N]
static int left
void set(GHOST_TButton mask, bool down)
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
void set(GHOST_TModifierKey mask, bool down)
GHOST_TTabletMode Active
ImBufByteBuffer byte_buffer
i
Definition text_draw.cc:230
void WM_exit(bContext *C, const int exit_code)