//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include "../kitty/monotonic.h" #include #include #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300) #define NSControlStateValueOn NSOnState #define NSControlStateValueOff NSOffState #define NSControlStateValueMixed NSMixedState #endif static uint32_t vk_code_to_functional_key_code(uint8_t key_code) { // {{{ switch(key_code) { /* start vk to functional (auto generated by gen-key-constants.py do not edit) */ case 0x35: return GLFW_FKEY_ESCAPE; case 0x24: return GLFW_FKEY_ENTER; case 0x30: return GLFW_FKEY_TAB; case 0x33: return GLFW_FKEY_BACKSPACE; case 0x72: return GLFW_FKEY_INSERT; case 0x75: return GLFW_FKEY_DELETE; case 0x7b: return GLFW_FKEY_LEFT; case 0x7c: return GLFW_FKEY_RIGHT; case 0x7e: return GLFW_FKEY_UP; case 0x7d: return GLFW_FKEY_DOWN; case 0x74: return GLFW_FKEY_PAGE_UP; case 0x79: return GLFW_FKEY_PAGE_DOWN; case 0x73: return GLFW_FKEY_HOME; case 0x77: return GLFW_FKEY_END; case 0x39: return GLFW_FKEY_CAPS_LOCK; case 0x47: return GLFW_FKEY_NUM_LOCK; case 0x6e: return GLFW_FKEY_MENU; case 0x7a: return GLFW_FKEY_F1; case 0x78: return GLFW_FKEY_F2; case 0x63: return GLFW_FKEY_F3; case 0x76: return GLFW_FKEY_F4; case 0x60: return GLFW_FKEY_F5; case 0x61: return GLFW_FKEY_F6; case 0x62: return GLFW_FKEY_F7; case 0x64: return GLFW_FKEY_F8; case 0x65: return GLFW_FKEY_F9; case 0x6d: return GLFW_FKEY_F10; case 0x67: return GLFW_FKEY_F11; case 0x6f: return GLFW_FKEY_F12; case 0x69: return GLFW_FKEY_F13; case 0x6b: return GLFW_FKEY_F14; case 0x71: return GLFW_FKEY_F15; case 0x6a: return GLFW_FKEY_F16; case 0x40: return GLFW_FKEY_F17; case 0x4f: return GLFW_FKEY_F18; case 0x50: return GLFW_FKEY_F19; case 0x5a: return GLFW_FKEY_F20; case 0x52: return GLFW_FKEY_KP_0; case 0x53: return GLFW_FKEY_KP_1; case 0x54: return GLFW_FKEY_KP_2; case 0x55: return GLFW_FKEY_KP_3; case 0x56: return GLFW_FKEY_KP_4; case 0x57: return GLFW_FKEY_KP_5; case 0x58: return GLFW_FKEY_KP_6; case 0x59: return GLFW_FKEY_KP_7; case 0x5b: return GLFW_FKEY_KP_8; case 0x5c: return GLFW_FKEY_KP_9; case 0x41: return GLFW_FKEY_KP_DECIMAL; case 0x4b: return GLFW_FKEY_KP_DIVIDE; case 0x43: return GLFW_FKEY_KP_MULTIPLY; case 0x4e: return GLFW_FKEY_KP_SUBTRACT; case 0x45: return GLFW_FKEY_KP_ADD; case 0x4c: return GLFW_FKEY_KP_ENTER; case 0x51: return GLFW_FKEY_KP_EQUAL; case 0x38: return GLFW_FKEY_LEFT_SHIFT; case 0x3b: return GLFW_FKEY_LEFT_CONTROL; case 0x3a: return GLFW_FKEY_LEFT_ALT; case 0x37: return GLFW_FKEY_LEFT_SUPER; case 0x3c: return GLFW_FKEY_RIGHT_SHIFT; case 0x3e: return GLFW_FKEY_RIGHT_CONTROL; case 0x3d: return GLFW_FKEY_RIGHT_ALT; case 0x36: return GLFW_FKEY_RIGHT_SUPER; /* end vk to functional */ default: return 0; } } // }}} static uint32_t vk_code_to_unicode(uint8_t key_code) { // {{{ switch(key_code) { /* start vk to unicode (auto generated by gen-key-constants.py do not edit) */ case 0x0: return 0x61; case 0x1: return 0x73; case 0x2: return 0x64; case 0x3: return 0x66; case 0x4: return 0x68; case 0x5: return 0x67; case 0x6: return 0x7a; case 0x7: return 0x78; case 0x8: return 0x63; case 0x9: return 0x76; case 0xb: return 0x62; case 0xc: return 0x71; case 0xd: return 0x77; case 0xe: return 0x65; case 0xf: return 0x72; case 0x10: return 0x79; case 0x11: return 0x74; case 0x12: return 0x31; case 0x13: return 0x32; case 0x14: return 0x33; case 0x15: return 0x34; case 0x16: return 0x36; case 0x17: return 0x35; case 0x18: return 0x3d; case 0x19: return 0x39; case 0x1a: return 0x37; case 0x1b: return 0x2d; case 0x1c: return 0x38; case 0x1d: return 0x30; case 0x1e: return 0x5d; case 0x1f: return 0x6f; case 0x20: return 0x75; case 0x21: return 0x5b; case 0x22: return 0x69; case 0x23: return 0x70; case 0x25: return 0x6c; case 0x26: return 0x6a; case 0x27: return 0x27; case 0x28: return 0x6b; case 0x29: return 0x3b; case 0x2a: return 0x5c; case 0x2b: return 0x2c; case 0x2c: return 0x2f; case 0x2d: return 0x6e; case 0x2e: return 0x6d; case 0x2f: return 0x2e; case 0x31: return 0x20; case 0x32: return 0x60; /* end vk to unicode */ default: return 0; } } // }}} static uint32_t mac_ucode_to_functional(uint32_t key_code) { // {{{ switch(key_code) { /* start macu to functional (auto generated by gen-key-constants.py do not edit) */ case NSCarriageReturnCharacter: return GLFW_FKEY_ENTER; case NSTabCharacter: return GLFW_FKEY_TAB; case NSBackspaceCharacter: return GLFW_FKEY_BACKSPACE; case NSInsertFunctionKey: return GLFW_FKEY_INSERT; case NSDeleteFunctionKey: return GLFW_FKEY_DELETE; case NSLeftArrowFunctionKey: return GLFW_FKEY_LEFT; case NSRightArrowFunctionKey: return GLFW_FKEY_RIGHT; case NSUpArrowFunctionKey: return GLFW_FKEY_UP; case NSDownArrowFunctionKey: return GLFW_FKEY_DOWN; case NSPageUpFunctionKey: return GLFW_FKEY_PAGE_UP; case NSPageDownFunctionKey: return GLFW_FKEY_PAGE_DOWN; case NSHomeFunctionKey: return GLFW_FKEY_HOME; case NSEndFunctionKey: return GLFW_FKEY_END; case NSScrollLockFunctionKey: return GLFW_FKEY_SCROLL_LOCK; case NSClearLineFunctionKey: return GLFW_FKEY_NUM_LOCK; case NSPrintScreenFunctionKey: return GLFW_FKEY_PRINT_SCREEN; case NSPauseFunctionKey: return GLFW_FKEY_PAUSE; case NSMenuFunctionKey: return GLFW_FKEY_MENU; case NSF1FunctionKey: return GLFW_FKEY_F1; case NSF2FunctionKey: return GLFW_FKEY_F2; case NSF3FunctionKey: return GLFW_FKEY_F3; case NSF4FunctionKey: return GLFW_FKEY_F4; case NSF5FunctionKey: return GLFW_FKEY_F5; case NSF6FunctionKey: return GLFW_FKEY_F6; case NSF7FunctionKey: return GLFW_FKEY_F7; case NSF8FunctionKey: return GLFW_FKEY_F8; case NSF9FunctionKey: return GLFW_FKEY_F9; case NSF10FunctionKey: return GLFW_FKEY_F10; case NSF11FunctionKey: return GLFW_FKEY_F11; case NSF12FunctionKey: return GLFW_FKEY_F12; case NSF13FunctionKey: return GLFW_FKEY_F13; case NSF14FunctionKey: return GLFW_FKEY_F14; case NSF15FunctionKey: return GLFW_FKEY_F15; case NSF16FunctionKey: return GLFW_FKEY_F16; case NSF17FunctionKey: return GLFW_FKEY_F17; case NSF18FunctionKey: return GLFW_FKEY_F18; case NSF19FunctionKey: return GLFW_FKEY_F19; case NSF20FunctionKey: return GLFW_FKEY_F20; case NSF21FunctionKey: return GLFW_FKEY_F21; case NSF22FunctionKey: return GLFW_FKEY_F22; case NSF23FunctionKey: return GLFW_FKEY_F23; case NSF24FunctionKey: return GLFW_FKEY_F24; case NSF25FunctionKey: return GLFW_FKEY_F25; case NSF26FunctionKey: return GLFW_FKEY_F26; case NSF27FunctionKey: return GLFW_FKEY_F27; case NSF28FunctionKey: return GLFW_FKEY_F28; case NSF29FunctionKey: return GLFW_FKEY_F29; case NSF30FunctionKey: return GLFW_FKEY_F30; case NSF31FunctionKey: return GLFW_FKEY_F31; case NSF32FunctionKey: return GLFW_FKEY_F32; case NSF33FunctionKey: return GLFW_FKEY_F33; case NSF34FunctionKey: return GLFW_FKEY_F34; case NSF35FunctionKey: return GLFW_FKEY_F35; case NSEnterCharacter: return GLFW_FKEY_KP_ENTER; /* end macu to functional */ default: return 0; } } // }}} static inline bool is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; } static inline uint32_t get_first_codepoint(UniChar *utf16, UniCharCount num) { if (!num) return 0; if (!is_surrogate(*utf16)) return *utf16; if (CFStringIsSurrogateHighCharacter(*utf16) && num > 1 && CFStringIsSurrogateLowCharacter(utf16[1])) return CFStringGetLongCharacterForSurrogatePair(utf16[0], utf16[1]); return 0; } static inline bool is_pua_char(uint32_t ch) { return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF); } static uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode) { UInt32 dead_key_state = 0; UniChar characters[256]; UniCharCount character_count = 0; uint32_t ans = vk_code_to_functional_key_code(keycode); if (ans) return ans; if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &dead_key_state, arraysz(characters), &character_count, characters) == noErr) { uint32_t cp = get_first_codepoint(characters, character_count); if (cp) { if (cp < 32 || (0xF700 <= cp && cp <= 0xF8FF)) return mac_ucode_to_functional(cp); if (cp >= 32 && !is_pua_char(cp)) return cp; } } return vk_code_to_unicode(keycode); } // Returns the style mask corresponding to the window settings // static NSUInteger getStyleMask(_GLFWwindow* window) { NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; if (window->monitor || !window->decorated) styleMask |= NSWindowStyleMaskBorderless; else { styleMask |= NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; if (window->resizable) styleMask |= NSWindowStyleMaskResizable; } return styleMask; } CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { NSWindow *nw = w->ns.object; NSDictionary *dict = [nw.screen deviceDescription]; NSNumber *displayIDns = dict[@"NSScreenNumber"]; if (displayIDns) return [displayIDns unsignedIntValue]; return (CGDirectDisplayID)-1; } static unsigned long long display_link_shutdown_timer = 0; #define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll) void _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) { display_link_shutdown_timer = 0; for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i]; if (dl->displayLink) CVDisplayLinkStop(dl->displayLink); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; } } static inline void requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { if (!callback) { w->ns.renderFrameRequested = false; w->ns.renderFrameCallback = NULL; return; } w->ns.renderFrameCallback = callback; w->ns.renderFrameRequested = true; CGDirectDisplayID displayID = displayIDForWindow(w); if (display_link_shutdown_timer) { _glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true); } else { display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL); } monotonic_t now = glfwGetTime(); for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i]; if (dl->displayID == displayID) { dl->lastRenderFrameRequestedAt = now; if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now; if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) { // display link is stuck need to recreate it because Apple cant even // get a simple timer right CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil; dl->first_unserviced_render_frame_request_at = now; _glfw_create_cv_display_link(dl); _glfwInputError(GLFW_PLATFORM_ERROR, "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating."); if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); } } else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) { CVDisplayLinkStop(dl->displayLink); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; } } } void _glfwRestartDisplayLinks(void) { _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) { if (window->ns.renderFrameRequested && window->ns.renderFrameCallback) { requestRenderFrame(window, window->ns.renderFrameCallback); } } } // Returns whether the cursor is in the content area of the specified window // static bool cursorInContentArea(_GLFWwindow* window) { const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; } // Hides the cursor if not already hidden // static void hideCursor(_GLFWwindow* window UNUSED) { if (!_glfw.ns.cursorHidden) { [NSCursor hide]; _glfw.ns.cursorHidden = true; } } // Shows the cursor if not already shown // static void showCursor(_GLFWwindow* window UNUSED) { if (_glfw.ns.cursorHidden) { [NSCursor unhide]; _glfw.ns.cursorHidden = false; } } // Updates the cursor image according to its cursor mode // static void updateCursorImage(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { showCursor(window); if (window->cursor) [(NSCursor*) window->cursor->ns.object set]; else [[NSCursor arrowCursor] set]; } else hideCursor(window); } // Apply chosen cursor mode to a focused window // static void updateCursorMode(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_DISABLED) { _glfw.ns.disabledCursorWindow = window; _glfwPlatformGetCursorPos(window, &_glfw.ns.restoreCursorPosX, &_glfw.ns.restoreCursorPosY); _glfwCenterCursorInContentArea(window); CGAssociateMouseAndMouseCursorPosition(false); } else if (_glfw.ns.disabledCursorWindow == window) { _glfw.ns.disabledCursorWindow = NULL; CGAssociateMouseAndMouseCursorPosition(true); _glfwPlatformSetCursorPos(window, _glfw.ns.restoreCursorPosX, _glfw.ns.restoreCursorPosY); } if (cursorInContentArea(window)) updateCursorImage(window); } // Make the specified window and its video mode active on its monitor // static void acquireMonitor(_GLFWwindow* window) { _glfwSetVideoModeNS(window->monitor, &window->videoMode); const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); const NSRect frame = NSMakeRect(bounds.origin.x, _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), bounds.size.width, bounds.size.height); [window->ns.object setFrame:frame display:YES]; _glfwInputMonitorWindow(window->monitor, window); } // Remove the window and restore the original video mode // static void releaseMonitor(_GLFWwindow* window) { if (window->monitor->window != window) return; _glfwInputMonitorWindow(window->monitor, NULL); _glfwRestoreVideoModeNS(window->monitor); } // Translates macOS key modifiers into GLFW ones // static int translateFlags(NSUInteger flags) { int mods = 0; if (flags & NSEventModifierFlagShift) mods |= GLFW_MOD_SHIFT; if (flags & NSEventModifierFlagControl) mods |= GLFW_MOD_CONTROL; if (flags & NSEventModifierFlagOption) mods |= GLFW_MOD_ALT; if (flags & NSEventModifierFlagCommand) mods |= GLFW_MOD_SUPER; if (flags & NSEventModifierFlagCapsLock) mods |= GLFW_MOD_CAPS_LOCK; return mods; } #define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); } static inline const char* format_mods(int mods) { static char buf[128]; char *p = buf, *s; #define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x) pr("mods: "); s = p; if (mods & GLFW_MOD_CONTROL) pr("ctrl+"); if (mods & GLFW_MOD_ALT) pr("alt+"); if (mods & GLFW_MOD_SHIFT) pr("shift+"); if (mods & GLFW_MOD_SUPER) pr("super+"); if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+"); if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+"); if (p == s) pr("none"); else p--; pr(" "); #undef pr return buf; } static inline const char* format_text(const char *src) { static char buf[256]; char *p = buf; const char *last_char = buf + sizeof(buf) - 1; if (!src[0]) return ""; while (*src) { int num = snprintf(p, sizeof(buf) - (p - buf), "0x%x ", (unsigned char)*(src++)); if (num < 0) return ""; if (p + num >= last_char) break; p += num; } if (p != buf) *(--p) = 0; return buf; } static const char* safe_name_for_keycode(unsigned int keycode) { const char *ans = _glfwPlatformGetNativeKeyName(keycode); if (!ans) return ""; if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = ""; return ans; } // Translates a macOS keycode to a GLFW keycode // static uint32_t translateKey(uint16_t vk_key, bool apply_keymap) { if (apply_keymap) return vk_to_unicode_key_with_current_layout(vk_key); uint32_t ans = vk_code_to_functional_key_code(vk_key); if (!ans) ans = vk_code_to_unicode(vk_key); return ans; } // Translate a GLFW keycode to a Cocoa modifier flag // static NSUInteger translateKeyToModifierFlag(uint32_t key) { switch (key) { case GLFW_FKEY_LEFT_SHIFT: case GLFW_FKEY_RIGHT_SHIFT: return NSEventModifierFlagShift; case GLFW_FKEY_LEFT_CONTROL: case GLFW_FKEY_RIGHT_CONTROL: return NSEventModifierFlagControl; case GLFW_FKEY_LEFT_ALT: case GLFW_FKEY_RIGHT_ALT: return NSEventModifierFlagOption; case GLFW_FKEY_LEFT_SUPER: case GLFW_FKEY_RIGHT_SUPER: return NSEventModifierFlagCommand; case GLFW_FKEY_CAPS_LOCK: return NSEventModifierFlagCapsLock; } return 0; } // Defines a constant for empty ranges in NSTextInputClient // static const NSRange kEmptyRange = { NSNotFound, 0 }; // SecureKeyboardEntryController {{{ @interface SecureKeyboardEntryController : NSObject @property (nonatomic, readonly) BOOL isDesired; @property (nonatomic, readonly, getter=isEnabled) BOOL enabled; + (instancetype)sharedInstance; - (void)toggle; - (void)update; @end @implementation SecureKeyboardEntryController { int _count; BOOL _desired; } + (instancetype)sharedInstance { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _desired = false; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil]; if ([NSApp isActive]) { [self update]; } } return self; } #pragma mark - API - (void)toggle { // Set _desired to the opposite of the current state. _desired = !_desired; debug_key("toggle called. Setting desired to %d", _desired); // Try to set the system's state of secure input to the desired state. [self update]; } - (BOOL)isEnabled { return !!IsSecureEventInputEnabled(); } - (BOOL)isDesired { return _desired; } #pragma mark - Notifications - (void)applicationDidResignActive:(NSNotification *)notification { (void)notification; if (_count > 0) { debug_key("Application resigning active."); [self update]; } } - (void)applicationDidBecomeActive:(NSNotification *)notification { (void)notification; if (self.isDesired) { debug_key("Application became active."); [self update]; } } #pragma mark - Private - (BOOL)allowed { return [NSApp isActive]; } - (void)update { debug_key("Update secure keyboard entry. desired=%d active=%d\n", (int)self.isDesired, (int)[NSApp isActive]); const BOOL secure = self.isDesired && [self allowed]; if (secure && _count > 0) { debug_key("Want to turn on secure input but it's already on\n"); return; } if (!secure && _count == 0) { debug_key("Want to turn off secure input but it's already off\n"); return; } debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled); if (secure) { OSErr err = EnableSecureEventInput(); debug_key("EnableSecureEventInput err=%d ", (int)err); if (err) { debug_key("EnableSecureEventInput failed with error %d ", (int)err); } else { _count += 1; } } else { OSErr err = DisableSecureEventInput(); debug_key("DisableSecureEventInput err=%d ", (int)err); if (err) { debug_key("DisableSecureEventInput failed with error %d ", (int)err); } else { _count -= 1; } } debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled); } @end // }}} // Delegate for window related notifications {{{ @interface GLFWWindowDelegate : NSObject { _GLFWwindow* window; } - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; @end @implementation GLFWWindowDelegate - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) window = initWindow; return self; } - (BOOL)windowShouldClose:(id)sender { (void)sender; _glfwInputWindowCloseRequest(window); return NO; } - (void)windowDidResize:(NSNotification *)notification { (void)notification; if (window->context.client != GLFW_NO_API) [window->context.nsgl.object update]; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); const int maximized = [window->ns.object isZoomed]; if (window->ns.maximized != maximized) { window->ns.maximized = maximized; _glfwInputWindowMaximize(window, maximized); } const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { window->ns.fbWidth = (int)fbRect.size.width; window->ns.fbHeight = (int)fbRect.size.height; _glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height); } if (contentRect.size.width != window->ns.width || contentRect.size.height != window->ns.height) { window->ns.width = (int)contentRect.size.width; window->ns.height = (int)contentRect.size.height; _glfwInputWindowSize(window, (int)contentRect.size.width, (int)contentRect.size.height); } } - (void)windowDidMove:(NSNotification *)notification { (void)notification; if (window->context.client != GLFW_NO_API) [window->context.nsgl.object update]; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); int x, y; _glfwPlatformGetWindowPos(window, &x, &y); _glfwInputWindowPos(window, x, y); } - (void)windowDidChangeOcclusionState:(NSNotification *)notification { (void)notification; _glfwInputWindowOcclusion(window, !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)); } - (void)windowDidMiniaturize:(NSNotification *)notification { (void)notification; if (window->monitor) releaseMonitor(window); _glfwInputWindowIconify(window, true); } - (void)windowDidDeminiaturize:(NSNotification *)notification { (void)notification; if (window->monitor) acquireMonitor(window); _glfwInputWindowIconify(window, false); } - (void)windowDidBecomeKey:(NSNotification *)notification { (void)notification; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); _glfwInputWindowFocus(window, true); updateCursorMode(window); if (window->cursorMode == GLFW_CURSOR_HIDDEN) hideCursor(window); if (_glfw.ns.disabledCursorWindow != window && cursorInContentArea(window)) { double x = 0, y = 0; _glfwPlatformGetCursorPos(window, &x, &y); _glfwInputCursorPos(window, x, y); } } - (void)windowDidResignKey:(NSNotification *)notification { (void)notification; if (window->monitor && window->autoIconify) _glfwPlatformIconifyWindow(window); showCursor(window); _glfwInputWindowFocus(window, false); } - (void)windowDidChangeScreen:(NSNotification *)notification { (void)notification; if (window->ns.renderFrameRequested && window->ns.renderFrameCallback) { // Ensure that if the window changed its monitor, CVDisplayLink // is running for the new monitor requestRenderFrame(window, window->ns.renderFrameCallback); } } @end // }}} // Content view class for the GLFW window {{{ @interface GLFWContentView : NSView { _GLFWwindow* window; NSTrackingArea* trackingArea; NSMutableAttributedString* markedText; NSRect markedRect; NSString *input_source_at_last_key_event; } - (void) removeGLFWWindow; - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; @end @implementation GLFWContentView - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) { window = initWindow; trackingArea = nil; markedText = [[NSMutableAttributedString alloc] init]; markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0); input_source_at_last_key_event = nil; [self updateTrackingAreas]; [self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]]; } return self; } - (void)dealloc { [trackingArea release]; [markedText release]; if (input_source_at_last_key_event) [input_source_at_last_key_event release]; [super dealloc]; } - (void) removeGLFWWindow { window = NULL; } - (_GLFWwindow*)glfwWindow { return window; } - (BOOL)isOpaque { return window && [window->ns.object isOpaque]; } - (BOOL)canBecomeKeyView { return YES; } - (BOOL)acceptsFirstResponder { return YES; } - (void) viewWillStartLiveResize { if (!window) return; _glfwInputLiveResize(window, true); } - (void)viewDidEndLiveResize { if (!window) return; _glfwInputLiveResize(window, false); } - (BOOL)wantsUpdateLayer { return YES; } - (void)updateLayer { if (!window) return; if (window->context.client != GLFW_NO_API) { @try { [window->context.nsgl.object update]; } @catch (NSException *e) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to update NSGL Context object with error: %s (%s)", [[e name] UTF8String], [[e reason] UTF8String]); } } _glfwInputWindowDamage(window); } - (void)cursorUpdate:(NSEvent *)event { (void)event; if (window) updateCursorImage(window); } - (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return NO; // changed by Kovid, to follow cocoa platform conventions } - (void)mouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)mouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseMoved:(NSEvent *)event { if (!window) return; if (window->cursorMode == GLFW_CURSOR_DISABLED) { const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; _glfwInputCursorPos(window, window->virtualCursorPosX + dx, window->virtualCursorPosY + dy); } else { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [event locationInWindow]; _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); } window->ns.cursorWarpDeltaX = 0; window->ns.cursorWarpDeltaY = 0; } - (void)rightMouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)rightMouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)otherMouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)otherMouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseExited:(NSEvent *)event { (void)event; if (!window) return; _glfwInputCursorEnter(window, false); } - (void)mouseEntered:(NSEvent *)event { (void)event; if (!window) return; _glfwInputCursorEnter(window, true); } - (void)viewDidChangeBackingProperties { if (!window) return; const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { window->ns.fbWidth = (int)fbRect.size.width; window->ns.fbHeight = (int)fbRect.size.height; _glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height); } const float xscale = fbRect.size.width / contentRect.size.width; const float yscale = fbRect.size.height / contentRect.size.height; if (xscale != window->ns.xscale || yscale != window->ns.yscale) { window->ns.xscale = xscale; window->ns.yscale = yscale; _glfwInputWindowContentScale(window, xscale, yscale); if (window->ns.retina && window->ns.layer) [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; } } - (void)drawRect:(NSRect)rect { (void)rect; if (!window) return; _glfwInputWindowDamage(window); } - (void)updateTrackingAreas { if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; [trackingArea release]; } const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag | NSTrackingCursorUpdate | NSTrackingInVisibleRect | NSTrackingAssumeInside; trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; } static inline UInt32 convert_cocoa_to_carbon_modifiers(NSUInteger flags) { UInt32 mods = 0; if (flags & NSEventModifierFlagShift) mods |= shiftKey; if (flags & NSEventModifierFlagControl) mods |= controlKey; if (flags & NSEventModifierFlagOption) mods |= optionKey; if (flags & NSEventModifierFlagCommand) mods |= cmdKey; if (flags & NSEventModifierFlagCapsLock) mods |= alphaLock; return (mods >> 8) & 0xFF; } static inline void convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) { CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, src, src_length, kCFAllocatorNull); CFStringGetCString(string, dest, dest_sz, kCFStringEncodingUTF8); CFRelease(string); } static inline bool alternate_key_is_ok(uint32_t key, uint32_t akey) { return akey > 31 && akey != key && !is_pua_char(akey); } static inline void add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) { ev->alternate_key = translateKey(ev->native_key, false); if (!alternate_key_is_ok(ev->key, ev->alternate_key)) ev->alternate_key = 0; if (ev->mods & GLFW_MOD_SHIFT) { NSString *ci = [event charactersIgnoringModifiers]; if (ci) { unsigned sz = [ci length]; if (sz > 0) { UniChar buf[2] = {0}; buf[0] = [ci characterAtIndex:0]; if (sz > 1) buf[1] = [ci characterAtIndex:1]; ev->shifted_key = get_first_codepoint(buf, sz); } } if (!alternate_key_is_ok(ev->key, ev->shifted_key)) ev->shifted_key = 0; } } static inline bool is_ascii_control_char(char x) { return x == 0 || (1 <= x && x <= 31) || x == 127; } - (void)keyDown:(NSEvent *)event { const bool previous_has_marked_text = [self hasMarkedText]; bool input_source_changed = false; NSTextInputContext *inpctx = [NSTextInputContext currentInputContext]; if (inpctx && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:inpctx.selectedKeyboardInputSource])) { input_source_at_last_key_event = [inpctx.selectedKeyboardInputSource retain]; input_source_changed = true; } const unsigned int keycode = [event keyCode]; const NSUInteger flags = [event modifierFlags]; const int mods = translateFlags(flags); const uint32_t key = translateKey(keycode, true); const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1; [self unmarkText]; _glfw.ns.text[0] = 0; GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .action = GLFW_PRESS, .mods = mods}; if (!_glfw.ns.unicodeData) { // Using the cocoa API for key handling is disabled, as there is no // reliable way to handle dead keys using it. Only use it if the // keyboard unicode data is not available. if (process_text) { // this will call insertText with the text for this event, if any [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } } else { if (input_source_changed) { debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n"); glfw_keyevent.text = NULL; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; window->ns.deadKeyState = 0; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } static UniChar text[256]; UniCharCount char_count = 0; const bool in_compose_sequence = window->ns.deadKeyState != 0; if (UCKeyTranslate( [(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDown, convert_cocoa_to_carbon_modifiers(flags), LMGetKbdType(), (process_text ? 0 : kUCKeyTranslateNoDeadKeysMask), &(window->ns.deadKeyState), sizeof(text)/sizeof(text[0]), &char_count, text ) != noErr) { debug_key("UCKeyTranslate failed for keycode: 0x%x (%s) %s\n", keycode, safe_name_for_keycode(keycode), format_mods(mods)); window->ns.deadKeyState = 0; return; } debug_key("\x1b[31mPress:\x1b[m native_key: 0x%x (%s) glfw_key: 0x%x %schar_count: %lu deadKeyState: %u repeat: %d ", keycode, safe_name_for_keycode(keycode), key, format_mods(mods), char_count, window->ns.deadKeyState, event.ARepeat); if (process_text) { // this will call insertText which will fill up _glfw.ns.text [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } else { window->ns.deadKeyState = 0; } if (window->ns.deadKeyState && (char_count == 0 || keycode == 0x75)) { // 0x75 is the delete key which needs to be ignored during a compose sequence glfw_keyevent.text = [[markedText string] UTF8String]; debug_key("Sending pre-edit text for dead key (text: %s markedText: %s).\n", format_text(_glfw.ns.text), glfw_keyevent.text); glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text return; } if (in_compose_sequence) { debug_key("Clearing pre-edit text at end of compose sequence\n"); glfw_keyevent.text = NULL; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } } if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes debug_key("text: %s glfw_key: %s marked_text: %s\n", format_text(_glfw.ns.text), _glfwGetKeyName(key), [[markedText string] UTF8String]); if (!window->ns.deadKeyState) { if ([self hasMarkedText]) { glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text } else if (previous_has_marked_text) { glfw_keyevent.text = NULL; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text } if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) { // do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it return; } } glfw_keyevent.text = _glfw.ns.text; glfw_keyevent.ime_state = GLFW_IME_NONE; add_alternate_keys(&glfw_keyevent, event); _glfwInputKeyboard(window, &glfw_keyevent); } - (void)flagsChanged:(NSEvent *)event { int action = GLFW_RELEASE; const unsigned int modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; const uint32_t key = translateKey([event keyCode], false); const int mods = translateFlags(modifierFlags); const NSUInteger keyFlag = translateKeyToModifierFlag(key); if (keyFlag & modifierFlags) { int current_action = GLFW_RELEASE; for (unsigned i = 0; i < arraysz(window->activated_keys); i++) { if (window->activated_keys[i].key == key) { current_action = window->activated_keys[i].action; break; } } if (current_action == GLFW_PRESS) action = GLFW_RELEASE; else action = GLFW_PRESS; } GLFWkeyevent glfw_keyevent = {.key = key, .native_key = [event keyCode], .action = action, .mods = mods}; _glfwInputKeyboard(window, &glfw_keyevent); } - (void)keyUp:(NSEvent *)event { const uint32_t keycode = [event keyCode]; const uint32_t key = translateKey(keycode, true); const int mods = translateFlags([event modifierFlags]); GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .action = GLFW_RELEASE, .mods = mods}; add_alternate_keys(&glfw_keyevent, event); debug_key("\x1b[32mRelease:\x1b[m native_key: 0x%x (%s) glfw_key: 0x%x %s\n", keycode, safe_name_for_keycode(keycode), key, format_mods(mods)); _glfwInputKeyboard(window, &glfw_keyevent); } - (void)scrollWheel:(NSEvent *)event { double deltaX = [event scrollingDeltaX]; double deltaY = [event scrollingDeltaY]; int flags = [event hasPreciseScrollingDeltas] ? 1 : 0; if (flags) { float xscale = 1, yscale = 1; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); if (xscale > 0) deltaX *= xscale; if (yscale > 0) deltaY *= yscale; } switch([event momentumPhase]) { case NSEventPhaseBegan: flags |= (1 << 1); break; case NSEventPhaseStationary: flags |= (2 << 1); break; case NSEventPhaseChanged: flags |= (3 << 1); break; case NSEventPhaseEnded: flags |= (4 << 1); break; case NSEventPhaseCancelled: flags |= (5 << 1); break; case NSEventPhaseMayBegin: flags |= (6 << 1); break; case NSEventPhaseNone: default: break; } _glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags])); } - (NSDragOperation)draggingEntered:(id )sender { (void)sender; // HACK: We don't know what to say here because we don't know what the // application wants to do with the paths return NSDragOperationGeneric; } - (BOOL)performDragOperation:(id )sender { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [sender draggingLocation]; _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); NSPasteboard* pasteboard = [sender draggingPasteboard]; NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options]; if (!objs) return NO; const NSUInteger count = [objs count]; if (count) { for (NSUInteger i = 0; i < count; i++) { id obj = objs[i]; if ([obj isKindOfClass:[NSURL class]]) { const char *path = [obj fileSystemRepresentation]; _glfwInputDrop(window, "text/plain;charset=utf-8", path, strlen(path)); } else if ([obj isKindOfClass:[NSString class]]) { const char *text = [obj UTF8String]; _glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text)); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Object is neither a URL nor a string"); } } } return YES; } - (BOOL)hasMarkedText { return [markedText length] > 0; } - (NSRange)markedRange { if ([markedText length] > 0) return NSMakeRange(0, [markedText length] - 1); else return kEmptyRange; } - (NSRange)selectedRange { return kEmptyRange; } - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { (void)selectedRange; (void)replacementRange; [markedText release]; if ([string isKindOfClass:[NSAttributedString class]]) markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; else markedText = [[NSMutableAttributedString alloc] initWithString:string]; } - (void)unmarkText { [[markedText mutableString] setString:@""]; } void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { [w->ns.view updateIMEStateFor: ev->type left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height]; } - (void)updateIMEStateFor:(GLFWIMEUpdateType)which left:(CGFloat)left top:(CGFloat)top cellWidth:(CGFloat)cellWidth cellHeight:(CGFloat)cellHeight { (void) which; left /= window->ns.xscale; top /= window->ns.yscale; cellWidth /= window->ns.xscale; cellHeight /= window->ns.yscale; debug_key("updateIMEState: %f, %f, %f, %f\n", left, top, cellWidth, cellHeight); const NSRect frame = [window->ns.view frame]; const NSRect rectInView = NSMakeRect(left, frame.size.height - top - cellHeight, cellWidth, cellHeight); markedRect = [window->ns.object convertRectToScreen: rectInView]; } - (NSArray*)validAttributesForMarkedText { return [NSArray array]; } - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { (void)range; (void)actualRange; return nil; } - (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return 0; } - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange { (void)range; (void)actualRange; return markedRect; } - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { (void)replacementRange; NSString* characters; if ([string isKindOfClass:[NSAttributedString class]]) characters = [string string]; else characters = (NSString*) string; // insertText can be called multiple times for a single key event char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text)); snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", [characters UTF8String]); _glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0; } - (void)doCommandBySelector:(SEL)selector { (void)selector; } @end // }}} // GLFW window class {{{ @interface GLFWWindow : NSWindow { _GLFWwindow* glfw_window; } - (instancetype)initWithGlfwWindow:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType initWindow:(_GLFWwindow *)initWindow; - (void) removeGLFWWindow; @end @implementation GLFWWindow - (instancetype)initWithGlfwWindow:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType initWindow:(_GLFWwindow *)initWindow { self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:NO]; if (self != nil) { glfw_window = initWindow; self.tabbingMode = NSWindowTabbingModeDisallowed; } return self; } - (void) removeGLFWWindow { glfw_window = NULL; } - (BOOL)validateMenuItem:(NSMenuItem *)item { if (item.action == @selector(performMiniaturize:)) return YES; if (item.action == @selector(toggleSecureInput:)) { SecureKeyboardEntryController *controller = [SecureKeyboardEntryController sharedInstance]; if (controller.isEnabled) { if (controller.isDesired) { item.state = NSControlStateValueOn; } else { item.state = NSControlStateValueMixed; } } else { item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff; } return YES; } return [super validateMenuItem:item]; } - (void)performMiniaturize:(id)sender { if (glfw_window && (!glfw_window->decorated || glfw_window->ns.titlebar_hidden)) [self miniaturize:self]; else [super performMiniaturize:sender]; } - (void)toggleSecureInput:(id)sender { (void)sender; [[SecureKeyboardEntryController sharedInstance] toggle]; } - (BOOL)canBecomeKeyWindow { // Required for NSWindowStyleMaskBorderless windows return YES; } - (BOOL)canBecomeMainWindow { return YES; } - (void)toggleFullScreen:(nullable id)sender { if (glfw_window && glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return; [super toggleFullScreen:sender]; } @end // }}} // Create the Cocoa window // static bool createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWfbconfig* fbconfig) { window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; if (window->ns.delegate == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window delegate"); return false; } NSRect contentRect; if (window->monitor) { GLFWvidmode mode; int xpos, ypos; _glfwPlatformGetVideoMode(window->monitor, &mode); _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); } else contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); window->ns.object = [[GLFWWindow alloc] initWithGlfwWindow:contentRect styleMask:getStyleMask(window) backing:NSBackingStoreBuffered initWindow:window ]; if (window->ns.object == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); return false; } if (window->monitor) [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; else { [(NSWindow*) window->ns.object center]; _glfw.ns.cascadePoint = NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: NSPointFromCGPoint(_glfw.ns.cascadePoint)]); if (wndconfig->resizable) { const NSWindowCollectionBehavior behavior = NSWindowCollectionBehaviorFullScreenPrimary | NSWindowCollectionBehaviorManaged; [window->ns.object setCollectionBehavior:behavior]; } if (wndconfig->floating) [window->ns.object setLevel:NSFloatingWindowLevel]; if (wndconfig->maximized) [window->ns.object zoom:nil]; } if (strlen(wndconfig->ns.frameName)) [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; window->ns.retina = wndconfig->ns.retina; if (fbconfig->transparent) { [window->ns.object setOpaque:NO]; [window->ns.object setHasShadow:NO]; [window->ns.object setBackgroundColor:[NSColor clearColor]]; } [window->ns.object setContentView:window->ns.view]; [window->ns.object makeFirstResponder:window->ns.view]; [window->ns.object setTitle:@(wndconfig->title)]; [window->ns.object setDelegate:window->ns.delegate]; [window->ns.object setAcceptsMouseMovedEvents:YES]; [window->ns.object setRestorable:NO]; _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { window->ns.deadKeyState = 0; if (!_glfw.ns.finishedLaunching) { [NSApp run]; _glfw.ns.finishedLaunching = true; } if (!createNativeWindow(window, wndconfig, fbconfig)) return false; [window->ns.object setColorSpace:[NSColorSpace sRGBColorSpace]]; if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwInitNSGL()) return false; if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) { // EGL implementation on macOS use CALayer* EGLNativeWindowType so we // need to get the layer for EGL window surface creation. [window->ns.view setWantsLayer:YES]; window->ns.layer = [window->ns.view layer]; if (!_glfwInitEGL()) return false; if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwInitOSMesa()) return false; if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) return false; } } if (window->monitor) { _glfwPlatformShowWindow(window); _glfwPlatformFocusWindow(window); acquireMonitor(window); } return true; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (_glfw.ns.disabledCursorWindow == window) _glfw.ns.disabledCursorWindow = NULL; [window->ns.object orderOut:nil]; if (window->monitor) releaseMonitor(window); if (window->context.destroy) window->context.destroy(window); [window->ns.object setDelegate:nil]; [window->ns.delegate release]; window->ns.delegate = nil; [window->ns.view removeGLFWWindow]; [window->ns.view release]; window->ns.view = nil; [window->ns.object removeGLFWWindow]; [window->ns.object close]; window->ns.object = nil; } void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char* title) { NSString* string = @(title); [window->ns.object setTitle:string]; // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it // if the window lacks NSWindowStyleMaskTitled [window->ns.object setMiniwindowTitle:string]; } void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED, int count UNUSED, const GLFWimage* images UNUSED) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Cocoa: Regular windows do not have icons on macOS"); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { const NSRect contentRect = [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; if (xpos) *xpos = (int)contentRect.origin.x; if (ypos) *ypos = (int)_glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) { const NSRect contentRect = [window->ns.view frame]; const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; [window->ns.object setFrameOrigin:frameRect.origin]; } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = [window->ns.view frame]; if (width) *width = (int)contentRect.size.width; if (height) *height = (int)contentRect.size.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) { if (window->monitor->window == window) acquireMonitor(window); } else { NSRect contentRect = [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; contentRect.origin.y += contentRect.size.height - height; contentRect.size = NSMakeSize(width, height); [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] display:YES]; } } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) { if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) [window->ns.object setContentMinSize:NSMakeSize(0, 0)]; else [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)]; if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)]; else [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)]; } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) { if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; else [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; } void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr) { if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE) [window->ns.object setResizeIncrements:NSMakeSize(widthincr, heightincr)]; else [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (width) *width = (int) fbRect.size.width; if (height) *height = (int) fbRect.size.height; } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { const NSRect contentRect = [window->ns.view frame]; const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; if (left) *left = (int)(contentRect.origin.x - frameRect.origin.x); if (top) *top = (int)(frameRect.origin.y + frameRect.size.height - contentRect.origin.y - contentRect.size.height); if (right) *right = (int)(frameRect.origin.x + frameRect.size.width - contentRect.origin.x - contentRect.size.width); if (bottom) *bottom = (int)(contentRect.origin.y - frameRect.origin.y); } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale) { const NSRect points = [window->ns.view frame]; const NSRect pixels = [window->ns.view convertRectToBacking:points]; if (xscale) *xscale = (float) (pixels.size.width / points.size.width); if (yscale) *yscale = (float) (pixels.size.height / points.size.height); } monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { return s_double_to_monotonic_t([NSEvent doubleClickInterval]); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { [window->ns.object miniaturize:nil]; } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if ([window->ns.object isMiniaturized]) [window->ns.object deminiaturize:nil]; else if ([window->ns.object isZoomed]) [window->ns.object zoom:nil]; } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (![window->ns.object isZoomed]) [window->ns.object zoom:nil]; } void _glfwPlatformShowWindow(_GLFWwindow* window) { [window->ns.object orderFront:nil]; } void _glfwPlatformHideWindow(_GLFWwindow* window) { [window->ns.object orderOut:nil]; } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED) { [NSApp requestUserAttention:NSInformationalRequest]; } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { NSBeep(); return true; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { // Make us the active application // HACK: This is here to prevent applications using only hidden windows from // being activated, but should probably not be done every time any // window is shown [NSApp activateIgnoringOtherApps:YES]; [window->ns.object makeKeyAndOrderFront:nil]; } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate UNUSED) { if (window->monitor == monitor) { if (monitor) { if (monitor->window == window) acquireMonitor(window); } else { const NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect styleMask:getStyleMask(window)]; [window->ns.object setFrame:frameRect display:YES]; } return; } if (window->monitor) releaseMonitor(window); _glfwInputWindowMonitor(window, monitor); const NSUInteger styleMask = getStyleMask(window); [window->ns.object setStyleMask:styleMask]; // HACK: Changing the style mask can cause the first responder to be cleared [window->ns.object makeFirstResponder:window->ns.view]; if (window->monitor) { [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; [window->ns.object setHasShadow:NO]; acquireMonitor(window); } else { NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect styleMask:styleMask]; [window->ns.object setFrame:frameRect display:YES]; if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, window->denom)]; } if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE) { [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, window->minheight)]; } if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE) { [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, window->maxheight)]; } if (window->floating) [window->ns.object setLevel:NSFloatingWindowLevel]; else [window->ns.object setLevel:NSNormalWindowLevel]; [window->ns.object setHasShadow:YES]; // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window // title property but the miniwindow title property is unaffected [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; } } int _glfwPlatformWindowFocused(_GLFWwindow* window) { return [window->ns.object isKeyWindow]; } int _glfwPlatformWindowOccluded(_GLFWwindow* window) { return !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible); } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return [window->ns.object isMiniaturized]; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { return [window->ns.object isVisible]; } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { return [window->ns.object isZoomed]; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { const NSPoint point = [NSEvent mouseLocation]; if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != [window->ns.object windowNumber]) { return false; } return NSMouseInRect(point, [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED) { [window->ns.object setStyleMask:getStyleMask(window)]; } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled UNUSED) { [window->ns.object setStyleMask:getStyleMask(window)]; [window->ns.object makeFirstResponder:window->ns.view]; } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled) { if (enabled) [window->ns.object setLevel:NSFloatingWindowLevel]; else [window->ns.object setLevel:NSNormalWindowLevel]; } void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled) { [window->ns.object setIgnoresMouseEvents:enabled]; } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return (float) [window->ns.object alphaValue]; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { [window->ns.object setAlphaValue:opacity]; } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED) { _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Cocoa: Raw mouse motion not yet implemented"); } bool _glfwPlatformRawMouseMotionSupported(void) { return false; } void _glfwDispatchRenderFrame(CGDirectDisplayID displayID) { _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) { w->ns.renderFrameRequested = false; w->ns.renderFrameCallback((GLFWwindow*)w); } w = w->next; } for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i]; if (dl->displayID == displayID) { dl->first_unserviced_render_frame_request_at = 0; } } } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; if (xpos) *xpos = pos.x; if (ypos) *ypos = contentRect.size.height - pos.y; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { updateCursorImage(window); const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; window->ns.cursorWarpDeltaX += x - pos.x; window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; if (window->monitor) { CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, CGPointMake(x, y)); } else { const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; const NSPoint globalPoint = globalRect.origin; CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, _glfwTransformYNS(globalPoint.y))); } } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED) { if (_glfwPlatformWindowFocused(window)) updateCursorMode(window); } const char* _glfwPlatformGetNativeKeyName(int keycode) { UInt32 deadKeyState = 0; UniChar characters[8]; UniCharCount characterCount = 0; if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, sizeof(characters) / sizeof(characters[0]), &characterCount, characters) != noErr) { return NULL; } if (!characterCount) return NULL; convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName)); return _glfw.ns.keyName; } int _glfwPlatformGetNativeKeyForKey(uint32_t glfw_key) { if (GLFW_FKEY_FIRST <= glfw_key && glfw_key <= GLFW_FKEY_LAST) { // {{{ switch(glfw_key) { /* start functional to macu (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ENTER: return NSCarriageReturnCharacter; case GLFW_FKEY_TAB: return NSTabCharacter; case GLFW_FKEY_BACKSPACE: return NSBackspaceCharacter; case GLFW_FKEY_INSERT: return NSInsertFunctionKey; case GLFW_FKEY_DELETE: return NSDeleteFunctionKey; case GLFW_FKEY_LEFT: return NSLeftArrowFunctionKey; case GLFW_FKEY_RIGHT: return NSRightArrowFunctionKey; case GLFW_FKEY_UP: return NSUpArrowFunctionKey; case GLFW_FKEY_DOWN: return NSDownArrowFunctionKey; case GLFW_FKEY_PAGE_UP: return NSPageUpFunctionKey; case GLFW_FKEY_PAGE_DOWN: return NSPageDownFunctionKey; case GLFW_FKEY_HOME: return NSHomeFunctionKey; case GLFW_FKEY_END: return NSEndFunctionKey; case GLFW_FKEY_SCROLL_LOCK: return NSScrollLockFunctionKey; case GLFW_FKEY_NUM_LOCK: return NSClearLineFunctionKey; case GLFW_FKEY_PRINT_SCREEN: return NSPrintScreenFunctionKey; case GLFW_FKEY_PAUSE: return NSPauseFunctionKey; case GLFW_FKEY_MENU: return NSMenuFunctionKey; case GLFW_FKEY_F1: return NSF1FunctionKey; case GLFW_FKEY_F2: return NSF2FunctionKey; case GLFW_FKEY_F3: return NSF3FunctionKey; case GLFW_FKEY_F4: return NSF4FunctionKey; case GLFW_FKEY_F5: return NSF5FunctionKey; case GLFW_FKEY_F6: return NSF6FunctionKey; case GLFW_FKEY_F7: return NSF7FunctionKey; case GLFW_FKEY_F8: return NSF8FunctionKey; case GLFW_FKEY_F9: return NSF9FunctionKey; case GLFW_FKEY_F10: return NSF10FunctionKey; case GLFW_FKEY_F11: return NSF11FunctionKey; case GLFW_FKEY_F12: return NSF12FunctionKey; case GLFW_FKEY_F13: return NSF13FunctionKey; case GLFW_FKEY_F14: return NSF14FunctionKey; case GLFW_FKEY_F15: return NSF15FunctionKey; case GLFW_FKEY_F16: return NSF16FunctionKey; case GLFW_FKEY_F17: return NSF17FunctionKey; case GLFW_FKEY_F18: return NSF18FunctionKey; case GLFW_FKEY_F19: return NSF19FunctionKey; case GLFW_FKEY_F20: return NSF20FunctionKey; case GLFW_FKEY_F21: return NSF21FunctionKey; case GLFW_FKEY_F22: return NSF22FunctionKey; case GLFW_FKEY_F23: return NSF23FunctionKey; case GLFW_FKEY_F24: return NSF24FunctionKey; case GLFW_FKEY_F25: return NSF25FunctionKey; case GLFW_FKEY_F26: return NSF26FunctionKey; case GLFW_FKEY_F27: return NSF27FunctionKey; case GLFW_FKEY_F28: return NSF28FunctionKey; case GLFW_FKEY_F29: return NSF29FunctionKey; case GLFW_FKEY_F30: return NSF30FunctionKey; case GLFW_FKEY_F31: return NSF31FunctionKey; case GLFW_FKEY_F32: return NSF32FunctionKey; case GLFW_FKEY_F33: return NSF33FunctionKey; case GLFW_FKEY_F34: return NSF34FunctionKey; case GLFW_FKEY_F35: return NSF35FunctionKey; case GLFW_FKEY_KP_ENTER: return NSEnterCharacter; /* end functional to macu */ default: return 0; } } // }}} if (!is_pua_char(glfw_key)) return glfw_key; return 0; } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot, int count) { NSImage* native; NSBitmapImageRep* rep; native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; if (native == nil) return false; for (int i = 0; i < count; i++) { const GLFWimage *src = image + i; rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:src->width pixelsHigh:src->height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:NSBitmapFormatAlphaNonpremultiplied bytesPerRow:src->width * 4 bitsPerPixel:32]; if (rep == nil) { [native release]; return false; } memcpy([rep bitmapData], src->pixels, src->width * src->height * 4); [native addRepresentation:rep]; [rep release]; } cursor->ns.object = [[NSCursor alloc] initWithImage:native hotSpot:NSMakePoint(xhot, yhot)]; [native release]; if (cursor->ns.object == nil) return false; return true; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) { #define C(name, val) case name: cursor->ns.object = [NSCursor val]; break; #define U(name, val) case name: cursor->ns.object = [[NSCursor class] performSelector:@selector(val)]; break; switch(shape) { C(GLFW_ARROW_CURSOR, arrowCursor); C(GLFW_IBEAM_CURSOR, IBeamCursor); C(GLFW_CROSSHAIR_CURSOR, crosshairCursor); C(GLFW_HAND_CURSOR, pointingHandCursor); C(GLFW_HRESIZE_CURSOR, resizeLeftRightCursor); C(GLFW_VRESIZE_CURSOR, resizeUpDownCursor); U(GLFW_NW_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); U(GLFW_NE_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); U(GLFW_SW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); U(GLFW_SE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); case GLFW_INVALID_CURSOR: return false; } #undef C #undef U if (!cursor->ns.object) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve standard cursor"); return false; } [cursor->ns.object retain]; return true; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->ns.object) [(NSCursor*) cursor->ns.object release]; } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED) { if (cursorInContentArea(window)) updateCursorImage(window); } bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) { NSWindow *window = w->ns.object; bool made_fullscreen = true; bool traditional = !(flags & 1); NSWindowStyleMask sm = [window styleMask]; if (traditional) { if (@available(macOS 10.16, *)) { // As of Big Turd NSWindowStyleMaskFullScreen is no longer useable if (!w->ns.in_traditional_fullscreen) { w->ns.pre_full_screen_style_mask = sm; [window setStyleMask: NSWindowStyleMaskBorderless]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; [window setFrame:[window.screen frame] display:YES]; w->ns.in_traditional_fullscreen = true; } else { made_fullscreen = false; [window setStyleMask: w->ns.pre_full_screen_style_mask]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; w->ns.in_traditional_fullscreen = false; } // for some reason despite calling this selector, the window doesnt actually get keyboard focus till you click on it. // presumably a bug with NSWindowStyleMaskBorderless windows [window performSelector:@selector(makeKeyAndOrderFront:) withObject:nil afterDelay:0]; } else { bool in_fullscreen = sm & NSWindowStyleMaskFullScreen; if (!(in_fullscreen)) { sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; } else { made_fullscreen = false; sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen); [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; } [window setStyleMask: sm]; } } else { bool in_fullscreen = sm & NSWindowStyleMaskFullScreen; if (in_fullscreen) made_fullscreen = false; [window toggleFullScreen: nil]; } return made_fullscreen; } void _glfwPlatformSetClipboardString(const char* string) { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; [pasteboard setString:@(string) forType:NSPasteboardTypeString]; } const char* _glfwPlatformGetClipboardString(void) { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "Cocoa: Failed to retrieve string from pasteboard"); return NULL; } NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; if (!object) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve object from pasteboard"); return NULL; } free(_glfw.ns.clipboardString); _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); return _glfw.ns.clipboardString; } EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) { int type = 0; if (_glfw.egl.ANGLE_platform_angle_opengl) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; } if (_glfw.egl.ANGLE_platform_angle_metal) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; } if (type) { *attribs = calloc(3, sizeof(EGLint)); (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; (*attribs)[1] = type; (*attribs)[2] = EGL_NONE; return EGL_PLATFORM_ANGLE_ANGLE; } } return 0; } EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) { return EGL_DEFAULT_DISPLAY; } EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) { return window->ns.layer; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) { extensions[0] = "VK_KHR_surface"; extensions[1] = "VK_EXT_metal_surface"; } else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) { extensions[0] = "VK_KHR_surface"; extensions[1] = "VK_MVK_macos_surface"; } } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED, VkPhysicalDevice device UNUSED, uint32_t queuefamily UNUSED) { return true; } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 // HACK: Dynamically load Core Animation to avoid adding an extra // dependency for the majority who don't use MoltenVK NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; if (!bundle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find QuartzCore.framework"); return VK_ERROR_EXTENSION_NOT_PRESENT; } // NOTE: Create the layer here as makeBackingLayer should not return nil window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; if (!window->ns.layer) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create layer for view"); return VK_ERROR_EXTENSION_NOT_PRESENT; } if (window->ns.retina) [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; [window->ns.view setLayer:window->ns.layer]; [window->ns.view setWantsLayer:YES]; VkResult err; if (_glfw.vk.EXT_metal_surface) { VkMetalSurfaceCreateInfoEXT sci; PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); if (!vkCreateMetalSurfaceEXT) { _glfwInputError(GLFW_API_UNAVAILABLE, "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; sci.pLayer = window->ns.layer; err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); } else { VkMacOSSurfaceCreateInfoMVK sci; PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); if (!vkCreateMacOSSurfaceMVK) { _glfwInputError(GLFW_API_UNAVAILABLE, "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; sci.pView = window->ns.view; err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); } if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create Vulkan surface: %s", _glfwGetVulkanResultString(err)); } return err; #else return VK_ERROR_EXTENSION_NOT_PRESENT; #endif } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); return window->ns.object; } GLFWAPI void glfwHideCocoaTitlebar(GLFWwindow* handle, bool yes) { @autoreleasepool { _GLFWwindow* w = (_GLFWwindow*) handle; NSWindow *window = w->ns.object; w->ns.titlebar_hidden = yes; NSButton *button; button = [window standardWindowButton: NSWindowCloseButton]; if (button) button.hidden = yes; button = [window standardWindowButton: NSWindowMiniaturizeButton]; if (button) button.hidden = yes; button = [window standardWindowButton: NSWindowZoomButton]; [window setTitlebarAppearsTransparent:yes]; if (button) button.hidden = yes; if (yes) { [window setTitleVisibility:NSWindowTitleHidden]; [window setStyleMask: [window styleMask] | NSWindowStyleMaskFullSizeContentView]; } else { [window setTitleVisibility:NSWindowTitleVisible]; [window setStyleMask: [window styleMask] & ~NSWindowStyleMaskFullSizeContentView]; } } // autoreleasepool } GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWcocoatextinputfilterfun previous = window->ns.textInputFilterCallback; window->ns.textInputFilterCallback = callback; return previous; } GLFWAPI GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback) { _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWhandlefileopen prev = _glfw.ns.file_open_callback; _glfw.ns.file_open_callback = callback; return prev; } GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback; window->ns.toggleFullscreenCallback = callback; return previous; } GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) { requestRenderFrame((_GLFWwindow*)w, callback); } GLFWAPI uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) { *cocoa_mods = 0; if (glfw_mods & GLFW_MOD_SHIFT) *cocoa_mods |= NSEventModifierFlagShift; if (glfw_mods & GLFW_MOD_CONTROL) *cocoa_mods |= NSEventModifierFlagControl; if (glfw_mods & GLFW_MOD_ALT) *cocoa_mods |= NSEventModifierFlagOption; if (glfw_mods & GLFW_MOD_SUPER) *cocoa_mods |= NSEventModifierFlagCommand; if (glfw_mods & GLFW_MOD_CAPS_LOCK) *cocoa_mods |= NSEventModifierFlagCapsLock; return _glfwPlatformGetNativeKeyForKey(glfw_key); } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Transforms a y-coordinate between the CG display and NS screen spaces // float _glfwTransformYNS(float y) { return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; } void _glfwCocoaPostEmptyEvent(void) { NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]; [NSApp postEvent:event atStart:YES]; }