1/*************************************************************************/ 2/* os_osx.mm */ 3/*************************************************************************/ 4/* This file is part of: */ 5/* GODOT ENGINE */ 6/* https://godotengine.org */ 7/*************************************************************************/ 8/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ 9/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ 10/* */ 11/* Permission is hereby granted, free of charge, to any person obtaining */ 12/* a copy of this software and associated documentation files (the */ 13/* "Software"), to deal in the Software without restriction, including */ 14/* without limitation the rights to use, copy, modify, merge, publish, */ 15/* distribute, sublicense, and/or sell copies of the Software, and to */ 16/* permit persons to whom the Software is furnished to do so, subject to */ 17/* the following conditions: */ 18/* */ 19/* The above copyright notice and this permission notice shall be */ 20/* included in all copies or substantial portions of the Software. */ 21/* */ 22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29/*************************************************************************/ 30#include "os_osx.h" 31 32#include "dir_access_osx.h" 33#include "drivers/gles2/rasterizer_instance_gles2.h" 34#include "main/main.h" 35#include "os/keyboard.h" 36#include "print_string.h" 37#include "scene/resources/texture.h" 38#include "sem_osx.h" 39#include "servers/physics/physics_server_sw.h" 40#include "servers/visual/visual_server_raster.h" 41#include "servers/visual/visual_server_wrap_mt.h" 42 43#include <Carbon/Carbon.h> 44#import <Cocoa/Cocoa.h> 45#include <IOKit/IOCFPlugIn.h> 46#include <IOKit/IOKitLib.h> 47#include <IOKit/hid/IOHIDKeys.h> 48#include <IOKit/hid/IOHIDLib.h> 49#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 50#include <os/log.h> 51#endif 52 53#include <fcntl.h> 54#include <libproc.h> 55#include <stdio.h> 56#include <stdlib.h> 57#include <sys/stat.h> 58#include <sys/types.h> 59#include <unistd.h> 60 61//uses portions of glfw 62 63//======================================================================== 64// GLFW 3.0 - www.glfw.org 65//------------------------------------------------------------------------ 66// Copyright (c) 2002-2006 Marcus Geelnard 67// Copyright (c) 2006-2010 Camilla Berglund <elmindreda@elmindreda.org> 68// 69// This software is provided 'as-is', without any express or implied 70// warranty. In no event will the authors be held liable for any damages 71// arising from the use of this software. 72// 73// Permission is granted to anyone to use this software for any purpose, 74// including commercial applications, and to alter it and redistribute it 75// freely, subject to the following restrictions: 76// 77// 1. The origin of this software must not be misrepresented; you must not 78// claim that you wrote the original software. If you use this software 79// in a product, an acknowledgment in the product documentation would 80// be appreciated but is not required. 81// 82// 2. Altered source versions must be plainly marked as such, and must not 83// be misrepresented as being the original software. 84// 85// 3. This notice may not be removed or altered from any source 86// distribution. 87// 88//======================================================================== 89 90#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 91#define NSWindowStyleMaskBorderless NSBorderlessWindowMask 92#endif 93 94static NSRect convertRectToBacking(NSRect contentRect) { 95 96#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 97 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) 98 return [OS_OSX::singleton->window_view convertRectToBacking:contentRect]; 99 else 100#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 101 return contentRect; 102} 103 104static InputModifierState translateFlags(NSUInteger flags) { 105 InputModifierState mod; 106 107 mod.shift = (flags & NSShiftKeyMask); 108 mod.control = (flags & NSControlKeyMask); 109 mod.alt = (flags & NSAlternateKeyMask); 110 mod.meta = (flags & NSCommandKeyMask); 111 112 return mod; 113} 114 115static int mouse_x = 0; 116static int mouse_y = 0; 117static int prev_mouse_x = 0; 118static int prev_mouse_y = 0; 119static int button_mask = 0; 120static bool mouse_down_control = false; 121 122@interface GodotApplication : NSApplication 123@end 124 125@implementation GodotApplication 126 127// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost 128// This works around an AppKit bug, where key up events while holding 129// down the command key don't get sent to the key window. 130- (void)sendEvent:(NSEvent *)event { 131 if ([event type] == NSKeyUp && ([event modifierFlags] & NSCommandKeyMask)) 132 [[self keyWindow] sendEvent:event]; 133 else 134 [super sendEvent:event]; 135} 136 137@end 138 139@interface GodotApplicationDelegate : NSObject 140@end 141 142@implementation GodotApplicationDelegate 143 144- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { 145 if (OS_OSX::singleton->get_main_loop()) 146 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); 147 148 return NSTerminateCancel; 149} 150 151- (void)applicationDidHide:(NSNotification *)notification { 152 /* 153 _Godotwindow* window; 154 155 for (window = _Godot.windowListHead; window; window = window->next) 156 _GodotInputWindowVisibility(window, GL_FALSE); 157*/ 158} 159 160- (void)applicationDidUnhide:(NSNotification *)notification { 161 /* 162 _Godotwindow* window; 163 164 for (window = _Godot.windowListHead; window; window = window->next) 165 { 166 if ([window_object isVisible]) 167 _GodotInputWindowVisibility(window, GL_TRUE); 168 } 169*/ 170} 171 172- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { 173 //_GodotInputMonitorChange(); 174} 175 176@end 177 178@interface GodotWindowDelegate : NSObject { 179 //_Godotwindow* window; 180} 181 182@end 183 184@implementation GodotWindowDelegate 185 186- (BOOL)windowShouldClose:(id)sender { 187 //_GodotInputWindowCloseRequest(window); 188 if (OS_OSX::singleton->get_main_loop()) 189 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); 190 return NO; 191} 192 193#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 194- (void)windowDidEnterFullScreen:(NSNotification *)notification { 195 OS_OSX::singleton->zoomed = true; 196} 197 198- (void)windowDidExitFullScreen:(NSNotification *)notification { 199 OS_OSX::singleton->zoomed = false; 200} 201#endif // MAC_OS_X_VERSION_MAX_ALLOWED 202 203- (void)windowDidChangeBackingProperties:(NSNotification *)notification { 204 if (!OS_OSX::singleton) 205 return; 206 207 NSWindow *window = (NSWindow *)[notification object]; 208 CGFloat newBackingScaleFactor = [window backingScaleFactor]; 209 CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; 210 211 if (newBackingScaleFactor != oldBackingScaleFactor) { 212 //Set new display scale and window size 213 OS_OSX::singleton->display_scale = newBackingScaleFactor; 214 215 const NSRect contentRect = [OS_OSX::singleton->window_view frame]; 216 const NSRect fbRect = contentRect; //convertRectToBacking(contentRect); 217 218 OS_OSX::singleton->window_size.width = fbRect.size.width * OS_OSX::singleton->display_scale; 219 OS_OSX::singleton->window_size.height = fbRect.size.height * OS_OSX::singleton->display_scale; 220 221 //Update context 222 if (OS_OSX::singleton->main_loop) { 223 [OS_OSX::singleton->context update]; 224 225 //Force window resize ??? 226 NSRect frame = [OS_OSX::singleton->window_object frame]; 227 [OS_OSX::singleton->window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, 1, 1) display:YES]; 228 [OS_OSX::singleton->window_object setFrame:frame display:YES]; 229 } 230 } 231} 232 233- (void)windowDidResize:(NSNotification *)notification { 234 [OS_OSX::singleton->context update]; 235 236 const NSRect contentRect = [OS_OSX::singleton->window_view frame]; 237 const NSRect fbRect = contentRect; //convertRectToBacking(contentRect); 238 239 OS_OSX::singleton->window_size.width = fbRect.size.width * OS_OSX::singleton->display_scale; 240 OS_OSX::singleton->window_size.height = fbRect.size.height * OS_OSX::singleton->display_scale; 241 242 if (OS_OSX::singleton->main_loop) { 243 Main::force_redraw(); 244 //Event retrieval blocks until resize is over. Call Main::iteration() directly. 245 Main::iteration(); 246 } 247 248 //_GodotInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); 249 //_GodotInputWindowSize(window, contentRect.size.width, contentRect.size.height); 250 //_GodotInputWindowDamage(window); 251 252 //if (window->cursorMode == Godot_CURSOR_DISABLED) 253 // centerCursor(window); 254} 255 256- (void)windowDidMove:(NSNotification *)notification { 257 //[window->nsgl.context update]; 258 259 //int x, y; 260 //_GodotPlatformGetWindowPos(window, &x, &y); 261 //_GodotInputWindowPos(window, x, y); 262 263 //if (window->cursorMode == Godot_CURSOR_DISABLED) 264 // centerCursor(window); 265} 266 267- (void)windowDidBecomeKey:(NSNotification *)notification { 268 //_GodotInputWindowFocus(window, GL_TRUE); 269 //_GodotPlatformSetCursorMode(window, window->cursorMode); 270 if (OS_OSX::singleton->get_main_loop()) 271 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); 272} 273 274- (void)windowDidResignKey:(NSNotification *)notification { 275 //_GodotInputWindowFocus(window, GL_FALSE); 276 //_GodotPlatformSetCursorMode(window, Godot_CURSOR_NORMAL); 277 if (OS_OSX::singleton->get_main_loop()) 278 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); 279} 280 281- (void)windowDidMiniaturize:(NSNotification *)notification { 282 OS_OSX::singleton->wm_minimized(true); 283 if (OS_OSX::singleton->get_main_loop()) 284 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); 285}; 286 287- (void)windowDidDeminiaturize:(NSNotification *)notification { 288 OS_OSX::singleton->wm_minimized(false); 289 if (OS_OSX::singleton->get_main_loop()) 290 OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); 291}; 292 293@end 294 295@interface GodotContentView : NSView { 296 NSTrackingArea *trackingArea; 297} 298 299@end 300 301@implementation GodotContentView 302 303+ (void)initialize { 304 if (self == [GodotContentView class]) { 305 /* 306 if (_glfw.ns.cursor == nil) { 307 NSImage* data = [[NSImage alloc] initWithSize:NSMakeSize(1, 1)]; 308 _glfw.ns.cursor = [[NSCursor alloc] initWithImage:data hotSpot:NSZeroPoint]; 309 [data release]; 310 } 311*/ 312 } 313} 314 315- (id)init { 316 self = [super init]; 317 trackingArea = nil; 318 [self updateTrackingAreas]; 319 [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; 320 return self; 321} 322 323- (void)dealloc { 324 [trackingArea release]; 325 [super dealloc]; 326} 327 328- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { 329 return NSDragOperationCopy; 330} 331 332- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { 333 return NSDragOperationCopy; 334} 335 336- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { 337 NSPasteboard *pboard = [sender draggingPasteboard]; 338 NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; 339 340 Vector<String> files; 341 for (int i = 0; i < filenames.count; i++) { 342 NSString *ns = [filenames objectAtIndex:i]; 343 char *utfs = strdup([ns UTF8String]); 344 String ret; 345 ret.parse_utf8(utfs); 346 free(utfs); 347 files.push_back(ret); 348 } 349 350 if (files.size()) { 351 OS_OSX::singleton->main_loop->drop_files(files, 0); 352 OS_OSX::singleton->move_window_to_foreground(); 353 } 354 355 return NO; 356} 357 358- (BOOL)isOpaque { 359 return YES; 360} 361 362- (BOOL)canBecomeKeyView { 363 return YES; 364} 365 366- (BOOL)acceptsFirstResponder { 367 return YES; 368} 369 370- (void)cursorUpdate:(NSEvent *)event { 371 //setModeCursor(window, window->cursorMode); 372} 373 374static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { 375 if (pressed) { 376 button_mask |= mask; 377 } else { 378 button_mask &= ~mask; 379 } 380 381 InputEvent ev; 382 383 ev.type = InputEvent::MOUSE_BUTTON; 384 ev.mouse_button.button_index = index; 385 ev.mouse_button.pressed = pressed; 386 ev.mouse_button.x = mouse_x; 387 ev.mouse_button.y = mouse_y; 388 ev.mouse_button.global_x = mouse_x; 389 ev.mouse_button.global_y = mouse_y; 390 ev.mouse_button.button_mask = button_mask; 391 if (index == BUTTON_LEFT && pressed) { 392 ev.mouse_button.doubleclick = [event clickCount] == 2; 393 } 394 ev.mouse_button.mod = translateFlags([event modifierFlags]); 395 396 OS_OSX::singleton->push_input(ev); 397} 398 399- (void)mouseDown:(NSEvent *)event { 400 if (([event modifierFlags] & NSControlKeyMask)) { 401 mouse_down_control = true; 402 _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); 403 } else { 404 mouse_down_control = false; 405 _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); 406 } 407} 408 409- (void)mouseDragged:(NSEvent *)event { 410 [self mouseMoved:event]; 411} 412 413- (void)mouseUp:(NSEvent *)event { 414 if (mouse_down_control) { 415 _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); 416 } else { 417 _mouseDownEvent(event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); 418 } 419} 420 421- (void)mouseMoved:(NSEvent *)event { 422 423 InputEvent ev; 424 ev.type = InputEvent::MOUSE_MOTION; 425 ev.mouse_motion.button_mask = button_mask; 426 prev_mouse_x = mouse_x; 427 prev_mouse_y = mouse_y; 428 const NSRect contentRect = [OS_OSX::singleton->window_view frame]; 429 const NSPoint p = [event locationInWindow]; 430 mouse_x = p.x * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]); 431 mouse_y = (contentRect.size.height - p.y) * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]); 432 ev.mouse_motion.x = mouse_x; 433 ev.mouse_motion.y = mouse_y; 434 ev.mouse_motion.global_x = mouse_x; 435 ev.mouse_motion.global_y = mouse_y; 436 ev.mouse_motion.relative_x = [event deltaX] * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]); 437 ev.mouse_motion.relative_y = [event deltaY] * OS_OSX::singleton->_mouse_scale([[event window] backingScaleFactor]); 438 ev.mouse_motion.mod = translateFlags([event modifierFlags]); 439 440 OS_OSX::singleton->input->set_mouse_pos(Point2(mouse_x, mouse_y)); 441 OS_OSX::singleton->push_input(ev); 442 443 /* 444 if (window->cursorMode == GLFW_CURSOR_DISABLED) 445 _glfwInputCursorMotion(window, [event deltaX], [event deltaY]); 446 else { 447 const NSRect contentRect = [window->ns.view frame]; 448 const NSPoint p = [event locationInWindow]; 449 450 _glfwInputCursorMotion(window, p.x, contentRect.size.height - p.y); 451 } 452*/ 453} 454 455- (void)rightMouseDown:(NSEvent *)event { 456 _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); 457} 458 459- (void)rightMouseDragged:(NSEvent *)event { 460 [self mouseMoved:event]; 461} 462 463- (void)rightMouseUp:(NSEvent *)event { 464 _mouseDownEvent(event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); 465} 466 467- (void)otherMouseDown:(NSEvent *)event { 468 469 if ((int)[event buttonNumber] != 2) 470 return; 471 472 _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); 473} 474 475- (void)otherMouseDragged:(NSEvent *)event { 476 [self mouseMoved:event]; 477} 478 479- (void)otherMouseUp:(NSEvent *)event { 480 481 if ((int)[event buttonNumber] != 2) 482 return; 483 484 _mouseDownEvent(event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); 485} 486 487- (void)mouseExited:(NSEvent *)event { 488 if (!OS_OSX::singleton) 489 return; 490 491 if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) 492 OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); 493 494 //_glfwInputCursorEnter(window, GL_FALSE); 495} 496 497- (void)mouseEntered:(NSEvent *)event { 498 //_glfwInputCursorEnter(window, GL_TRUE); 499 if (!OS_OSX::singleton) 500 return; 501 502 if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) 503 OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); 504 505 if (OS_OSX::singleton->input) { 506 OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; 507 OS_OSX::singleton->set_cursor_shape(OS::CURSOR_ARROW); 508 } 509} 510 511- (void)viewDidChangeBackingProperties { 512 /* 513 const NSRect contentRect = [window->ns.view frame]; 514 const NSRect fbRect = convertRectToBacking(window, contentRect); 515 516 _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); 517*/ 518} 519 520- (void)updateTrackingAreas { 521 if (trackingArea != nil) { 522 [self removeTrackingArea:trackingArea]; 523 [trackingArea release]; 524 } 525 526 NSTrackingAreaOptions options = 527 NSTrackingMouseEnteredAndExited | 528 NSTrackingActiveInKeyWindow | 529 NSTrackingCursorUpdate | 530 NSTrackingInVisibleRect; 531 532 trackingArea = [[NSTrackingArea alloc] 533 initWithRect:[self bounds] 534 options:options 535 owner:self 536 userInfo:nil]; 537 538 [self addTrackingArea:trackingArea]; 539 [super updateTrackingAreas]; 540} 541 542// Translates a OS X keycode to a Godot keycode 543// 544static int translateKey(unsigned int key) { 545 // Keyboard symbol translation table 546 static const unsigned int table[128] = { 547 /* 00 */ KEY_A, 548 /* 01 */ KEY_S, 549 /* 02 */ KEY_D, 550 /* 03 */ KEY_F, 551 /* 04 */ KEY_H, 552 /* 05 */ KEY_G, 553 /* 06 */ KEY_Z, 554 /* 07 */ KEY_X, 555 /* 08 */ KEY_C, 556 /* 09 */ KEY_V, 557 /* 0a */ KEY_UNKNOWN, 558 /* 0b */ KEY_B, 559 /* 0c */ KEY_Q, 560 /* 0d */ KEY_W, 561 /* 0e */ KEY_E, 562 /* 0f */ KEY_R, 563 /* 10 */ KEY_Y, 564 /* 11 */ KEY_T, 565 /* 12 */ KEY_1, 566 /* 13 */ KEY_2, 567 /* 14 */ KEY_3, 568 /* 15 */ KEY_4, 569 /* 16 */ KEY_6, 570 /* 17 */ KEY_5, 571 /* 18 */ KEY_EQUAL, 572 /* 19 */ KEY_9, 573 /* 1a */ KEY_7, 574 /* 1b */ KEY_MINUS, 575 /* 1c */ KEY_8, 576 /* 1d */ KEY_0, 577 /* 1e */ KEY_BRACERIGHT, 578 /* 1f */ KEY_O, 579 /* 20 */ KEY_U, 580 /* 21 */ KEY_BRACELEFT, 581 /* 22 */ KEY_I, 582 /* 23 */ KEY_P, 583 /* 24 */ KEY_RETURN, 584 /* 25 */ KEY_L, 585 /* 26 */ KEY_J, 586 /* 27 */ KEY_APOSTROPHE, 587 /* 28 */ KEY_K, 588 /* 29 */ KEY_SEMICOLON, 589 /* 2a */ KEY_BACKSLASH, 590 /* 2b */ KEY_COMMA, 591 /* 2c */ KEY_SLASH, 592 /* 2d */ KEY_N, 593 /* 2e */ KEY_M, 594 /* 2f */ KEY_PERIOD, 595 /* 30 */ KEY_TAB, 596 /* 31 */ KEY_SPACE, 597 /* 32 */ KEY_QUOTELEFT, 598 /* 33 */ KEY_BACKSPACE, 599 /* 34 */ KEY_UNKNOWN, 600 /* 35 */ KEY_ESCAPE, 601 /* 36 */ KEY_META, 602 /* 37 */ KEY_META, 603 /* 38 */ KEY_SHIFT, 604 /* 39 */ KEY_CAPSLOCK, 605 /* 3a */ KEY_ALT, 606 /* 3b */ KEY_CONTROL, 607 /* 3c */ KEY_SHIFT, 608 /* 3d */ KEY_ALT, 609 /* 3e */ KEY_CONTROL, 610 /* 3f */ KEY_UNKNOWN, /* Function */ 611 /* 40 */ KEY_UNKNOWN, 612 /* 41 */ KEY_KP_PERIOD, 613 /* 42 */ KEY_UNKNOWN, 614 /* 43 */ KEY_KP_MULTIPLY, 615 /* 44 */ KEY_UNKNOWN, 616 /* 45 */ KEY_KP_ADD, 617 /* 46 */ KEY_UNKNOWN, 618 /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ 619 /* 48 */ KEY_UNKNOWN, /* VolumeUp */ 620 /* 49 */ KEY_UNKNOWN, /* VolumeDown */ 621 /* 4a */ KEY_UNKNOWN, /* Mute */ 622 /* 4b */ KEY_KP_DIVIDE, 623 /* 4c */ KEY_ENTER, 624 /* 4d */ KEY_UNKNOWN, 625 /* 4e */ KEY_KP_SUBTRACT, 626 /* 4f */ KEY_UNKNOWN, 627 /* 50 */ KEY_UNKNOWN, 628 /* 51 */ KEY_EQUAL, //wtf equal? 629 /* 52 */ KEY_KP_0, 630 /* 53 */ KEY_KP_1, 631 /* 54 */ KEY_KP_2, 632 /* 55 */ KEY_KP_3, 633 /* 56 */ KEY_KP_4, 634 /* 57 */ KEY_KP_5, 635 /* 58 */ KEY_KP_6, 636 /* 59 */ KEY_KP_7, 637 /* 5a */ KEY_UNKNOWN, 638 /* 5b */ KEY_KP_8, 639 /* 5c */ KEY_KP_9, 640 /* 5d */ KEY_UNKNOWN, 641 /* 5e */ KEY_UNKNOWN, 642 /* 5f */ KEY_UNKNOWN, 643 /* 60 */ KEY_F5, 644 /* 61 */ KEY_F6, 645 /* 62 */ KEY_F7, 646 /* 63 */ KEY_F3, 647 /* 64 */ KEY_F8, 648 /* 65 */ KEY_F9, 649 /* 66 */ KEY_UNKNOWN, 650 /* 67 */ KEY_F11, 651 /* 68 */ KEY_UNKNOWN, 652 /* 69 */ KEY_F13, 653 /* 6a */ KEY_F16, 654 /* 6b */ KEY_F14, 655 /* 6c */ KEY_UNKNOWN, 656 /* 6d */ KEY_F10, 657 /* 6e */ KEY_UNKNOWN, 658 /* 6f */ KEY_F12, 659 /* 70 */ KEY_UNKNOWN, 660 /* 71 */ KEY_F15, 661 /* 72 */ KEY_INSERT, /* Really Help... */ 662 /* 73 */ KEY_HOME, 663 /* 74 */ KEY_PAGEUP, 664 /* 75 */ KEY_DELETE, 665 /* 76 */ KEY_F4, 666 /* 77 */ KEY_END, 667 /* 78 */ KEY_F2, 668 /* 79 */ KEY_PAGEDOWN, 669 /* 7a */ KEY_F1, 670 /* 7b */ KEY_LEFT, 671 /* 7c */ KEY_RIGHT, 672 /* 7d */ KEY_DOWN, 673 /* 7e */ KEY_UP, 674 /* 7f */ KEY_UNKNOWN, 675 }; 676 677 if (key >= 128) 678 return KEY_UNKNOWN; 679 680 return table[key]; 681} 682 683- (void)keyDown:(NSEvent *)event { 684 InputEvent ev; 685 ev.type = InputEvent::KEY; 686 ev.key.pressed = true; 687 ev.key.mod = translateFlags([event modifierFlags]); 688 ev.key.scancode = latin_keyboard_keycode_convert(translateKey([event keyCode])); 689 ev.key.echo = [event isARepeat]; 690 691 NSString *characters = [event characters]; 692 NSUInteger i, length = [characters length]; 693 694 if (length > 0 && keycode_has_unicode(ev.key.scancode)) { 695 for (i = 0; i < length; i++) { 696 ev.key.unicode = [characters characterAtIndex:i]; 697 OS_OSX::singleton->push_input(ev); 698 ev.key.scancode = 0; 699 } 700 } else { 701 OS_OSX::singleton->push_input(ev); 702 } 703} 704 705- (void)flagsChanged:(NSEvent *)event { 706 InputEvent ev; 707 int key = [event keyCode]; 708 int mod = [event modifierFlags]; 709 710 ev.type = InputEvent::KEY; 711 712 if (key == 0x36 || key == 0x37) { 713 if (mod & NSCommandKeyMask) { 714 mod &= ~NSCommandKeyMask; 715 ev.key.pressed = true; 716 } else { 717 ev.key.pressed = false; 718 } 719 } else if (key == 0x38 || key == 0x3c) { 720 if (mod & NSShiftKeyMask) { 721 mod &= ~NSShiftKeyMask; 722 ev.key.pressed = true; 723 } else { 724 ev.key.pressed = false; 725 } 726 } else if (key == 0x3a || key == 0x3d) { 727 if (mod & NSAlternateKeyMask) { 728 mod &= ~NSAlternateKeyMask; 729 ev.key.pressed = true; 730 } else { 731 ev.key.pressed = false; 732 } 733 } else if (key == 0x3b || key == 0x3e) { 734 if (mod & NSControlKeyMask) { 735 mod &= ~NSControlKeyMask; 736 ev.key.pressed = true; 737 } else { 738 ev.key.pressed = false; 739 } 740 } else { 741 return; 742 } 743 744 ev.key.mod = translateFlags(mod); 745 ev.key.scancode = latin_keyboard_keycode_convert(translateKey(key)); 746 747 OS_OSX::singleton->push_input(ev); 748} 749 750- (void)keyUp:(NSEvent *)event { 751 752 InputEvent ev; 753 ev.type = InputEvent::KEY; 754 ev.key.pressed = false; 755 ev.key.mod = translateFlags([event modifierFlags]); 756 ev.key.scancode = latin_keyboard_keycode_convert(translateKey([event keyCode])); 757 OS_OSX::singleton->push_input(ev); 758 759 /* 760 const int key = translateKey([event keyCode]); 761 const int mods = translateFlags([event modifierFlags]); 762 _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); 763*/ 764} 765 766inline void sendScrollEvent(int button, double factor) { 767 InputEvent ev; 768 ev.type = InputEvent::MOUSE_BUTTON; 769 ev.mouse_button.button_index = button; 770 ev.mouse_button.factor = factor; 771 ev.mouse_button.pressed = true; 772 ev.mouse_button.x = mouse_x; 773 ev.mouse_button.y = mouse_y; 774 ev.mouse_button.global_x = mouse_x; 775 ev.mouse_button.global_y = mouse_y; 776 ev.mouse_button.button_mask = button_mask; 777 OS_OSX::singleton->push_input(ev); 778 ev.mouse_button.pressed = false; 779 OS_OSX::singleton->push_input(ev); 780} 781 782- (void)scrollWheel:(NSEvent *)event { 783 784 double deltaX, deltaY; 785 786#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 787 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) { 788 deltaX = [event scrollingDeltaX]; 789 deltaY = [event scrollingDeltaY]; 790 791 if ([event hasPreciseScrollingDeltas]) { 792 deltaX *= 0.03; 793 deltaY *= 0.03; 794 } 795 } else 796#endif // MAC_OS_X_VERSION_MAX_ALLOWED 797 { 798 deltaX = [event deltaX]; 799 deltaY = [event deltaY]; 800 } 801 802 if (fabs(deltaX)) { 803 sendScrollEvent(0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3)); 804 } 805 if (fabs(deltaY)) { 806 sendScrollEvent(0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3)); 807 } 808} 809 810@end 811 812@interface GodotWindow : NSWindow { 813} 814@end 815 816@implementation GodotWindow 817 818- (BOOL)canBecomeKeyWindow { 819 // Required for NSBorderlessWindowMask windows 820 return YES; 821} 822 823@end 824 825int OS_OSX::get_video_driver_count() const { 826 return 1; 827} 828 829const char *OS_OSX::get_video_driver_name(int p_driver) const { 830 return "GLES2"; 831} 832 833OS::VideoMode OS_OSX::get_default_video_mode() const { 834 835 VideoMode vm; 836 vm.width = 1024; 837 vm.height = 600; 838 vm.fullscreen = false; 839 vm.resizable = true; 840 return vm; 841} 842 843void OS_OSX::initialize_core() { 844 845 crash_handler.initialize(); 846 847 OS_Unix::initialize_core(); 848 849 DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES); 850 DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA); 851 DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM); 852 853 SemaphoreOSX::make_default(); 854} 855 856static bool keyboard_layout_dirty = true; 857static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { 858 keyboard_layout_dirty = true; 859} 860 861static bool displays_arrangement_dirty = true; 862static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { 863 displays_arrangement_dirty = true; 864} 865 866void OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { 867 868 /*** OSX INITIALIZATION ***/ 869 /*** OSX INITIALIZATION ***/ 870 /*** OSX INITIALIZATION ***/ 871 872 keyboard_layout_dirty = true; 873 displays_arrangement_dirty = true; 874 875 // Register to be notified on keyboard layout changes 876 CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), 877 NULL, keyboard_layout_changed, 878 kTISNotifySelectedKeyboardInputSourceChanged, NULL, 879 CFNotificationSuspensionBehaviorDeliverImmediately); 880 881 // Register to be notified on displays arrangement changes 882 CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL); 883 884 if (is_hidpi_allowed() && [[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)]) { 885 for (NSScreen *screen in [NSScreen screens]) { 886 float s = [screen backingScaleFactor]; 887 if (s > display_scale) { 888 display_scale = s; 889 } 890 } 891 } 892 893 window_delegate = [[GodotWindowDelegate alloc] init]; 894 895 // Don't use accumulation buffer support; it's not accelerated 896 // Aux buffers probably aren't accelerated either 897 898 unsigned int styleMask; 899 900 if (p_desired.borderless_window) { 901 styleMask = NSWindowStyleMaskBorderless; 902 } else { 903 styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | (p_desired.resizable ? NSResizableWindowMask : 0); 904 } 905 906 window_object = [[GodotWindow alloc] 907 initWithContentRect:NSMakeRect(0, 0, p_desired.width, p_desired.height) 908 styleMask:styleMask 909 backing:NSBackingStoreBuffered 910 defer:NO]; 911 912 ERR_FAIL_COND(window_object == nil); 913 914 window_view = [[GodotContentView alloc] init]; 915 916 window_size.width = p_desired.width * display_scale; 917 window_size.height = p_desired.height * display_scale; 918 919#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 920 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6 && display_scale > 1) { 921 [window_view setWantsBestResolutionOpenGLSurface:YES]; 922 //if (current_videomode.resizable) 923 [window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 924 } 925#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 926 927 //[window_object setTitle:[NSString stringWithUTF8String:"GodotEnginies"]]; 928 [window_object setContentView:window_view]; 929 [window_object setDelegate:window_delegate]; 930 [window_object setAcceptsMouseMovedEvents:YES]; 931 [window_object center]; 932 933#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 934 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) 935 [window_object setRestorable:NO]; 936#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 937 938 unsigned int attributeCount = 0; 939 940 // OS X needs non-zero color size, so set resonable values 941 int colorBits = 32; 942 943// Fail if a robustness strategy was requested 944#define ADD_ATTR(x) \ 945 { attributes[attributeCount++] = x; } 946#define ADD_ATTR2(x, y) \ 947 { \ 948 ADD_ATTR(x); \ 949 ADD_ATTR(y); \ 950 } 951 952 // Arbitrary array size here 953 NSOpenGLPixelFormatAttribute attributes[40]; 954 955 ADD_ATTR(NSOpenGLPFADoubleBuffer); 956 ADD_ATTR(NSOpenGLPFAClosestPolicy); 957 958#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 959 if (false /* use gl3*/) 960 ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); 961#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 962 963 ADD_ATTR2(NSOpenGLPFAColorSize, colorBits); 964 965 /* 966 if (fbconfig->alphaBits > 0) 967 ADD_ATTR2(NSOpenGLPFAAlphaSize, fbconfig->alphaBits); 968*/ 969 970 ADD_ATTR2(NSOpenGLPFADepthSize, 24); 971 972 ADD_ATTR2(NSOpenGLPFAStencilSize, 8); 973 974 /* 975 if (fbconfig->stereo) 976 ADD_ATTR(NSOpenGLPFAStereo); 977*/ 978 979 /* 980 if (fbconfig->samples > 0) { 981 ADD_ATTR2(NSOpenGLPFASampleBuffers, 1); 982 ADD_ATTR2(NSOpenGLPFASamples, fbconfig->samples); 983 } 984*/ 985 986 // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB 987 // frambuffer, so there's no need (and no way) to request it 988 989 ADD_ATTR(0); 990 991#undef ADD_ATTR 992#undef ADD_ATTR2 993 994 pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; 995 ERR_FAIL_COND(pixelFormat == nil); 996 997 context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; 998 999 ERR_FAIL_COND(context == nil); 1000 1001 [context setView:window_view]; 1002 1003 [context makeCurrentContext]; 1004 1005 [NSApp activateIgnoringOtherApps:YES]; 1006 1007 [window_object makeKeyAndOrderFront:nil]; 1008 1009 if (p_desired.fullscreen) 1010 zoomed = true; 1011 1012 /*** END OSX INITIALIZATION ***/ 1013 /*** END OSX INITIALIZATION ***/ 1014 /*** END OSX INITIALIZATION ***/ 1015 1016 bool use_gl2 = p_video_driver != 1; 1017 1018 AudioDriverManagerSW::add_driver(&audio_driver_osx); 1019 1020 rasterizer = instance_RasterizerGLES2(); 1021 1022 visual_server = memnew(VisualServerRaster(rasterizer)); 1023 1024 if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { 1025 visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); 1026 } 1027 1028 visual_server->init(); 1029 1030 AudioDriverManagerSW::get_driver(p_audio_driver)->set_singleton(); 1031 1032 if (AudioDriverManagerSW::get_driver(p_audio_driver)->init() != OK) { 1033 1034 ERR_PRINT("Initializing audio failed."); 1035 } 1036 1037 sample_manager = memnew(SampleManagerMallocSW); 1038 audio_server = memnew(AudioServerSW(sample_manager)); 1039 1040 audio_server->set_mixer_params(AudioMixerSW::INTERPOLATION_LINEAR, false); 1041 audio_server->init(); 1042 1043 spatial_sound_server = memnew(SpatialSoundServerSW); 1044 spatial_sound_server->init(); 1045 1046 spatial_sound_2d_server = memnew(SpatialSound2DServerSW); 1047 spatial_sound_2d_server->init(); 1048 1049 physics_server = memnew(PhysicsServerSW); 1050 physics_server->init(); 1051 //physics_2d_server = memnew( Physics2DServerSW ); 1052 physics_2d_server = Physics2DServerWrapMT::init_server<Physics2DServerSW>(); 1053 physics_2d_server->init(); 1054 1055 input = memnew(InputDefault); 1056 joystick_osx = memnew(JoystickOSX); 1057 1058 _ensure_data_dir(); 1059 1060 restore_rect = Rect2(get_window_position(), get_window_size()); 1061} 1062 1063void OS_OSX::finalize() { 1064 1065 CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); 1066 CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); 1067 1068 delete_main_loop(); 1069 1070 spatial_sound_server->finish(); 1071 memdelete(spatial_sound_server); 1072 spatial_sound_2d_server->finish(); 1073 memdelete(spatial_sound_2d_server); 1074 1075 memdelete(joystick_osx); 1076 memdelete(input); 1077 1078 memdelete(sample_manager); 1079 1080 audio_server->finish(); 1081 memdelete(audio_server); 1082 1083 visual_server->finish(); 1084 memdelete(visual_server); 1085 memdelete(rasterizer); 1086 1087 physics_server->finish(); 1088 memdelete(physics_server); 1089 1090 physics_2d_server->finish(); 1091 memdelete(physics_2d_server); 1092} 1093 1094void OS_OSX::set_main_loop(MainLoop *p_main_loop) { 1095 1096 main_loop = p_main_loop; 1097 input->set_main_loop(p_main_loop); 1098} 1099 1100void OS_OSX::delete_main_loop() { 1101 1102 if (!main_loop) 1103 return; 1104 1105 memdelete(main_loop); 1106 main_loop = NULL; 1107} 1108 1109String OS_OSX::get_name() { 1110 1111 return "OSX"; 1112} 1113 1114void OS_OSX::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { 1115 1116#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 1117 if (!_print_error_enabled) 1118 return; 1119 1120 const char *err_details; 1121 if (p_rationale && p_rationale[0]) 1122 err_details = p_rationale; 1123 else 1124 err_details = p_code; 1125 1126 switch (p_type) { 1127 case ERR_ERROR: 1128 os_log_error(OS_LOG_DEFAULT, "ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.", p_function, err_details, p_file, p_line); 1129 print("\E[1;31mERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); 1130 print("\E[0;31m At: %s:%i.\E[0m\n", p_file, p_line); 1131 break; 1132 case ERR_WARNING: 1133 os_log_info(OS_LOG_DEFAULT, "WARNING: %{public}s: %{public}s\nAt: %{public}s:%i.", p_function, err_details, p_file, p_line); 1134 print("\E[1;33mWARNING: %s: \E[0m\E[1m%s\n", p_function, err_details); 1135 print("\E[0;33m At: %s:%i.\E[0m\n", p_file, p_line); 1136 break; 1137 case ERR_SCRIPT: 1138 os_log_error(OS_LOG_DEFAULT, "SCRIPT ERROR: %{public}s: %{public}s\nAt: %{public}s:%i.", p_function, err_details, p_file, p_line); 1139 print("\E[1;35mSCRIPT ERROR: %s: \E[0m\E[1m%s\n", p_function, err_details); 1140 print("\E[0;35m At: %s:%i.\E[0m\n", p_file, p_line); 1141 break; 1142 } 1143#else 1144 OS_Unix::print_error(p_function, p_file, p_line, p_code, p_rationale, p_type); 1145#endif 1146} 1147 1148void OS_OSX::alert(const String &p_alert, const String &p_title) { 1149 // Set OS X-compliant variables 1150 NSAlert *window = [[NSAlert alloc] init]; 1151 NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; 1152 NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; 1153 1154 [window addButtonWithTitle:@"OK"]; 1155 [window setMessageText:ns_title]; 1156 [window setInformativeText:ns_alert]; 1157 [window setAlertStyle:NSWarningAlertStyle]; 1158 1159 // Display it, then release 1160 [window runModal]; 1161 [window release]; 1162} 1163 1164void OS_OSX::set_cursor_shape(CursorShape p_shape) { 1165 1166 if (cursor_shape == p_shape) 1167 return; 1168 1169 if (mouse_mode != MOUSE_MODE_VISIBLE) { 1170 cursor_shape = p_shape; 1171 return; 1172 } 1173 1174 if (cursors[p_shape] != NULL) { 1175 [cursors[p_shape] set]; 1176 } else { 1177 switch (p_shape) { 1178 case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; 1179 case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break; 1180 case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break; 1181 case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break; 1182 case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break; 1183 case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break; 1184 case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break; 1185 case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break; 1186 case CURSOR_FORBIDDEN: [[NSCursor arrowCursor] set]; break; 1187 case CURSOR_VSIZE: [[NSCursor resizeUpDownCursor] set]; break; 1188 case CURSOR_HSIZE: [[NSCursor resizeLeftRightCursor] set]; break; 1189 case CURSOR_BDIAGSIZE: [[NSCursor arrowCursor] set]; break; 1190 case CURSOR_FDIAGSIZE: [[NSCursor arrowCursor] set]; break; 1191 case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break; 1192 case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break; 1193 case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break; 1194 case CURSOR_HELP: [[NSCursor arrowCursor] set]; break; 1195 default: {}; 1196 } 1197 } 1198 1199 cursor_shape = p_shape; 1200} 1201 1202void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { 1203 if (p_cursor.is_valid()) { 1204 Ref<ImageTexture> texture = p_cursor; 1205 Ref<AtlasTexture> atlas_texture = p_cursor; 1206 Size2 texture_size; 1207 Rect2 atlas_rect; 1208 1209 if (!texture.is_valid() && atlas_texture.is_valid()) { 1210 texture = atlas_texture->get_atlas(); 1211 1212 atlas_rect.size.width = texture->get_width(); 1213 atlas_rect.size.height = texture->get_height(); 1214 atlas_rect.pos.x = atlas_texture->get_region().pos.x; 1215 atlas_rect.pos.y = atlas_texture->get_region().pos.y; 1216 1217 texture_size.width = atlas_texture->get_region().size.x; 1218 texture_size.height = atlas_texture->get_region().size.y; 1219 } else if (texture.is_valid()) { 1220 texture_size.width = texture->get_width(); 1221 texture_size.height = texture->get_height(); 1222 } 1223 1224 ERR_FAIL_COND(!texture.is_valid()); 1225 ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); 1226 1227 Image image = texture->get_data(); 1228 1229 NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] 1230 initWithBitmapDataPlanes:NULL 1231 pixelsWide:int(texture_size.width) 1232 pixelsHigh:int(texture_size.height) 1233 bitsPerSample:8 1234 samplesPerPixel:4 1235 hasAlpha:YES 1236 isPlanar:NO 1237 colorSpaceName:NSDeviceRGBColorSpace 1238 bytesPerRow:int(texture_size.width) * 4 1239 bitsPerPixel:32]; 1240 ERR_FAIL_COND(imgrep == nil); 1241 uint8_t *pixels = [imgrep bitmapData]; 1242 1243 int len = int(texture_size.width * texture_size.height); 1244 DVector<uint8_t> data = image.get_data(); 1245 DVector<uint8_t>::Read r = data.read(); 1246 1247 /* Premultiply the alpha channel */ 1248 for (int i = 0; i < len; i++) { 1249 int row_index = floor(i / texture_size.width) + atlas_rect.pos.y; 1250 int column_index = (i % int(texture_size.width)) + atlas_rect.pos.x; 1251 1252 if (atlas_texture.is_valid()) { 1253 column_index = MIN(column_index, atlas_rect.size.width - 1); 1254 row_index = MIN(row_index, atlas_rect.size.height - 1); 1255 } 1256 1257 uint32_t color = image.get_pixel(column_index, row_index).to_ARGB32(); 1258 1259 uint8_t alpha = (color >> 24) & 0xFF; 1260 pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255; 1261 pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255; 1262 pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255; 1263 pixels[i * 4 + 3] = alpha; 1264 } 1265 1266 NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)]; 1267 [nsimage addRepresentation:imgrep]; 1268 1269 [cursors[p_shape] release]; 1270 NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; 1271 1272 cursors[p_shape] = cursor; 1273 1274 if (p_shape == CURSOR_ARROW) { 1275 if (mouse_mode == MOUSE_MODE_VISIBLE) { 1276 [cursor set]; 1277 } 1278 } 1279 1280 [imgrep release]; 1281 [nsimage release]; 1282 } 1283} 1284 1285void OS_OSX::set_mouse_show(bool p_show) { 1286} 1287 1288void OS_OSX::set_mouse_grab(bool p_grab) { 1289} 1290 1291bool OS_OSX::is_mouse_grab_enabled() const { 1292 1293 return mouse_grab; 1294} 1295 1296void OS_OSX::warp_mouse_pos(const Point2 &p_to) { 1297 1298 //copied from windows impl with osx native calls 1299 if (mouse_mode == MOUSE_MODE_CAPTURED) { 1300 mouse_x = p_to.x; 1301 mouse_y = p_to.y; 1302 } else { 1303 //set OS position 1304 1305 //local point in window coords 1306 const NSRect contentRect = [window_view frame]; 1307 NSRect pointInWindowRect = NSMakeRect(p_to.x / display_scale, contentRect.size.height - (p_to.y / display_scale) - 1, 0, 0); 1308 NSPoint pointOnScreen = [[window_view window] convertRectToScreen:pointInWindowRect].origin; 1309 1310 //point in scren coords 1311 CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; 1312 1313 //do the warping 1314 CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); 1315 CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); 1316 CGAssociateMouseAndMouseCursorPosition(false); 1317 CGWarpMouseCursorPosition(lMouseWarpPos); 1318 CGAssociateMouseAndMouseCursorPosition(true); 1319 } 1320} 1321 1322Point2 OS_OSX::get_mouse_pos() const { 1323 1324 return Vector2(mouse_x, mouse_y); 1325} 1326 1327int OS_OSX::get_mouse_button_state() const { 1328 return button_mask; 1329} 1330 1331void OS_OSX::set_window_title(const String &p_title) { 1332 title = p_title; 1333 1334 [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; 1335} 1336 1337void OS_OSX::set_icon(const Image &p_icon) { 1338 1339 Image img = p_icon; 1340 img.convert(Image::FORMAT_RGBA); 1341 NSBitmapImageRep *imgrep = [[[NSBitmapImageRep alloc] 1342 initWithBitmapDataPlanes:NULL 1343 pixelsWide:p_icon.get_width() 1344 pixelsHigh:p_icon.get_height() 1345 bitsPerSample:8 1346 samplesPerPixel:4 1347 hasAlpha:YES 1348 isPlanar:NO 1349 colorSpaceName:NSDeviceRGBColorSpace 1350 bytesPerRow:p_icon.get_width() * 4 1351 bitsPerPixel:32] autorelease]; 1352 ERR_FAIL_COND(imgrep == nil); 1353 uint8_t *pixels = [imgrep bitmapData]; 1354 1355 int len = img.get_width() * img.get_height(); 1356 DVector<uint8_t> data = img.get_data(); 1357 DVector<uint8_t>::Read r = data.read(); 1358 1359 /* Premultiply the alpha channel */ 1360 for (int i = 0; i < len; i++) { 1361 uint8_t alpha = r[i * 4 + 3]; 1362 pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); 1363 pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); 1364 pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); 1365 pixels[i * 4 + 3] = alpha; 1366 } 1367 1368 NSImage *nsimg = [[[NSImage alloc] initWithSize:NSMakeSize(img.get_width(), img.get_height())] autorelease]; 1369 ERR_FAIL_COND(nsimg == nil); 1370 [nsimg addRepresentation:imgrep]; 1371 1372 [NSApp setApplicationIconImage:nsimg]; 1373} 1374 1375MainLoop *OS_OSX::get_main_loop() const { 1376 1377 return main_loop; 1378} 1379 1380bool OS_OSX::can_draw() const { 1381 1382 return true; 1383} 1384 1385void OS_OSX::set_clipboard(const String &p_text) { 1386 1387 NSArray *types = [NSArray arrayWithObjects:NSStringPboardType, nil]; 1388 1389 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; 1390 [pasteboard declareTypes:types owner:nil]; 1391 [pasteboard setString:[NSString stringWithUTF8String:p_text.utf8().get_data()] 1392 forType:NSStringPboardType]; 1393} 1394 1395String OS_OSX::get_clipboard() const { 1396 1397 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; 1398 1399 if (![[pasteboard types] containsObject:NSStringPboardType]) { 1400 return ""; 1401 } 1402 1403 NSString *object = [pasteboard stringForType:NSStringPboardType]; 1404 if (!object) { 1405 return ""; 1406 } 1407 1408 char *utfs = strdup([object UTF8String]); 1409 String ret; 1410 ret.parse_utf8(utfs); 1411 free(utfs); 1412 1413 return ret; 1414} 1415 1416void OS_OSX::release_rendering_thread() { 1417 1418 [NSOpenGLContext clearCurrentContext]; 1419} 1420 1421void OS_OSX::make_rendering_thread() { 1422 1423 [context makeCurrentContext]; 1424} 1425 1426Error OS_OSX::shell_open(String p_uri) { 1427 1428 [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[NSString stringWithUTF8String:p_uri.utf8().get_data()]]]; 1429 return OK; 1430} 1431 1432String OS_OSX::get_locale() const { 1433 NSString *locale_code = [[NSLocale currentLocale] localeIdentifier]; 1434 return [locale_code UTF8String]; 1435} 1436 1437void OS_OSX::swap_buffers() { 1438 1439 [context flushBuffer]; 1440} 1441 1442void OS_OSX::wm_minimized(bool p_minimized) { 1443 1444 minimized = p_minimized; 1445}; 1446 1447void OS_OSX::set_video_mode(const VideoMode &p_video_mode, int p_screen) { 1448} 1449 1450OS::VideoMode OS_OSX::get_video_mode(int p_screen) const { 1451 1452 VideoMode vm; 1453 vm.width = window_size.width; 1454 vm.height = window_size.height; 1455 1456 return vm; 1457} 1458 1459void OS_OSX::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { 1460} 1461 1462int OS_OSX::get_screen_count() const { 1463 NSArray *screenArray = [NSScreen screens]; 1464 return [screenArray count]; 1465}; 1466 1467// Returns the native top-left screen coordinate of the smallest rectangle 1468// that encompasses all screens. Needed in get_screen_position(), 1469// get_window_position, and set_window_position() 1470// to convert between OS X native screen coordinates and the ones expected by Godot 1471Point2 OS_OSX::get_screens_origin() const { 1472 static Point2 origin; 1473 1474 if (displays_arrangement_dirty) { 1475 origin = Point2(); 1476 1477 for (int i = 0; i < get_screen_count(); i++) { 1478 Point2 position = get_native_screen_position(i); 1479 if (position.x < origin.x) { 1480 origin.x = position.x; 1481 } 1482 if (position.y > origin.y) { 1483 origin.y = position.y; 1484 } 1485 } 1486 1487 displays_arrangement_dirty = false; 1488 } 1489 1490 return origin; 1491} 1492 1493static int get_screen_index(NSScreen *screen) { 1494 const NSUInteger index = [[NSScreen screens] indexOfObject:screen]; 1495 return index == NSNotFound ? 0 : index; 1496} 1497 1498int OS_OSX::get_current_screen() const { 1499 if (window_object) { 1500 return get_screen_index([window_object screen]); 1501 } else { 1502 return get_screen_index([NSScreen mainScreen]); 1503 } 1504}; 1505 1506void OS_OSX::set_current_screen(int p_screen) { 1507 Vector2 wpos = get_window_position() - get_screen_position(get_current_screen()); 1508 set_window_position(wpos + get_screen_position(p_screen)); 1509}; 1510 1511Point2 OS_OSX::get_native_screen_position(int p_screen) const { 1512 NSArray *screenArray = [NSScreen screens]; 1513 if (p_screen < [screenArray count]) { 1514 float displayScale = 1.0; 1515 1516 if (display_scale > 1.0 && [[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { 1517 displayScale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; 1518 } 1519 1520 NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; 1521 // Return the top-left corner of the screen, for OS X the y starts at the bottom 1522 return Point2(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale; 1523 } 1524 1525 return Point2(); 1526} 1527 1528Point2 OS_OSX::get_screen_position(int p_screen) const { 1529 Point2 position = get_native_screen_position(p_screen) - get_screens_origin(); 1530 // OS X native y-coordinate relative to get_screens_origin() is negative, 1531 // Godot expects a positive value 1532 position.y *= -1; 1533 return position; 1534} 1535 1536int OS_OSX::get_screen_dpi(int p_screen) const { 1537 NSArray *screenArray = [NSScreen screens]; 1538 if (p_screen < [screenArray count]) { 1539 float displayScale = 1.0; 1540 1541 if (display_scale > 1.0 && [[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { 1542 displayScale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; 1543 } 1544 1545 NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; 1546 NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; 1547 CGSize displayPhysicalSize = CGDisplayScreenSize( 1548 [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); 1549 1550 return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale; 1551 } 1552 1553 return 72; 1554} 1555 1556Size2 OS_OSX::get_screen_size(int p_screen) const { 1557 NSArray *screenArray = [NSScreen screens]; 1558 if (p_screen < [screenArray count]) { 1559 float displayScale = 1.0; 1560 1561 if (display_scale > 1.0 && [[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { 1562 displayScale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; 1563 } 1564 1565 // Note: Use frame to get the whole screen size 1566 NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; 1567 return Size2(nsrect.size.width, nsrect.size.height) * displayScale; 1568 } 1569 1570 return Size2(); 1571} 1572 1573Point2 OS_OSX::get_native_window_position() const { 1574 NSRect nsrect = [window_object frame]; 1575 1576 Point2 pos; 1577 1578 // Return the position of the top-left corner, for OS X the y starts at the bottom 1579 pos.x = nsrect.origin.x * display_scale; 1580 pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale; 1581 1582 return pos; 1583}; 1584 1585Point2 OS_OSX::get_window_position() const { 1586 Point2 position = get_native_window_position() - get_screens_origin(); 1587 // OS X native y-coordinate relative to get_screens_origin() is negative, 1588 // Godot expects a positive value 1589 position.y *= -1; 1590 return position; 1591} 1592 1593void OS_OSX::_update_window() { 1594 bool borderless_full = false; 1595 1596 if (get_borderless_window()) { 1597 NSRect frameRect = [window_object frame]; 1598 NSRect screenRect = [[window_object screen] frame]; 1599 1600 // Check if our window covers up the screen 1601 if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && 1602 frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { 1603 borderless_full = true; 1604 } 1605 } 1606 1607 if (borderless_full) { 1608 // If the window covers up the screen set the level to above the main menu and hide on deactivate 1609 [window_object setLevel:NSMainMenuWindowLevel + 1]; 1610 [window_object setHidesOnDeactivate:YES]; 1611 } else { 1612 // Reset these when our window is not a borderless window that covers up the screen 1613 [window_object setLevel:NSNormalWindowLevel]; 1614 [window_object setHidesOnDeactivate:NO]; 1615 } 1616} 1617 1618void OS_OSX::set_native_window_position(const Point2 &p_position) { 1619 1620 NSPoint pos; 1621 1622 pos.x = p_position.x / display_scale; 1623 pos.y = p_position.y / display_scale; 1624 1625 [window_object setFrameTopLeftPoint:pos]; 1626 1627 _update_window(); 1628}; 1629 1630void OS_OSX::set_window_position(const Point2 &p_position) { 1631 Point2 position = p_position; 1632 // OS X native y-coordinate relative to get_screens_origin() is negative, 1633 // Godot passes a positive value 1634 position.y *= -1; 1635 set_native_window_position(get_screens_origin() + position); 1636}; 1637 1638Size2 OS_OSX::get_window_size() const { 1639 1640 return window_size; 1641}; 1642 1643Size2 OS_OSX::get_real_window_size() const { 1644 1645 NSRect frame = [window_object frame]; 1646 return Size2(frame.size.width, frame.size.height); 1647} 1648 1649void OS_OSX::set_window_size(const Size2 p_size) { 1650 1651 Size2 size = p_size; 1652 1653 if (get_borderless_window() == false) { 1654 // NSRect used by setFrame includes the title bar, so add it to our size.y 1655 CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; 1656 if (menuBarHeight != 0.f) { 1657 size.y += menuBarHeight; 1658#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101104 1659 } else { 1660 size.y += [[NSStatusBar systemStatusBar] thickness]; 1661#endif 1662 } 1663 } 1664 1665 NSRect frame = [window_object frame]; 1666 [window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES]; 1667 1668 _update_window(); 1669}; 1670 1671void OS_OSX::set_window_fullscreen(bool p_enabled) { 1672 1673 if (zoomed != p_enabled) { 1674#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 1675 [window_object toggleFullScreen:nil]; 1676#else 1677 [window_object performZoom:nil]; 1678#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 1679 } 1680 zoomed = p_enabled; 1681}; 1682 1683bool OS_OSX::is_window_fullscreen() const { 1684 1685#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 1686 if ([window_object respondsToSelector:@selector(isZoomed)]) 1687 return [window_object isZoomed]; 1688#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ 1689 1690 return zoomed; 1691}; 1692 1693void OS_OSX::set_window_resizable(bool p_enabled) { 1694 1695 if (p_enabled) 1696 [window_object setStyleMask:[window_object styleMask] | NSResizableWindowMask]; 1697 else 1698 [window_object setStyleMask:[window_object styleMask] & ~NSResizableWindowMask]; 1699}; 1700 1701bool OS_OSX::is_window_resizable() const { 1702 1703 return [window_object styleMask] & NSResizableWindowMask; 1704}; 1705 1706void OS_OSX::set_window_minimized(bool p_enabled) { 1707 1708 if (p_enabled) 1709 [window_object performMiniaturize:nil]; 1710 else 1711 [window_object deminiaturize:nil]; 1712}; 1713 1714bool OS_OSX::is_window_minimized() const { 1715 1716 if ([window_object respondsToSelector:@selector(isMiniaturized)]) 1717 return [window_object isMiniaturized]; 1718 1719 return minimized; 1720}; 1721 1722void OS_OSX::set_window_maximized(bool p_enabled) { 1723 1724 if (p_enabled) { 1725 restore_rect = Rect2(get_window_position(), get_window_size()); 1726 [window_object setFrame:[[[NSScreen screens] objectAtIndex:get_current_screen()] visibleFrame] display:YES]; 1727 } else { 1728 set_window_size(restore_rect.size); 1729 set_window_position(restore_rect.pos); 1730 }; 1731 maximized = p_enabled; 1732}; 1733 1734bool OS_OSX::is_window_maximized() const { 1735 1736 // don't know 1737 return maximized; 1738}; 1739 1740void OS_OSX::move_window_to_foreground() { 1741 1742 [window_object orderFrontRegardless]; 1743} 1744 1745void OS_OSX::set_window_always_on_top(bool p_enabled) { 1746 if (is_window_always_on_top() == p_enabled) 1747 return; 1748 1749 if (p_enabled) 1750 [window_object setLevel:NSFloatingWindowLevel]; 1751 else 1752 [window_object setLevel:NSNormalWindowLevel]; 1753} 1754 1755bool OS_OSX::is_window_always_on_top() const { 1756 return [window_object level] == NSFloatingWindowLevel; 1757} 1758 1759void OS_OSX::request_attention() { 1760 1761 [NSApp requestUserAttention:NSCriticalRequest]; 1762} 1763 1764void OS_OSX::set_borderless_window(int p_borderless) { 1765 1766 // OrderOut prevents a lose focus bug with the window 1767 [window_object orderOut:nil]; 1768 1769 if (p_borderless) { 1770 [window_object setStyleMask:NSWindowStyleMaskBorderless]; 1771 } else { 1772 [window_object setStyleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask]; 1773 1774 // Force update of the window styles 1775 NSRect frameRect = [window_object frame]; 1776 [window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; 1777 [window_object setFrame:frameRect display:NO]; 1778 1779 // Restore the window title 1780 [window_object setTitle:[NSString stringWithUTF8String:title.utf8().get_data()]]; 1781 } 1782 1783 _update_window(); 1784 1785 [window_object makeKeyAndOrderFront:nil]; 1786} 1787 1788bool OS_OSX::get_borderless_window() { 1789 1790 return [window_object styleMask] == NSWindowStyleMaskBorderless; 1791} 1792 1793String OS_OSX::get_executable_path() const { 1794 1795 int ret; 1796 pid_t pid; 1797 char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; 1798 1799 pid = getpid(); 1800 ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); 1801 if (ret <= 0) { 1802 return OS::get_executable_path(); 1803 } else { 1804 String path; 1805 path.parse_utf8(pathbuf); 1806 1807 return path; 1808 } 1809} 1810 1811// Returns string representation of keys, if they are printable. 1812// 1813static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) { 1814 1815 TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); 1816 if (!currentKeyboard) 1817 return nil; 1818 1819 CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 1820 if (!layoutData) 1821 return nil; 1822 1823 const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); 1824 1825 OSStatus err; 1826 CFMutableStringRef output = CFStringCreateMutable(NULL, 0); 1827 1828 for (int i = 0; i < length; ++i) { 1829 1830 UInt32 keysDown = 0; 1831 UniChar chars[4]; 1832 UniCharCount realLength; 1833 1834 err = UCKeyTranslate(keyboardLayout, 1835 keyCode[i], 1836 kUCKeyActionDisplay, 1837 0, 1838 LMGetKbdType(), 1839 kUCKeyTranslateNoDeadKeysBit, 1840 &keysDown, 1841 sizeof(chars) / sizeof(chars[0]), 1842 &realLength, 1843 chars); 1844 1845 if (err != noErr) { 1846 CFRelease(output); 1847 return nil; 1848 } 1849 1850 CFStringAppendCharacters(output, chars, 1); 1851 } 1852 1853 //CFStringUppercase(output, NULL); 1854 1855 return (NSString *)output; 1856} 1857 1858OS::LatinKeyboardVariant OS_OSX::get_latin_keyboard_variant() const { 1859 1860 static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY; 1861 1862 if (keyboard_layout_dirty) { 1863 1864 layout = LATIN_KEYBOARD_QWERTY; 1865 1866 CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y }; 1867 NSString *test = createStringForKeys(keys, 6); 1868 1869 if ([test isEqualToString:@"qwertz"]) { 1870 layout = LATIN_KEYBOARD_QWERTZ; 1871 } else if ([test isEqualToString:@"azerty"]) { 1872 layout = LATIN_KEYBOARD_AZERTY; 1873 } else if ([test isEqualToString:@"qzerty"]) { 1874 layout = LATIN_KEYBOARD_QZERTY; 1875 } else if ([test isEqualToString:@"',.pyf"]) { 1876 layout = LATIN_KEYBOARD_DVORAK; 1877 } else if ([test isEqualToString:@"xvlcwk"]) { 1878 layout = LATIN_KEYBOARD_NEO; 1879 } else if ([test isEqualToString:@"qwfpgj"]) { 1880 layout = LATIN_KEYBOARD_COLEMAK; 1881 } 1882 1883 [test release]; 1884 1885 keyboard_layout_dirty = false; 1886 return layout; 1887 } 1888 1889 return layout; 1890} 1891 1892void OS_OSX::process_events() { 1893 1894 while (true) { 1895 NSEvent *event = [NSApp 1896 nextEventMatchingMask:NSAnyEventMask 1897 untilDate:[NSDate distantPast] 1898 inMode:NSDefaultRunLoopMode 1899 dequeue:YES]; 1900 if (event == nil) 1901 break; 1902 1903 [NSApp sendEvent:event]; 1904 } 1905 1906 [autoreleasePool drain]; 1907 autoreleasePool = [[NSAutoreleasePool alloc] init]; 1908} 1909 1910void OS_OSX::push_input(const InputEvent &p_event) { 1911 1912 InputEvent ev = p_event; 1913 ev.ID = last_id++; 1914 input->parse_input_event(ev); 1915} 1916 1917void OS_OSX::run() { 1918 1919 force_quit = false; 1920 1921 if (!main_loop) 1922 return; 1923 1924 main_loop->init(); 1925 1926 if (zoomed) { 1927 zoomed = false; 1928 set_window_fullscreen(true); 1929 } 1930 1931 //uint64_t last_ticks=get_ticks_usec(); 1932 1933 //int frames=0; 1934 //uint64_t frame=0; 1935 1936 while (!force_quit) { 1937 1938 process_events(); // get rid of pending events 1939 last_id = joystick_osx->process_joysticks(last_id); 1940 if (Main::iteration() == true) 1941 break; 1942 }; 1943 1944 main_loop->finish(); 1945} 1946 1947void OS_OSX::set_mouse_mode(MouseMode p_mode) { 1948 1949 if (p_mode == mouse_mode) 1950 return; 1951 1952 if (p_mode == MOUSE_MODE_CAPTURED) { 1953 // Apple Docs state that the display parameter is not used. 1954 // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." 1955 // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html 1956 CGDisplayHideCursor(kCGDirectMainDisplay); 1957 CGAssociateMouseAndMouseCursorPosition(false); 1958 } else if (p_mode == MOUSE_MODE_HIDDEN) { 1959 CGDisplayHideCursor(kCGDirectMainDisplay); 1960 CGAssociateMouseAndMouseCursorPosition(true); 1961 } else { 1962 CGDisplayShowCursor(kCGDirectMainDisplay); 1963 CGAssociateMouseAndMouseCursorPosition(true); 1964 } 1965 1966 mouse_mode = p_mode; 1967} 1968 1969OS::MouseMode OS_OSX::get_mouse_mode() const { 1970 1971 return mouse_mode; 1972} 1973 1974String OS_OSX::get_joy_guid(int p_device) const { 1975 return input->get_joy_guid_remapped(p_device); 1976} 1977 1978Error OS_OSX::move_path_to_trash(String p_dir) { 1979 NSFileManager *fm = [NSFileManager defaultManager]; 1980 NSURL *url = [NSURL fileURLWithPath:@(p_dir.utf8().get_data())]; 1981 NSError *err; 1982 1983 if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { 1984 ERR_PRINTS("trashItemAtURL error: " + String(err.localizedDescription.UTF8String)); 1985 return FAILED; 1986 } 1987 1988 return OK; 1989} 1990 1991void OS_OSX::set_use_vsync(bool p_enable) { 1992 CGLContextObj ctx = CGLGetCurrentContext(); 1993 if (ctx) { 1994 GLint swapInterval = p_enable ? 1 : 0; 1995 CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); 1996 } 1997} 1998 1999bool OS_OSX::is_vsync_enabled() const { 2000 GLint swapInterval = 0; 2001 CGLContextObj ctx = CGLGetCurrentContext(); 2002 if (ctx) { 2003 CGLGetParameter(ctx, kCGLCPSwapInterval, &swapInterval); 2004 } 2005 return swapInterval ? true : false; 2006} 2007 2008OS_OSX *OS_OSX::singleton = NULL; 2009 2010OS_OSX::OS_OSX() { 2011 2012 mouse_mode = OS::MOUSE_MODE_VISIBLE; 2013 main_loop = NULL; 2014 singleton = this; 2015 autoreleasePool = [[NSAutoreleasePool alloc] init]; 2016 2017 eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 2018 ERR_FAIL_COND(!eventSource); 2019 2020 CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); 2021 2022 /* 2023 if (pthread_key_create(&_Godot.nsgl.current, NULL) != 0) { 2024 _GodotInputError(Godot_PLATFORM_ERROR, 2025 "NSGL: Failed to create context TLS"); 2026 return GL_FALSE; 2027 } 2028*/ 2029 2030 framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); 2031 ERR_FAIL_COND(!framework); 2032 2033 // Implicitly create shared NSApplication instance 2034 [GodotApplication sharedApplication]; 2035 2036 // In case we are unbundled, make us a proper UI application 2037 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 2038 2039 // Menu bar setup must go between sharedApplication above and 2040 // finishLaunching below, in order to properly emulate the behavior 2041 // of NSApplicationMain 2042 NSMenuItem *menu_item; 2043 NSString *title; 2044 2045 NSString *nsappname = [[[NSBundle mainBundle] performSelector:@selector(localizedInfoDictionary)] objectForKey:@"CFBundleName"]; 2046 if (nsappname == nil) 2047 nsappname = [[NSProcessInfo processInfo] processName]; 2048 2049 // Setup Apple menu 2050 NSMenu *apple_menu = [[NSMenu alloc] initWithTitle:@""]; 2051 title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; 2052 [apple_menu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; 2053 2054 [apple_menu addItem:[NSMenuItem separatorItem]]; 2055 2056 NSMenu *services = [[NSMenu alloc] initWithTitle:@""]; 2057 menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; 2058 [apple_menu setSubmenu:services forItem:menu_item]; 2059 [NSApp setServicesMenu:services]; 2060 [services release]; 2061 2062 [apple_menu addItem:[NSMenuItem separatorItem]]; 2063 2064 title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname]; 2065 [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; 2066 2067 menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; 2068 [menu_item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)]; 2069 2070 [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""]; 2071 2072 [apple_menu addItem:[NSMenuItem separatorItem]]; 2073 2074 title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; 2075 [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; 2076 2077 // Setup menu bar 2078 NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; 2079 menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; 2080 [main_menu setSubmenu:apple_menu forItem:menu_item]; 2081 [NSApp setMainMenu:main_menu]; 2082 2083 [main_menu release]; 2084 [apple_menu release]; 2085 2086 [NSApp finishLaunching]; 2087 2088 delegate = [[GodotApplicationDelegate alloc] init]; 2089 ERR_FAIL_COND(!delegate); 2090 [NSApp setDelegate:delegate]; 2091 2092 last_id = 1; 2093 cursor_shape = CURSOR_ARROW; 2094 2095 maximized = false; 2096 minimized = false; 2097 window_size = Vector2(1024, 600); 2098 zoomed = false; 2099 display_scale = 1.0; 2100} 2101 2102void OS_OSX::disable_crash_handler() { 2103 crash_handler.disable(); 2104} 2105 2106bool OS_OSX::is_disable_crash_handler() const { 2107 return crash_handler.is_disabled(); 2108} 2109