3 * ***** BEGIN GPL LICENSE BLOCK *****
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20 * All rights reserved.
22 * The Original Code is: all of this file.
24 * Contributor(s): Maarten Gribnau 05/2001
25 * Damien Plisson 09/2009
28 * ***** END GPL LICENSE BLOCK *****
31 #import <Cocoa/Cocoa.h>
33 /*For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible)*/
34 #include <Carbon/Carbon.h>
35 //#include <HIToolbox/Events.h>
38 #include <sys/types.h>
39 #include <sys/sysctl.h>
41 #include "GHOST_SystemCocoa.h"
43 #include "GHOST_DisplayManagerCocoa.h"
44 #include "GHOST_EventKey.h"
45 #include "GHOST_EventButton.h"
46 #include "GHOST_EventCursor.h"
47 #include "GHOST_EventWheel.h"
48 #include "GHOST_EventNDOF.h"
49 #include "GHOST_EventTrackpad.h"
50 #include "GHOST_EventDragnDrop.h"
51 #include "GHOST_EventString.h"
53 #include "GHOST_TimerManager.h"
54 #include "GHOST_TimerTask.h"
55 #include "GHOST_WindowManager.h"
56 #include "GHOST_WindowCocoa.h"
57 #include "GHOST_NDOFManagerCocoa.h"
58 #include "AssertMacros.h"
60 #pragma mark KeyMap, mouse converters
61 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
62 /* Keycodes not defined in Tiger */
68 * These constants are the virtual keycodes defined originally in
69 * Inside Mac Volume V, pg. V-191. They identify physical keys on a
70 * keyboard. Those constants with "ANSI" in the name are labeled
71 * according to the key position on an ANSI-standard US keyboard.
72 * For example, kVK_ANSI_A indicates the virtual keycode for the key
73 * with the letter 'A' in the US keyboard layout. Other keyboard
74 * layouts may have the 'A' key label on a different physical key;
75 * in this case, pressing 'A' will generate a different virtual
102 kVK_ANSI_Equal = 0x18,
105 kVK_ANSI_Minus = 0x1B,
108 kVK_ANSI_RightBracket = 0x1E,
111 kVK_ANSI_LeftBracket = 0x21,
116 kVK_ANSI_Quote = 0x27,
118 kVK_ANSI_Semicolon = 0x29,
119 kVK_ANSI_Backslash = 0x2A,
120 kVK_ANSI_Comma = 0x2B,
121 kVK_ANSI_Slash = 0x2C,
124 kVK_ANSI_Period = 0x2F,
125 kVK_ANSI_Grave = 0x32,
126 kVK_ANSI_KeypadDecimal = 0x41,
127 kVK_ANSI_KeypadMultiply = 0x43,
128 kVK_ANSI_KeypadPlus = 0x45,
129 kVK_ANSI_KeypadClear = 0x47,
130 kVK_ANSI_KeypadDivide = 0x4B,
131 kVK_ANSI_KeypadEnter = 0x4C,
132 kVK_ANSI_KeypadMinus = 0x4E,
133 kVK_ANSI_KeypadEquals = 0x51,
134 kVK_ANSI_Keypad0 = 0x52,
135 kVK_ANSI_Keypad1 = 0x53,
136 kVK_ANSI_Keypad2 = 0x54,
137 kVK_ANSI_Keypad3 = 0x55,
138 kVK_ANSI_Keypad4 = 0x56,
139 kVK_ANSI_Keypad5 = 0x57,
140 kVK_ANSI_Keypad6 = 0x58,
141 kVK_ANSI_Keypad7 = 0x59,
142 kVK_ANSI_Keypad8 = 0x5B,
143 kVK_ANSI_Keypad9 = 0x5C
146 /* keycodes for keys that are independent of keyboard layout*/
158 kVK_RightShift = 0x3C,
159 kVK_RightOption = 0x3D,
160 kVK_RightControl = 0x3E,
164 kVK_VolumeDown = 0x49,
185 kVK_ForwardDelete = 0x75,
191 kVK_LeftArrow = 0x7B,
192 kVK_RightArrow = 0x7C,
193 kVK_DownArrow = 0x7D,
197 /* ISO keyboards only*/
199 kVK_ISO_Section = 0x0A
202 /* JIS keyboards only*/
205 kVK_JIS_Underscore = 0x5E,
206 kVK_JIS_KeypadComma = 0x5F,
212 static GHOST_TButtonMask convertButton(int button)
216 return GHOST_kButtonMaskLeft;
218 return GHOST_kButtonMaskRight;
220 return GHOST_kButtonMaskMiddle;
222 return GHOST_kButtonMaskButton4;
224 return GHOST_kButtonMaskButton5;
226 return GHOST_kButtonMaskLeft;
231 * Converts Mac rawkey codes (same for Cocoa & Carbon)
232 * into GHOST key codes
233 * @param rawCode The raw physical key code
234 * @param recvChar the character ignoring modifiers (except for shift)
235 * @return Ghost key code
237 static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
240 //printf("\nrecvchar %c 0x%x",recvChar,recvChar);
242 /*Physical keycodes not used due to map changes in int'l keyboards
243 case kVK_ANSI_A: return GHOST_kKeyA;
244 case kVK_ANSI_B: return GHOST_kKeyB;
245 case kVK_ANSI_C: return GHOST_kKeyC;
246 case kVK_ANSI_D: return GHOST_kKeyD;
247 case kVK_ANSI_E: return GHOST_kKeyE;
248 case kVK_ANSI_F: return GHOST_kKeyF;
249 case kVK_ANSI_G: return GHOST_kKeyG;
250 case kVK_ANSI_H: return GHOST_kKeyH;
251 case kVK_ANSI_I: return GHOST_kKeyI;
252 case kVK_ANSI_J: return GHOST_kKeyJ;
253 case kVK_ANSI_K: return GHOST_kKeyK;
254 case kVK_ANSI_L: return GHOST_kKeyL;
255 case kVK_ANSI_M: return GHOST_kKeyM;
256 case kVK_ANSI_N: return GHOST_kKeyN;
257 case kVK_ANSI_O: return GHOST_kKeyO;
258 case kVK_ANSI_P: return GHOST_kKeyP;
259 case kVK_ANSI_Q: return GHOST_kKeyQ;
260 case kVK_ANSI_R: return GHOST_kKeyR;
261 case kVK_ANSI_S: return GHOST_kKeyS;
262 case kVK_ANSI_T: return GHOST_kKeyT;
263 case kVK_ANSI_U: return GHOST_kKeyU;
264 case kVK_ANSI_V: return GHOST_kKeyV;
265 case kVK_ANSI_W: return GHOST_kKeyW;
266 case kVK_ANSI_X: return GHOST_kKeyX;
267 case kVK_ANSI_Y: return GHOST_kKeyY;
268 case kVK_ANSI_Z: return GHOST_kKeyZ;*/
270 /* Numbers keys mapped to handle some int'l keyboard (e.g. French)*/
271 case kVK_ISO_Section: return GHOST_kKeyUnknown;
272 case kVK_ANSI_1: return GHOST_kKey1;
273 case kVK_ANSI_2: return GHOST_kKey2;
274 case kVK_ANSI_3: return GHOST_kKey3;
275 case kVK_ANSI_4: return GHOST_kKey4;
276 case kVK_ANSI_5: return GHOST_kKey5;
277 case kVK_ANSI_6: return GHOST_kKey6;
278 case kVK_ANSI_7: return GHOST_kKey7;
279 case kVK_ANSI_8: return GHOST_kKey8;
280 case kVK_ANSI_9: return GHOST_kKey9;
281 case kVK_ANSI_0: return GHOST_kKey0;
283 case kVK_ANSI_Keypad0: return GHOST_kKeyNumpad0;
284 case kVK_ANSI_Keypad1: return GHOST_kKeyNumpad1;
285 case kVK_ANSI_Keypad2: return GHOST_kKeyNumpad2;
286 case kVK_ANSI_Keypad3: return GHOST_kKeyNumpad3;
287 case kVK_ANSI_Keypad4: return GHOST_kKeyNumpad4;
288 case kVK_ANSI_Keypad5: return GHOST_kKeyNumpad5;
289 case kVK_ANSI_Keypad6: return GHOST_kKeyNumpad6;
290 case kVK_ANSI_Keypad7: return GHOST_kKeyNumpad7;
291 case kVK_ANSI_Keypad8: return GHOST_kKeyNumpad8;
292 case kVK_ANSI_Keypad9: return GHOST_kKeyNumpad9;
293 case kVK_ANSI_KeypadDecimal: return GHOST_kKeyNumpadPeriod;
294 case kVK_ANSI_KeypadEnter: return GHOST_kKeyNumpadEnter;
295 case kVK_ANSI_KeypadPlus: return GHOST_kKeyNumpadPlus;
296 case kVK_ANSI_KeypadMinus: return GHOST_kKeyNumpadMinus;
297 case kVK_ANSI_KeypadMultiply: return GHOST_kKeyNumpadAsterisk;
298 case kVK_ANSI_KeypadDivide: return GHOST_kKeyNumpadSlash;
299 case kVK_ANSI_KeypadClear: return GHOST_kKeyUnknown;
301 case kVK_F1: return GHOST_kKeyF1;
302 case kVK_F2: return GHOST_kKeyF2;
303 case kVK_F3: return GHOST_kKeyF3;
304 case kVK_F4: return GHOST_kKeyF4;
305 case kVK_F5: return GHOST_kKeyF5;
306 case kVK_F6: return GHOST_kKeyF6;
307 case kVK_F7: return GHOST_kKeyF7;
308 case kVK_F8: return GHOST_kKeyF8;
309 case kVK_F9: return GHOST_kKeyF9;
310 case kVK_F10: return GHOST_kKeyF10;
311 case kVK_F11: return GHOST_kKeyF11;
312 case kVK_F12: return GHOST_kKeyF12;
313 case kVK_F13: return GHOST_kKeyF13;
314 case kVK_F14: return GHOST_kKeyF14;
315 case kVK_F15: return GHOST_kKeyF15;
316 case kVK_F16: return GHOST_kKeyF16;
317 case kVK_F17: return GHOST_kKeyF17;
318 case kVK_F18: return GHOST_kKeyF18;
319 case kVK_F19: return GHOST_kKeyF19;
320 case kVK_F20: return GHOST_kKeyF20;
322 case kVK_UpArrow: return GHOST_kKeyUpArrow;
323 case kVK_DownArrow: return GHOST_kKeyDownArrow;
324 case kVK_LeftArrow: return GHOST_kKeyLeftArrow;
325 case kVK_RightArrow: return GHOST_kKeyRightArrow;
327 case kVK_Return: return GHOST_kKeyEnter;
328 case kVK_Delete: return GHOST_kKeyBackSpace;
329 case kVK_ForwardDelete: return GHOST_kKeyDelete;
330 case kVK_Escape: return GHOST_kKeyEsc;
331 case kVK_Tab: return GHOST_kKeyTab;
332 case kVK_Space: return GHOST_kKeySpace;
334 case kVK_Home: return GHOST_kKeyHome;
335 case kVK_End: return GHOST_kKeyEnd;
336 case kVK_PageUp: return GHOST_kKeyUpPage;
337 case kVK_PageDown: return GHOST_kKeyDownPage;
339 /*case kVK_ANSI_Minus: return GHOST_kKeyMinus;
340 case kVK_ANSI_Equal: return GHOST_kKeyEqual;
341 case kVK_ANSI_Comma: return GHOST_kKeyComma;
342 case kVK_ANSI_Period: return GHOST_kKeyPeriod;
343 case kVK_ANSI_Slash: return GHOST_kKeySlash;
344 case kVK_ANSI_Semicolon: return GHOST_kKeySemicolon;
345 case kVK_ANSI_Quote: return GHOST_kKeyQuote;
346 case kVK_ANSI_Backslash: return GHOST_kKeyBackslash;
347 case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
348 case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
349 case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;*/
354 return GHOST_kKeyUnknown;
357 /* alphanumerical or punctuation key that is remappable in int'l keyboards */
358 if ((recvChar >= 'A') && (recvChar <= 'Z')) {
359 return (GHOST_TKey) (recvChar - 'A' + GHOST_kKeyA);
360 } else if ((recvChar >= 'a') && (recvChar <= 'z')) {
361 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
363 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
364 KeyboardLayoutRef keyLayout;
365 UCKeyboardLayout *uchrData;
367 KLGetCurrentKeyboardLayout(&keyLayout);
368 KLGetKeyboardLayoutProperty(keyLayout, kKLuchrData, (const void **)
370 /*get actual character value of the "remappable" keys in int'l keyboards,
371 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
372 then fallback on using the received charactersIgnoringModifiers */
374 UInt32 deadKeyState=0;
375 UniCharCount actualStrLength=0;
377 UCKeyTranslate(uchrData, rawCode, keyAction, 0,
378 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
381 /* Leopard and Snow Leopard 64bit compatible API*/
382 CFDataRef uchrHandle; /*the keyboard layout*/
383 TISInputSourceRef kbdTISHandle;
385 kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
386 uchrHandle = (CFDataRef)TISGetInputSourceProperty(kbdTISHandle,kTISPropertyUnicodeKeyLayoutData);
387 CFRelease(kbdTISHandle);
389 /*get actual character value of the "remappable" keys in int'l keyboards,
390 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
391 then fallback on using the received charactersIgnoringModifiers */
393 UInt32 deadKeyState=0;
394 UniCharCount actualStrLength=0;
396 UCKeyTranslate((UCKeyboardLayout*)CFDataGetBytePtr(uchrHandle), rawCode, keyAction, 0,
397 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
401 case '-': return GHOST_kKeyMinus;
402 case '=': return GHOST_kKeyEqual;
403 case ',': return GHOST_kKeyComma;
404 case '.': return GHOST_kKeyPeriod;
405 case '/': return GHOST_kKeySlash;
406 case ';': return GHOST_kKeySemicolon;
407 case '\'': return GHOST_kKeyQuote;
408 case '\\': return GHOST_kKeyBackslash;
409 case '[': return GHOST_kKeyLeftBracket;
410 case ']': return GHOST_kKeyRightBracket;
411 case '`': return GHOST_kKeyAccentGrave;
413 return GHOST_kKeyUnknown;
417 return GHOST_kKeyUnknown;
421 #pragma mark defines for 10.6 api not documented in 10.5
422 #ifndef MAC_OS_X_VERSION_10_6
424 /* The following event types are available on some hardware on 10.5.2 and later */
425 NSEventTypeGesture = 29,
426 NSEventTypeMagnify = 30,
427 NSEventTypeSwipe = 31,
428 NSEventTypeRotate = 18,
429 NSEventTypeBeginGesture = 19,
430 NSEventTypeEndGesture = 20
433 @interface NSEvent(GestureEvents)
434 /* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
435 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
436 - (float)magnification; // change in magnification.
438 - (CGFloat)magnification; // change in magnification.
445 #pragma mark Utility functions
447 #define FIRSTFILEBUFLG 512
448 static bool g_hasFirstFile = false;
449 static char g_firstFileBuf[512];
451 //TODO:Need to investigate this. Function called too early in creator.c to have g_hasFirstFile == true
452 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) {
453 if (g_hasFirstFile) {
454 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
455 buf[FIRSTFILEBUFLG - 1] = '\0';
462 #if defined(WITH_QUICKTIME) && !defined(USE_QTKIT)
463 //Need to place this quicktime function in an ObjC file
464 //It is used to avoid memory leak when raising the quicktime "compression settings" standard dialog
468 extern int fromcocoa_request_qtcodec_settings(bContext *C, wmOperator *op);
471 int cocoa_request_qtcodec_settings(bContext *C, wmOperator *op)
474 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
476 result = fromcocoa_request_qtcodec_settings(C, op);
485 #pragma mark Cocoa objects
489 * ObjC object to capture applicationShouldTerminate, and send quit event
491 @interface CocoaAppDelegate : NSObject {
492 GHOST_SystemCocoa *systemCocoa;
494 - (void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
495 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
496 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
497 - (void)applicationWillTerminate:(NSNotification *)aNotification;
498 - (void)applicationWillBecomeActive:(NSNotification *)aNotification;
501 @implementation CocoaAppDelegate : NSObject
502 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
504 systemCocoa = sysCocoa;
507 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
509 return systemCocoa->handleOpenDocumentRequest(filename);
512 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
514 //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be cancelled
515 //Note that Cmd+Q is already handled by keyhandler
516 if (systemCocoa->handleQuitRequest() == GHOST_kExitNow)
517 return NSTerminateCancel;//NSTerminateNow;
519 return NSTerminateCancel;
522 // To avoid cancelling a log off process, we must use Cocoa termination process
523 // And this function is the only chance to perform clean up
524 // So WM_exit needs to be called directly, as the event loop will never run before termination
525 - (void)applicationWillTerminate:(NSNotification *)aNotification
527 /*G.afbreek = 0; //Let Cocoa perform the termination at the end
531 - (void)applicationWillBecomeActive:(NSNotification *)aNotification
533 systemCocoa->handleApplicationBecomeActiveEvent();
539 #pragma mark initialization/finalization
542 GHOST_SystemCocoa::GHOST_SystemCocoa()
545 struct timeval boottime;
547 char *rstring = NULL;
550 m_pressedMouseButtons =0;
551 m_isGestureInProgress = false;
554 m_tablet_mouse_id = TOOL_ID_NONE;
555 m_tablet_pen_id = TOOL_ID_NONE;
556 m_tablet_pen_mode = GHOST_kTabletModeNone;
557 m_outsideLoopEventProcessed = false;
558 m_needDelayedApplicationBecomeActiveEventProcessing = false;
559 m_displayManager = new GHOST_DisplayManagerCocoa ();
560 GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
561 m_displayManager->initialize();
563 //NSEvent timeStamp is given in system uptime, state start date is boot time
565 mib[1] = KERN_BOOTTIME;
566 len = sizeof(struct timeval);
568 sysctl(mib, 2, &boottime, &len, NULL, 0);
569 m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
571 m_start_time_2 = CFAbsoluteTimeGetCurrent();
573 //Detect multitouch trackpad
576 sysctl( mib, 2, NULL, &len, NULL, 0 );
577 rstring = (char*)malloc( len );
578 sysctl( mib, 2, rstring, &len, NULL, 0 );
580 //Hack on MacBook revision, as multitouch avail. function missing
581 m_hasMultiTouchTrackpad =
582 (strstr(rstring,"MacBookAir") ||
583 (strstr(rstring,"MacBook") && (rstring[strlen(rstring)-3]>='5') && (rstring[strlen(rstring)-3]<='9')));
588 m_ignoreWindowSizedMessages = false;
590 m_input_fidelity_hint = HI_FI; // just for testing...
593 GHOST_SystemCocoa::~GHOST_SystemCocoa()
598 GHOST_TSuccess GHOST_SystemCocoa::init()
600 GHOST_TSuccess success = GHOST_System::init();
603 m_ndofManager = new GHOST_NDOFManagerCocoa(*this);
605 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
607 [NSApplication sharedApplication];
609 if ([NSApp mainMenu] == nil) {
610 NSMenu *mainMenubar = [[NSMenu alloc] init];
611 NSMenuItem *menuItem;
615 //Create the application menu
616 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
618 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
619 [appMenu addItem:[NSMenuItem separatorItem]];
621 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
622 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
624 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
625 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
627 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
629 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
630 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
632 menuItem = [[NSMenuItem alloc] init];
633 [menuItem setSubmenu:appMenu];
635 [mainMenubar addItem:menuItem];
637 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
640 //Create the window menu
641 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
643 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
644 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
646 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
648 menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
649 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
651 menuItem = [[NSMenuItem alloc] init];
652 [menuItem setSubmenu:windowMenu];
654 [mainMenubar addItem:menuItem];
657 [NSApp setMainMenu:mainMenubar];
658 [NSApp setWindowsMenu:windowMenu];
659 [windowMenu release];
662 if ([NSApp delegate] == nil) {
663 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
664 [appDelegate setSystemCocoa:this];
665 [NSApp setDelegate:appDelegate];
668 [NSApp finishLaunching];
676 #pragma mark window management
678 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
681 //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
682 struct timeval currentTime;
684 gettimeofday(¤tTime, NULL);
686 //Return timestamp of system uptime
687 return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
690 double now = CFAbsoluteTimeGetCurrent();
691 return 1000 * (now - m_start_time_2);
695 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
697 //Note that OS X supports monitor hot plug
698 // We do not support multiple monitors at the moment
699 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
701 GHOST_TUns8 count = [[NSScreen screens] count];
708 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
710 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
711 //Get visible frame, that is frame excluding dock and top menu bar
712 NSRect frame = [[NSScreen mainScreen] visibleFrame];
714 //Returns max window contents (excluding title bar...)
715 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
716 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
718 width = contentRect.size.width;
719 height = contentRect.size.height;
725 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
726 const STR_String& title,
731 GHOST_TWindowState state,
732 GHOST_TDrawingContextType type,
734 const GHOST_TUns16 numOfAASamples,
735 const GHOST_TEmbedderWindowID parentWindow
738 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
739 GHOST_IWindow* window = 0;
741 //Get the available rect for including window contents
742 NSRect frame = [[NSScreen mainScreen] visibleFrame];
743 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
744 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
746 //Ensures window top left is inside this available rect
747 left = left > contentRect.origin.x ? left : contentRect.origin.x;
748 top = top > contentRect.origin.y ? top : contentRect.origin.y;
750 window = new GHOST_WindowCocoa (this, title, left, top, width, height, state, type, stereoVisual, numOfAASamples);
753 if (window->getValid()) {
754 // Store the pointer to the window
755 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
756 m_windowManager->addWindow(window);
757 m_windowManager->setActiveWindow(window);
758 //Need to tell window manager the new window is the active one (Cocoa does not send the event activate upon window creation)
759 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window));
760 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
763 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
769 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
775 GHOST_TSuccess GHOST_SystemCocoa::beginFullScreen(const GHOST_DisplaySetting& setting, GHOST_IWindow** window, const bool stereoVisual)
777 GHOST_IWindow* currentWindow = m_windowManager->getActiveWindow();
778 *window = currentWindow;
780 if(!currentWindow) return GHOST_kFailure;
782 return currentWindow->setState(GHOST_kWindowStateFullScreen);
785 GHOST_TSuccess GHOST_SystemCocoa::endFullScreen(void)
787 GHOST_IWindow* currentWindow = m_windowManager->getActiveWindow();
788 if(!currentWindow) return GHOST_kFailure;
790 return currentWindow->setState(GHOST_kWindowStateNormal);
796 * @note : returns coordinates in Cocoa screen coordinates
798 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
800 NSPoint mouseLoc = [NSEvent mouseLocation];
802 // Returns the mouse location in screen coordinates
803 x = (GHOST_TInt32)mouseLoc.x;
804 y = (GHOST_TInt32)mouseLoc.y;
806 return GHOST_kSuccess;
810 * @note : expect Cocoa screen coordinates
812 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
815 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
816 if (!window) return GHOST_kFailure;
818 //Cursor and mouse dissociation placed here not to interfere with continuous grab
819 // (in cont. grab setMouseCursorPosition is directly called)
820 CGAssociateMouseAndMouseCursorPosition(false);
821 setMouseCursorPosition(x, y);
822 CGAssociateMouseAndMouseCursorPosition(true);
824 //Force mouse move event (not pushed by Cocoa)
825 window->screenToClient(x, y, wx, wy);
826 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, wx,wy));
827 m_outsideLoopEventProcessed = true;
829 return GHOST_kSuccess;
832 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(float xf, float yf)
834 // float xf=(float)x, yf=(float)y;
835 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
836 if (!window) return GHOST_kFailure;
838 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
839 NSScreen *windowScreen = window->getScreen();
840 NSRect screenRect = [windowScreen frame];
842 //Set position relative to current screen
843 xf -= screenRect.origin.x;
844 yf -= screenRect.origin.y;
846 //Quartz Display Services uses the old coordinates (top left origin)
847 yf = screenRect.size.height -yf;
849 CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
852 return GHOST_kSuccess;
856 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
858 keys.set(GHOST_kModifierKeyCommand, (m_modifierMask & NSCommandKeyMask) ? true : false);
859 keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
860 keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
861 keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
863 return GHOST_kSuccess;
866 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
869 buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
870 buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
871 buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
872 buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
873 buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
874 return GHOST_kSuccess;
879 #pragma mark Event handlers
882 * The event queue polling function
884 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
886 bool anyProcessed = false;
888 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
890 //TODO : implement timer ??
893 event = [NSApp nextEventMatchingMask:NSAnyEventMask
894 untilDate:[NSDate distantPast]
895 inMode:NSDefaultRunLoopMode
902 switch ([event type]) {
904 if ([event isARepeat])
909 handleKeyEvent(event);
910 // resend to ensure Mac-wide events are handled
911 [NSApp sendEvent:event];
914 case NSLeftMouseDown:
916 case NSLeftMouseDragged:
917 case NSRightMouseDown:
919 case NSRightMouseDragged:
920 case NSOtherMouseDown:
922 case NSOtherMouseDragged:
924 switch ([event subtype])
926 case NSMouseEventSubtype:
927 handleMouseEvent(event);
929 case NSTabletPointEventSubtype:
930 if ([event deviceID] == m_tablet_mouse_id)
931 handleMouseEvent(event);
933 handleTabletEvent(event);
935 case NSTabletProximityEventSubtype:
936 // I think only LMB down/up sends this.
937 // Always preceded by a real NSTabletProximity event, so it's redundant.
938 // handleTabletProximity(event);
941 // Mac OS 10.6 introduces a Touch subtype
942 // that we ignore for now.
947 handleMouseEvent(event);
950 case NSTabletProximity:
951 handleTabletProximity(event);
955 if ([event deviceID] == m_tablet_pen_id)
956 handleTabletEvent(event);
958 // Treat tablet mouse like any other mouse.
959 // TODO: teach Windows and Linux the same trick
961 // It continues to send events even when still, to mimic the pen's
962 // ability to vary pressure without moving. Since the mouse is
963 // unable to vary its pressure, filter them out as noise!
965 bool didMove = [event deltaX] != 0 and [event deltaY] != 0;
967 handleMouseEvent(event);
968 // LMB Down gets sent for the initial point (and LMB Up for the final), so this is safe.
972 case NSEventTypeMagnify:
973 case NSEventTypeRotate:
974 case NSEventTypeBeginGesture:
975 case NSEventTypeEndGesture:
976 handleMouseEvent(event);
977 // break out into handleGestureEvent?
980 /* Trackpad features, fired only from OS X 10.5.2
981 case NSEventTypeGesture:
982 case NSEventTypeSwipe:
990 NSAppKitDefined = 13,
991 NSSystemDefined = 14,
992 NSApplicationDefined = 15,
994 NSCursorUpdate = 17,*/
996 } while (event != nil);
1000 if (m_needDelayedApplicationBecomeActiveEventProcessing)
1001 handleApplicationBecomeActiveEvent();
1003 if (m_outsideLoopEventProcessed) {
1004 m_outsideLoopEventProcessed = false;
1008 return anyProcessed;
1012 //Note: called from NSApplication delegate
1013 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
1015 //Update the modifiers key mask, as its status may have changed when the application was not active
1016 //(that is when update events are sent to another application)
1017 unsigned int modifiers;
1018 GHOST_IWindow* window = m_windowManager->getActiveWindow();
1021 m_needDelayedApplicationBecomeActiveEventProcessing = true;
1022 return GHOST_kFailure;
1024 else m_needDelayedApplicationBecomeActiveEventProcessing = false;
1026 modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
1028 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1029 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1031 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1032 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1034 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1035 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1037 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1038 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyCommand) );
1041 m_modifierMask = modifiers;
1043 m_outsideLoopEventProcessed = true;
1044 return GHOST_kSuccess;
1047 void GHOST_SystemCocoa::notifyExternalEventProcessed()
1049 m_outsideLoopEventProcessed = true;
1052 //Note: called from NSWindow delegate
1053 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
1055 if (!validWindow(window)) {
1056 return GHOST_kFailure;
1060 case GHOST_kEventWindowClose:
1061 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
1063 case GHOST_kEventWindowActivate:
1064 m_windowManager->setActiveWindow(window);
1065 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1066 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
1068 case GHOST_kEventWindowDeactivate:
1069 m_windowManager->setWindowInactive(window);
1070 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
1072 case GHOST_kEventWindowUpdate:
1073 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
1075 case GHOST_kEventWindowMove:
1076 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
1078 case GHOST_kEventWindowSize:
1079 if (!m_ignoreWindowSizedMessages) {
1080 window->updateDrawingContext();
1081 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
1085 return GHOST_kFailure;
1089 m_outsideLoopEventProcessed = true;
1090 return GHOST_kSuccess;
1093 //Note: called from NSWindow subclass
1094 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
1095 GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
1097 if (!validWindow(window)) {
1098 return GHOST_kFailure;
1102 case GHOST_kEventDraggingEntered:
1103 case GHOST_kEventDraggingUpdated:
1104 case GHOST_kEventDraggingExited:
1105 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
1108 case GHOST_kEventDraggingDropDone:
1110 GHOST_TUns8 * temp_buff;
1111 GHOST_TStringArray *strArray;
1112 NSArray *droppedArray;
1113 size_t pastedTextSize;
1114 NSString *droppedStr;
1115 GHOST_TEventDataPtr eventData;
1118 if (!data) return GHOST_kFailure;
1120 switch (draggedObjectType) {
1121 case GHOST_kDragnDropTypeFilenames:
1122 droppedArray = (NSArray*)data;
1124 strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
1125 if (!strArray) return GHOST_kFailure;
1127 strArray->count = [droppedArray count];
1128 if (strArray->count == 0) return GHOST_kFailure;
1130 strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
1132 for (i=0;i<strArray->count;i++)
1134 droppedStr = [droppedArray objectAtIndex:i];
1136 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1137 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1140 strArray->count = i;
1144 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1145 temp_buff[pastedTextSize] = '\0';
1147 strArray->strings[i] = temp_buff;
1150 eventData = (GHOST_TEventDataPtr) strArray;
1153 case GHOST_kDragnDropTypeString:
1154 droppedStr = (NSString*)data;
1155 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1157 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1159 if (temp_buff == NULL) {
1160 return GHOST_kFailure;
1163 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1165 temp_buff[pastedTextSize] = '\0';
1167 eventData = (GHOST_TEventDataPtr) temp_buff;
1170 case GHOST_kDragnDropTypeBitmap:
1172 NSImage *droppedImg = (NSImage*)data;
1173 NSSize imgSize = [droppedImg size];
1175 GHOST_TUns8 *rasterRGB = NULL;
1176 GHOST_TUns8 *rasterRGBA = NULL;
1177 GHOST_TUns8 *toIBuf = NULL;
1178 int x, y, to_i, from_i;
1179 NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
1180 NSEnumerator *enumerator;
1181 NSImageRep *representation;
1183 ibuf = IMB_allocImBuf (imgSize.width , imgSize.height, 32, IB_rect, 0);
1185 [droppedImg release];
1186 return GHOST_kFailure;
1189 /*Get the bitmap of the image*/
1190 enumerator = [[droppedImg representations] objectEnumerator];
1191 while ((representation = [enumerator nextObject])) {
1192 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1193 bitmapImage = (NSBitmapImageRep *)representation;
1197 if (bitmapImage == nil) return GHOST_kFailure;
1199 if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
1200 && ![bitmapImage isPlanar]) {
1201 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
1202 toIBuf = (GHOST_TUns8*)ibuf->rect;
1203 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
1204 for (y = 0; y < imgSize.height; y++) {
1205 to_i = (imgSize.height-y-1)*imgSize.width;
1206 from_i = y*imgSize.width;
1207 memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
1211 /* Tell cocoa image resolution is same as current system one */
1212 [bitmapImage setSize:imgSize];
1214 /* Convert the image in a RGBA 32bit format */
1215 /* As Core Graphics does not support contexts with non premutliplied alpha,
1216 we need to get alpha key values in a separate batch */
1218 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1219 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1220 pixelsWide:imgSize.width
1221 pixelsHigh:imgSize.height
1222 bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1223 colorSpaceName:NSDeviceRGBColorSpace
1224 bitmapFormat:(NSBitmapFormat)0
1225 bytesPerRow:4*imgSize.width
1226 bitsPerPixel:32/*RGB format padded to 32bits*/];
1228 [NSGraphicsContext saveGraphicsState];
1229 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1231 [NSGraphicsContext restoreGraphicsState];
1233 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1234 if (rasterRGB == NULL) {
1235 [bitmapImage release];
1236 [blBitmapFormatImageRGB release];
1237 [droppedImg release];
1238 return GHOST_kFailure;
1241 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1242 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1243 pixelsWide:imgSize.width
1244 pixelsHigh:imgSize.height
1245 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1246 colorSpaceName:NSDeviceRGBColorSpace
1247 bitmapFormat:(NSBitmapFormat)0
1248 bytesPerRow:4*imgSize.width
1249 bitsPerPixel:32/* RGBA */];
1251 [NSGraphicsContext saveGraphicsState];
1252 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1254 [NSGraphicsContext restoreGraphicsState];
1256 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1257 if (rasterRGBA == NULL) {
1258 [bitmapImage release];
1259 [blBitmapFormatImageRGB release];
1260 [blBitmapFormatImageRGBA release];
1261 [droppedImg release];
1262 return GHOST_kFailure;
1265 /*Copy the image to ibuf, flipping it vertically*/
1266 toIBuf = (GHOST_TUns8*)ibuf->rect;
1267 for (y = 0; y < imgSize.height; y++) {
1268 for (x = 0; x < imgSize.width; x++) {
1269 to_i = (imgSize.height-y-1)*imgSize.width + x;
1270 from_i = y*imgSize.width + x;
1272 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1273 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1274 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1275 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1279 [blBitmapFormatImageRGB release];
1280 [blBitmapFormatImageRGBA release];
1281 [droppedImg release];
1284 eventData = (GHOST_TEventDataPtr) ibuf;
1289 return GHOST_kFailure;
1292 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1296 return GHOST_kFailure;
1298 m_outsideLoopEventProcessed = true;
1299 return GHOST_kSuccess;
1303 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1305 GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1307 //Discard quit event if we are in cursor grab sequence
1308 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1309 return GHOST_kExitCancel;
1311 //Check open windows if some changes are not saved
1312 if (m_windowManager->getAnyModifiedState())
1314 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit ?",
1315 @"Cancel", @"Quit Anyway", nil);
1316 if (shouldQuit == NSAlertAlternateReturn)
1318 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1319 return GHOST_kExitNow;
1321 //Give back focus to the blender window if user selected cancel quit
1322 NSArray *windowsList = [NSApp orderedWindows];
1323 if ([windowsList count]) {
1324 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1325 //Handle the modifiers keyes changed state issue
1326 //as recovering from the quit dialog is like application
1327 //gaining focus back.
1328 //Main issue fixed is Cmd modifier not being cleared
1329 handleApplicationBecomeActiveEvent();
1335 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1336 m_outsideLoopEventProcessed = true;
1337 return GHOST_kExitNow;
1340 return GHOST_kExitCancel;
1343 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1345 NSString *filepath = (NSString*)filepathStr;
1346 int confirmOpen = NSAlertAlternateReturn;
1347 NSArray *windowsList;
1349 size_t filenameTextSize;
1350 GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1356 //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1357 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1358 return GHOST_kExitCancel;
1360 //Check open windows if some changes are not saved
1361 if (m_windowManager->getAnyModifiedState())
1363 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1364 @"Current document has not been saved.\nDo you really want to proceed?",
1365 @"Cancel", @"Open", nil);
1368 //Give back focus to the blender window
1369 windowsList = [NSApp orderedWindows];
1370 if ([windowsList count]) {
1371 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1374 if (confirmOpen == NSAlertAlternateReturn)
1376 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1378 temp_buff = (char*) malloc(filenameTextSize+1);
1380 if (temp_buff == NULL) {
1381 return GHOST_kFailure;
1384 strncpy(temp_buff, [filepath cStringUsingEncoding:NSISOLatin1StringEncoding], filenameTextSize);
1386 temp_buff[filenameTextSize] = '\0';
1388 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1395 GHOST_TSuccess GHOST_SystemCocoa::handleTabletProximity(void *eventPtr)
1397 printf("tablet prox: ");
1398 NSEvent *event = (NSEvent *)eventPtr;
1399 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)
1400 m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1403 printf("\nW failure for event 0x%x",[event type]);
1404 return GHOST_kFailure;
1407 // don't involve the window!
1408 // GHOST_TabletData& ct = window->GetCocoaTabletData();
1410 GHOST_TTabletMode active_tool;
1413 switch ([event pointingDeviceType])
1415 case NSPenPointingDevice:
1417 active_tool = GHOST_kTabletModeStylus;
1418 tool_id_ptr = &m_tablet_pen_id;
1420 case NSEraserPointingDevice:
1422 active_tool = GHOST_kTabletModeEraser;
1423 tool_id_ptr = &m_tablet_pen_id;
1425 case NSCursorPointingDevice:
1427 active_tool = GHOST_kTabletModeNone;
1428 tool_id_ptr = &m_tablet_mouse_id;
1431 printf("<!> unknown device %d\n", [event pointingDeviceType]);
1432 return GHOST_kFailure; // fail on unknown device
1435 if ([event isEnteringProximity]) {
1436 printf("entering\n");
1437 *tool_id_ptr = [event deviceID];
1439 m_tablet_pen_mode = active_tool;
1441 // ct.Active = active_tool;
1442 // ct.Pressure = (active_tool == GHOST_kTabletModeNone) ? /*mouse*/ 1 : /*pen*/ 0;
1446 // this is a good place to remember the tool's capabilities
1449 printf("leaving\n");
1450 *tool_id_ptr = TOOL_ID_NONE;
1452 m_tablet_pen_mode = GHOST_kTabletModeNone;
1454 // ct.Active = GHOST_kTabletModeNone;
1460 return GHOST_kSuccess;
1463 void GHOST_SystemCocoa::fillTabletData(GHOST_TabletData& tablet, void* event_ptr)
1465 NSEvent* event = (NSEvent*)event_ptr;
1466 NSPoint tilt = [event tilt];
1468 tablet.Active = m_tablet_pen_mode;
1469 tablet.Pressure = [event pressure];
1470 tablet.Xtilt = tilt.x;
1471 tablet.Ytilt = tilt.y;
1473 printf("> pressure = %.2f tilt = %.2f %2f\n", tablet.Pressure, tablet.Xtilt, tablet.Ytilt);
1476 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr)
1478 puts("tablet point");
1479 NSEvent *event = (NSEvent*)eventPtr;
1480 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)
1481 m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1484 //printf("\nW failure for event 0x%x",[event type]);
1485 return GHOST_kFailure;
1489 // don't involve the window!
1490 GHOST_TabletData& ct = window->GetCocoaTabletData();
1492 ct.Pressure = [event pressure];
1493 NSPoint tilt = [event tilt];
1498 switch ([event type])
1500 case NSLeftMouseDown:
1502 if (m_input_fidelity_hint == HI_FI)
1504 printf("hi-fi on\n");
1505 [NSEvent setMouseCoalescingEnabled:NO];
1507 GHOST_EventButton* e = new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber]));
1508 GHOST_TEventButtonData* data = (GHOST_TEventButtonData*) e->getData();
1509 fillTabletData(data->tablet, event);
1515 if (m_input_fidelity_hint == HI_FI)
1517 printf("hi-fi off\n");
1518 [NSEvent setMouseCoalescingEnabled:YES];
1520 // no tablet data needed for 'pen up'
1521 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1526 NSPoint pos = [event locationInWindow];
1527 GHOST_EventCursor* e = new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, pos.x, pos.y);
1528 GHOST_TEventCursorData* data = (GHOST_TEventCursorData*) e->getData();
1529 fillTabletData(data->tablet, event);
1535 return GHOST_kSuccess;
1539 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1542 NSEvent *event = (NSEvent *)eventPtr;
1543 GHOST_Window* window =
1544 (GHOST_Window*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1547 printf("\nW failure for event 0x%x",[event type]);
1548 return GHOST_kFailure;
1551 static float warp_dx = 0, warp_dy = 0; // need to reset these. e.g. each grab operation should get its own.
1552 // ^^ not currently useful, try m_cursorDelta_* instead.
1554 switch ([event type]) {
1555 case NSLeftMouseDown:
1556 case NSRightMouseDown:
1557 case NSOtherMouseDown:
1558 printf("button down\n");
1559 if (m_input_fidelity_hint == HI_FI)
1561 printf("hi-fi on\n");
1562 [NSEvent setMouseCoalescingEnabled:NO];
1564 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1568 case NSRightMouseUp:
1569 case NSOtherMouseUp:
1570 printf("button up\n");
1571 if (m_input_fidelity_hint == HI_FI)
1573 printf("hi-fi off\n");
1574 [NSEvent setMouseCoalescingEnabled:YES];
1576 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1577 // cheap hack, should reset when grab ends.
1578 warp_dx = warp_dy = 0;
1581 case NSLeftMouseDragged:
1582 case NSRightMouseDragged:
1583 case NSOtherMouseDragged:
1586 NSPoint mousePos = [event locationInWindow];
1587 float event_dx = [event deltaX];
1588 float event_dy = [event deltaY];
1590 bool coalesced = [NSEvent isMouseCoalescingEnabled];
1593 printf("move <%.2f,%.2f> to (%.2f,%.2f)\n", event_dx, event_dy, mousePos.x, mousePos.y);
1595 event_dy = -event_dy; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1597 switch (window->getCursorGrabMode()) {
1598 case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1600 printf(" - grab hide\n");
1601 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum;
1603 window->getCursorGrabInitPos(x_warp, y_warp);
1605 window->getCursorGrabAccum(x_accum, y_accum);
1606 x_accum += event_dx;
1607 y_accum += event_dy;
1608 window->setCursorGrabAccum(x_accum, y_accum);
1610 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x_warp+x_accum, y_warp+y_accum));
1613 case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1615 NSPoint mousePos = [event locationInWindow];
1616 GHOST_TInt32 x_mouse= mousePos.x;
1617 GHOST_TInt32 y_mouse= mousePos.y;
1618 GHOST_TInt32 x_accum, y_accum, x_cur, y_cur;
1619 GHOST_Rect bounds, windowBounds, correctedBounds;
1621 /* fallback to window bounds */
1622 if(window->getCursorGrabBounds(bounds)==GHOST_kFailure)
1623 window->getClientBounds(bounds);
1625 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1626 window->getClientBounds(windowBounds);
1627 window->screenToClient(bounds.m_l,bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1628 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1629 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1630 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1632 //Update accumulation counts
1633 window->getCursorGrabAccum(x_accum, y_accum);
1634 x_accum += event_dx - m_cursorDelta_x;
1635 y_accum += event_dy - m_cursorDelta_y;
1636 window->setCursorGrabAccum(x_accum, y_accum);
1638 //Warp mouse cursor if needed
1639 x_mouse += event_dx - m_cursorDelta_x;
1640 y_mouse += event_dy - m_cursorDelta_y;
1641 correctedBounds.wrapPoint(x_mouse, y_mouse, 2);
1643 //Compensate for mouse moved event taking cursor position set into account
1644 m_cursorDelta_x = x_mouse-mousePos.x;
1645 m_cursorDelta_y = y_mouse-mousePos.y;
1647 //Set new cursor position
1648 window->clientToScreen(x_mouse, y_mouse, x_cur, y_cur);
1649 setMouseCursorPosition(x_cur, y_cur); /* wrap */
1652 window->getCursorGrabInitPos(x_cur, y_cur);
1653 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x_cur + x_accum, y_cur + y_accum));
1658 //Normal cursor operation: send mouse position in window
1659 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, mousePos.x, mousePos.y));
1661 m_cursorDelta_y=0; //Mouse motion occurred between two cursor warps, so we can reset the delta counter
1664 } // END cursor grab mode
1666 } // END mouse moved
1669 /* Send trackpad event if inside a trackpad gesture, send wheel event otherwise */
1670 if (m_hasMultiTouchTrackpad and m_isGestureInProgress) {
1671 NSPoint mousePos = [event locationInWindow];
1672 double dx = [event deltaX];
1673 double dy = -[event deltaY];
1675 const double deltaMax = 50.0;
1677 if ((dx == 0) && (dy == 0)) break;
1679 /* Quadratic acceleration */
1680 dx = dx*(fabs(dx)+0.5);
1681 if (dx<0.0) dx-=0.5; else dx+=0.5;
1682 if (dx< -deltaMax) dx= -deltaMax; else if (dx>deltaMax) dx=deltaMax;
1684 dy = dy*(fabs(dy)+0.5);
1685 if (dy<0.0) dy-=0.5; else dy+=0.5;
1686 if (dy< -deltaMax) dy= -deltaMax; else if (dy>deltaMax) dy=deltaMax;
1688 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventScroll, mousePos.x, mousePos.y, dx, dy));
1693 double deltaF = [event deltaY];
1694 if (deltaF == 0.0) break; //discard trackpad delta=0 events
1696 delta = deltaF > 0.0 ? 1 : -1;
1697 pushEvent(new GHOST_EventWheel([event timestamp]*1000, window, delta));
1701 case NSEventTypeMagnify:
1703 NSPoint mousePos = [event locationInWindow];
1704 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventMagnify, mousePos.x, mousePos.y, [event magnification]*250.0 + 0.1, 0));
1707 case NSEventTypeRotate:
1709 NSPoint mousePos = [event locationInWindow];
1710 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventRotate, mousePos.x, mousePos.y, -[event rotation] * 5.0, 0));
1713 case NSEventTypeBeginGesture:
1714 m_isGestureInProgress = true;
1716 case NSEventTypeEndGesture:
1717 m_isGestureInProgress = false;
1720 printf("<!> unknown event type %d\n", [event type]);
1721 return GHOST_kFailure;
1724 return GHOST_kSuccess;
1728 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1730 NSEvent *event = (NSEvent *)eventPtr;
1731 GHOST_IWindow* window;
1732 unsigned int modifiers;
1733 NSString *characters;
1734 NSData *convertedCharacters;
1736 unsigned char ascii;
1737 NSString* charsIgnoringModifiers;
1739 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1741 //printf("\nW failure for event 0x%x",[event type]);
1742 return GHOST_kFailure;
1745 switch ([event type]) {
1748 charsIgnoringModifiers = [event charactersIgnoringModifiers];
1749 if ([charsIgnoringModifiers length]>0)
1750 keyCode = convertKey([event keyCode],
1751 [charsIgnoringModifiers characterAtIndex:0],
1752 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1754 keyCode = convertKey([event keyCode],0,
1755 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1758 characters = [event characters];
1759 if ([characters length]>0) { //Check for dead keys
1760 //Convert characters to iso latin 1 encoding
1761 convertedCharacters = [characters dataUsingEncoding:NSISOLatin1StringEncoding];
1762 if ([convertedCharacters length]>0)
1763 ascii =((char*)[convertedCharacters bytes])[0];
1765 ascii = 0; //Character not available in iso latin 1 encoding
1770 if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1771 break; //Cmd-Q is directly handled by Cocoa
1773 if ([event type] == NSKeyDown) {
1774 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyDown, window, keyCode, ascii) );
1775 //printf("\nKey down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii);
1777 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyUp, window, keyCode, ascii) );
1778 //printf("\nKey up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii);
1782 case NSFlagsChanged:
1783 modifiers = [event modifierFlags];
1785 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1786 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1788 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1789 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1791 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1792 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1794 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1795 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyCommand) );
1798 m_modifierMask = modifiers;
1802 return GHOST_kFailure;
1806 return GHOST_kSuccess;
1810 #pragma mark Clipboard get/set
1812 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1814 GHOST_TUns8 * temp_buff;
1815 size_t pastedTextSize;
1817 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1819 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1821 if (pasteBoard == nil) {
1826 NSArray *supportedTypes =
1827 [NSArray arrayWithObjects: NSStringPboardType, nil];
1829 NSString *bestType = [[NSPasteboard generalPasteboard]
1830 availableTypeFromArray:supportedTypes];
1832 if (bestType == nil) {
1837 NSString * textPasted = [pasteBoard stringForType:NSStringPboardType];
1839 if (textPasted == nil) {
1844 pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1846 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1848 if (temp_buff == NULL) {
1853 strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSISOLatin1StringEncoding], pastedTextSize);
1855 temp_buff[pastedTextSize] = '\0';
1866 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1868 NSString *textToCopy;
1870 if(selection) {return;} // for copying the selection, used on X11
1872 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1874 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1876 if (pasteBoard == nil) {
1881 NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1883 [pasteBoard declareTypes:supportedTypes owner:nil];
1885 textToCopy = [NSString stringWithCString:buffer encoding:NSISOLatin1StringEncoding];
1887 [pasteBoard setString:textToCopy forType:NSStringPboardType];
1892 #pragma mark Base directories retrieval
1894 const GHOST_TUns8* GHOST_SystemCocoa::getSystemDir() const
1896 static GHOST_TUns8 tempPath[512] = "";
1897 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1901 paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, YES);
1903 if ([paths count] > 0)
1904 basePath = [paths objectAtIndex:0];
1910 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
1916 const GHOST_TUns8* GHOST_SystemCocoa::getUserDir() const
1918 static GHOST_TUns8 tempPath[512] = "";
1919 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1923 paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
1925 if ([paths count] > 0)
1926 basePath = [paths objectAtIndex:0];
1932 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
1938 const GHOST_TUns8* GHOST_SystemCocoa::getBinaryDir() const
1940 static GHOST_TUns8 tempPath[512] = "";
1941 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1944 basePath = [[NSBundle mainBundle] bundlePath];
1946 if (basePath == nil) {
1951 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);