The constants `NSControlStateValueOn`, `NSControlStateValueOff` and `NSControlStateValueMixed` are only available for macOS 10.13 or above according to https://developer.apple.com/documentation/appkit/nscontrolstatevalueon?language=objc. Without this commit, compilation fails with this message: ``` glfw/cocoa_window.m:1537:28: error: use of undeclared identifier 'NSControlStateValueOn' item.state = NSControlStateValueOn; ^ glfw/cocoa_window.m:1539:28: error: use of undeclared identifier 'NSControlStateValueMixed' item.state = NSControlStateValueMixed; ^ glfw/cocoa_window.m:1542:47: error: use of undeclared identifier 'NSControlStateValueOn' item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff; ^ glfw/cocoa_window.m:1542:71: error: use of undeclared identifier 'NSControlStateValueOff' item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff; ^ ``` To fix this, simply redefine the constants to use the old and now deprecated constants on older macOS versions. The code that causes this was introduced in 98519bf326834a126e89489a6a221ff21fab100f.
2700 lines
88 KiB
Objective-C
2700 lines
88 KiB
Objective-C
//========================================================================
|
|
// GLFW 3.4 macOS - www.glfw.org
|
|
//------------------------------------------------------------------------
|
|
// Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
|
|
//
|
|
// 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 <float.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|
|
}
|
|
|
|
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 (!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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 "<none>";
|
|
while (*src) {
|
|
int num = snprintf(p, sizeof(buf) - (p - buf), "0x%x ", (unsigned char)*(src++));
|
|
if (num < 0) return "<error>";
|
|
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 "<noname>";
|
|
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
|
|
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 <NSTextInputClient>
|
|
{
|
|
_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 <NSDraggingInfo>)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 <NSDraggingInfo>)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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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];
|
|
}
|