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