2017-11-19 20:54:36 +03:00
|
|
|
//========================================================================
|
2019-07-18 20:08:14 +03:00
|
|
|
// GLFW 3.4 macOS - www.glfw.org
|
2017-11-19 20:54:36 +03:00
|
|
|
//------------------------------------------------------------------------
|
2019-07-18 20:11:34 +03:00
|
|
|
// Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
|
2017-11-19 20:54:36 +03:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
//========================================================================
|
2019-07-18 20:52:52 +03:00
|
|
|
// It is fine to use C99 in this file because it will not be built with VS
|
|
|
|
//========================================================================
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
#include "internal.h"
|
2019-08-02 19:49:02 +03:00
|
|
|
#include "../kitty/monotonic.h"
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2022-08-20 19:08:28 +03:00
|
|
|
#include <Availability.h>
|
2022-09-08 19:15:13 +03:00
|
|
|
#import <CoreServices/CoreServices.h>
|
|
|
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
2017-11-19 20:54:36 +03:00
|
|
|
#include <float.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2021-04-21 17:23:42 +03:00
|
|
|
|
2021-11-11 20:02:30 +03:00
|
|
|
static const char*
|
|
|
|
polymorphic_string_as_utf8(id string) {
|
|
|
|
if (string == nil) return "(nil)";
|
|
|
|
NSString* characters;
|
|
|
|
if ([string isKindOfClass:[NSAttributedString class]])
|
|
|
|
characters = [string string];
|
|
|
|
else
|
|
|
|
characters = (NSString*) string;
|
|
|
|
return [characters UTF8String];
|
|
|
|
}
|
2019-09-01 12:45:40 +03:00
|
|
|
|
2021-01-16 10:49:44 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
} // }}}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static bool
|
2021-01-16 10:49:44 +03:00
|
|
|
is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; }
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static uint32_t
|
2021-01-16 10:49:44 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static bool
|
2021-01-16 10:49:44 +03:00
|
|
|
is_pua_char(uint32_t ch) {
|
2021-01-16 16:01:51 +03:00
|
|
|
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
2021-01-16 10:49:44 +03:00
|
|
|
}
|
|
|
|
|
2022-01-15 07:44:06 +03:00
|
|
|
uint32_t
|
2021-01-16 10:49:44 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-09-01 12:45:40 +03:00
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
// Returns the style mask corresponding to the window settings
|
|
|
|
//
|
|
|
|
static NSUInteger getStyleMask(_GLFWwindow* window)
|
|
|
|
{
|
2019-11-21 20:34:28 +03:00
|
|
|
NSUInteger styleMask = NSWindowStyleMaskMiniaturizable;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (window->monitor || !window->decorated)
|
|
|
|
styleMask |= NSWindowStyleMaskBorderless;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
styleMask |= NSWindowStyleMaskTitled |
|
2019-11-21 20:34:28 +03:00
|
|
|
NSWindowStyleMaskClosable;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (window->resizable)
|
|
|
|
styleMask |= NSWindowStyleMaskResizable;
|
|
|
|
}
|
|
|
|
|
|
|
|
return styleMask;
|
|
|
|
}
|
|
|
|
|
2019-05-28 15:24:34 +03:00
|
|
|
|
|
|
|
CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) {
|
|
|
|
NSWindow *nw = w->ns.object;
|
|
|
|
NSDictionary *dict = [nw.screen deviceDescription];
|
2019-07-26 00:14:56 +03:00
|
|
|
NSNumber *displayIDns = dict[@"NSScreenNumber"];
|
2019-05-28 15:24:34 +03:00
|
|
|
if (displayIDns) return [displayIDns unsignedIntValue];
|
|
|
|
return (CGDirectDisplayID)-1;
|
|
|
|
}
|
|
|
|
|
2019-06-29 11:51:47 +03:00
|
|
|
static unsigned long long display_link_shutdown_timer = 0;
|
2019-08-02 19:49:02 +03:00
|
|
|
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
|
2019-06-29 11:51:47 +03:00
|
|
|
|
|
|
|
void
|
2019-07-01 08:12:07 +03:00
|
|
|
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
|
2019-06-29 11:51:47 +03:00
|
|
|
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);
|
2019-07-05 17:46:32 +03:00
|
|
|
dl->lastRenderFrameRequestedAt = 0;
|
2021-05-12 04:59:51 +03:00
|
|
|
dl->first_unserviced_render_frame_request_at = 0;
|
2019-06-29 11:51:47 +03:00
|
|
|
}
|
|
|
|
}
|
2019-05-28 15:24:34 +03:00
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static void
|
2019-05-28 15:24:34 +03:00
|
|
|
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
|
|
|
|
if (!callback) {
|
2019-06-08 05:44:30 +03:00
|
|
|
w->ns.renderFrameRequested = false;
|
2019-05-28 15:24:34 +03:00
|
|
|
w->ns.renderFrameCallback = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
w->ns.renderFrameCallback = callback;
|
2019-06-08 05:44:30 +03:00
|
|
|
w->ns.renderFrameRequested = true;
|
2019-05-28 15:24:34 +03:00
|
|
|
CGDirectDisplayID displayID = displayIDForWindow(w);
|
2019-06-29 11:51:47 +03:00
|
|
|
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);
|
|
|
|
}
|
2019-08-02 19:49:02 +03:00
|
|
|
monotonic_t now = glfwGetTime();
|
2021-08-01 08:40:46 +03:00
|
|
|
bool found_display_link = false;
|
|
|
|
_GLFWDisplayLinkNS *dl = NULL;
|
2019-05-28 15:24:34 +03:00
|
|
|
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
|
2021-08-01 08:40:46 +03:00
|
|
|
dl = &_glfw.ns.displayLinks.entries[i];
|
2019-05-28 15:24:34 +03:00
|
|
|
if (dl->displayID == displayID) {
|
2021-08-01 08:40:46 +03:00
|
|
|
found_display_link = true;
|
2019-07-05 17:46:32 +03:00
|
|
|
dl->lastRenderFrameRequestedAt = now;
|
2021-05-12 04:59:51 +03:00
|
|
|
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
|
2019-07-11 20:04:28 +03:00
|
|
|
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
2021-05-12 04:59:51 +03:00
|
|
|
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);
|
|
|
|
}
|
2019-07-11 20:04:28 +03:00
|
|
|
} else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
|
|
|
|
CVDisplayLinkStop(dl->displayLink);
|
|
|
|
dl->lastRenderFrameRequestedAt = 0;
|
2021-05-12 04:59:51 +03:00
|
|
|
dl->first_unserviced_render_frame_request_at = 0;
|
2019-05-28 15:24:34 +03:00
|
|
|
}
|
|
|
|
}
|
2021-08-01 08:40:46 +03:00
|
|
|
if (!found_display_link) {
|
|
|
|
dl = _glfw_create_display_link(displayID);
|
|
|
|
if (dl) {
|
|
|
|
dl->lastRenderFrameRequestedAt = now;
|
2021-08-01 10:29:22 +03:00
|
|
|
dl->first_unserviced_render_frame_request_at = now;
|
2021-08-01 08:40:46 +03:00
|
|
|
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 15:24:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-03 17:34:54 +03:00
|
|
|
// Returns whether the cursor is in the content area of the specified window
|
2017-11-19 20:54:36 +03:00
|
|
|
//
|
2019-06-08 05:42:42 +03:00
|
|
|
static bool cursorInContentArea(_GLFWwindow* window)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
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
|
|
|
|
//
|
2019-07-01 08:12:07 +03:00
|
|
|
static void hideCursor(_GLFWwindow* window UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
if (!_glfw.ns.cursorHidden)
|
|
|
|
{
|
|
|
|
[NSCursor hide];
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfw.ns.cursorHidden = true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shows the cursor if not already shown
|
|
|
|
//
|
2019-07-01 08:12:07 +03:00
|
|
|
static void showCursor(_GLFWwindow* window UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
if (_glfw.ns.cursorHidden)
|
|
|
|
{
|
|
|
|
[NSCursor unhide];
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfw.ns.cursorHidden = false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2018-04-26 06:01:52 +03:00
|
|
|
// 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);
|
2019-02-03 17:34:54 +03:00
|
|
|
_glfwCenterCursorInContentArea(window);
|
2018-04-26 06:01:52 +03:00
|
|
|
CGAssociateMouseAndMouseCursorPosition(false);
|
|
|
|
}
|
|
|
|
else if (_glfw.ns.disabledCursorWindow == window)
|
|
|
|
{
|
|
|
|
_glfw.ns.disabledCursorWindow = NULL;
|
|
|
|
CGAssociateMouseAndMouseCursorPosition(true);
|
|
|
|
_glfwPlatformSetCursorPos(window,
|
|
|
|
_glfw.ns.restoreCursorPosX,
|
|
|
|
_glfw.ns.restoreCursorPosY);
|
|
|
|
}
|
|
|
|
|
2019-02-03 17:34:54 +03:00
|
|
|
if (cursorInContentArea(window))
|
2018-04-26 06:01:52 +03:00
|
|
|
updateCursorImage(window);
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
// Make the specified window and its video mode active on its monitor
|
|
|
|
//
|
2018-02-08 06:45:40 +03:00
|
|
|
static void acquireMonitor(_GLFWwindow* window)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2018-02-08 06:45:40 +03:00
|
|
|
_glfwSetVideoModeNS(window->monitor, &window->videoMode);
|
2017-11-19 20:54:36 +03:00
|
|
|
const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID);
|
|
|
|
const NSRect frame = NSMakeRect(bounds.origin.x,
|
2019-04-18 07:42:41 +03:00
|
|
|
_glfwTransformYNS(bounds.origin.y + bounds.size.height - 1),
|
2017-11-19 20:54:36 +03:00
|
|
|
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
|
|
|
|
//
|
2021-04-22 06:58:11 +03:00
|
|
|
static int
|
|
|
|
translateFlags(NSUInteger flags)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
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;
|
2017-12-20 06:37:06 +03:00
|
|
|
if (flags & NSEventModifierFlagCapsLock)
|
|
|
|
mods |= GLFW_MOD_CAPS_LOCK;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
return mods;
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static const char*
|
2018-03-30 11:47:39 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static const char*
|
2018-03-30 11:47:39 +03:00
|
|
|
format_text(const char *src) {
|
|
|
|
static char buf[256];
|
|
|
|
char *p = buf;
|
2019-08-28 03:11:20 +03:00
|
|
|
const char *last_char = buf + sizeof(buf) - 1;
|
2018-03-30 11:47:39 +03:00
|
|
|
if (!src[0]) return "<none>";
|
|
|
|
while (*src) {
|
2019-08-28 03:11:20 +03:00
|
|
|
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;
|
2018-03-30 11:47:39 +03:00
|
|
|
}
|
|
|
|
if (p != buf) *(--p) = 0;
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char*
|
2019-10-17 21:41:48 +03:00
|
|
|
safe_name_for_keycode(unsigned int keycode) {
|
|
|
|
const char *ans = _glfwPlatformGetNativeKeyName(keycode);
|
2018-03-30 11:47:39 +03:00
|
|
|
if (!ans) return "<noname>";
|
|
|
|
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
// Translates a macOS keycode to a GLFW keycode
|
|
|
|
//
|
2021-01-16 10:49:44 +03:00
|
|
|
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;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2021-11-13 11:10:24 +03:00
|
|
|
static NSRect
|
|
|
|
get_window_size_without_border_in_logical_pixels(_GLFWwindow *window) {
|
|
|
|
return [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
// Defines a constant for empty ranges in NSTextInputClient
|
|
|
|
//
|
|
|
|
static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|
|
|
|
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
// Delegate for window related notifications {{{
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
@interface GLFWWindowDelegate : NSObject
|
|
|
|
{
|
|
|
|
_GLFWwindow* window;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
2022-02-18 11:14:25 +03:00
|
|
|
- (void)request_delayed_cursor_update:(id)sender;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GLFWWindowDelegate
|
|
|
|
|
|
|
|
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil)
|
|
|
|
window = initWindow;
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)windowShouldClose:(id)sender
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)sender;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputWindowCloseRequest(window);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (window->context.client != GLFW_NO_API)
|
|
|
|
[window->context.nsgl.object update];
|
|
|
|
|
|
|
|
if (_glfw.ns.disabledCursorWindow == window)
|
2019-02-03 17:34:54 +03:00
|
|
|
_glfwCenterCursorInContentArea(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
const int maximized = [window->ns.object isZoomed];
|
|
|
|
if (window->ns.maximized != maximized)
|
|
|
|
{
|
|
|
|
window->ns.maximized = maximized;
|
|
|
|
_glfwInputWindowMaximize(window, maximized);
|
|
|
|
}
|
|
|
|
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
|
|
|
|
|
|
|
|
if (fbRect.size.width != window->ns.fbWidth ||
|
|
|
|
fbRect.size.height != window->ns.fbHeight)
|
|
|
|
{
|
2019-09-27 17:26:48 +03:00
|
|
|
window->ns.fbWidth = (int)fbRect.size.width;
|
|
|
|
window->ns.fbHeight = (int)fbRect.size.height;
|
|
|
|
_glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (contentRect.size.width != window->ns.width ||
|
|
|
|
contentRect.size.height != window->ns.height)
|
|
|
|
{
|
2019-09-27 17:26:48 +03:00
|
|
|
window->ns.width = (int)contentRect.size.width;
|
|
|
|
window->ns.height = (int)contentRect.size.height;
|
|
|
|
_glfwInputWindowSize(window, (int)contentRect.size.width, (int)contentRect.size.height);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidMove:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (window->context.client != GLFW_NO_API)
|
|
|
|
[window->context.nsgl.object update];
|
|
|
|
|
|
|
|
if (_glfw.ns.disabledCursorWindow == window)
|
2019-02-03 17:34:54 +03:00
|
|
|
_glfwCenterCursorInContentArea(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
int x, y;
|
|
|
|
_glfwPlatformGetWindowPos(window, &x, &y);
|
|
|
|
_glfwInputWindowPos(window, x, y);
|
|
|
|
}
|
|
|
|
|
2019-02-18 07:04:26 +03:00
|
|
|
- (void)windowDidChangeOcclusionState:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2019-02-18 07:04:26 +03:00
|
|
|
_glfwInputWindowOcclusion(window, !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible));
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (void)windowDidMiniaturize:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (window->monitor)
|
|
|
|
releaseMonitor(window);
|
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputWindowIconify(window, true);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidDeminiaturize:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (window->monitor)
|
|
|
|
acquireMonitor(window);
|
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputWindowIconify(window, false);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidBecomeKey:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (_glfw.ns.disabledCursorWindow == window)
|
2019-02-03 17:34:54 +03:00
|
|
|
_glfwCenterCursorInContentArea(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputWindowFocus(window, true);
|
2018-04-26 06:01:52 +03:00
|
|
|
updateCursorMode(window);
|
2019-02-03 06:28:16 +03:00
|
|
|
if (window->cursorMode == GLFW_CURSOR_HIDDEN) hideCursor(window);
|
2019-02-03 17:34:54 +03:00
|
|
|
if (_glfw.ns.disabledCursorWindow != window && cursorInContentArea(window))
|
2018-10-31 06:08:50 +03:00
|
|
|
{
|
|
|
|
double x = 0, y = 0;
|
|
|
|
_glfwPlatformGetCursorPos(window, &x, &y);
|
|
|
|
_glfwInputCursorPos(window, x, y);
|
|
|
|
}
|
2022-02-18 08:52:19 +03:00
|
|
|
// macOS will send a delayed event to update the cursor to arrow after switching desktops.
|
|
|
|
// So we need to delay and update the cursor once after that.
|
2022-02-18 11:14:25 +03:00
|
|
|
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidResignKey:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (window->monitor && window->autoIconify)
|
|
|
|
_glfwPlatformIconifyWindow(window);
|
2019-02-03 06:28:16 +03:00
|
|
|
showCursor(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputWindowFocus(window, false);
|
2021-11-28 18:09:35 +03:00
|
|
|
// IME is cancelled when losing the focus
|
|
|
|
if ([window->ns.view hasMarkedText]) {
|
2022-01-26 13:21:20 +03:00
|
|
|
[[window->ns.view inputContext] discardMarkedText];
|
2021-11-28 18:09:35 +03:00
|
|
|
[window->ns.view unmarkText];
|
|
|
|
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
|
|
|
_glfwInputKeyboard(window, &dummy);
|
2022-01-27 07:37:17 +03:00
|
|
|
_glfw.ns.text[0] = 0;
|
2021-11-28 18:09:35 +03:00
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-05-28 15:24:34 +03:00
|
|
|
- (void)windowDidChangeScreen:(NSNotification *)notification
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)notification;
|
2019-05-28 15:24:34 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-18 11:14:25 +03:00
|
|
|
- (void)request_delayed_cursor_update:(id)sender
|
2022-02-18 08:52:19 +03:00
|
|
|
{
|
|
|
|
(void)sender;
|
2022-02-18 11:14:25 +03:00
|
|
|
if (window) window->ns.delayed_cursor_update_requested = true;
|
2022-02-18 08:52:19 +03:00
|
|
|
}
|
2022-02-18 08:50:46 +03:00
|
|
|
|
|
|
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
(void)notification;
|
|
|
|
if (window) window->ns.in_fullscreen_transition = true;
|
|
|
|
}
|
|
|
|
|
2022-02-17 10:47:03 +03:00
|
|
|
- (void)windowDidEnterFullScreen:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
(void)notification;
|
2022-02-18 08:50:46 +03:00
|
|
|
if (window) window->ns.in_fullscreen_transition = false;
|
2022-02-18 11:14:25 +03:00
|
|
|
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
2022-02-18 08:50:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowWillExitFullScreen:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
(void)notification;
|
|
|
|
if (window) window->ns.in_fullscreen_transition = true;
|
2022-02-17 10:47:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)windowDidExitFullScreen:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
(void)notification;
|
2022-02-18 08:50:46 +03:00
|
|
|
if (window) window->ns.in_fullscreen_transition = false;
|
2022-02-18 11:14:25 +03:00
|
|
|
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
2022-02-17 10:47:03 +03:00
|
|
|
}
|
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
@end // }}}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2022-01-13 16:49:52 +03:00
|
|
|
// Text input context class for the GLFW content view {{{
|
|
|
|
|
|
|
|
@interface GLFWTextInputContext : NSTextInputContext
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GLFWTextInputContext
|
|
|
|
- (void)doCommandBySelector:(SEL)selector
|
|
|
|
{
|
|
|
|
// interpretKeyEvents: May call insertText: or doCommandBySelector:.
|
2022-01-15 07:44:06 +03:00
|
|
|
// With the default macOS keybindings, pressing certain key combinations
|
2022-01-13 16:49:52 +03:00
|
|
|
// (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound.
|
|
|
|
debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
|
|
|
}
|
|
|
|
@end // }}}
|
2019-10-08 00:49:46 +03:00
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
// Content view class for the GLFW window {{{
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
@interface GLFWContentView : NSView <NSTextInputClient>
|
|
|
|
{
|
|
|
|
_GLFWwindow* window;
|
|
|
|
NSTrackingArea* trackingArea;
|
2022-01-13 16:49:52 +03:00
|
|
|
GLFWTextInputContext* input_context;
|
2017-11-19 20:54:36 +03:00
|
|
|
NSMutableAttributedString* markedText;
|
2019-05-02 10:01:40 +03:00
|
|
|
NSRect markedRect;
|
2021-11-12 14:57:37 +03:00
|
|
|
bool marked_text_cleared_by_insert;
|
2022-01-22 13:21:43 +03:00
|
|
|
int in_key_handler;
|
2020-02-17 13:00:03 +03:00
|
|
|
NSString *input_source_at_last_key_event;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
- (void) removeGLFWWindow;
|
2017-11-19 20:54:36 +03:00
|
|
|
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GLFWContentView
|
|
|
|
|
|
|
|
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil)
|
|
|
|
{
|
|
|
|
window = initWindow;
|
|
|
|
trackingArea = nil;
|
2022-01-13 16:49:52 +03:00
|
|
|
input_context = [[GLFWTextInputContext alloc] initWithClient:self];
|
2017-11-19 20:54:36 +03:00
|
|
|
markedText = [[NSMutableAttributedString alloc] init];
|
2019-05-02 10:01:40 +03:00
|
|
|
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
|
2020-02-17 13:00:03 +03:00
|
|
|
input_source_at_last_key_event = nil;
|
2022-01-22 13:21:43 +03:00
|
|
|
in_key_handler = 0;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
[self updateTrackingAreas];
|
2019-08-23 22:27:00 +03:00
|
|
|
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[trackingArea release];
|
|
|
|
[markedText release];
|
2020-02-17 13:00:03 +03:00
|
|
|
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
|
2022-01-14 09:18:03 +03:00
|
|
|
[input_context release];
|
2017-11-19 20:54:36 +03:00
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
2019-07-04 06:33:13 +03:00
|
|
|
- (void) removeGLFWWindow
|
|
|
|
{
|
|
|
|
window = NULL;
|
|
|
|
}
|
|
|
|
|
2018-09-12 13:15:08 +03:00
|
|
|
- (_GLFWwindow*)glfwWindow {
|
|
|
|
return window;
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (BOOL)isOpaque
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
return window && [window->ns.object isOpaque];
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)canBecomeKeyView
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2019-02-28 12:45:31 +03:00
|
|
|
- (void) viewWillStartLiveResize
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-02-28 12:45:31 +03:00
|
|
|
_glfwInputLiveResize(window, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidEndLiveResize
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-02-28 12:45:31 +03:00
|
|
|
_glfwInputLiveResize(window, false);
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (BOOL)wantsUpdateLayer
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2018-10-26 19:59:05 +03:00
|
|
|
- (void)updateLayer
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2018-11-22 15:55:22 +03:00
|
|
|
if (window->context.client != GLFW_NO_API) {
|
|
|
|
@try {
|
|
|
|
[window->context.nsgl.object update];
|
|
|
|
} @catch (NSException *e) {
|
|
|
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
2019-08-24 02:37:27 +03:00
|
|
|
"Failed to update NSGL Context object with error: %s (%s)",
|
|
|
|
[[e name] UTF8String], [[e reason] UTF8String]);
|
2018-11-22 15:55:22 +03:00
|
|
|
}
|
|
|
|
}
|
2018-10-26 19:59:05 +03:00
|
|
|
|
|
|
|
_glfwInputWindowDamage(window);
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (void)cursorUpdate:(NSEvent *)event
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)event;
|
2019-07-04 06:33:13 +03:00
|
|
|
if (window) updateCursorImage(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2018-12-26 07:28:12 +03:00
|
|
|
- (BOOL)acceptsFirstMouse:(NSEvent *)event
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)event;
|
2018-12-26 07:28:12 +03:00
|
|
|
return NO; // changed by Kovid, to follow cocoa platform conventions
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (void)mouseDown:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-07-18 21:21:07 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
GLFW_MOUSE_BUTTON_LEFT,
|
|
|
|
GLFW_PRESS,
|
|
|
|
translateFlags([event modifierFlags]));
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseDragged:(NSEvent *)event
|
|
|
|
{
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseUp:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-07-18 21:21:07 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
GLFW_MOUSE_BUTTON_LEFT,
|
|
|
|
GLFW_RELEASE,
|
|
|
|
translateFlags([event modifierFlags]));
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseMoved:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
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];
|
2019-04-18 07:42:41 +03:00
|
|
|
// NOTE: The returned location uses base 0,1 not 0,0
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSPoint pos = [event locationInWindow];
|
|
|
|
|
2019-05-21 20:24:00 +03:00
|
|
|
_glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
window->ns.cursorWarpDeltaX = 0;
|
|
|
|
window->ns.cursorWarpDeltaY = 0;
|
2022-02-18 08:52:19 +03:00
|
|
|
|
2022-02-18 11:14:25 +03:00
|
|
|
if (window->ns.delayed_cursor_update_requested) {
|
|
|
|
window->ns.delayed_cursor_update_requested = false;
|
2022-02-18 08:52:19 +03:00
|
|
|
if (cursorInContentArea(window)) updateCursorImage(window);
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseDown:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
GLFW_MOUSE_BUTTON_RIGHT,
|
|
|
|
GLFW_PRESS,
|
|
|
|
translateFlags([event modifierFlags]));
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseDragged:(NSEvent *)event
|
|
|
|
{
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)rightMouseUp:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
GLFW_MOUSE_BUTTON_RIGHT,
|
|
|
|
GLFW_RELEASE,
|
|
|
|
translateFlags([event modifierFlags]));
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)otherMouseDown:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
(int) [event buttonNumber],
|
|
|
|
GLFW_PRESS,
|
|
|
|
translateFlags([event modifierFlags]));
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)otherMouseDragged:(NSEvent *)event
|
|
|
|
{
|
|
|
|
[self mouseMoved:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)otherMouseUp:(NSEvent *)event
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputMouseClick(window,
|
|
|
|
(int) [event buttonNumber],
|
|
|
|
GLFW_RELEASE,
|
|
|
|
translateFlags([event modifierFlags]));
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseExited:(NSEvent *)event
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)event;
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputCursorEnter(window, false);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)mouseEntered:(NSEvent *)event
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)event;
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2019-06-08 05:44:30 +03:00
|
|
|
_glfwInputCursorEnter(window, true);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidChangeBackingProperties
|
|
|
|
{
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
|
|
|
|
|
|
|
|
if (fbRect.size.width != window->ns.fbWidth ||
|
|
|
|
fbRect.size.height != window->ns.fbHeight)
|
|
|
|
{
|
2019-09-27 17:26:48 +03:00
|
|
|
window->ns.fbWidth = (int)fbRect.size.width;
|
|
|
|
window->ns.fbHeight = (int)fbRect.size.height;
|
|
|
|
_glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
2018-01-12 03:04:53 +03:00
|
|
|
|
|
|
|
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);
|
2018-03-27 12:37:25 +03:00
|
|
|
|
2019-03-28 10:39:22 +03:00
|
|
|
if (window->ns.retina && window->ns.layer)
|
2018-03-27 12:37:25 +03:00
|
|
|
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
|
2018-01-12 03:04:53 +03:00
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawRect:(NSRect)rect
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)rect;
|
2019-07-04 06:33:13 +03:00
|
|
|
if (!window) return;
|
2017-11-19 20:54:36 +03:00
|
|
|
_glfwInputWindowDamage(window);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateTrackingAreas
|
|
|
|
{
|
2022-02-18 08:52:00 +03:00
|
|
|
if (window && [window->ns.object areCursorRectsEnabled])
|
|
|
|
[window->ns.object disableCursorRects];
|
2017-11-19 20:54:36 +03:00
|
|
|
if (trackingArea != nil)
|
|
|
|
{
|
|
|
|
[self removeTrackingArea:trackingArea];
|
|
|
|
[trackingArea release];
|
|
|
|
}
|
|
|
|
|
|
|
|
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
|
2018-07-30 18:56:20 +03:00
|
|
|
NSTrackingActiveAlways |
|
2017-11-19 20:54:36 +03:00
|
|
|
NSTrackingEnabledDuringMouseDrag |
|
|
|
|
NSTrackingCursorUpdate |
|
|
|
|
NSTrackingInVisibleRect |
|
|
|
|
NSTrackingAssumeInside;
|
|
|
|
|
|
|
|
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
|
|
|
options:options
|
|
|
|
owner:self
|
|
|
|
userInfo:nil];
|
|
|
|
|
|
|
|
[self addTrackingArea:trackingArea];
|
|
|
|
[super updateTrackingAreas];
|
|
|
|
}
|
|
|
|
|
2022-01-13 16:49:52 +03:00
|
|
|
- (NSTextInputContext *)inputContext
|
|
|
|
{
|
|
|
|
return input_context;
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static UInt32
|
2018-04-20 11:55:53 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static void
|
2018-04-20 11:55:53 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static bool
|
2021-01-16 16:01:51 +03:00
|
|
|
alternate_key_is_ok(uint32_t key, uint32_t akey) {
|
|
|
|
return akey > 31 && akey != key && !is_pua_char(akey);
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static void
|
2021-01-16 16:01:51 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:41:17 +03:00
|
|
|
static bool
|
2018-04-20 11:55:53 +03:00
|
|
|
is_ascii_control_char(char x) {
|
|
|
|
return x == 0 || (1 <= x && x <= 31) || x == 127;
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (void)keyDown:(NSEvent *)event
|
|
|
|
{
|
2021-11-12 14:57:37 +03:00
|
|
|
#define CLEAR_PRE_EDIT_TEXT glfw_keyevent.text = NULL; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
#define UPDATE_PRE_EDIT_TEXT glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
|
2020-02-17 13:00:03 +03:00
|
|
|
const bool previous_has_marked_text = [self hasMarkedText];
|
2022-01-26 13:25:37 +03:00
|
|
|
if (input_context && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:input_context.selectedKeyboardInputSource])) {
|
2021-10-11 09:37:35 +03:00
|
|
|
if (input_source_at_last_key_event) {
|
|
|
|
debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n");
|
|
|
|
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
|
|
|
window->ns.deadKeyState = 0;
|
|
|
|
_glfwInputKeyboard(window, &dummy); // clear pre-edit text
|
2022-01-19 09:36:54 +03:00
|
|
|
[input_source_at_last_key_event release];
|
|
|
|
input_source_at_last_key_event = nil;
|
2021-10-11 09:37:35 +03:00
|
|
|
}
|
2022-01-26 13:25:37 +03:00
|
|
|
input_source_at_last_key_event = [input_context.selectedKeyboardInputSource retain];
|
2021-11-11 18:44:53 +03:00
|
|
|
[self unmarkText];
|
2020-02-17 13:00:03 +03:00
|
|
|
}
|
|
|
|
|
2019-10-17 21:41:48 +03:00
|
|
|
const unsigned int keycode = [event keyCode];
|
2018-04-20 11:55:53 +03:00
|
|
|
const NSUInteger flags = [event modifierFlags];
|
|
|
|
const int mods = translateFlags(flags);
|
2021-01-16 16:01:51 +03:00
|
|
|
const uint32_t key = translateKey(keycode, true);
|
2022-01-15 11:26:18 +03:00
|
|
|
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1);
|
2018-03-30 11:47:39 +03:00
|
|
|
_glfw.ns.text[0] = 0;
|
2021-11-12 06:22:32 +03:00
|
|
|
if (keycode == 0x33 /* backspace */ || keycode == 0x35 /* escape */) [self unmarkText];
|
2021-11-29 16:56:16 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_PRESS, .mods = mods};
|
2018-04-20 11:55:53 +03:00
|
|
|
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
|
2022-01-10 18:45:24 +03:00
|
|
|
[self interpretKeyEvents:@[event]];
|
2018-04-20 11:55:53 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
static UniChar text[256];
|
|
|
|
UniCharCount char_count = 0;
|
2019-05-03 11:00:56 +03:00
|
|
|
const bool in_compose_sequence = window->ns.deadKeyState != 0;
|
2018-04-20 11:55:53 +03:00
|
|
|
if (UCKeyTranslate(
|
|
|
|
[(NSData*) _glfw.ns.unicodeData bytes],
|
2019-10-17 21:41:48 +03:00
|
|
|
keycode,
|
2018-04-20 11:55:53 +03:00
|
|
|
kUCKeyActionDown,
|
|
|
|
convert_cocoa_to_carbon_modifiers(flags),
|
|
|
|
LMGetKbdType(),
|
|
|
|
(process_text ? 0 : kUCKeyTranslateNoDeadKeysMask),
|
|
|
|
&(window->ns.deadKeyState),
|
|
|
|
sizeof(text)/sizeof(text[0]),
|
|
|
|
&char_count,
|
|
|
|
text
|
|
|
|
) != noErr) {
|
2021-04-22 06:58:11 +03:00
|
|
|
debug_key("UCKeyTranslate failed for keycode: 0x%x (%s) %s\n",
|
|
|
|
keycode, safe_name_for_keycode(keycode), format_mods(mods));
|
2018-04-20 11:55:53 +03:00
|
|
|
window->ns.deadKeyState = 0;
|
|
|
|
return;
|
|
|
|
}
|
2021-04-22 06:58:11 +03:00
|
|
|
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);
|
2021-11-12 14:57:37 +03:00
|
|
|
marked_text_cleared_by_insert = false;
|
2018-04-20 11:55:53 +03:00
|
|
|
if (process_text) {
|
2022-01-22 20:50:37 +03:00
|
|
|
in_key_handler = 1;
|
2018-10-21 17:57:03 +03:00
|
|
|
// this will call insertText which will fill up _glfw.ns.text
|
2022-01-10 18:45:24 +03:00
|
|
|
[self interpretKeyEvents:@[event]];
|
2022-01-22 20:50:37 +03:00
|
|
|
in_key_handler = 0;
|
2018-04-20 11:55:53 +03:00
|
|
|
} else {
|
|
|
|
window->ns.deadKeyState = 0;
|
|
|
|
}
|
2019-10-17 21:41:48 +03:00
|
|
|
if (window->ns.deadKeyState && (char_count == 0 || keycode == 0x75)) {
|
2018-10-21 17:57:03 +03:00
|
|
|
// 0x75 is the delete key which needs to be ignored during a compose sequence
|
2021-04-22 06:58:11 +03:00
|
|
|
debug_key("Sending pre-edit text for dead key (text: %s markedText: %s).\n", format_text(_glfw.ns.text), glfw_keyevent.text);
|
2021-11-12 14:57:37 +03:00
|
|
|
UPDATE_PRE_EDIT_TEXT;
|
2018-04-20 11:55:53 +03:00
|
|
|
return;
|
2019-05-03 11:07:57 +03:00
|
|
|
}
|
|
|
|
if (in_compose_sequence) {
|
2021-04-22 06:58:11 +03:00
|
|
|
debug_key("Clearing pre-edit text at end of compose sequence\n");
|
2021-11-12 14:57:37 +03:00
|
|
|
CLEAR_PRE_EDIT_TEXT;
|
2018-04-20 11:55:53 +03:00
|
|
|
}
|
2018-04-19 15:14:31 +03:00
|
|
|
}
|
2018-08-29 04:18:25 +03:00
|
|
|
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
|
2021-11-12 12:40:33 +03:00
|
|
|
debug_key("text: %s glfw_key: %s marked_text: (%s)\n",
|
|
|
|
format_text(_glfw.ns.text), _glfwGetKeyName(key), [[markedText string] UTF8String]);
|
2021-11-12 14:57:37 +03:00
|
|
|
bool bracketed_ime = false;
|
2019-05-02 10:01:40 +03:00
|
|
|
if (!window->ns.deadKeyState) {
|
|
|
|
if ([self hasMarkedText]) {
|
2021-11-12 14:57:37 +03:00
|
|
|
if (!marked_text_cleared_by_insert) {
|
|
|
|
UPDATE_PRE_EDIT_TEXT;
|
|
|
|
} else bracketed_ime = true;
|
2019-05-02 10:01:40 +03:00
|
|
|
} else if (previous_has_marked_text) {
|
2021-11-12 14:57:37 +03:00
|
|
|
CLEAR_PRE_EDIT_TEXT;
|
2019-05-02 10:01:40 +03:00
|
|
|
}
|
|
|
|
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
|
2022-01-19 12:20:12 +03:00
|
|
|
debug_key("Ignoring key press as IME is active and it generated no text\n");
|
2019-05-02 10:01:40 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-11-12 14:57:37 +03:00
|
|
|
if (bracketed_ime) {
|
|
|
|
// insertText followed by setMarkedText
|
|
|
|
CLEAR_PRE_EDIT_TEXT;
|
|
|
|
}
|
2019-09-24 22:58:18 +03:00
|
|
|
glfw_keyevent.text = _glfw.ns.text;
|
2021-03-23 08:12:07 +03:00
|
|
|
glfw_keyevent.ime_state = GLFW_IME_NONE;
|
2021-01-16 16:01:51 +03:00
|
|
|
add_alternate_keys(&glfw_keyevent, event);
|
2019-09-24 22:58:18 +03:00
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
2021-11-12 14:57:37 +03:00
|
|
|
if (bracketed_ime) {
|
|
|
|
// insertText followed by setMarkedText
|
|
|
|
UPDATE_PRE_EDIT_TEXT;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)flagsChanged:(NSEvent *)event
|
|
|
|
{
|
2021-01-09 08:44:37 +03:00
|
|
|
int action = GLFW_RELEASE;
|
2022-01-22 12:17:57 +03:00
|
|
|
const char old_first_char = _glfw.ns.text[0];
|
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
const NSUInteger modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
2022-01-13 17:43:55 +03:00
|
|
|
const uint32_t key = vk_code_to_functional_key_code([event keyCode]);
|
2022-01-22 12:17:57 +03:00
|
|
|
const unsigned int keycode = [event keyCode];
|
|
|
|
const int mods = translateFlags(modifierFlags);
|
|
|
|
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1);
|
2022-01-22 12:27:13 +03:00
|
|
|
const char *mod_name = "unknown";
|
2022-01-22 12:17:57 +03:00
|
|
|
|
2022-01-13 17:43:55 +03:00
|
|
|
switch(key) {
|
|
|
|
case GLFW_FKEY_CAPS_LOCK:
|
2022-01-23 13:45:18 +03:00
|
|
|
mod_name = "capslock";
|
2022-01-13 17:43:55 +03:00
|
|
|
action = modifierFlags & NSEventModifierFlagCapsLock ? GLFW_PRESS : GLFW_RELEASE; break;
|
|
|
|
case GLFW_FKEY_LEFT_SUPER:
|
|
|
|
case GLFW_FKEY_RIGHT_SUPER:
|
2022-01-22 12:27:13 +03:00
|
|
|
mod_name = "super";
|
2022-01-13 17:43:55 +03:00
|
|
|
action = modifierFlags & NSEventModifierFlagCommand ? GLFW_PRESS : GLFW_RELEASE; break;
|
|
|
|
case GLFW_FKEY_LEFT_CONTROL:
|
|
|
|
case GLFW_FKEY_RIGHT_CONTROL:
|
2022-01-22 12:27:13 +03:00
|
|
|
mod_name = "ctrl";
|
2022-01-13 17:43:55 +03:00
|
|
|
action = modifierFlags & NSEventModifierFlagControl ? GLFW_PRESS : GLFW_RELEASE; break;
|
|
|
|
case GLFW_FKEY_LEFT_ALT:
|
|
|
|
case GLFW_FKEY_RIGHT_ALT:
|
2022-01-22 12:27:13 +03:00
|
|
|
mod_name = "alt";
|
2022-01-13 17:43:55 +03:00
|
|
|
action = modifierFlags & NSEventModifierFlagOption ? GLFW_PRESS : GLFW_RELEASE; break;
|
|
|
|
case GLFW_FKEY_LEFT_SHIFT:
|
|
|
|
case GLFW_FKEY_RIGHT_SHIFT:
|
2022-01-22 12:27:13 +03:00
|
|
|
mod_name = "shift";
|
2022-01-13 17:43:55 +03:00
|
|
|
action = modifierFlags & NSEventModifierFlagShift ? GLFW_PRESS : GLFW_RELEASE; break;
|
|
|
|
default:
|
|
|
|
return;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
2022-01-22 12:17:57 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = action, .mods = mods};
|
2022-01-23 13:45:18 +03:00
|
|
|
debug_key("\x1b[33mflagsChanged:\x1b[m modifier: %s native_key: 0x%x (%s) glfw_key: 0x%x %s\n",
|
2022-01-22 12:27:13 +03:00
|
|
|
mod_name, keycode, safe_name_for_keycode(keycode), key, format_mods(mods));
|
2022-01-22 12:17:57 +03:00
|
|
|
marked_text_cleared_by_insert = false;
|
2022-01-26 13:25:37 +03:00
|
|
|
if (process_text && input_context) {
|
2022-01-22 12:17:57 +03:00
|
|
|
// this will call insertText which will fill up _glfw.ns.text
|
2022-01-22 13:21:43 +03:00
|
|
|
in_key_handler = 2;
|
2022-01-26 13:25:37 +03:00
|
|
|
[input_context handleEvent:event];
|
2022-01-22 13:21:43 +03:00
|
|
|
in_key_handler = 0;
|
2022-01-22 20:50:37 +03:00
|
|
|
if (marked_text_cleared_by_insert) {
|
2022-01-23 13:45:18 +03:00
|
|
|
debug_key("Clearing pre-edit text because insertText called from flagsChanged\n");
|
2022-01-22 20:50:37 +03:00
|
|
|
CLEAR_PRE_EDIT_TEXT;
|
2022-01-22 13:47:01 +03:00
|
|
|
if (_glfw.ns.text[0]) glfw_keyevent.text = _glfw.ns.text;
|
|
|
|
else _glfw.ns.text[0] = old_first_char;
|
2022-01-22 12:27:13 +03:00
|
|
|
}
|
2022-01-22 12:17:57 +03:00
|
|
|
}
|
|
|
|
glfw_keyevent.ime_state = GLFW_IME_NONE;
|
2019-09-24 22:58:18 +03:00
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)keyUp:(NSEvent *)event
|
|
|
|
{
|
2021-01-16 16:01:51 +03:00
|
|
|
const uint32_t keycode = [event keyCode];
|
|
|
|
const uint32_t key = translateKey(keycode, true);
|
2017-11-19 20:54:36 +03:00
|
|
|
const int mods = translateFlags([event modifierFlags]);
|
2019-09-24 22:58:18 +03:00
|
|
|
|
2021-11-29 16:56:16 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_RELEASE, .mods = mods};
|
2021-01-16 16:01:51 +03:00
|
|
|
add_alternate_keys(&glfw_keyevent, event);
|
2021-04-22 06:58:11 +03:00
|
|
|
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));
|
2019-09-24 22:58:18 +03:00
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2022-01-22 12:17:57 +03:00
|
|
|
#undef CLEAR_PRE_EDIT_TEXT
|
|
|
|
#undef UPDATE_PRE_EDIT_TEXT
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (void)scrollWheel:(NSEvent *)event
|
|
|
|
{
|
2019-07-01 06:02:47 +03:00
|
|
|
double deltaX = [event scrollingDeltaX];
|
|
|
|
double deltaY = [event scrollingDeltaY];
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2018-08-24 08:30:58 +03:00
|
|
|
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
2018-11-04 09:37:55 +03:00
|
|
|
if (flags) {
|
|
|
|
float xscale = 1, yscale = 1;
|
|
|
|
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
2018-11-05 05:26:33 +03:00
|
|
|
if (xscale > 0) deltaX *= xscale;
|
2018-11-04 09:37:55 +03:00
|
|
|
if (yscale > 0) deltaY *= yscale;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2018-12-26 08:33:36 +03:00
|
|
|
switch([event momentumPhase]) {
|
|
|
|
case NSEventPhaseBegan:
|
|
|
|
flags |= (1 << 1); break;
|
2018-12-27 17:12:57 +03:00
|
|
|
case NSEventPhaseStationary:
|
2018-12-26 08:33:36 +03:00
|
|
|
flags |= (2 << 1); break;
|
2018-12-27 17:12:57 +03:00
|
|
|
case NSEventPhaseChanged:
|
2018-12-26 08:33:36 +03:00
|
|
|
flags |= (3 << 1); break;
|
2018-12-27 17:12:57 +03:00
|
|
|
case NSEventPhaseEnded:
|
|
|
|
flags |= (4 << 1); break;
|
|
|
|
case NSEventPhaseCancelled:
|
|
|
|
flags |= (5 << 1); break;
|
|
|
|
case NSEventPhaseMayBegin:
|
|
|
|
flags |= (6 << 1); break;
|
|
|
|
case NSEventPhaseNone:
|
2018-12-26 08:33:36 +03:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-04-29 17:32:55 +03:00
|
|
|
_glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags]));
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)sender;
|
2018-11-20 07:54:55 +03:00
|
|
|
// HACK: We don't know what to say here because we don't know what the
|
2019-06-21 16:50:20 +03:00
|
|
|
// application wants to do with the paths
|
2018-11-20 07:54:55 +03:00
|
|
|
return NSDragOperationGeneric;
|
2018-10-11 16:06:24 +03:00
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2018-10-11 16:06:24 +03:00
|
|
|
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
|
|
|
{
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSRect contentRect = [window->ns.view frame];
|
2019-04-18 07:42:41 +03:00
|
|
|
// 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);
|
2019-06-21 16:50:20 +03:00
|
|
|
|
2018-11-20 07:54:55 +03:00
|
|
|
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
|
|
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
2019-08-23 22:27:00 +03:00
|
|
|
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]]
|
2018-11-20 07:54:55 +03:00
|
|
|
options:options];
|
2019-08-23 22:27:00 +03:00
|
|
|
if (!objs) return NO;
|
|
|
|
const NSUInteger count = [objs count];
|
2022-02-25 07:45:45 +03:00
|
|
|
NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; // auto-released
|
2017-11-19 20:54:36 +03:00
|
|
|
if (count)
|
|
|
|
{
|
2018-11-20 07:54:55 +03:00
|
|
|
for (NSUInteger i = 0; i < count; i++)
|
2019-08-23 22:27:00 +03:00
|
|
|
{
|
|
|
|
id obj = objs[i];
|
|
|
|
if ([obj isKindOfClass:[NSURL class]]) {
|
2022-02-25 07:45:45 +03:00
|
|
|
NSURL *url = (NSURL*)obj;
|
|
|
|
if ([uri_list length] > 0) [uri_list appendString:@("\n")];
|
2022-03-25 04:14:56 +03:00
|
|
|
if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString];
|
2022-02-25 07:45:45 +03:00
|
|
|
else [uri_list appendString:url.absoluteString];
|
2019-08-23 22:27:00 +03:00
|
|
|
} else if ([obj isKindOfClass:[NSString class]]) {
|
2020-03-19 11:04:15 +03:00
|
|
|
const char *text = [obj UTF8String];
|
|
|
|
_glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text));
|
2019-08-23 22:27:00 +03:00
|
|
|
} else {
|
|
|
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
|
|
"Cocoa: Object is neither a URL nor a string");
|
|
|
|
}
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
2022-02-25 07:45:45 +03:00
|
|
|
if ([uri_list length] > 0) _glfwInputDrop(window, "text/uri-list", uri_list.UTF8String, strlen(uri_list.UTF8String));
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2022-01-22 13:38:50 +03:00
|
|
|
const char *s = polymorphic_string_as_utf8(string);
|
|
|
|
debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", s, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
|
|
|
|
if (string == nil || !s[0]) {
|
|
|
|
bool had_marked_text = [self hasMarkedText];
|
|
|
|
[self unmarkText];
|
2022-01-23 13:50:55 +03:00
|
|
|
if (had_marked_text && (!in_key_handler || in_key_handler == 2)) {
|
2022-01-26 13:28:37 +03:00
|
|
|
debug_key("Clearing pre-edit because setMarkedText called from %s\n", in_key_handler ? "flagsChanged" : "event loop");
|
2022-01-22 13:38:50 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2021-11-11 18:09:55 +03:00
|
|
|
if ([string isKindOfClass:[NSAttributedString class]]) {
|
|
|
|
if (((NSMutableAttributedString*)string).length == 0) { [self unmarkText]; return; }
|
2021-11-11 18:23:14 +03:00
|
|
|
[markedText release];
|
2017-11-19 20:54:36 +03:00
|
|
|
markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
|
2021-11-11 18:09:55 +03:00
|
|
|
} else {
|
|
|
|
if (((NSString*)string).length == 0) { [self unmarkText]; return; }
|
2021-11-11 18:23:14 +03:00
|
|
|
[markedText release];
|
2017-11-19 20:54:36 +03:00
|
|
|
markedText = [[NSMutableAttributedString alloc] initWithString:string];
|
2021-11-11 18:09:55 +03:00
|
|
|
}
|
2022-01-23 13:50:55 +03:00
|
|
|
if (!in_key_handler || in_key_handler == 2) {
|
2022-01-23 18:10:41 +03:00
|
|
|
debug_key("Updating IME text in kitty from setMarkedText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
|
2022-01-22 19:01:08 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.text=[[markedText string] UTF8String], .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
2022-01-22 13:43:44 +03:00
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unmarkText
|
|
|
|
{
|
|
|
|
[[markedText mutableString] setString:@""];
|
|
|
|
}
|
|
|
|
|
2021-03-23 13:35:22 +03:00
|
|
|
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
2022-01-27 05:07:18 +03:00
|
|
|
[w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
|
2019-05-02 10:01:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-23 14:01:25 +03:00
|
|
|
- (void)updateIMEStateFor:(GLFWIMEUpdateType)which
|
2022-01-27 05:07:18 +03:00
|
|
|
focused:(bool)focused
|
2019-05-02 10:01:40 +03:00
|
|
|
left:(CGFloat)left
|
|
|
|
top:(CGFloat)top
|
|
|
|
cellWidth:(CGFloat)cellWidth
|
|
|
|
cellHeight:(CGFloat)cellHeight
|
|
|
|
{
|
2022-01-27 05:07:18 +03:00
|
|
|
if (which == GLFW_IME_UPDATE_FOCUS && !focused && [self hasMarkedText] && window) {
|
|
|
|
[input_context discardMarkedText];
|
|
|
|
[self unmarkText];
|
|
|
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
}
|
2021-11-12 06:48:34 +03:00
|
|
|
if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return;
|
2019-05-02 10:01:40 +03:00
|
|
|
left /= window->ns.xscale;
|
|
|
|
top /= window->ns.yscale;
|
|
|
|
cellWidth /= window->ns.xscale;
|
|
|
|
cellHeight /= window->ns.yscale;
|
2021-11-28 19:41:21 +03:00
|
|
|
debug_key("updateIMEPosition: left=%f, top=%f, width=%f, height=%f\n", left, top, cellWidth, cellHeight);
|
2019-05-02 10:01:40 +03:00
|
|
|
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];
|
2021-11-28 19:41:21 +03:00
|
|
|
if (_glfwPlatformWindowFocused(window)) [[window->ns.view inputContext] invalidateCharacterCoordinates];
|
2019-05-02 10:01:40 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (NSArray*)validAttributesForMarkedText
|
|
|
|
{
|
|
|
|
return [NSArray array];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
|
|
|
|
actualRange:(NSRangePointer)actualRange
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)range; (void)actualRange;
|
2017-11-19 20:54:36 +03:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)characterIndexForPoint:(NSPoint)point
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)point;
|
2017-11-19 20:54:36 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSRect)firstRectForCharacterRange:(NSRange)range
|
|
|
|
actualRange:(NSRangePointer)actualRange
|
|
|
|
{
|
2019-07-01 08:12:07 +03:00
|
|
|
(void)range; (void)actualRange;
|
2019-05-02 10:01:40 +03:00
|
|
|
return markedRect;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
|
|
|
|
{
|
2021-11-11 20:02:30 +03:00
|
|
|
const char *utf8 = polymorphic_string_as_utf8(string);
|
|
|
|
debug_key("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length);
|
2022-01-23 17:22:24 +03:00
|
|
|
if ([self hasMarkedText] && !is_ascii_control_char(utf8[0])) {
|
2021-11-12 14:57:37 +03:00
|
|
|
[self unmarkText];
|
|
|
|
marked_text_cleared_by_insert = true;
|
2022-01-22 13:21:43 +03:00
|
|
|
if (!in_key_handler) {
|
2022-01-23 13:45:18 +03:00
|
|
|
debug_key("Clearing pre-edit because insertText called from event loop\n");
|
2022-01-22 13:21:43 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
}
|
2021-11-12 14:57:37 +03:00
|
|
|
}
|
2019-02-14 12:40:13 +03:00
|
|
|
// insertText can be called multiple times for a single key event
|
|
|
|
char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text));
|
2021-11-11 20:02:30 +03:00
|
|
|
snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", utf8);
|
2018-03-30 11:47:39 +03:00
|
|
|
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
|
2022-01-22 20:50:37 +03:00
|
|
|
if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) {
|
2022-01-23 16:40:27 +03:00
|
|
|
if (!is_ascii_control_char(_glfw.ns.text[0])) {
|
2022-01-23 18:10:41 +03:00
|
|
|
debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
|
2022-01-23 16:40:27 +03:00
|
|
|
GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT};
|
|
|
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
|
|
|
}
|
2022-01-22 13:21:43 +03:00
|
|
|
_glfw.ns.text[0] = 0;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)doCommandBySelector:(SEL)selector
|
|
|
|
{
|
2021-11-11 20:11:59 +03:00
|
|
|
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2022-08-09 06:02:12 +03:00
|
|
|
- (BOOL)isAccessibilityElement
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector
|
|
|
|
{
|
|
|
|
if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText)) return YES;
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2022-08-20 19:08:28 +03:00
|
|
|
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400)
|
|
|
|
|
2022-08-09 06:02:12 +03:00
|
|
|
- (NSAccessibilityRole)accessibilityRole
|
|
|
|
{
|
|
|
|
return NSAccessibilityTextAreaRole;
|
|
|
|
}
|
|
|
|
|
2022-08-20 19:08:28 +03:00
|
|
|
#endif
|
|
|
|
|
2022-08-09 06:02:12 +03:00
|
|
|
- (NSString *)accessibilitySelectedText
|
|
|
|
{
|
2022-08-09 06:24:47 +03:00
|
|
|
NSString *text = nil;
|
2022-08-09 06:02:12 +03:00
|
|
|
if (_glfw.callbacks.get_current_selection) {
|
2022-08-09 06:35:13 +03:00
|
|
|
char *s = _glfw.callbacks.get_current_selection();
|
2022-08-09 06:02:12 +03:00
|
|
|
if (s) {
|
|
|
|
text = [NSString stringWithUTF8String:s];
|
2022-08-09 06:24:47 +03:00
|
|
|
free(s);
|
2022-08-09 06:02:12 +03:00
|
|
|
}
|
|
|
|
}
|
2022-08-09 06:24:47 +03:00
|
|
|
return text;
|
2022-08-09 06:02:12 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
@end
|
2019-07-04 07:26:59 +03:00
|
|
|
// }}}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2019-10-08 00:49:46 +03:00
|
|
|
// GLFW window class {{{
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
@interface GLFWWindow : NSWindow {
|
|
|
|
_GLFWwindow* glfw_window;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithGlfwWindow:(NSRect)contentRect
|
|
|
|
styleMask:(NSWindowStyleMask)style
|
|
|
|
backing:(NSBackingStoreType)backingStoreType
|
|
|
|
initWindow:(_GLFWwindow *)initWindow;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
- (void) removeGLFWWindow;
|
2017-11-19 20:54:36 +03:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GLFWWindow
|
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
- (instancetype)initWithGlfwWindow:(NSRect)contentRect
|
|
|
|
styleMask:(NSWindowStyleMask)style
|
|
|
|
backing:(NSBackingStoreType)backingStoreType
|
|
|
|
initWindow:(_GLFWwindow *)initWindow
|
|
|
|
{
|
|
|
|
self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:NO];
|
2021-02-25 08:55:04 +03:00
|
|
|
if (self != nil) {
|
|
|
|
glfw_window = initWindow;
|
|
|
|
self.tabbingMode = NSWindowTabbingModeDisallowed;
|
|
|
|
}
|
2019-07-04 07:26:59 +03:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) removeGLFWWindow
|
|
|
|
{
|
|
|
|
glfw_window = NULL;
|
|
|
|
}
|
|
|
|
|
2020-11-20 05:00:46 +03:00
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem *)item {
|
|
|
|
if (item.action == @selector(performMiniaturize:)) 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];
|
|
|
|
}
|
2019-07-04 07:26:59 +03:00
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
- (BOOL)canBecomeKeyWindow
|
|
|
|
{
|
|
|
|
// Required for NSWindowStyleMaskBorderless windows
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)canBecomeMainWindow
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2018-09-12 13:15:08 +03:00
|
|
|
- (void)toggleFullScreen:(nullable id)sender
|
|
|
|
{
|
2022-02-18 08:50:46 +03:00
|
|
|
if (glfw_window) {
|
|
|
|
if (glfw_window->ns.in_fullscreen_transition) return;
|
|
|
|
if (glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return;
|
|
|
|
glfw_window->ns.in_fullscreen_transition = true;
|
|
|
|
}
|
2021-10-23 10:36:47 +03:00
|
|
|
// When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen.
|
|
|
|
const NSSize original = [self resizeIncrements];
|
|
|
|
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
2018-09-12 13:15:08 +03:00
|
|
|
[super toggleFullScreen:sender];
|
2021-10-23 10:36:47 +03:00
|
|
|
[self setResizeIncrements:original];
|
2021-12-12 18:25:53 +03:00
|
|
|
// When the window decoration is hidden, toggling fullscreen causes the style mask to be changed,
|
|
|
|
// and causes the first responder to be cleared.
|
|
|
|
if (glfw_window && !glfw_window->decorated && glfw_window->ns.view) [self makeFirstResponder:glfw_window->ns.view];
|
2018-09-12 13:15:08 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
@end
|
2019-07-04 07:26:59 +03:00
|
|
|
// }}}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
|
|
|
|
// Create the Cocoa window
|
|
|
|
//
|
2019-06-08 05:42:42 +03:00
|
|
|
static bool createNativeWindow(_GLFWwindow* window,
|
2017-11-19 20:54:36 +03:00
|
|
|
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");
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
2019-07-04 07:26:59 +03:00
|
|
|
initWithGlfwWindow:contentRect
|
2017-11-19 20:54:36 +03:00
|
|
|
styleMask:getStyleMask(window)
|
|
|
|
backing:NSBackingStoreBuffered
|
2019-07-04 07:26:59 +03:00
|
|
|
initWindow:window
|
|
|
|
];
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (window->ns.object == nil)
|
|
|
|
{
|
|
|
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window");
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (window->monitor)
|
|
|
|
[window->ns.object setLevel:NSMainMenuWindowLevel + 1];
|
|
|
|
else
|
|
|
|
{
|
2019-10-08 00:49:46 +03:00
|
|
|
[(NSWindow*) window->ns.object center];
|
2017-11-19 20:54:36 +03:00
|
|
|
_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];
|
|
|
|
}
|
|
|
|
|
2017-12-20 06:37:06 +03:00
|
|
|
if (strlen(wndconfig->ns.frameName))
|
2019-06-21 16:50:20 +03:00
|
|
|
[window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)];
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
|
2019-03-28 10:39:22 +03:00
|
|
|
window->ns.retina = wndconfig->ns.retina;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (fbconfig->transparent)
|
|
|
|
{
|
|
|
|
[window->ns.object setOpaque:NO];
|
2019-03-06 06:42:16 +03:00
|
|
|
[window->ns.object setHasShadow:NO];
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.object setBackgroundColor:[NSColor clearColor]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[window->ns.object setContentView:window->ns.view];
|
|
|
|
[window->ns.object makeFirstResponder:window->ns.view];
|
2019-06-21 16:50:20 +03:00
|
|
|
[window->ns.object setTitle:@(wndconfig->title)];
|
2017-11-19 20:54:36 +03:00
|
|
|
[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);
|
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
return true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
////// GLFW platform API //////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
|
|
|
const _GLFWwndconfig* wndconfig,
|
|
|
|
const _GLFWctxconfig* ctxconfig,
|
|
|
|
const _GLFWfbconfig* fbconfig)
|
|
|
|
{
|
2018-04-20 11:55:53 +03:00
|
|
|
window->ns.deadKeyState = 0;
|
2019-07-23 20:57:18 +03:00
|
|
|
if (!_glfw.ns.finishedLaunching)
|
|
|
|
{
|
|
|
|
[NSApp run];
|
|
|
|
_glfw.ns.finishedLaunching = true;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (!createNativeWindow(window, wndconfig, fbconfig))
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2022-04-26 08:26:34 +03:00
|
|
|
switch((GlfwCocoaColorSpaces)wndconfig->ns.color_space) {
|
|
|
|
case SRGB_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace sRGBColorSpace]]; break;
|
|
|
|
case DISPLAY_P3_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace displayP3ColorSpace]]; break;
|
|
|
|
case DEFAULT_COLORSPACE: break;
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (ctxconfig->client != GLFW_NO_API)
|
|
|
|
{
|
|
|
|
if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
|
|
|
|
{
|
|
|
|
if (!_glfwInitNSGL())
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig))
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
|
|
|
|
{
|
2020-05-23 01:27:41 +03:00
|
|
|
// 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];
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
if (!_glfwInitEGL())
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
|
|
|
|
{
|
|
|
|
if (!_glfwInitOSMesa())
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (window->monitor)
|
|
|
|
{
|
|
|
|
_glfwPlatformShowWindow(window);
|
|
|
|
_glfwPlatformFocusWindow(window);
|
2018-02-08 06:45:40 +03:00
|
|
|
acquireMonitor(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-06-08 05:44:30 +03:00
|
|
|
return true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-07-04 06:33:13 +03:00
|
|
|
[window->ns.view removeGLFWWindow];
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.view release];
|
|
|
|
window->ns.view = nil;
|
|
|
|
|
2019-07-04 07:26:59 +03:00
|
|
|
[window->ns.object removeGLFWWindow];
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.object close];
|
|
|
|
window->ns.object = nil;
|
|
|
|
}
|
|
|
|
|
2019-12-12 15:49:25 +03:00
|
|
|
void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char* title)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2019-06-21 16:50:20 +03:00
|
|
|
NSString* string = @(title);
|
2017-11-19 20:54:36 +03:00
|
|
|
[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];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
|
|
|
|
int count UNUSED, const GLFWimage* images UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2020-06-03 21:04:54 +03:00
|
|
|
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
|
|
|
"Cocoa: Regular windows do not have icons on macOS");
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (xpos)
|
2019-09-27 17:26:48 +03:00
|
|
|
*xpos = (int)contentRect.origin.x;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (ypos)
|
2019-09-27 17:26:48 +03:00
|
|
|
*ypos = (int)_glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2019-04-18 07:42:41 +03:00
|
|
|
const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0);
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect];
|
|
|
|
[window->ns.object setFrameOrigin:frameRect.origin];
|
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (width)
|
2019-09-27 17:26:48 +03:00
|
|
|
*width = (int)contentRect.size.width;
|
2017-11-19 20:54:36 +03:00
|
|
|
if (height)
|
2019-09-27 17:26:48 +03:00
|
|
|
*height = (int)contentRect.size.height;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
|
|
|
{
|
|
|
|
if (window->monitor)
|
|
|
|
{
|
|
|
|
if (window->monitor->window == window)
|
|
|
|
acquireMonitor(window);
|
|
|
|
}
|
|
|
|
else
|
2019-09-08 06:46:07 +03:00
|
|
|
{
|
2021-10-24 10:08:44 +03:00
|
|
|
// Disable window resizing in fullscreen.
|
|
|
|
if ([window->ns.object styleMask] & NSWindowStyleMaskFullScreen || window->ns.in_traditional_fullscreen) return;
|
2021-11-13 11:10:24 +03:00
|
|
|
NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2019-09-08 06:46:07 +03:00
|
|
|
contentRect.origin.y += contentRect.size.height - height;
|
|
|
|
contentRect.size = NSMakeSize(width, height);
|
|
|
|
[window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect]
|
|
|
|
display:YES];
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2020-07-20 17:33:39 +03:00
|
|
|
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
2020-07-20 17:33:39 +03:00
|
|
|
else
|
|
|
|
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
2019-11-13 22:38:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
|
|
|
{
|
2021-10-12 12:09:06 +03:00
|
|
|
if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE) {
|
|
|
|
float xscale = 1, yscale = 1;
|
|
|
|
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
|
|
|
[window->ns.object setResizeIncrements:NSMakeSize(widthincr / xscale, heightincr / yscale)];
|
|
|
|
} else {
|
2019-11-13 22:38:12 +03:00
|
|
|
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
2021-10-12 12:09:06 +03:00
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
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)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect];
|
|
|
|
|
|
|
|
if (left)
|
2019-09-27 17:26:48 +03:00
|
|
|
*left = (int)(contentRect.origin.x - frameRect.origin.x);
|
2017-11-19 20:54:36 +03:00
|
|
|
if (top)
|
2019-09-27 17:26:48 +03:00
|
|
|
*top = (int)(frameRect.origin.y + frameRect.size.height -
|
|
|
|
contentRect.origin.y - contentRect.size.height);
|
2017-11-19 20:54:36 +03:00
|
|
|
if (right)
|
2019-09-27 17:26:48 +03:00
|
|
|
*right = (int)(frameRect.origin.x + frameRect.size.width -
|
|
|
|
contentRect.origin.x - contentRect.size.width);
|
2017-11-19 20:54:36 +03:00
|
|
|
if (bottom)
|
2019-09-27 17:26:48 +03:00
|
|
|
*bottom = (int)(contentRect.origin.y - frameRect.origin.y);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
|
|
|
float* xscale, float* yscale)
|
|
|
|
{
|
2021-11-13 11:10:24 +03:00
|
|
|
const NSRect points = get_window_size_without_border_in_logical_pixels(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-08-02 19:49:02 +03:00
|
|
|
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
|
2018-10-26 12:57:33 +03:00
|
|
|
{
|
2019-08-02 19:49:02 +03:00
|
|
|
return s_double_to_monotonic_t([NSEvent doubleClickInterval]);
|
2018-10-26 12:57:33 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
[NSApp requestUserAttention:NSInformationalRequest];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
|
2017-11-20 20:33:27 +03:00
|
|
|
{
|
|
|
|
NSBeep();
|
2019-06-08 05:44:30 +03:00
|
|
|
return true;
|
2017-11-20 20:33:27 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
// Make us the active application
|
2019-06-21 16:50:20 +03:00
|
|
|
// 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
|
2017-11-19 20:54:36 +03:00
|
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
|
|
[window->ns.object makeKeyAndOrderFront:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|
|
|
_GLFWmonitor* monitor,
|
|
|
|
int xpos, int ypos,
|
|
|
|
int width, int height,
|
2019-07-01 08:12:07 +03:00
|
|
|
int refreshRate UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
if (window->monitor == monitor)
|
|
|
|
{
|
|
|
|
if (monitor)
|
|
|
|
{
|
|
|
|
if (monitor->window == window)
|
|
|
|
acquireMonitor(window);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const NSRect contentRect =
|
2019-04-18 07:42:41 +03:00
|
|
|
NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height);
|
2017-11-19 20:54:36 +03:00
|
|
|
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];
|
2018-02-08 06:45:40 +03:00
|
|
|
// HACK: Changing the style mask can cause the first responder to be cleared
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.object makeFirstResponder:window->ns.view];
|
|
|
|
|
2019-07-01 03:29:32 +03:00
|
|
|
if (window->monitor)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
[window->ns.object setLevel:NSMainMenuWindowLevel + 1];
|
|
|
|
[window->ns.object setHasShadow:NO];
|
|
|
|
|
|
|
|
acquireMonitor(window);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-04-18 07:42:41 +03:00
|
|
|
NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1),
|
2017-11-19 20:54:36 +03:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2019-02-18 07:04:26 +03:00
|
|
|
int _glfwPlatformWindowOccluded(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
return !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible);
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2018-01-12 03:04:53 +03:00
|
|
|
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
const NSPoint point = [NSEvent mouseLocation];
|
|
|
|
|
|
|
|
if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
|
|
|
|
[window->ns.object windowNumber])
|
|
|
|
{
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2018-01-12 03:04:53 +03:00
|
|
|
}
|
|
|
|
|
2019-04-18 07:28:09 +03:00
|
|
|
return NSMouseInRect(point,
|
|
|
|
[window->ns.object convertRectToScreen:[window->ns.view frame]], NO);
|
2018-01-12 03:04:53 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
[window->ns.object setStyleMask:getStyleMask(window)];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
[window->ns.object setStyleMask:getStyleMask(window)];
|
|
|
|
[window->ns.object makeFirstResponder:window->ns.view];
|
|
|
|
}
|
|
|
|
|
2019-06-08 05:42:42 +03:00
|
|
|
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
if (enabled)
|
|
|
|
[window->ns.object setLevel:NSFloatingWindowLevel];
|
|
|
|
else
|
|
|
|
[window->ns.object setLevel:NSNormalWindowLevel];
|
|
|
|
}
|
|
|
|
|
2020-07-16 22:15:00 +03:00
|
|
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
|
|
|
{
|
|
|
|
[window->ns.object setIgnoresMouseEvents:enabled];
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
return (float) [window->ns.object alphaValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
|
|
|
{
|
|
|
|
[window->ns.object setAlphaValue:opacity];
|
|
|
|
}
|
|
|
|
|
2020-05-24 20:47:52 +03:00
|
|
|
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
|
2020-05-24 20:35:59 +03:00
|
|
|
{
|
2020-06-03 21:04:54 +03:00
|
|
|
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
|
|
|
"Cocoa: Raw mouse motion not yet implemented");
|
2020-05-24 20:35:59 +03:00
|
|
|
}
|
|
|
|
|
2020-05-24 20:47:52 +03:00
|
|
|
bool _glfwPlatformRawMouseMotionSupported(void)
|
2020-05-24 20:35:59 +03:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-02-25 13:21:39 +03:00
|
|
|
void
|
2019-03-21 14:21:01 +03:00
|
|
|
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
|
|
|
|
_GLFWwindow *w = _glfw.windowListHead;
|
|
|
|
while (w) {
|
|
|
|
if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) {
|
2019-06-08 05:44:30 +03:00
|
|
|
w->ns.renderFrameRequested = false;
|
2019-03-21 14:21:01 +03:00
|
|
|
w->ns.renderFrameCallback((GLFWwindow*)w);
|
|
|
|
}
|
|
|
|
w = w->next;
|
2019-02-25 13:21:39 +03:00
|
|
|
}
|
2021-05-12 04:59:51 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-02-20 12:38:07 +03:00
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
|
|
|
|
{
|
|
|
|
const NSRect contentRect = [window->ns.view frame];
|
2019-04-18 07:42:41 +03:00
|
|
|
// NOTE: The returned location uses base 0,1 not 0,0
|
2017-11-19 20:54:36 +03:00
|
|
|
const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
|
|
|
|
|
|
|
|
if (xpos)
|
|
|
|
*xpos = pos.x;
|
|
|
|
if (ypos)
|
2019-04-18 07:42:41 +03:00
|
|
|
*ypos = contentRect.size.height - pos.y;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
|
|
|
{
|
|
|
|
updateCursorImage(window);
|
|
|
|
|
|
|
|
const NSRect contentRect = [window->ns.view frame];
|
2019-04-18 07:42:41 +03:00
|
|
|
// NOTE: The returned location uses base 0,1 not 0,0
|
2017-11-19 20:54:36 +03:00
|
|
|
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,
|
2019-03-06 06:38:08 +03:00
|
|
|
_glfwTransformYNS(globalPoint.y)));
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2018-04-26 06:01:52 +03:00
|
|
|
if (_glfwPlatformWindowFocused(window))
|
|
|
|
updateCursorMode(window);
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-10-17 21:41:48 +03:00
|
|
|
const char* _glfwPlatformGetNativeKeyName(int keycode)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
UInt32 deadKeyState = 0;
|
|
|
|
UniChar characters[8];
|
|
|
|
UniCharCount characterCount = 0;
|
|
|
|
|
|
|
|
if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes],
|
2019-10-17 21:41:48 +03:00
|
|
|
keycode,
|
2017-11-19 20:54:36 +03:00
|
|
|
kUCKeyActionDisplay,
|
|
|
|
0,
|
|
|
|
LMGetKbdType(),
|
|
|
|
kUCKeyTranslateNoDeadKeysBit,
|
|
|
|
&deadKeyState,
|
|
|
|
sizeof(characters) / sizeof(characters[0]),
|
|
|
|
&characterCount,
|
|
|
|
characters) != noErr)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!characterCount)
|
|
|
|
return NULL;
|
|
|
|
|
2018-04-20 11:55:53 +03:00
|
|
|
convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName));
|
2017-11-19 20:54:36 +03:00
|
|
|
return _glfw.ns.keyName;
|
|
|
|
}
|
|
|
|
|
2021-01-16 10:49:44 +03:00
|
|
|
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;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|
|
|
const GLFWimage* image,
|
2018-07-07 08:09:30 +03:00
|
|
|
int xhot, int yhot, int count)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
|
|
|
NSImage* native;
|
|
|
|
NSBitmapImageRep* rep;
|
|
|
|
|
2018-07-07 08:09:30 +03:00
|
|
|
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
|
|
|
if (native == nil)
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2018-07-07 08:09:30 +03:00
|
|
|
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
|
2018-10-03 18:21:25 +03:00
|
|
|
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
2018-07-07 08:09:30 +03:00
|
|
|
bytesPerRow:src->width * 4
|
|
|
|
bitsPerPixel:32];
|
2020-11-01 15:39:16 +03:00
|
|
|
if (rep == nil) {
|
|
|
|
[native release];
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2020-11-01 15:39:16 +03:00
|
|
|
}
|
2018-07-07 08:09:30 +03:00
|
|
|
|
|
|
|
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
|
|
|
[native addRepresentation:rep];
|
|
|
|
[rep release];
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
|
|
|
hotSpot:NSMakePoint(xhot, yhot)];
|
2019-06-21 16:50:20 +03:00
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
[native release];
|
|
|
|
if (cursor->ns.object == nil)
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
|
|
|
return true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-03-21 10:36:13 +03:00
|
|
|
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:
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2019-03-21 10:36:13 +03:00
|
|
|
}
|
|
|
|
#undef C
|
|
|
|
#undef U
|
2017-11-19 20:54:36 +03:00
|
|
|
|
|
|
|
if (!cursor->ns.object)
|
|
|
|
{
|
|
|
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
|
|
"Cocoa: Failed to retrieve standard cursor");
|
2019-06-08 05:44:30 +03:00
|
|
|
return false;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[cursor->ns.object retain];
|
2019-06-08 05:44:30 +03:00
|
|
|
return true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
|
|
|
{
|
|
|
|
if (cursor->ns.object)
|
|
|
|
[(NSCursor*) cursor->ns.object release];
|
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2019-02-03 17:34:54 +03:00
|
|
|
if (cursorInContentArea(window))
|
2017-11-19 20:54:36 +03:00
|
|
|
updateCursorImage(window);
|
|
|
|
}
|
|
|
|
|
2021-11-11 10:03:12 +03:00
|
|
|
bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) {
|
|
|
|
NSWindow *window = w->ns.object;
|
|
|
|
bool traditional = !(flags & 1);
|
2022-01-11 17:31:37 +03:00
|
|
|
if (traditional) { if(@available(macOS 10.15.7, *)) return w->ns.in_traditional_fullscreen; }
|
2021-11-11 10:03:12 +03:00
|
|
|
NSWindowStyleMask sm = [window styleMask];
|
|
|
|
return sm & NSWindowStyleMaskFullScreen;
|
|
|
|
}
|
|
|
|
|
2019-05-12 13:23:06 +03:00
|
|
|
bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
|
|
|
NSWindow *window = w->ns.object;
|
|
|
|
bool made_fullscreen = true;
|
2019-05-12 13:37:54 +03:00
|
|
|
bool traditional = !(flags & 1);
|
2019-05-12 13:23:06 +03:00
|
|
|
NSWindowStyleMask sm = [window styleMask];
|
|
|
|
if (traditional) {
|
2022-01-11 17:31:37 +03:00
|
|
|
if (@available(macOS 10.15.7, *)) {
|
2022-01-11 16:39:48 +03:00
|
|
|
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
|
2022-01-11 17:31:37 +03:00
|
|
|
// Also no longer compatible after a minor release of macOS 10.15.7
|
2020-11-16 07:53:01 +03:00
|
|
|
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;
|
|
|
|
}
|
2019-05-12 13:23:06 +03:00
|
|
|
} else {
|
2020-11-16 07:53:01 +03:00
|
|
|
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];
|
2019-05-12 13:23:06 +03:00
|
|
|
}
|
2021-11-13 11:50:18 +03:00
|
|
|
// Changing the style mask causes the first responder to be cleared
|
|
|
|
[window makeFirstResponder:w->ns.view];
|
2021-11-13 07:50:57 +03:00
|
|
|
// If the dock and menubar are hidden going from maximized to fullscreen doesnt change the window size
|
|
|
|
// and macOS forgets to trigger windowDidResize, so call it ourselves
|
|
|
|
NSNotification *notification = [NSNotification notificationWithName:NSWindowDidResizeNotification object:window];
|
|
|
|
[w->ns.delegate performSelector:@selector(windowDidResize:) withObject:notification afterDelay:0];
|
2019-05-12 13:23:06 +03:00
|
|
|
} else {
|
2020-11-16 07:53:01 +03:00
|
|
|
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
2019-05-12 13:23:06 +03:00
|
|
|
if (in_fullscreen) made_fullscreen = false;
|
|
|
|
[window toggleFullScreen: nil];
|
|
|
|
}
|
|
|
|
return made_fullscreen;
|
|
|
|
}
|
|
|
|
|
2022-09-08 19:15:13 +03:00
|
|
|
// Clipboard {{{
|
2022-08-06 10:29:15 +03:00
|
|
|
|
2022-09-09 09:26:22 +03:00
|
|
|
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
|
|
|
|
|
2022-09-09 08:29:10 +03:00
|
|
|
static NSString*
|
|
|
|
mime_to_uti(const char *mime) {
|
|
|
|
if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString;
|
|
|
|
if (@available(macOS 11.0, *)) {
|
|
|
|
UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released
|
2022-09-09 13:09:00 +03:00
|
|
|
if (t != nil && !t.dynamic) return t.identifier;
|
2022-09-09 08:29:10 +03:00
|
|
|
}
|
2022-09-09 09:26:22 +03:00
|
|
|
return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released
|
2022-09-09 08:29:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
uti_to_mime(NSString *uti) {
|
|
|
|
if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain";
|
2022-09-09 09:26:22 +03:00
|
|
|
if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String];
|
2022-09-09 08:29:10 +03:00
|
|
|
if (@available(macOS 11.0, *)) {
|
|
|
|
UTType *t = [UTType typeWithIdentifier:uti]; // auto-released
|
|
|
|
if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String];
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2022-09-08 19:15:13 +03:00
|
|
|
static void
|
|
|
|
list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) {
|
|
|
|
#define w(x) { if (ok) ok = write_data(object, x, strlen(x)); }
|
2017-11-19 20:54:36 +03:00
|
|
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
2022-05-30 12:10:45 +03:00
|
|
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
2022-09-08 19:15:13 +03:00
|
|
|
BOOL has_file_urls = [pasteboard canReadObjectForClasses:@[[NSURL class]] options:options];
|
|
|
|
BOOL has_strings = [pasteboard canReadObjectForClasses:@[[NSString class]] options:nil];
|
|
|
|
/* NSLog(@"has_file_urls: %d has_strings: %d", has_file_urls, has_strings); */
|
|
|
|
bool ok = true;
|
|
|
|
if (has_strings) w("text/plain");
|
|
|
|
if (has_file_urls) w("text/local-path-list");
|
2022-09-09 08:29:10 +03:00
|
|
|
for (NSPasteboardItem * item in pasteboard.pasteboardItems) {
|
|
|
|
for (NSPasteboardType type in item.types) {
|
|
|
|
/* NSLog(@"%@", type); */
|
|
|
|
const char *mime = uti_to_mime(type);
|
|
|
|
if (mime && mime[0] && ![@(mime) hasPrefix:@"text/plain"]) {
|
|
|
|
/* NSLog(@"ut: %@ mt: %@ tags: %@", ut, ut.preferredMIMEType, ut.tags); */
|
|
|
|
w(mime);
|
2022-05-30 12:10:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 19:15:13 +03:00
|
|
|
#undef w
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2022-09-09 05:09:38 +03:00
|
|
|
static void
|
|
|
|
get_text_plain(GLFWclipboardwritedatafun write_data, void *object) {
|
|
|
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
|
|
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
|
|
|
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
|
|
|
|
bool found = false;
|
|
|
|
if (objs) {
|
|
|
|
const NSUInteger count = [objs count];
|
|
|
|
if (count) {
|
2022-09-09 10:42:12 +03:00
|
|
|
NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
|
|
|
NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
2022-09-09 05:09:38 +03:00
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
|
|
id obj = objs[i];
|
|
|
|
if ([obj isKindOfClass:[NSURL class]]) {
|
|
|
|
NSURL *url = (NSURL*)obj;
|
|
|
|
if (url.fileURL && url.fileSystemRepresentation) {
|
2022-09-09 10:42:12 +03:00
|
|
|
if ([path_list length] > 0) [path_list appendBytes:"\n" length:1];
|
|
|
|
[path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)];
|
2022-09-09 05:09:38 +03:00
|
|
|
}
|
|
|
|
} else if ([obj isKindOfClass:[NSString class]]) {
|
2022-09-09 10:42:12 +03:00
|
|
|
if ([text_list length] > 0) [text_list appendBytes:"\n" length:1];
|
|
|
|
[text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
|
2022-09-09 05:09:38 +03:00
|
|
|
}
|
|
|
|
}
|
2022-09-09 10:42:12 +03:00
|
|
|
const NSMutableData *text = nil;
|
|
|
|
if (path_list.length > 0) text = path_list;
|
|
|
|
else if (text_list.length > 0) text = text_list;
|
2022-09-09 05:09:38 +03:00
|
|
|
if (text) {
|
|
|
|
found = true;
|
2022-09-09 10:42:12 +03:00
|
|
|
write_data(object, text.mutableBytes, text.length);
|
2022-09-09 05:09:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard");
|
|
|
|
}
|
|
|
|
|
2022-09-08 19:15:13 +03:00
|
|
|
void
|
|
|
|
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
|
|
|
if (clipboard_type != GLFW_CLIPBOARD) return;
|
|
|
|
if (mime_type == NULL) {
|
|
|
|
list_clipboard_mimetypes(write_data, object);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-09 05:09:38 +03:00
|
|
|
if (strcmp(mime_type, "text/plain") == 0) {
|
|
|
|
get_text_plain(write_data, object);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-09 08:29:10 +03:00
|
|
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
2022-09-09 09:26:22 +03:00
|
|
|
/* NSLog(@"mime: %s uti: %@", mime_type, mime_to_uti(mime_type)); */
|
2022-09-09 08:29:10 +03:00
|
|
|
NSPasteboardType t = [pasteboard availableTypeFromArray:@[mime_to_uti(mime_type)]];
|
2022-09-09 09:26:22 +03:00
|
|
|
/* NSLog(@"available type: %@", t); */
|
2022-09-09 08:29:10 +03:00
|
|
|
if (t != nil) {
|
2022-09-09 09:26:22 +03:00
|
|
|
NSData *data = [pasteboard dataForType:t]; // auto-released
|
|
|
|
/* NSLog(@"data: %@", data); */
|
2022-09-09 08:29:10 +03:00
|
|
|
if (data != nil && data.length > 0) {
|
|
|
|
write_data(object, data.bytes, data.length);
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 19:15:13 +03:00
|
|
|
}
|
|
|
|
|
2022-09-09 05:09:38 +03:00
|
|
|
static NSMutableData*
|
|
|
|
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime) {
|
|
|
|
NSMutableData *ans = [NSMutableData dataWithCapacity:8192];
|
|
|
|
if (ans == nil) return nil;
|
|
|
|
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
|
|
|
|
void *iter = chunk.iter;
|
|
|
|
if (!iter) return ans;
|
|
|
|
while (true) {
|
|
|
|
chunk = cd->get_data(mime, iter, cd->ctype);
|
|
|
|
if (!chunk.sz) break;
|
|
|
|
[ans appendBytes:chunk.data length:chunk.sz];
|
|
|
|
if (chunk.free) chunk.free((void*)chunk.free_data);
|
|
|
|
}
|
|
|
|
cd->get_data(NULL, iter, cd->ctype);
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
_glfwPlatformSetClipboard(GLFWClipboardType t) {
|
2022-09-08 19:15:13 +03:00
|
|
|
if (t != GLFW_CLIPBOARD) return;
|
2022-09-09 05:09:38 +03:00
|
|
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
2022-09-09 08:29:10 +03:00
|
|
|
NSMutableArray<NSPasteboardType> *ptypes = [NSMutableArray arrayWithCapacity:_glfw.clipboard.num_mime_types]; // auto-released
|
|
|
|
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
|
|
|
|
[ptypes addObject:mime_to_uti(_glfw.clipboard.mime_types[i])];
|
|
|
|
}
|
|
|
|
[pasteboard declareTypes:ptypes owner:nil];
|
|
|
|
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
|
|
|
|
NSMutableData *data = get_clipboard_data(&_glfw.clipboard, _glfw.clipboard.mime_types[i]); // auto-released
|
2022-09-09 09:26:22 +03:00
|
|
|
/* NSLog(@"putting data: %@ for: %s with UTI: %@", data, _glfw.clipboard.mime_types[i], ptypes[i]); */
|
2022-09-09 08:29:10 +03:00
|
|
|
if (data != nil) [pasteboard setData:data forType:ptypes[i]];
|
2022-09-09 05:09:38 +03:00
|
|
|
}
|
2022-09-08 19:15:13 +03:00
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
|
2020-06-29 12:57:31 +03:00
|
|
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
2020-06-26 13:31:48 +03:00
|
|
|
{
|
2020-06-29 12:57:31 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 13:31:48 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-26 13:11:48 +03:00
|
|
|
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
|
|
|
{
|
|
|
|
return EGL_DEFAULT_DISPLAY;
|
|
|
|
}
|
|
|
|
|
|
|
|
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
|
|
|
{
|
|
|
|
return window->ns.layer;
|
|
|
|
}
|
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
|
|
|
{
|
2020-01-17 16:08:47 +03:00
|
|
|
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";
|
|
|
|
}
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
2019-07-01 08:12:07 +03:00
|
|
|
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED,
|
|
|
|
VkPhysicalDevice device UNUSED,
|
|
|
|
uint32_t queuefamily UNUSED)
|
2017-11-19 20:54:36 +03:00
|
|
|
{
|
2019-06-08 05:44:30 +03:00
|
|
|
return true;
|
2017-11-19 20:54:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-03-28 10:39:22 +03:00
|
|
|
if (window->ns.retina)
|
|
|
|
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
|
2019-06-21 16:50:20 +03:00
|
|
|
|
2019-03-28 10:39:22 +03:00
|
|
|
[window->ns.view setLayer:window->ns.layer];
|
2017-11-19 20:54:36 +03:00
|
|
|
[window->ns.view setWantsLayer:YES];
|
|
|
|
|
2020-01-17 16:08:47 +03:00
|
|
|
VkResult err;
|
|
|
|
|
|
|
|
if (_glfw.vk.EXT_metal_surface)
|
|
|
|
{
|
|
|
|
VkMetalSurfaceCreateInfoEXT sci;
|
2017-11-19 20:54:36 +03:00
|
|
|
|
2020-01-17 16:08:47 +03:00
|
|
|
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;
|
|
|
|
}
|
2020-01-17 15:14:32 +03:00
|
|
|
|
2020-01-17 16:08:47 +03:00
|
|
|
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);
|
|
|
|
}
|
2020-01-17 15:14:32 +03:00
|
|
|
|
2017-11-19 20:54:36 +03:00
|
|
|
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;
|
|
|
|
}
|
2018-04-19 15:14:31 +03:00
|
|
|
|
2020-11-20 05:00:46 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-04-19 15:14:31 +03:00
|
|
|
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;
|
|
|
|
}
|
2018-06-06 19:48:33 +03:00
|
|
|
|
2022-02-03 17:10:59 +03:00
|
|
|
GLFWAPI GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) {
|
2021-03-10 19:03:29 +03:00
|
|
|
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
2022-02-03 17:10:59 +03:00
|
|
|
GLFWhandleurlopen prev = _glfw.ns.url_open_callback;
|
|
|
|
_glfw.ns.url_open_callback = callback;
|
2021-03-10 19:03:29 +03:00
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
|
2018-09-12 13:15:08 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-02-20 12:38:07 +03:00
|
|
|
GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) {
|
|
|
|
requestRenderFrame((_GLFWwindow*)w, callback);
|
|
|
|
}
|
|
|
|
|
2021-01-16 10:49:44 +03:00
|
|
|
GLFWAPI uint32_t
|
|
|
|
glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) {
|
2018-06-06 19:48:33 +03:00
|
|
|
*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;
|
2021-01-16 10:49:44 +03:00
|
|
|
return _glfwPlatformGetNativeKeyForKey(glfw_key);
|
2018-06-06 19:48:33 +03:00
|
|
|
}
|
2019-03-06 06:38:08 +03:00
|
|
|
|
2019-06-21 16:50:20 +03:00
|
|
|
|
2019-03-06 06:38:08 +03:00
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
////// GLFW internal API //////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Transforms a y-coordinate between the CG display and NS screen spaces
|
|
|
|
//
|
|
|
|
float _glfwTransformYNS(float y)
|
|
|
|
{
|
2019-04-18 07:42:41 +03:00
|
|
|
return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
|
2019-03-06 06:38:08 +03:00
|
|
|
}
|
2019-07-18 11:55:11 +03:00
|
|
|
|
|
|
|
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];
|
|
|
|
}
|