From 88afee4222ec3583976f573f52500b9884e12cbc Mon Sep 17 00:00:00 2001 From: Bayemite Date: Mon, 31 May 2021 20:58:29 +1000 Subject: [PATCH 01/16] implemented get window title for win32 --- include/GLFW/glfw3.h | 23 ++++++++++++++++++++++- src/internal.h | 1 + src/win32_window.c | 31 +++++++++++++++++++++++++++++++ src/window.c | 9 +++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 7728dad1..00da3ce9 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2895,6 +2895,26 @@ GLFWAPI int glfwWindowShouldClose(GLFWwindow* window); */ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); +/*! @brief Retrieves the title of the specified window. + * + * This function gets the window title, encoded as UTF-8, of the specified + * window. + * + * @param[in] window The window to query. + * @return A copy of the UTF-8 encoded window title, or NULL if an error has occurred. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref window_title + * @sa @ref glfwSetWindowTitle + * + * @ingroup window + */ +GLFWAPI char* glfwGetWindowTitle(GLFWwindow* window); + /*! @brief Sets the title of the specified window. * * This function sets the window title, encoded as UTF-8, of the specified @@ -2912,7 +2932,8 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); * @thread_safety This function must only be called from the main thread. * * @sa @ref window_title - * + * @sa @ref glfwGetWindowTitle + * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * diff --git a/src/internal.h b/src/internal.h index ce9783f9..4ab44d67 100644 --- a/src/internal.h +++ b/src/internal.h @@ -650,6 +650,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); void _glfwPlatformDestroyWindow(_GLFWwindow* window); +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window); void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); diff --git a/src/win32_window.c b/src/win32_window.c index 52a9c680..0fa02e22 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1462,6 +1462,37 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) DestroyIcon(window->win32.smallIcon); } +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + int count = GetWindowTextLengthW(window->win32.handle); + if(count == 0) + { + SetLastError(0); + int error = GetLastError(); + + if(error != 0) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Querying window title failed"); + return NULL; + } + else + return calloc(1, sizeof(char)); // single \0 + + } + else + { + count += 1; // the \0 + WCHAR* wideTitle = calloc(count, sizeof(WCHAR)); + GetWindowTextW(window->win32.handle, wideTitle, count); + + char* title = _glfwCreateUTF8FromWideStringWin32(wideTitle); + if(!title) + return calloc(1, sizeof(char)); // single \0 + + return title; + } +} + void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { WCHAR* wideTitle = _glfwCreateWideStringFromUTF8Win32(title); diff --git a/src/window.c b/src/window.c index 518b27fd..e312342e 100644 --- a/src/window.c +++ b/src/window.c @@ -501,6 +501,15 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* handle, int value) window->shouldClose = value; } +GLFWAPI char* glfwGetWindowTitle(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return _glfwPlatformGetWindowTitle(window); +} + GLFWAPI void glfwSetWindowTitle(GLFWwindow* handle, const char* title) { _GLFWwindow* window = (_GLFWwindow*) handle; From 5a8f94f02fe4deba0e9b6a925816ccffc5ec1945 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Wed, 2 Jun 2021 21:41:12 +1000 Subject: [PATCH 02/16] added null window's getWindowTitle --- src/null_window.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/null_window.c b/src/null_window.c index 3e446641..736e0c44 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -150,6 +150,11 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) window->context.destroy(window); } +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + return NULL; +} + void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { } From cb73bd0ff352c2a1bf9a53d904dc500977be28ef Mon Sep 17 00:00:00 2001 From: Bayemite Date: Wed, 2 Jun 2021 21:41:43 +1000 Subject: [PATCH 03/16] Possibly helpful comment added --- include/GLFW/glfw3.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 00da3ce9..a971c6dc 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2906,6 +2906,8 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * + * @remark Do not forget to free the returned char* when you are done with it. + * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_title From 610635f5110af5d6e4fffe024fafeff49d660188 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Wed, 2 Jun 2021 21:42:24 +1000 Subject: [PATCH 04/16] added getWindowTitle for X11 --- src/x11_window.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/x11_window.c b/src/x11_window.c index 3f2277d6..ac44ac43 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2074,6 +2074,64 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) XFlush(_glfw.x11.display); } +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + // Uses XGetWMName instead of XFetchName (which occasionally fails for some reason) + XTextProperty textProperty; + int len; + char** charList = NULL; + char* title; + + _glfwGrabErrorHandlerX11(); + + if (XGetWMName(_glfw.x11.display, window->x11.handle, &textProperty) != 0) + { + _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Could not get window title"); + _glfwReleaseErrorHandlerX11(); + return NULL; + } + + int ret = Xutf8TextPropertyToTextList(_glfw.x11.display, &textProperty, &charList, &len); + if (ret != Success) + { + _glfwReleaseErrorHandlerX11(); + + if (ret == XNoMemory) + _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: No memory to convert window title to UTF-8"); + else if (ret == XLocaleNotSupported || ret == XConverterNotFound) + _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Cannot convert window title, unsupported locale"); + + if (textProperty.value != NULL) + XFree(textProperty.value); + + return NULL; + } + if (len < 1) + { + _glfwReleaseErrorHandlerX11(); + + _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Could not convert window title to UTF-8"); + + if (textProperty.value != NULL) + XFree(textProperty.value); + if (charList != NULL) + XFreeStringList(charList); + + return NULL; + } + + title = _glfw_strdup(charList[0]); + + _glfwReleaseErrorHandlerX11(); + + if (textProperty.value != NULL) + XFree(textProperty.value); + if (charList != NULL) + XFreeStringList(charList); + + return title; +} + void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { if (_glfw.x11.xlib.utf8) From ec2c2f64e4dd320ae06c0eee4fc2515544987557 Mon Sep 17 00:00:00 2001 From: bayemite Date: Thu, 3 Jun 2021 18:10:38 +1000 Subject: [PATCH 05/16] tested + fixed x11 implementation --- src/x11_window.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/x11_window.c b/src/x11_window.c index ac44ac43..786ae2f9 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -31,7 +31,8 @@ #include #include - +#include +#include #include #include @@ -2084,7 +2085,7 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) _glfwGrabErrorHandlerX11(); - if (XGetWMName(_glfw.x11.display, window->x11.handle, &textProperty) != 0) + if (XGetWMName(_glfw.x11.display, window->x11.handle, &textProperty) == 0) { _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Could not get window title"); _glfwReleaseErrorHandlerX11(); @@ -2095,7 +2096,6 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) if (ret != Success) { _glfwReleaseErrorHandlerX11(); - if (ret == XNoMemory) _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: No memory to convert window title to UTF-8"); else if (ret == XLocaleNotSupported || ret == XConverterNotFound) @@ -2106,18 +2106,17 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) return NULL; } - if (len < 1) + + if (len < 1) // empty title { _glfwReleaseErrorHandlerX11(); - _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Could not convert window title to UTF-8"); - if (textProperty.value != NULL) XFree(textProperty.value); if (charList != NULL) XFreeStringList(charList); - return NULL; + return calloc(1, sizeof(char)); } title = _glfw_strdup(charList[0]); From d8dd2cef7caa3f8b0fe8157a35aee5b84eeae6ca Mon Sep 17 00:00:00 2001 From: Bayemite Date: Thu, 3 Jun 2021 21:27:05 +1000 Subject: [PATCH 06/16] wayland implementation (needs testing) --- src/wl_window.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wl_window.c b/src/wl_window.c index 939f9c19..a1d3205b 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -878,6 +878,13 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) free(window->wl.monitors); } +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + if(window->wl.title) + return _glfw_strdup(window->wl.title); + return calloc(1, sizeof(char)); +} + void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { if (window->wl.title) From f1468113064a8a5f4d20f00d38f7fdc5b3323f20 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Thu, 3 Jun 2021 21:36:35 +1000 Subject: [PATCH 07/16] cocoa implementation (needs testing) --- src/cocoa_window.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cocoa_window.m b/src/cocoa_window.m index b618dccc..452d0823 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -978,6 +978,14 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) } // autoreleasepool } +void _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + // TODO: This is UNTESTED + @autoreleasepool { + return window->ns.object.title; + } +} + void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { @autoreleasepool { From 5ddb905a6ab4d715c30fecfb4054b71374effbb6 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Thu, 3 Jun 2021 21:52:57 +1000 Subject: [PATCH 08/16] Update changelog --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b5b4f7ea..6c91d674 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ information on what to include when reporting a bug. values to select ANGLE backend (#1380) - Added `GLFW_X11_XCB_VULKAN_SURFACE` init hint for selecting X11 Vulkan surface extension (#1793) + - Added `glfwGetWindowTitle` function for GLFWwindow for querying window titles - Made joystick subsystem initialize at first use (#1284,#1646) - Made `GLFW_DOUBLEBUFFER` a read-only window attribute - Updated the minimum required CMake version to 3.1 From 3c44229af809cdec2914f2aab9af5176b741e4f5 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Thu, 3 Jun 2021 22:02:24 +1000 Subject: [PATCH 09/16] fix C89 forbidden mixed declarations --- include/GLFW/glfw3.h | 4 ++-- src/win32_window.c | 22 ++++++++++++++-------- src/x11_window.c | 16 ++++++++-------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index a971c6dc..97e69e19 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2907,7 +2907,7 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); * GLFW_PLATFORM_ERROR. * * @remark Do not forget to free the returned char* when you are done with it. - * + * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_title @@ -2935,7 +2935,7 @@ GLFWAPI char* glfwGetWindowTitle(GLFWwindow* window); * * @sa @ref window_title * @sa @ref glfwGetWindowTitle - * + * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * diff --git a/src/win32_window.c b/src/win32_window.c index 0fa02e22..9aa106cc 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1464,11 +1464,14 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) { - int count = GetWindowTextLengthW(window->win32.handle); + int count; + SetLastError(0); + + count = GetWindowTextLengthW(window->win32.handle); if(count == 0) { - SetLastError(0); - int error = GetLastError(); + int error; + error = GetLastError(); if(error != 0) { @@ -1477,18 +1480,21 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) } else return calloc(1, sizeof(char)); // single \0 - + } else { + WCHAR* wideTitle; + char* title; count += 1; // the \0 - WCHAR* wideTitle = calloc(count, sizeof(WCHAR)); + + wideTitle = calloc(count, sizeof(WCHAR)); GetWindowTextW(window->win32.handle, wideTitle, count); - - char* title = _glfwCreateUTF8FromWideStringWin32(wideTitle); + + title = _glfwCreateUTF8FromWideStringWin32(wideTitle); if(!title) return calloc(1, sizeof(char)); // single \0 - + return title; } } diff --git a/src/x11_window.c b/src/x11_window.c index 786ae2f9..e8968c17 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2084,7 +2084,7 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) char* title; _glfwGrabErrorHandlerX11(); - + if (XGetWMName(_glfw.x11.display, window->x11.handle, &textProperty) == 0) { _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Could not get window title"); @@ -2100,10 +2100,10 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: No memory to convert window title to UTF-8"); else if (ret == XLocaleNotSupported || ret == XConverterNotFound) _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Cannot convert window title, unsupported locale"); - + if (textProperty.value != NULL) XFree(textProperty.value); - + return NULL; } @@ -2112,19 +2112,19 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) _glfwReleaseErrorHandlerX11(); if (textProperty.value != NULL) - XFree(textProperty.value); + XFree(textProperty.value); if (charList != NULL) XFreeStringList(charList); - + return calloc(1, sizeof(char)); } - + title = _glfw_strdup(charList[0]); - + _glfwReleaseErrorHandlerX11(); if (textProperty.value != NULL) - XFree(textProperty.value); + XFree(textProperty.value); if (charList != NULL) XFreeStringList(charList); From 377054de3f097b3c2ca874956e0842a904b3abe2 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Mon, 7 Jun 2021 19:46:03 +1000 Subject: [PATCH 10/16] add x11 dependencies --- .travis.yml | 2 ++ CMakeLists.txt | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index ff675629..f669e3ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ matrix: - libxcursor-dev - libxi-dev - libxext-dev + - libx11-dev env: - BUILD_SHARED_LIBS=ON - CFLAGS=-Werror @@ -32,6 +33,7 @@ matrix: - libxcursor-dev - libxi-dev - libxext-dev + - libx11-dev env: - BUILD_SHARED_LIBS=OFF - CFLAGS=-Werror diff --git a/CMakeLists.txt b/CMakeLists.txt index 59ab473c..aa171f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,10 @@ if (_GLFW_X11) # Set up library and include paths list(APPEND glfw_INCLUDE_DIRS "${X11_X11_INCLUDE_PATH}") + if (NOT X11_X11_INCLUDE_PATH) + message(FATAL_ERROR "X111 headers not found; install libx11 development package") + endif() + # Check for XRandR (modern resolution switching and gamma control) if (NOT X11_Xrandr_INCLUDE_PATH) message(FATAL_ERROR "RandR headers not found; install libxrandr development package") @@ -187,6 +191,8 @@ if (_GLFW_X11) if (NOT X11_Xshape_INCLUDE_PATH) message(FATAL_ERROR "X Shape headers not found; install libxext development package") endif() + + list(APPEND glfw_LIBRARIES ${X11_X11_LIB}) endif() #-------------------------------------------------------------------- From 4d076583b239126b8617e4e5331f7c60f8e89e5c Mon Sep 17 00:00:00 2001 From: Bayemite Date: Tue, 15 Jun 2021 17:36:31 +1000 Subject: [PATCH 11/16] update stub --- src/cocoa_window.m | 3859 ++++++++++++++++++++++---------------------- 1 file changed, 1928 insertions(+), 1931 deletions(-) diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 452d0823..3321e0b9 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1,1931 +1,1928 @@ -//======================================================================== -// GLFW 3.4 macOS - www.glfw.org -//------------------------------------------------------------------------ -// Copyright (c) 2009-2019 Camilla LΓΆwy -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would -// be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not -// be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source -// distribution. -// -//======================================================================== -// It is fine to use C99 in this file because it will not be built with VS -//======================================================================== - -#include "internal.h" - -#include -#include - -// Returns the style mask corresponding to the window settings -// -static NSUInteger getStyleMask(_GLFWwindow* window) -{ - NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; - - if (window->monitor || !window->decorated) - styleMask |= NSWindowStyleMaskBorderless; - else - { - styleMask |= NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable; - - if (window->resizable) - styleMask |= NSWindowStyleMaskResizable; - } - - return styleMask; -} - -// Returns whether the cursor is in the content area of the specified window -// -static GLFWbool cursorInContentArea(_GLFWwindow* window) -{ - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; -} - -// Hides the cursor if not already hidden -// -static void hideCursor(_GLFWwindow* window) -{ - if (!_glfw.ns.cursorHidden) - { - [NSCursor hide]; - _glfw.ns.cursorHidden = GLFW_TRUE; - } -} - -// Shows the cursor if not already shown -// -static void showCursor(_GLFWwindow* window) -{ - if (_glfw.ns.cursorHidden) - { - [NSCursor unhide]; - _glfw.ns.cursorHidden = GLFW_FALSE; - } -} - -// Updates the cursor image according to its cursor mode -// -static void updateCursorImage(_GLFWwindow* window) -{ - if (window->cursorMode == GLFW_CURSOR_NORMAL) - { - showCursor(window); - - if (window->cursor) - [(NSCursor*) window->cursor->ns.object set]; - else - [[NSCursor arrowCursor] set]; - } - else - hideCursor(window); -} - -// Apply chosen cursor mode to a focused window -// -static void updateCursorMode(_GLFWwindow* window) -{ - if (window->cursorMode == GLFW_CURSOR_DISABLED) - { - _glfw.ns.disabledCursorWindow = window; - _glfwPlatformGetCursorPos(window, - &_glfw.ns.restoreCursorPosX, - &_glfw.ns.restoreCursorPosY); - _glfwCenterCursorInContentArea(window); - CGAssociateMouseAndMouseCursorPosition(false); - } - else if (_glfw.ns.disabledCursorWindow == window) - { - _glfw.ns.disabledCursorWindow = NULL; - CGAssociateMouseAndMouseCursorPosition(true); - _glfwPlatformSetCursorPos(window, - _glfw.ns.restoreCursorPosX, - _glfw.ns.restoreCursorPosY); - } - - if (cursorInContentArea(window)) - updateCursorImage(window); -} - -// Make the specified window and its video mode active on its monitor -// -static void acquireMonitor(_GLFWwindow* window) -{ - _glfwSetVideoModeNS(window->monitor, &window->videoMode); - const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); - const NSRect frame = NSMakeRect(bounds.origin.x, - _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), - bounds.size.width, - bounds.size.height); - - [window->ns.object setFrame:frame display:YES]; - - _glfwInputMonitorWindow(window->monitor, window); -} - -// Remove the window and restore the original video mode -// -static void releaseMonitor(_GLFWwindow* window) -{ - if (window->monitor->window != window) - return; - - _glfwInputMonitorWindow(window->monitor, NULL); - _glfwRestoreVideoModeNS(window->monitor); -} - -// Translates macOS key modifiers into GLFW ones -// -static int translateFlags(NSUInteger flags) -{ - int mods = 0; - - if (flags & NSEventModifierFlagShift) - mods |= GLFW_MOD_SHIFT; - if (flags & NSEventModifierFlagControl) - mods |= GLFW_MOD_CONTROL; - if (flags & NSEventModifierFlagOption) - mods |= GLFW_MOD_ALT; - if (flags & NSEventModifierFlagCommand) - mods |= GLFW_MOD_SUPER; - if (flags & NSEventModifierFlagCapsLock) - mods |= GLFW_MOD_CAPS_LOCK; - - return mods; -} - -// Translates a macOS keycode to a GLFW keycode -// -static int translateKey(unsigned int key) -{ - if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) - return GLFW_KEY_UNKNOWN; - - return _glfw.ns.keycodes[key]; -} - -// Translate a GLFW keycode to a Cocoa modifier flag -// -static NSUInteger translateKeyToModifierFlag(int key) -{ - switch (key) - { - case GLFW_KEY_LEFT_SHIFT: - case GLFW_KEY_RIGHT_SHIFT: - return NSEventModifierFlagShift; - case GLFW_KEY_LEFT_CONTROL: - case GLFW_KEY_RIGHT_CONTROL: - return NSEventModifierFlagControl; - case GLFW_KEY_LEFT_ALT: - case GLFW_KEY_RIGHT_ALT: - return NSEventModifierFlagOption; - case GLFW_KEY_LEFT_SUPER: - case GLFW_KEY_RIGHT_SUPER: - return NSEventModifierFlagCommand; - case GLFW_KEY_CAPS_LOCK: - return NSEventModifierFlagCapsLock; - } - - return 0; -} - -// Defines a constant for empty ranges in NSTextInputClient -// -static const NSRange kEmptyRange = { NSNotFound, 0 }; - - -//------------------------------------------------------------------------ -// Delegate for window related notifications -//------------------------------------------------------------------------ - -@interface GLFWWindowDelegate : NSObject -{ - _GLFWwindow* window; -} - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - -@end - -@implementation GLFWWindowDelegate - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow -{ - self = [super init]; - if (self != nil) - window = initWindow; - - return self; -} - -- (BOOL)windowShouldClose:(id)sender -{ - _glfwInputWindowCloseRequest(window); - return NO; -} - -- (void)windowDidResize:(NSNotification *)notification -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - const int maximized = [window->ns.object isZoomed]; - if (window->ns.maximized != maximized) - { - window->ns.maximized = maximized; - _glfwInputWindowMaximize(window, maximized); - } - - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (fbRect.size.width != window->ns.fbWidth || - fbRect.size.height != window->ns.fbHeight) - { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); - } - - if (contentRect.size.width != window->ns.width || - contentRect.size.height != window->ns.height) - { - window->ns.width = contentRect.size.width; - window->ns.height = contentRect.size.height; - _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); - } -} - -- (void)windowDidMove:(NSNotification *)notification -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - int x, y; - _glfwPlatformGetWindowPos(window, &x, &y); - _glfwInputWindowPos(window, x, y); -} - -- (void)windowDidMiniaturize:(NSNotification *)notification -{ - if (window->monitor) - releaseMonitor(window); - - _glfwInputWindowIconify(window, GLFW_TRUE); -} - -- (void)windowDidDeminiaturize:(NSNotification *)notification -{ - if (window->monitor) - acquireMonitor(window); - - _glfwInputWindowIconify(window, GLFW_FALSE); -} - -- (void)windowDidBecomeKey:(NSNotification *)notification -{ - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - _glfwInputWindowFocus(window, GLFW_TRUE); - updateCursorMode(window); -} - -- (void)windowDidResignKey:(NSNotification *)notification -{ - if (window->monitor && window->autoIconify) - _glfwPlatformIconifyWindow(window); - - _glfwInputWindowFocus(window, GLFW_FALSE); -} - -- (void)windowDidChangeOcclusionState:(NSNotification* )notification -{ - if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) - window->ns.occluded = GLFW_FALSE; - else - window->ns.occluded = GLFW_TRUE; -} - -@end - - -//------------------------------------------------------------------------ -// Content view class for the GLFW window -//------------------------------------------------------------------------ - -@interface GLFWContentView : NSView -{ - _GLFWwindow* window; - NSTrackingArea* trackingArea; - NSMutableAttributedString* markedText; -} - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - -@end - -@implementation GLFWContentView - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow -{ - self = [super init]; - if (self != nil) - { - window = initWindow; - trackingArea = nil; - markedText = [[NSMutableAttributedString alloc] init]; - - [self updateTrackingAreas]; - // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available - // on 10.7 without having been deprecated yet - [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]]; - } - - return self; -} - -- (void)dealloc -{ - [trackingArea release]; - [markedText release]; - [super dealloc]; -} - -- (BOOL)isOpaque -{ - return [window->ns.object isOpaque]; -} - -- (BOOL)canBecomeKeyView -{ - return YES; -} - -- (BOOL)acceptsFirstResponder -{ - return YES; -} - -- (BOOL)wantsUpdateLayer -{ - return YES; -} - -- (void)updateLayer -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - _glfwInputWindowDamage(window); -} - -- (void)cursorUpdate:(NSEvent *)event -{ - updateCursorImage(window); -} - -- (BOOL)acceptsFirstMouse:(NSEvent *)event -{ - return YES; -} - -- (void)mouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_LEFT, - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)mouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_LEFT, - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)mouseMoved:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_DISABLED) - { - const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; - const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; - - _glfwInputCursorPos(window, - window->virtualCursorPosX + dx, - window->virtualCursorPosY + dy); - } - else - { - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [event locationInWindow]; - - _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); - } - - window->ns.cursorWarpDeltaX = 0; - window->ns.cursorWarpDeltaY = 0; -} - -- (void)rightMouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_RIGHT, - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)rightMouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)rightMouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_RIGHT, - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)otherMouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - (int) [event buttonNumber], - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)otherMouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)otherMouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - (int) [event buttonNumber], - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)mouseExited:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_HIDDEN) - showCursor(window); - - _glfwInputCursorEnter(window, GLFW_FALSE); -} - -- (void)mouseEntered:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_HIDDEN) - hideCursor(window); - - _glfwInputCursorEnter(window, GLFW_TRUE); -} - -- (void)viewDidChangeBackingProperties -{ - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (fbRect.size.width != window->ns.fbWidth || - fbRect.size.height != window->ns.fbHeight) - { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); - } - - const float xscale = fbRect.size.width / contentRect.size.width; - const float yscale = fbRect.size.height / contentRect.size.height; - - if (xscale != window->ns.xscale || yscale != window->ns.yscale) - { - window->ns.xscale = xscale; - window->ns.yscale = yscale; - _glfwInputWindowContentScale(window, xscale, yscale); - - if (window->ns.retina && window->ns.layer) - [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; - } -} - -- (void)drawRect:(NSRect)rect -{ - _glfwInputWindowDamage(window); -} - -- (void)updateTrackingAreas -{ - if (trackingArea != nil) - { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingEnabledDuringMouseDrag | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect | - NSTrackingAssumeInside; - - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -- (void)keyDown:(NSEvent *)event -{ - const int key = translateKey([event keyCode]); - const int mods = translateFlags([event modifierFlags]); - - _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); - - [self interpretKeyEvents:@[event]]; -} - -- (void)flagsChanged:(NSEvent *)event -{ - int action; - const unsigned int modifierFlags = - [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; - const int key = translateKey([event keyCode]); - const int mods = translateFlags(modifierFlags); - const NSUInteger keyFlag = translateKeyToModifierFlag(key); - - if (keyFlag & modifierFlags) - { - if (window->keys[key] == GLFW_PRESS) - action = GLFW_RELEASE; - else - action = GLFW_PRESS; - } - else - action = GLFW_RELEASE; - - _glfwInputKey(window, key, [event keyCode], action, mods); -} - -- (void)keyUp:(NSEvent *)event -{ - const int key = translateKey([event keyCode]); - const int mods = translateFlags([event modifierFlags]); - _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); -} - -- (void)scrollWheel:(NSEvent *)event -{ - double deltaX = [event scrollingDeltaX]; - double deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) - { - deltaX *= 0.1; - deltaY *= 0.1; - } - - if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) - _glfwInputScroll(window, deltaX, deltaY); -} - -- (NSDragOperation)draggingEntered:(id )sender -{ - // HACK: We don't know what to say here because we don't know what the - // application wants to do with the paths - return NSDragOperationGeneric; -} - -- (BOOL)performDragOperation:(id )sender -{ - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [sender draggingLocation]; - _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); - - NSPasteboard* pasteboard = [sender draggingPasteboard]; - NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; - NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] - options:options]; - const NSUInteger count = [urls count]; - if (count) - { - char** paths = calloc(count, sizeof(char*)); - - for (NSUInteger i = 0; i < count; i++) - paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]); - - _glfwInputDrop(window, (int) count, (const char**) paths); - - for (NSUInteger i = 0; i < count; i++) - free(paths[i]); - free(paths); - } - - 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 -{ - [markedText release]; - if ([string isKindOfClass:[NSAttributedString class]]) - markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - else - markedText = [[NSMutableAttributedString alloc] initWithString:string]; -} - -- (void)unmarkText -{ - [[markedText mutableString] setString:@""]; -} - -- (NSArray*)validAttributesForMarkedText -{ - return [NSArray array]; -} - -- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range - actualRange:(NSRangePointer)actualRange -{ - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)point -{ - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)range - actualRange:(NSRangePointer)actualRange -{ - const NSRect frame = [window->ns.view frame]; - return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); -} - -- (void)insertText:(id)string replacementRange:(NSRange)replacementRange -{ - NSString* characters; - NSEvent* event = [NSApp currentEvent]; - const int mods = translateFlags([event modifierFlags]); - const int plain = !(mods & GLFW_MOD_SUPER); - - if ([string isKindOfClass:[NSAttributedString class]]) - characters = [string string]; - else - characters = (NSString*) string; - - NSRange range = NSMakeRange(0, [characters length]); - while (range.length) - { - uint32_t codepoint = 0; - - if ([characters getBytes:&codepoint - maxLength:sizeof(codepoint) - usedLength:NULL - encoding:NSUTF32StringEncoding - options:0 - range:range - remainingRange:&range]) - { - if (codepoint >= 0xf700 && codepoint <= 0xf7ff) - continue; - - _glfwInputChar(window, codepoint, mods, plain); - } - } -} - -- (void)doCommandBySelector:(SEL)selector -{ -} - -@end - - -//------------------------------------------------------------------------ -// GLFW window class -//------------------------------------------------------------------------ - -@interface GLFWWindow : NSWindow {} -@end - -@implementation GLFWWindow - -- (BOOL)canBecomeKeyWindow -{ - // Required for NSWindowStyleMaskBorderless windows - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - return YES; -} - -@end - - -// Create the Cocoa window -// -static GLFWbool createNativeWindow(_GLFWwindow* window, - const _GLFWwndconfig* wndconfig, - const _GLFWfbconfig* fbconfig) -{ - window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; - if (window->ns.delegate == nil) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create window delegate"); - return GLFW_FALSE; - } - - NSRect contentRect; - - if (window->monitor) - { - GLFWvidmode mode; - int xpos, ypos; - - _glfwPlatformGetVideoMode(window->monitor, &mode); - _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); - - contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); - } - else - contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); - - window->ns.object = [[GLFWWindow alloc] - initWithContentRect:contentRect - styleMask:getStyleMask(window) - backing:NSBackingStoreBuffered - defer:NO]; - - if (window->ns.object == nil) - { - _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); - return GLFW_FALSE; - } - - if (window->monitor) - [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; - else - { - [(NSWindow*) window->ns.object center]; - _glfw.ns.cascadePoint = - NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: - NSPointFromCGPoint(_glfw.ns.cascadePoint)]); - - if (wndconfig->resizable) - { - const NSWindowCollectionBehavior behavior = - NSWindowCollectionBehaviorFullScreenPrimary | - NSWindowCollectionBehaviorManaged; - [window->ns.object setCollectionBehavior:behavior]; - } - - if (wndconfig->floating) - [window->ns.object setLevel:NSFloatingWindowLevel]; - - if (wndconfig->maximized) - [window->ns.object zoom:nil]; - } - - if (strlen(wndconfig->ns.frameName)) - [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; - - window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; - window->ns.retina = wndconfig->ns.retina; - - if (fbconfig->transparent) - { - [window->ns.object setOpaque:NO]; - [window->ns.object setHasShadow:NO]; - [window->ns.object setBackgroundColor:[NSColor clearColor]]; - } - - [window->ns.object setContentView:window->ns.view]; - [window->ns.object makeFirstResponder:window->ns.view]; - [window->ns.object setTitle:@(wndconfig->title)]; - [window->ns.object setDelegate:window->ns.delegate]; - [window->ns.object setAcceptsMouseMovedEvents:YES]; - [window->ns.object setRestorable:NO]; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 - if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)]) - [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed]; -#endif - - _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); - _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); - - return GLFW_TRUE; -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW internal API ////// -////////////////////////////////////////////////////////////////////////// - -// Transforms a y-coordinate between the CG display and NS screen spaces -// -float _glfwTransformYNS(float y) -{ - return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW platform API ////// -////////////////////////////////////////////////////////////////////////// - -int _glfwPlatformCreateWindow(_GLFWwindow* window, - const _GLFWwndconfig* wndconfig, - const _GLFWctxconfig* ctxconfig, - const _GLFWfbconfig* fbconfig) -{ - @autoreleasepool { - - if (!createNativeWindow(window, wndconfig, fbconfig)) - return GLFW_FALSE; - - if (ctxconfig->client != GLFW_NO_API) - { - if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) - { - if (!_glfwInitNSGL()) - return GLFW_FALSE; - if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) - { - // EGL implementation on macOS use CALayer* EGLNativeWindowType so we - // need to get the layer for EGL window surface creation. - [window->ns.view setWantsLayer:YES]; - window->ns.layer = [window->ns.view layer]; - - if (!_glfwInitEGL()) - return GLFW_FALSE; - if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) - { - if (!_glfwInitOSMesa()) - return GLFW_FALSE; - if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - } - - if (window->monitor) - { - _glfwPlatformShowWindow(window); - _glfwPlatformFocusWindow(window); - acquireMonitor(window); - } - - return GLFW_TRUE; - - } // autoreleasepool -} - -void _glfwPlatformDestroyWindow(_GLFWwindow* window) -{ - @autoreleasepool { - - if (_glfw.ns.disabledCursorWindow == window) - _glfw.ns.disabledCursorWindow = NULL; - - [window->ns.object orderOut:nil]; - - if (window->monitor) - releaseMonitor(window); - - if (window->context.destroy) - window->context.destroy(window); - - [window->ns.object setDelegate:nil]; - [window->ns.delegate release]; - window->ns.delegate = nil; - - [window->ns.view release]; - window->ns.view = nil; - - [window->ns.object close]; - window->ns.object = nil; - - // HACK: Allow Cocoa to catch up before returning - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -void _glfwPlatformGetWindowTitle(_GLFWwindow* window) -{ - // TODO: This is UNTESTED - @autoreleasepool { - return window->ns.object.title; - } -} - -void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) -{ - @autoreleasepool { - NSString* string = @(title); - [window->ns.object setTitle:string]; - // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it - // if the window lacks NSWindowStyleMaskTitled - [window->ns.object setMiniwindowTitle:string]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowIcon(_GLFWwindow* window, - int count, const GLFWimage* images) -{ - _glfwInputError(GLFW_FEATURE_UNAVAILABLE, - "Cocoa: Regular windows do not have icons on macOS"); -} - -void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) -{ - @autoreleasepool { - - const NSRect contentRect = - [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; - - if (xpos) - *xpos = contentRect.origin.x; - if (ypos) - *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); - - } // autoreleasepool -} - -void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); - const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; - [window->ns.object setFrameOrigin:frameRect.origin]; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - - if (width) - *width = contentRect.size.width; - if (height) - *height = contentRect.size.height; - - } // autoreleasepool -} - -void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) -{ - @autoreleasepool { - - if (window->monitor) - { - if (window->monitor->window == window) - acquireMonitor(window); - } - else - { - NSRect contentRect = - [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; - contentRect.origin.y += contentRect.size.height - height; - contentRect.size = NSMakeSize(width, height); - [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] - display:YES]; - } - - } // autoreleasepool -} - -void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, - int minwidth, int minheight, - int maxwidth, int maxheight) -{ - @autoreleasepool { - - 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)]; - - } // autoreleasepool -} - -void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) -{ - @autoreleasepool { - if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) - [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; - else - [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; - } // autoreleasepool -} - -void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (width) - *width = (int) fbRect.size.width; - if (height) - *height = (int) fbRect.size.height; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, - int* left, int* top, - int* right, int* bottom) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; - - if (left) - *left = contentRect.origin.x - frameRect.origin.x; - if (top) - *top = frameRect.origin.y + frameRect.size.height - - contentRect.origin.y - contentRect.size.height; - if (right) - *right = frameRect.origin.x + frameRect.size.width - - contentRect.origin.x - contentRect.size.width; - if (bottom) - *bottom = contentRect.origin.y - frameRect.origin.y; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, - float* xscale, float* yscale) -{ - @autoreleasepool { - - const NSRect points = [window->ns.view frame]; - const NSRect pixels = [window->ns.view convertRectToBacking:points]; - - if (xscale) - *xscale = (float) (pixels.size.width / points.size.width); - if (yscale) - *yscale = (float) (pixels.size.height / points.size.height); - - } // autoreleasepool -} - -void _glfwPlatformIconifyWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object miniaturize:nil]; - } // autoreleasepool -} - -void _glfwPlatformRestoreWindow(_GLFWwindow* window) -{ - @autoreleasepool { - if ([window->ns.object isMiniaturized]) - [window->ns.object deminiaturize:nil]; - else if ([window->ns.object isZoomed]) - [window->ns.object zoom:nil]; - } // autoreleasepool -} - -void _glfwPlatformMaximizeWindow(_GLFWwindow* window) -{ - @autoreleasepool { - if (![window->ns.object isZoomed]) - [window->ns.object zoom:nil]; - } // autoreleasepool -} - -void _glfwPlatformShowWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object orderFront:nil]; - } // autoreleasepool -} - -void _glfwPlatformHideWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object orderOut:nil]; - } // autoreleasepool -} - -void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) -{ - @autoreleasepool { - [NSApp requestUserAttention:NSInformationalRequest]; - } // autoreleasepool -} - -void _glfwPlatformFocusWindow(_GLFWwindow* window) -{ - @autoreleasepool { - // Make us the active application - // HACK: This is here to prevent applications using only hidden windows from - // being activated, but should probably not be done every time any - // window is shown - [NSApp activateIgnoringOtherApps:YES]; - [window->ns.object makeKeyAndOrderFront:nil]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, - _GLFWmonitor* monitor, - int xpos, int ypos, - int width, int height, - int refreshRate) -{ - @autoreleasepool { - - if (window->monitor == monitor) - { - if (monitor) - { - if (monitor->window == window) - acquireMonitor(window); - } - else - { - const NSRect contentRect = - NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); - const NSRect frameRect = - [window->ns.object frameRectForContentRect:contentRect - styleMask:getStyleMask(window)]; - - [window->ns.object setFrame:frameRect display:YES]; - } - - return; - } - - if (window->monitor) - releaseMonitor(window); - - _glfwInputWindowMonitor(window, monitor); - - // HACK: Allow the state cached in Cocoa to catch up to reality - // TODO: Solve this in a less terrible way - _glfwPlatformPollEvents(); - - const NSUInteger styleMask = getStyleMask(window); - [window->ns.object setStyleMask:styleMask]; - // HACK: Changing the style mask can cause the first responder to be cleared - [window->ns.object makeFirstResponder:window->ns.view]; - - if (window->monitor) - { - [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; - [window->ns.object setHasShadow:NO]; - - acquireMonitor(window); - } - else - { - NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), - width, height); - NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect - styleMask:styleMask]; - [window->ns.object setFrame:frameRect display:YES]; - - if (window->numer != GLFW_DONT_CARE && - window->denom != GLFW_DONT_CARE) - { - [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, - window->denom)]; - } - - if (window->minwidth != GLFW_DONT_CARE && - window->minheight != GLFW_DONT_CARE) - { - [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, - window->minheight)]; - } - - if (window->maxwidth != GLFW_DONT_CARE && - window->maxheight != GLFW_DONT_CARE) - { - [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, - window->maxheight)]; - } - - if (window->floating) - [window->ns.object setLevel:NSFloatingWindowLevel]; - else - [window->ns.object setLevel:NSNormalWindowLevel]; - - [window->ns.object setHasShadow:YES]; - // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window - // title property but the miniwindow title property is unaffected - [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; - } - - } // autoreleasepool -} - -int _glfwPlatformWindowFocused(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isKeyWindow]; - } // autoreleasepool -} - -int _glfwPlatformWindowIconified(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isMiniaturized]; - } // autoreleasepool -} - -int _glfwPlatformWindowVisible(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isVisible]; - } // autoreleasepool -} - -int _glfwPlatformWindowMaximized(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isZoomed]; - } // autoreleasepool -} - -int _glfwPlatformWindowHovered(_GLFWwindow* window) -{ - @autoreleasepool { - - const NSPoint point = [NSEvent mouseLocation]; - - if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != - [window->ns.object windowNumber]) - { - return GLFW_FALSE; - } - - return NSMouseInRect(point, - [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); - - } // autoreleasepool -} - -int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) -{ - @autoreleasepool { - return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setStyleMask:getStyleMask(window)]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setStyleMask:getStyleMask(window)]; - [window->ns.object makeFirstResponder:window->ns.view]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - if (enabled) - [window->ns.object setLevel:NSFloatingWindowLevel]; - else - [window->ns.object setLevel:NSNormalWindowLevel]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setIgnoresMouseEvents:enabled]; - } -} - -float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) -{ - @autoreleasepool { - return (float) [window->ns.object alphaValue]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) -{ - @autoreleasepool { - [window->ns.object setAlphaValue:opacity]; - } // autoreleasepool -} - -void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) -{ - _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, - "Cocoa: Raw mouse motion not yet implemented"); -} - -GLFWbool _glfwPlatformRawMouseMotionSupported(void) -{ - return GLFW_FALSE; -} - -void _glfwPlatformPollEvents(void) -{ - @autoreleasepool { - - for (;;) - { - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - - } // autoreleasepool -} - -void _glfwPlatformWaitEvents(void) -{ - @autoreleasepool { - - // I wanted to pass NO to dequeue:, and rely on PollEvents to - // dequeue and send. For reasons not at all clear to me, passing - // NO to dequeue: causes this method never to return. - NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantFuture] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -void _glfwPlatformWaitEventsTimeout(double timeout) -{ - @autoreleasepool { - - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:date - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event) - [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -void _glfwPlatformPostEmptyEvent(void) -{ - @autoreleasepool { - - 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]; - - } // autoreleasepool -} - -void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - - if (xpos) - *xpos = pos.x; - if (ypos) - *ypos = contentRect.size.height - pos.y; - - } // autoreleasepool -} - -void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) -{ - @autoreleasepool { - - updateCursorImage(window); - - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - - window->ns.cursorWarpDeltaX += x - pos.x; - window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; - - if (window->monitor) - { - CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, - CGPointMake(x, y)); - } - else - { - const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); - const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; - const NSPoint globalPoint = globalRect.origin; - - CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, - _glfwTransformYNS(globalPoint.y))); - } - - } // autoreleasepool -} - -void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) -{ - @autoreleasepool { - if (_glfwPlatformWindowFocused(window)) - updateCursorMode(window); - } // autoreleasepool -} - -const char* _glfwPlatformGetScancodeName(int scancode) -{ - @autoreleasepool { - - if (scancode < 0 || scancode > 0xff || - _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) - { - _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode"); - return NULL; - } - - const int key = _glfw.ns.keycodes[scancode]; - - UInt32 deadKeyState = 0; - UniChar characters[4]; - UniCharCount characterCount = 0; - - if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], - scancode, - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &deadKeyState, - sizeof(characters) / sizeof(characters[0]), - &characterCount, - characters) != noErr) - { - return NULL; - } - - if (!characterCount) - return NULL; - - CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - characters, - characterCount, - kCFAllocatorNull); - CFStringGetCString(string, - _glfw.ns.keynames[key], - sizeof(_glfw.ns.keynames[key]), - kCFStringEncodingUTF8); - CFRelease(string); - - return _glfw.ns.keynames[key]; - - } // autoreleasepool -} - -int _glfwPlatformGetKeyScancode(int key) -{ - return _glfw.ns.scancodes[key]; -} - -int _glfwPlatformCreateCursor(_GLFWcursor* cursor, - const GLFWimage* image, - int xhot, int yhot) -{ - @autoreleasepool { - - NSImage* native; - NSBitmapImageRep* rep; - - rep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - pixelsWide:image->width - pixelsHigh:image->height - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSCalibratedRGBColorSpace - bitmapFormat:NSBitmapFormatAlphaNonpremultiplied - bytesPerRow:image->width * 4 - bitsPerPixel:32]; - - if (rep == nil) - return GLFW_FALSE; - - memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); - - native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; - [native addRepresentation:rep]; - - cursor->ns.object = [[NSCursor alloc] initWithImage:native - hotSpot:NSMakePoint(xhot, yhot)]; - - [native release]; - [rep release]; - - if (cursor->ns.object == nil) - return GLFW_FALSE; - - return GLFW_TRUE; - - } // autoreleasepool -} - -int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) -{ - @autoreleasepool { - - SEL cursorSelector = NULL; - - // HACK: Try to use a private message - switch (shape) - { - case GLFW_RESIZE_EW_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor"); - break; - case GLFW_RESIZE_NS_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor"); - break; - case GLFW_RESIZE_NWSE_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor"); - break; - case GLFW_RESIZE_NESW_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor"); - break; - } - - if (cursorSelector && [NSCursor respondsToSelector:cursorSelector]) - { - id object = [NSCursor performSelector:cursorSelector]; - if ([object isKindOfClass:[NSCursor class]]) - cursor->ns.object = object; - } - - if (!cursor->ns.object) - { - switch (shape) - { - case GLFW_ARROW_CURSOR: - cursor->ns.object = [NSCursor arrowCursor]; - break; - case GLFW_IBEAM_CURSOR: - cursor->ns.object = [NSCursor IBeamCursor]; - break; - case GLFW_CROSSHAIR_CURSOR: - cursor->ns.object = [NSCursor crosshairCursor]; - break; - case GLFW_POINTING_HAND_CURSOR: - cursor->ns.object = [NSCursor pointingHandCursor]; - break; - case GLFW_RESIZE_EW_CURSOR: - cursor->ns.object = [NSCursor resizeLeftRightCursor]; - break; - case GLFW_RESIZE_NS_CURSOR: - cursor->ns.object = [NSCursor resizeUpDownCursor]; - break; - case GLFW_RESIZE_ALL_CURSOR: - cursor->ns.object = [NSCursor closedHandCursor]; - break; - case GLFW_NOT_ALLOWED_CURSOR: - cursor->ns.object = [NSCursor operationNotAllowedCursor]; - break; - } - } - - if (!cursor->ns.object) - { - _glfwInputError(GLFW_CURSOR_UNAVAILABLE, - "Cocoa: Standard cursor shape unavailable"); - return GLFW_FALSE; - } - - [cursor->ns.object retain]; - return GLFW_TRUE; - - } // autoreleasepool -} - -void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) -{ - @autoreleasepool { - if (cursor->ns.object) - [(NSCursor*) cursor->ns.object release]; - } // autoreleasepool -} - -void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) -{ - @autoreleasepool { - if (cursorInContentArea(window)) - updateCursorImage(window); - } // autoreleasepool -} - -void _glfwPlatformSetClipboardString(const char* string) -{ - @autoreleasepool { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; - [pasteboard setString:@(string) forType:NSPasteboardTypeString]; - } // autoreleasepool -} - -const char* _glfwPlatformGetClipboardString(void) -{ - @autoreleasepool { - - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - - if (![[pasteboard types] containsObject:NSPasteboardTypeString]) - { - _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "Cocoa: Failed to retrieve string from pasteboard"); - return NULL; - } - - NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; - if (!object) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to retrieve object from pasteboard"); - return NULL; - } - - free(_glfw.ns.clipboardString); - _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); - - return _glfw.ns.clipboardString; - - } // autoreleasepool -} - -EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) -{ - if (_glfw.egl.ANGLE_platform_angle) - { - int type = 0; - - if (_glfw.egl.ANGLE_platform_angle_opengl) - { - if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) - type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; - } - - if (_glfw.egl.ANGLE_platform_angle_metal) - { - if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) - type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; - } - - if (type) - { - *attribs = calloc(3, sizeof(EGLint)); - (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; - (*attribs)[1] = type; - (*attribs)[2] = EGL_NONE; - return EGL_PLATFORM_ANGLE_ANGLE; - } - } - - return 0; -} - -EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) -{ - return EGL_DEFAULT_DISPLAY; -} - -EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) -{ - return window->ns.layer; -} - -void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) -{ - if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) - { - extensions[0] = "VK_KHR_surface"; - extensions[1] = "VK_EXT_metal_surface"; - } - else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) - { - extensions[0] = "VK_KHR_surface"; - extensions[1] = "VK_MVK_macos_surface"; - } -} - -int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, - VkPhysicalDevice device, - uint32_t queuefamily) -{ - return GLFW_TRUE; -} - -VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, - _GLFWwindow* window, - const VkAllocationCallbacks* allocator, - VkSurfaceKHR* surface) -{ - @autoreleasepool { - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 - // HACK: Dynamically load Core Animation to avoid adding an extra - // dependency for the majority who don't use MoltenVK - NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; - if (!bundle) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to find QuartzCore.framework"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - // NOTE: Create the layer here as makeBackingLayer should not return nil - window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; - if (!window->ns.layer) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create layer for view"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - if (window->ns.retina) - [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; - - [window->ns.view setLayer:window->ns.layer]; - [window->ns.view setWantsLayer:YES]; - - VkResult err; - - if (_glfw.vk.EXT_metal_surface) - { - VkMetalSurfaceCreateInfoEXT sci; - - PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; - vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) - vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); - if (!vkCreateMetalSurfaceEXT) - { - _glfwInputError(GLFW_API_UNAVAILABLE, - "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - memset(&sci, 0, sizeof(sci)); - sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; - sci.pLayer = window->ns.layer; - - err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); - } - else - { - VkMacOSSurfaceCreateInfoMVK sci; - - PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; - vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) - vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); - if (!vkCreateMacOSSurfaceMVK) - { - _glfwInputError(GLFW_API_UNAVAILABLE, - "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - memset(&sci, 0, sizeof(sci)); - sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - sci.pView = window->ns.view; - - err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); - } - - if (err) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create Vulkan surface: %s", - _glfwGetVulkanResultString(err)); - } - - return err; -#else - return VK_ERROR_EXTENSION_NOT_PRESENT; -#endif - - } // autoreleasepool -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW native API ////// -////////////////////////////////////////////////////////////////////////// - -GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) -{ - _GLFWwindow* window = (_GLFWwindow*) handle; - _GLFW_REQUIRE_INIT_OR_RETURN(nil); - return window->ns.object; -} - +//======================================================================== +// GLFW 3.4 macOS - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2009-2019 Camilla LΓΆwy +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// It is fine to use C99 in this file because it will not be built with VS +//======================================================================== + +#include "internal.h" + +#include +#include + +// Returns the style mask corresponding to the window settings +// +static NSUInteger getStyleMask(_GLFWwindow* window) +{ + NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; + + if (window->monitor || !window->decorated) + styleMask |= NSWindowStyleMaskBorderless; + else + { + styleMask |= NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable; + + if (window->resizable) + styleMask |= NSWindowStyleMaskResizable; + } + + return styleMask; +} + +// Returns whether the cursor is in the content area of the specified window +// +static GLFWbool cursorInContentArea(_GLFWwindow* window) +{ + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; +} + +// Hides the cursor if not already hidden +// +static void hideCursor(_GLFWwindow* window) +{ + if (!_glfw.ns.cursorHidden) + { + [NSCursor hide]; + _glfw.ns.cursorHidden = GLFW_TRUE; + } +} + +// Shows the cursor if not already shown +// +static void showCursor(_GLFWwindow* window) +{ + if (_glfw.ns.cursorHidden) + { + [NSCursor unhide]; + _glfw.ns.cursorHidden = GLFW_FALSE; + } +} + +// Updates the cursor image according to its cursor mode +// +static void updateCursorImage(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_NORMAL) + { + showCursor(window); + + if (window->cursor) + [(NSCursor*) window->cursor->ns.object set]; + else + [[NSCursor arrowCursor] set]; + } + else + hideCursor(window); +} + +// Apply chosen cursor mode to a focused window +// +static void updateCursorMode(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + _glfw.ns.disabledCursorWindow = window; + _glfwPlatformGetCursorPos(window, + &_glfw.ns.restoreCursorPosX, + &_glfw.ns.restoreCursorPosY); + _glfwCenterCursorInContentArea(window); + CGAssociateMouseAndMouseCursorPosition(false); + } + else if (_glfw.ns.disabledCursorWindow == window) + { + _glfw.ns.disabledCursorWindow = NULL; + CGAssociateMouseAndMouseCursorPosition(true); + _glfwPlatformSetCursorPos(window, + _glfw.ns.restoreCursorPosX, + _glfw.ns.restoreCursorPosY); + } + + if (cursorInContentArea(window)) + updateCursorImage(window); +} + +// Make the specified window and its video mode active on its monitor +// +static void acquireMonitor(_GLFWwindow* window) +{ + _glfwSetVideoModeNS(window->monitor, &window->videoMode); + const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); + const NSRect frame = NSMakeRect(bounds.origin.x, + _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), + bounds.size.width, + bounds.size.height); + + [window->ns.object setFrame:frame display:YES]; + + _glfwInputMonitorWindow(window->monitor, window); +} + +// Remove the window and restore the original video mode +// +static void releaseMonitor(_GLFWwindow* window) +{ + if (window->monitor->window != window) + return; + + _glfwInputMonitorWindow(window->monitor, NULL); + _glfwRestoreVideoModeNS(window->monitor); +} + +// Translates macOS key modifiers into GLFW ones +// +static int translateFlags(NSUInteger flags) +{ + int mods = 0; + + if (flags & NSEventModifierFlagShift) + mods |= GLFW_MOD_SHIFT; + if (flags & NSEventModifierFlagControl) + mods |= GLFW_MOD_CONTROL; + if (flags & NSEventModifierFlagOption) + mods |= GLFW_MOD_ALT; + if (flags & NSEventModifierFlagCommand) + mods |= GLFW_MOD_SUPER; + if (flags & NSEventModifierFlagCapsLock) + mods |= GLFW_MOD_CAPS_LOCK; + + return mods; +} + +// Translates a macOS keycode to a GLFW keycode +// +static int translateKey(unsigned int key) +{ + if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) + return GLFW_KEY_UNKNOWN; + + return _glfw.ns.keycodes[key]; +} + +// Translate a GLFW keycode to a Cocoa modifier flag +// +static NSUInteger translateKeyToModifierFlag(int key) +{ + switch (key) + { + case GLFW_KEY_LEFT_SHIFT: + case GLFW_KEY_RIGHT_SHIFT: + return NSEventModifierFlagShift; + case GLFW_KEY_LEFT_CONTROL: + case GLFW_KEY_RIGHT_CONTROL: + return NSEventModifierFlagControl; + case GLFW_KEY_LEFT_ALT: + case GLFW_KEY_RIGHT_ALT: + return NSEventModifierFlagOption; + case GLFW_KEY_LEFT_SUPER: + case GLFW_KEY_RIGHT_SUPER: + return NSEventModifierFlagCommand; + case GLFW_KEY_CAPS_LOCK: + return NSEventModifierFlagCapsLock; + } + + return 0; +} + +// Defines a constant for empty ranges in NSTextInputClient +// +static const NSRange kEmptyRange = { NSNotFound, 0 }; + + +//------------------------------------------------------------------------ +// Delegate for window related notifications +//------------------------------------------------------------------------ + +@interface GLFWWindowDelegate : NSObject +{ + _GLFWwindow* window; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWWindowDelegate + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + window = initWindow; + + return self; +} + +- (BOOL)windowShouldClose:(id)sender +{ + _glfwInputWindowCloseRequest(window); + return NO; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + const int maximized = [window->ns.object isZoomed]; + if (window->ns.maximized != maximized) + { + window->ns.maximized = maximized; + _glfwInputWindowMaximize(window, maximized); + } + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } + + if (contentRect.size.width != window->ns.width || + contentRect.size.height != window->ns.height) + { + window->ns.width = contentRect.size.width; + window->ns.height = contentRect.size.height; + _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); + } +} + +- (void)windowDidMove:(NSNotification *)notification +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + int x, y; + _glfwPlatformGetWindowPos(window, &x, &y); + _glfwInputWindowPos(window, x, y); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification +{ + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowIconify(window, GLFW_TRUE); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification +{ + if (window->monitor) + acquireMonitor(window); + + _glfwInputWindowIconify(window, GLFW_FALSE); +} + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + _glfwInputWindowFocus(window, GLFW_TRUE); + updateCursorMode(window); +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + if (window->monitor && window->autoIconify) + _glfwPlatformIconifyWindow(window); + + _glfwInputWindowFocus(window, GLFW_FALSE); +} + +- (void)windowDidChangeOcclusionState:(NSNotification* )notification +{ + if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) + window->ns.occluded = GLFW_FALSE; + else + window->ns.occluded = GLFW_TRUE; +} + +@end + + +//------------------------------------------------------------------------ +// Content view class for the GLFW window +//------------------------------------------------------------------------ + +@interface GLFWContentView : NSView +{ + _GLFWwindow* window; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWContentView + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + { + window = initWindow; + trackingArea = nil; + markedText = [[NSMutableAttributedString alloc] init]; + + [self updateTrackingAreas]; + // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available + // on 10.7 without having been deprecated yet + [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]]; + } + + return self; +} + +- (void)dealloc +{ + [trackingArea release]; + [markedText release]; + [super dealloc]; +} + +- (BOOL)isOpaque +{ + return [window->ns.object isOpaque]; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (void)updateLayer +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + _glfwInputWindowDamage(window); +} + +- (void)cursorUpdate:(NSEvent *)event +{ + updateCursorImage(window); +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)event +{ + return YES; +} + +- (void)mouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseMoved:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; + const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; + + _glfwInputCursorPos(window, + window->virtualCursorPosX + dx, + window->virtualCursorPosY + dy); + } + else + { + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [event locationInWindow]; + + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + } + + window->ns.cursorWarpDeltaX = 0; + window->ns.cursorWarpDeltaY = 0; +} + +- (void)rightMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseExited:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + showCursor(window); + + _glfwInputCursorEnter(window, GLFW_FALSE); +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + hideCursor(window); + + _glfwInputCursorEnter(window, GLFW_TRUE); +} + +- (void)viewDidChangeBackingProperties +{ + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } + + const float xscale = fbRect.size.width / contentRect.size.width; + const float yscale = fbRect.size.height / contentRect.size.height; + + if (xscale != window->ns.xscale || yscale != window->ns.yscale) + { + window->ns.xscale = xscale; + window->ns.yscale = yscale; + _glfwInputWindowContentScale(window, xscale, yscale); + + if (window->ns.retina && window->ns.layer) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + } +} + +- (void)drawRect:(NSRect)rect +{ + _glfwInputWindowDamage(window); +} + +- (void)updateTrackingAreas +{ + if (trackingArea != nil) + { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:options + owner:self + userInfo:nil]; + + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (void)keyDown:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); + + [self interpretKeyEvents:@[event]]; +} + +- (void)flagsChanged:(NSEvent *)event +{ + int action; + const unsigned int modifierFlags = + [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + const int key = translateKey([event keyCode]); + const int mods = translateFlags(modifierFlags); + const NSUInteger keyFlag = translateKeyToModifierFlag(key); + + if (keyFlag & modifierFlags) + { + if (window->keys[key] == GLFW_PRESS) + action = GLFW_RELEASE; + else + action = GLFW_PRESS; + } + else + action = GLFW_RELEASE; + + _glfwInputKey(window, key, [event keyCode], action, mods); +} + +- (void)keyUp:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); +} + +- (void)scrollWheel:(NSEvent *)event +{ + double deltaX = [event scrollingDeltaX]; + double deltaY = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) + { + deltaX *= 0.1; + deltaY *= 0.1; + } + + if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) + _glfwInputScroll(window, deltaX, deltaY); +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + // HACK: We don't know what to say here because we don't know what the + // application wants to do with the paths + return NSDragOperationGeneric; +} + +- (BOOL)performDragOperation:(id )sender +{ + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [sender draggingLocation]; + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + + NSPasteboard* pasteboard = [sender draggingPasteboard]; + NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; + NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] + options:options]; + const NSUInteger count = [urls count]; + if (count) + { + char** paths = calloc(count, sizeof(char*)); + + for (NSUInteger i = 0; i < count; i++) + paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]); + + _glfwInputDrop(window, (int) count, (const char**) paths); + + for (NSUInteger i = 0; i < count; i++) + free(paths[i]); + free(paths); + } + + 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 +{ + [markedText release]; + if ([string isKindOfClass:[NSAttributedString class]]) + markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; + else + markedText = [[NSMutableAttributedString alloc] initWithString:string]; +} + +- (void)unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*)validAttributesForMarkedText +{ + return [NSArray array]; +} + +- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + const NSRect frame = [window->ns.view frame]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + NSString* characters; + NSEvent* event = [NSApp currentEvent]; + const int mods = translateFlags([event modifierFlags]); + const int plain = !(mods & GLFW_MOD_SUPER); + + if ([string isKindOfClass:[NSAttributedString class]]) + characters = [string string]; + else + characters = (NSString*) string; + + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) + { + uint32_t codepoint = 0; + + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if (codepoint >= 0xf700 && codepoint <= 0xf7ff) + continue; + + _glfwInputChar(window, codepoint, mods, plain); + } + } +} + +- (void)doCommandBySelector:(SEL)selector +{ +} + +@end + + +//------------------------------------------------------------------------ +// GLFW window class +//------------------------------------------------------------------------ + +@interface GLFWWindow : NSWindow {} +@end + +@implementation GLFWWindow + +- (BOOL)canBecomeKeyWindow +{ + // Required for NSWindowStyleMaskBorderless windows + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +@end + + +// Create the Cocoa window +// +static GLFWbool createNativeWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWfbconfig* fbconfig) +{ + window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; + if (window->ns.delegate == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create window delegate"); + return GLFW_FALSE; + } + + NSRect contentRect; + + if (window->monitor) + { + GLFWvidmode mode; + int xpos, ypos; + + _glfwPlatformGetVideoMode(window->monitor, &mode); + _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); + + contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); + } + else + contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); + + window->ns.object = [[GLFWWindow alloc] + initWithContentRect:contentRect + styleMask:getStyleMask(window) + backing:NSBackingStoreBuffered + defer:NO]; + + if (window->ns.object == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); + return GLFW_FALSE; + } + + if (window->monitor) + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + else + { + [(NSWindow*) window->ns.object center]; + _glfw.ns.cascadePoint = + NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: + NSPointFromCGPoint(_glfw.ns.cascadePoint)]); + + if (wndconfig->resizable) + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | + NSWindowCollectionBehaviorManaged; + [window->ns.object setCollectionBehavior:behavior]; + } + + if (wndconfig->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + + if (wndconfig->maximized) + [window->ns.object zoom:nil]; + } + + if (strlen(wndconfig->ns.frameName)) + [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; + + window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; + window->ns.retina = wndconfig->ns.retina; + + if (fbconfig->transparent) + { + [window->ns.object setOpaque:NO]; + [window->ns.object setHasShadow:NO]; + [window->ns.object setBackgroundColor:[NSColor clearColor]]; + } + + [window->ns.object setContentView:window->ns.view]; + [window->ns.object makeFirstResponder:window->ns.view]; + [window->ns.object setTitle:@(wndconfig->title)]; + [window->ns.object setDelegate:window->ns.delegate]; + [window->ns.object setAcceptsMouseMovedEvents:YES]; + [window->ns.object setRestorable:NO]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)]) + [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed]; +#endif + + _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); + _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); + + return GLFW_TRUE; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +// Transforms a y-coordinate between the CG display and NS screen spaces +// +float _glfwTransformYNS(float y) +{ + return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +int _glfwPlatformCreateWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig) +{ + @autoreleasepool { + + if (!createNativeWindow(window, wndconfig, fbconfig)) + return GLFW_FALSE; + + if (ctxconfig->client != GLFW_NO_API) + { + if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) + { + if (!_glfwInitNSGL()) + return GLFW_FALSE; + if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) + { + // EGL implementation on macOS use CALayer* EGLNativeWindowType so we + // need to get the layer for EGL window surface creation. + [window->ns.view setWantsLayer:YES]; + window->ns.layer = [window->ns.view layer]; + + if (!_glfwInitEGL()) + return GLFW_FALSE; + if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) + { + if (!_glfwInitOSMesa()) + return GLFW_FALSE; + if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + } + + if (window->monitor) + { + _glfwPlatformShowWindow(window); + _glfwPlatformFocusWindow(window); + acquireMonitor(window); + } + + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + + if (_glfw.ns.disabledCursorWindow == window) + _glfw.ns.disabledCursorWindow = NULL; + + [window->ns.object orderOut:nil]; + + if (window->monitor) + releaseMonitor(window); + + if (window->context.destroy) + window->context.destroy(window); + + [window->ns.object setDelegate:nil]; + [window->ns.delegate release]; + window->ns.delegate = nil; + + [window->ns.view release]; + window->ns.view = nil; + + [window->ns.object close]; + window->ns.object = nil; + + // HACK: Allow Cocoa to catch up before returning + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + return NULL; +} + +void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) +{ + @autoreleasepool { + NSString* string = @(title); + [window->ns.object setTitle:string]; + // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it + // if the window lacks NSWindowStyleMaskTitled + [window->ns.object setMiniwindowTitle:string]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Cocoa: Regular windows do not have icons on macOS"); +} + +void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + + if (xpos) + *xpos = contentRect.origin.x; + if (ypos) + *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); + + } // autoreleasepool +} + +void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); + const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; + [window->ns.object setFrameOrigin:frameRect.origin]; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + + if (width) + *width = contentRect.size.width; + if (height) + *height = contentRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) +{ + @autoreleasepool { + + if (window->monitor) + { + if (window->monitor->window == window) + acquireMonitor(window); + } + else + { + NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + contentRect.origin.y += contentRect.size.height - height; + contentRect.size = NSMakeSize(width, height); + [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] + display:YES]; + } + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + @autoreleasepool { + + 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)]; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + @autoreleasepool { + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; + else + [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; + } // autoreleasepool +} + +void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (width) + *width = (int) fbRect.size.width; + if (height) + *height = (int) fbRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, + int* left, int* top, + int* right, int* bottom) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; + + if (left) + *left = contentRect.origin.x - frameRect.origin.x; + if (top) + *top = frameRect.origin.y + frameRect.size.height - + contentRect.origin.y - contentRect.size.height; + if (right) + *right = frameRect.origin.x + frameRect.size.width - + contentRect.origin.x - contentRect.size.width; + if (bottom) + *bottom = contentRect.origin.y - frameRect.origin.y; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, + float* xscale, float* yscale) +{ + @autoreleasepool { + + const NSRect points = [window->ns.view frame]; + const NSRect pixels = [window->ns.view convertRectToBacking:points]; + + if (xscale) + *xscale = (float) (pixels.size.width / points.size.width); + if (yscale) + *yscale = (float) (pixels.size.height / points.size.height); + + } // autoreleasepool +} + +void _glfwPlatformIconifyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object miniaturize:nil]; + } // autoreleasepool +} + +void _glfwPlatformRestoreWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if ([window->ns.object isMiniaturized]) + [window->ns.object deminiaturize:nil]; + else if ([window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformMaximizeWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if (![window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformShowWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformHideWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderOut:nil]; + } // autoreleasepool +} + +void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) +{ + @autoreleasepool { + [NSApp requestUserAttention:NSInformationalRequest]; + } // autoreleasepool +} + +void _glfwPlatformFocusWindow(_GLFWwindow* window) +{ + @autoreleasepool { + // Make us the active application + // HACK: This is here to prevent applications using only hidden windows from + // being activated, but should probably not be done every time any + // window is shown + [NSApp activateIgnoringOtherApps:YES]; + [window->ns.object makeKeyAndOrderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, + _GLFWmonitor* monitor, + int xpos, int ypos, + int width, int height, + int refreshRate) +{ + @autoreleasepool { + + if (window->monitor == monitor) + { + if (monitor) + { + if (monitor->window == window) + acquireMonitor(window); + } + else + { + const NSRect contentRect = + NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); + const NSRect frameRect = + [window->ns.object frameRectForContentRect:contentRect + styleMask:getStyleMask(window)]; + + [window->ns.object setFrame:frameRect display:YES]; + } + + return; + } + + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowMonitor(window, monitor); + + // HACK: Allow the state cached in Cocoa to catch up to reality + // TODO: Solve this in a less terrible way + _glfwPlatformPollEvents(); + + const NSUInteger styleMask = getStyleMask(window); + [window->ns.object setStyleMask:styleMask]; + // HACK: Changing the style mask can cause the first responder to be cleared + [window->ns.object makeFirstResponder:window->ns.view]; + + if (window->monitor) + { + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + [window->ns.object setHasShadow:NO]; + + acquireMonitor(window); + } + else + { + NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), + width, height); + NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect + styleMask:styleMask]; + [window->ns.object setFrame:frameRect display:YES]; + + if (window->numer != GLFW_DONT_CARE && + window->denom != GLFW_DONT_CARE) + { + [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, + window->denom)]; + } + + if (window->minwidth != GLFW_DONT_CARE && + window->minheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, + window->minheight)]; + } + + if (window->maxwidth != GLFW_DONT_CARE && + window->maxheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, + window->maxheight)]; + } + + if (window->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + + [window->ns.object setHasShadow:YES]; + // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window + // title property but the miniwindow title property is unaffected + [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; + } + + } // autoreleasepool +} + +int _glfwPlatformWindowFocused(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isKeyWindow]; + } // autoreleasepool +} + +int _glfwPlatformWindowIconified(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isMiniaturized]; + } // autoreleasepool +} + +int _glfwPlatformWindowVisible(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isVisible]; + } // autoreleasepool +} + +int _glfwPlatformWindowMaximized(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isZoomed]; + } // autoreleasepool +} + +int _glfwPlatformWindowHovered(_GLFWwindow* window) +{ + @autoreleasepool { + + const NSPoint point = [NSEvent mouseLocation]; + + if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != + [window->ns.object windowNumber]) + { + return GLFW_FALSE; + } + + return NSMouseInRect(point, + [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); + + } // autoreleasepool +} + +int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) +{ + @autoreleasepool { + return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setStyleMask:getStyleMask(window)]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setStyleMask:getStyleMask(window)]; + [window->ns.object makeFirstResponder:window->ns.view]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + if (enabled) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setIgnoresMouseEvents:enabled]; + } +} + +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + @autoreleasepool { + return (float) [window->ns.object alphaValue]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + @autoreleasepool { + [window->ns.object setAlphaValue:opacity]; + } // autoreleasepool +} + +void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Cocoa: Raw mouse motion not yet implemented"); +} + +GLFWbool _glfwPlatformRawMouseMotionSupported(void) +{ + return GLFW_FALSE; +} + +void _glfwPlatformPollEvents(void) +{ + @autoreleasepool { + + for (;;) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event == nil) + break; + + [NSApp sendEvent:event]; + } + + } // autoreleasepool +} + +void _glfwPlatformWaitEvents(void) +{ + @autoreleasepool { + + // I wanted to pass NO to dequeue:, and rely on PollEvents to + // dequeue and send. For reasons not at all clear to me, passing + // NO to dequeue: causes this method never to return. + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + @autoreleasepool { + + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformPostEmptyEvent(void) +{ + @autoreleasepool { + + 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]; + + } // autoreleasepool +} + +void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + if (xpos) + *xpos = pos.x; + if (ypos) + *ypos = contentRect.size.height - pos.y; + + } // autoreleasepool +} + +void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) +{ + @autoreleasepool { + + updateCursorImage(window); + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + window->ns.cursorWarpDeltaX += x - pos.x; + window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; + + if (window->monitor) + { + CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, + CGPointMake(x, y)); + } + else + { + const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); + const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; + const NSPoint globalPoint = globalRect.origin; + + CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, + _glfwTransformYNS(globalPoint.y))); + } + + } // autoreleasepool +} + +void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) +{ + @autoreleasepool { + if (_glfwPlatformWindowFocused(window)) + updateCursorMode(window); + } // autoreleasepool +} + +const char* _glfwPlatformGetScancodeName(int scancode) +{ + @autoreleasepool { + + if (scancode < 0 || scancode > 0xff || + _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode"); + return NULL; + } + + const int key = _glfw.ns.keycodes[scancode]; + + UInt32 deadKeyState = 0; + UniChar characters[4]; + UniCharCount characterCount = 0; + + if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], + scancode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &deadKeyState, + sizeof(characters) / sizeof(characters[0]), + &characterCount, + characters) != noErr) + { + return NULL; + } + + if (!characterCount) + return NULL; + + CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + characters, + characterCount, + kCFAllocatorNull); + CFStringGetCString(string, + _glfw.ns.keynames[key], + sizeof(_glfw.ns.keynames[key]), + kCFStringEncodingUTF8); + CFRelease(string); + + return _glfw.ns.keynames[key]; + + } // autoreleasepool +} + +int _glfwPlatformGetKeyScancode(int key) +{ + return _glfw.ns.scancodes[key]; +} + +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, + const GLFWimage* image, + int xhot, int yhot) +{ + @autoreleasepool { + + NSImage* native; + NSBitmapImageRep* rep; + + rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:image->width + pixelsHigh:image->height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:NSBitmapFormatAlphaNonpremultiplied + bytesPerRow:image->width * 4 + bitsPerPixel:32]; + + if (rep == nil) + return GLFW_FALSE; + + memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); + + native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; + [native addRepresentation:rep]; + + cursor->ns.object = [[NSCursor alloc] initWithImage:native + hotSpot:NSMakePoint(xhot, yhot)]; + + [native release]; + [rep release]; + + if (cursor->ns.object == nil) + return GLFW_FALSE; + + return GLFW_TRUE; + + } // autoreleasepool +} + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) +{ + @autoreleasepool { + + SEL cursorSelector = NULL; + + // HACK: Try to use a private message + switch (shape) + { + case GLFW_RESIZE_EW_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor"); + break; + case GLFW_RESIZE_NS_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor"); + break; + case GLFW_RESIZE_NWSE_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor"); + break; + case GLFW_RESIZE_NESW_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor"); + break; + } + + if (cursorSelector && [NSCursor respondsToSelector:cursorSelector]) + { + id object = [NSCursor performSelector:cursorSelector]; + if ([object isKindOfClass:[NSCursor class]]) + cursor->ns.object = object; + } + + if (!cursor->ns.object) + { + switch (shape) + { + case GLFW_ARROW_CURSOR: + cursor->ns.object = [NSCursor arrowCursor]; + break; + case GLFW_IBEAM_CURSOR: + cursor->ns.object = [NSCursor IBeamCursor]; + break; + case GLFW_CROSSHAIR_CURSOR: + cursor->ns.object = [NSCursor crosshairCursor]; + break; + case GLFW_POINTING_HAND_CURSOR: + cursor->ns.object = [NSCursor pointingHandCursor]; + break; + case GLFW_RESIZE_EW_CURSOR: + cursor->ns.object = [NSCursor resizeLeftRightCursor]; + break; + case GLFW_RESIZE_NS_CURSOR: + cursor->ns.object = [NSCursor resizeUpDownCursor]; + break; + case GLFW_RESIZE_ALL_CURSOR: + cursor->ns.object = [NSCursor closedHandCursor]; + break; + case GLFW_NOT_ALLOWED_CURSOR: + cursor->ns.object = [NSCursor operationNotAllowedCursor]; + break; + } + } + + if (!cursor->ns.object) + { + _glfwInputError(GLFW_CURSOR_UNAVAILABLE, + "Cocoa: Standard cursor shape unavailable"); + return GLFW_FALSE; + } + + [cursor->ns.object retain]; + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursor->ns.object) + [(NSCursor*) cursor->ns.object release]; + } // autoreleasepool +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursorInContentArea(window)) + updateCursorImage(window); + } // autoreleasepool +} + +void _glfwPlatformSetClipboardString(const char* string) +{ + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(string) forType:NSPasteboardTypeString]; + } // autoreleasepool +} + +const char* _glfwPlatformGetClipboardString(void) +{ + @autoreleasepool { + + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, + "Cocoa: Failed to retrieve string from pasteboard"); + return NULL; + } + + NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; + if (!object) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to retrieve object from pasteboard"); + return NULL; + } + + free(_glfw.ns.clipboardString); + _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); + + return _glfw.ns.clipboardString; + + } // autoreleasepool +} + +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.ANGLE_platform_angle) + { + int type = 0; + + if (_glfw.egl.ANGLE_platform_angle_opengl) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_metal) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) + type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; + } + + if (type) + { + *attribs = calloc(3, sizeof(EGLint)); + (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + (*attribs)[1] = type; + (*attribs)[2] = EGL_NONE; + return EGL_PLATFORM_ANGLE_ANGLE; + } + } + + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + return window->ns.layer; +} + +void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) +{ + if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_EXT_metal_surface"; + } + else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_MVK_macos_surface"; + } +} + +int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, + VkPhysicalDevice device, + uint32_t queuefamily) +{ + return GLFW_TRUE; +} + +VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, + _GLFWwindow* window, + const VkAllocationCallbacks* allocator, + VkSurfaceKHR* surface) +{ + @autoreleasepool { + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 + // HACK: Dynamically load Core Animation to avoid adding an extra + // dependency for the majority who don't use MoltenVK + NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; + if (!bundle) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to find QuartzCore.framework"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + // NOTE: Create the layer here as makeBackingLayer should not return nil + window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; + if (!window->ns.layer) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create layer for view"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + if (window->ns.retina) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + + [window->ns.view setLayer:window->ns.layer]; + [window->ns.view setWantsLayer:YES]; + + VkResult err; + + if (_glfw.vk.EXT_metal_surface) + { + VkMetalSurfaceCreateInfoEXT sci; + + PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; + vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) + vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); + if (!vkCreateMetalSurfaceEXT) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + sci.pLayer = window->ns.layer; + + err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); + } + else + { + VkMacOSSurfaceCreateInfoMVK sci; + + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; + vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) + vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); + if (!vkCreateMacOSSurfaceMVK) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + sci.pView = window->ns.view; + + err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); + } + + if (err) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create Vulkan surface: %s", + _glfwGetVulkanResultString(err)); + } + + return err; +#else + return VK_ERROR_EXTENSION_NOT_PRESENT; +#endif + + } // autoreleasepool +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT_OR_RETURN(nil); + return window->ns.object; +} + From 70948d0bccf271cd3742b5cad59cea291b1bbb27 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Tue, 15 Jun 2021 17:36:31 +1000 Subject: [PATCH 12/16] implemented get window title for win32 --- src/win32_window.c | 1 + src/x11_window.c | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/win32_window.c b/src/win32_window.c index 9aa106cc..69840fc6 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1471,6 +1471,7 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) if(count == 0) { int error; + SetLastError(0); error = GetLastError(); if(error != 0) diff --git a/src/x11_window.c b/src/x11_window.c index e8968c17..e5ad70aa 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2084,7 +2084,6 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) char* title; _glfwGrabErrorHandlerX11(); - if (XGetWMName(_glfw.x11.display, window->x11.handle, &textProperty) == 0) { _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Could not get window title"); @@ -2115,7 +2114,6 @@ char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) XFree(textProperty.value); if (charList != NULL) XFreeStringList(charList); - return calloc(1, sizeof(char)); } From ec676bdaf8df66b1f3d54ad693ad9a0a0fd31aa9 Mon Sep 17 00:00:00 2001 From: Bayemite Date: Thu, 3 Jun 2021 21:36:35 +1000 Subject: [PATCH 13/16] cocoa implementation stub (needs testing) --- .appveyor.yml | 19 +- .github/workflows/build.yml | 116 ++ .travis.yml | 116 -- README.md | 2 +- examples/heightmap.c | 10 +- src/cocoa_window.m | 3856 +++++++++++++++++------------------ testing/CMakeLists.txt | 21 + testing/test.cpp | 48 + 8 files changed, 2122 insertions(+), 2066 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml create mode 100644 testing/CMakeLists.txt create mode 100644 testing/test.cpp diff --git a/.appveyor.yml b/.appveyor.yml index a21829a6..2742949b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,10 +1,10 @@ image: - Visual Studio 2015 - - Visual Studio 2019 branches: only: - ci - master + - latest - 3.3-stable skip_tags: true environment: @@ -21,26 +21,13 @@ environment: - GENERATOR: Visual Studio 10 2010 BUILD_SHARED_LIBS: OFF CFLAGS: /WX - - GENERATOR: Visual Studio 16 2019 - BUILD_SHARED_LIBS: ON - CFLAGS: /WX - - GENERATOR: Visual Studio 16 2019 - BUILD_SHARED_LIBS: OFF - CFLAGS: /WX matrix: fast_finish: true - exclude: - - image: Visual Studio 2015 - GENERATOR: Visual Studio 16 2019 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 10 2010 - - image: Visual Studio 2019 - GENERATOR: MinGW Makefiles for: - matrix: - except: - - GENERATOR: Visual Studio 10 2010 + only: + - GENERATOR: MinGW Makefiles build_script: - set PATH=%PATH:C:\Program Files\Git\usr\bin=C:\MinGW\bin% - cmake -S . -B build -G "%GENERATOR%" -DBUILD_SHARED_LIBS=%BUILD_SHARED_LIBS% diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..d87ae8d1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,116 @@ +name: Build +on: + pull_request: + push: + branches: [ ci, master, latest, 3.3-stable ] +permissions: + statuses: write + contents: read + +jobs: + build-linux-x11-clang: + name: X11 (Linux, Clang) + runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt update + sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev + + - name: Configure static library + run: cmake -S . -B build-static + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + + build-linux-wayland-clang: + name: Wayland (Linux, Clang) + runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt update + sudo apt install wayland-protocols libwayland-dev libxkbcommon-dev + + - name: Configure static library + run: cmake -S . -B build-static -D GLFW_USE_WAYLAND=ON + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -D GLFW_USE_WAYLAND=ON -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + + build-linux-null-clang: + name: Null (Linux, Clang) + runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt update + sudo apt install libosmesa6-dev + + - name: Configure static library + run: cmake -S . -B build-static -D GLFW_USE_OSMESA=ON + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -D GLFW_USE_OSMESA=ON -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + + build-macos-cocoa-clang: + name: Cocoa (macOS, Clang) + runs-on: macos-latest + env: + CFLAGS: -Werror + MACOSX_DEPLOYMENT_TARGET: 10.8 + steps: + - uses: actions/checkout@v2 + + - name: Configure static library + run: cmake -S . -B build-static + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + + build-windows-win32-vs2019: + name: Win32 (Windows, VS2019) + runs-on: windows-latest + env: + CFLAGS: /WX + steps: + - uses: actions/checkout@v2 + + - name: Configure static library + run: cmake -S . -B build-static -G "Visual Studio 16 2019" + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -G "Visual Studio 16 2019" -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f669e3ae..00000000 --- a/.travis.yml +++ /dev/null @@ -1,116 +0,0 @@ -language: c -compiler: clang -branches: - only: - - ci - - master - - 3.3-stable -matrix: - include: - - os: linux - dist: xenial - name: "X11 shared library" - addons: - apt: - packages: - - libxrandr-dev - - libxinerama-dev - - libxcursor-dev - - libxi-dev - - libxext-dev - - libx11-dev - env: - - BUILD_SHARED_LIBS=ON - - CFLAGS=-Werror - - os: linux - dist: xenial - name: "X11 static library" - addons: - apt: - packages: - - libxrandr-dev - - libxinerama-dev - - libxcursor-dev - - libxi-dev - - libxext-dev - - libx11-dev - env: - - BUILD_SHARED_LIBS=OFF - - CFLAGS=-Werror - - os: linux - dist: focal - name: "Wayland shared library" - addons: - apt: - packages: - - wayland-protocols - - libwayland-dev - - libxkbcommon-dev - - libegl1-mesa-dev - env: - - USE_WAYLAND=ON - - BUILD_SHARED_LIBS=ON - - CFLAGS=-Werror - - os: linux - dist: focal - name: "Wayland static library" - addons: - apt: - packages: - - wayland-protocols - - libwayland-dev - - libxkbcommon-dev - - libegl1-mesa-dev - env: - - USE_WAYLAND=ON - - BUILD_SHARED_LIBS=OFF - - CFLAGS=-Werror - - os: linux - dist: bionic - name: "Null shared library" - addons: - apt: - packages: - - libosmesa6-dev - env: - - BUILD_SHARED_LIBS=ON - - USE_OSMESA=ON - - CFLAGS=-Werror - - os: linux - dist: bionic - name: "Null static library" - addons: - apt: - packages: - - libosmesa6-dev - env: - - BUILD_SHARED_LIBS=OFF - - USE_OSMESA=ON - - CFLAGS=-Werror - - os: osx - name: "Cocoa shared library" - env: - - BUILD_SHARED_LIBS=ON - - CFLAGS=-Werror - - MACOSX_DEPLOYMENT_TARGET=10.8 - - os: osx - name: "Cocoa static library" - env: - - BUILD_SHARED_LIBS=OFF - - CFLAGS=-Werror - - MACOSX_DEPLOYMENT_TARGET=10.8 -script: - - if grep -Inr '\s$' src include docs tests examples CMake *.md .gitattributes .gitignore; then - echo Trailing whitespace found, aborting; - exit 1; - fi - - mkdir build - - cd build - - cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DGLFW_USE_WAYLAND=${USE_WAYLAND} -DGLFW_USE_OSMESA=${USE_OSMESA} .. - - cmake --build . -notifications: - email: - recipients: - - ci@glfw.org - on_success: never - on_failure: always diff --git a/README.md b/README.md index 6c91d674..14051105 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GLFW -[![Build status](https://travis-ci.org/glfw/glfw.svg?branch=master)](https://travis-ci.org/glfw/glfw) +[![Build status](https://github.com/glfw/glfw/actions/workflows/build.yml/badge.svg)](https://github.com/glfw/glfw/actions) [![Build status](https://ci.appveyor.com/api/projects/status/0kf0ct9831i5l6sp/branch/master?svg=true)](https://ci.appveyor.com/project/elmindreda/glfw) [![Coverity Scan](https://scan.coverity.com/projects/4884/badge.svg)](https://scan.coverity.com/projects/glfw-glfw) diff --git a/examples/heightmap.c b/examples/heightmap.c index 13a3c1e1..988dd0b8 100644 --- a/examples/heightmap.c +++ b/examples/heightmap.c @@ -292,12 +292,12 @@ static void generate_heightmap__circle(float* center_x, float* center_y, { float sign; /* random value for element in between [0-1.0] */ - *center_x = (MAP_SIZE * rand()) / (1.0f * RAND_MAX); - *center_y = (MAP_SIZE * rand()) / (1.0f * RAND_MAX); - *size = (MAX_CIRCLE_SIZE * rand()) / (1.0f * RAND_MAX); - sign = (1.0f * rand()) / (1.0f * RAND_MAX); + *center_x = (MAP_SIZE * rand()) / (float) RAND_MAX; + *center_y = (MAP_SIZE * rand()) / (float) RAND_MAX; + *size = (MAX_CIRCLE_SIZE * rand()) / (float) RAND_MAX; + sign = (1.0f * rand()) / (float) RAND_MAX; sign = (sign < DISPLACEMENT_SIGN_LIMIT) ? -1.0f : 1.0f; - *displacement = (sign * (MAX_DISPLACEMENT * rand())) / (1.0f * RAND_MAX); + *displacement = (sign * (MAX_DISPLACEMENT * rand())) / (float) RAND_MAX; } /* Run the specified number of iterations of the generation process for the diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 3321e0b9..d9e0da83 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1,1928 +1,1928 @@ -//======================================================================== -// GLFW 3.4 macOS - www.glfw.org -//------------------------------------------------------------------------ -// Copyright (c) 2009-2019 Camilla LΓΆwy -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would -// be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not -// be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source -// distribution. -// -//======================================================================== -// It is fine to use C99 in this file because it will not be built with VS -//======================================================================== - -#include "internal.h" - -#include -#include - -// Returns the style mask corresponding to the window settings -// -static NSUInteger getStyleMask(_GLFWwindow* window) -{ - NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; - - if (window->monitor || !window->decorated) - styleMask |= NSWindowStyleMaskBorderless; - else - { - styleMask |= NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable; - - if (window->resizable) - styleMask |= NSWindowStyleMaskResizable; - } - - return styleMask; -} - -// Returns whether the cursor is in the content area of the specified window -// -static GLFWbool cursorInContentArea(_GLFWwindow* window) -{ - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; -} - -// Hides the cursor if not already hidden -// -static void hideCursor(_GLFWwindow* window) -{ - if (!_glfw.ns.cursorHidden) - { - [NSCursor hide]; - _glfw.ns.cursorHidden = GLFW_TRUE; - } -} - -// Shows the cursor if not already shown -// -static void showCursor(_GLFWwindow* window) -{ - if (_glfw.ns.cursorHidden) - { - [NSCursor unhide]; - _glfw.ns.cursorHidden = GLFW_FALSE; - } -} - -// Updates the cursor image according to its cursor mode -// -static void updateCursorImage(_GLFWwindow* window) -{ - if (window->cursorMode == GLFW_CURSOR_NORMAL) - { - showCursor(window); - - if (window->cursor) - [(NSCursor*) window->cursor->ns.object set]; - else - [[NSCursor arrowCursor] set]; - } - else - hideCursor(window); -} - -// Apply chosen cursor mode to a focused window -// -static void updateCursorMode(_GLFWwindow* window) -{ - if (window->cursorMode == GLFW_CURSOR_DISABLED) - { - _glfw.ns.disabledCursorWindow = window; - _glfwPlatformGetCursorPos(window, - &_glfw.ns.restoreCursorPosX, - &_glfw.ns.restoreCursorPosY); - _glfwCenterCursorInContentArea(window); - CGAssociateMouseAndMouseCursorPosition(false); - } - else if (_glfw.ns.disabledCursorWindow == window) - { - _glfw.ns.disabledCursorWindow = NULL; - CGAssociateMouseAndMouseCursorPosition(true); - _glfwPlatformSetCursorPos(window, - _glfw.ns.restoreCursorPosX, - _glfw.ns.restoreCursorPosY); - } - - if (cursorInContentArea(window)) - updateCursorImage(window); -} - -// Make the specified window and its video mode active on its monitor -// -static void acquireMonitor(_GLFWwindow* window) -{ - _glfwSetVideoModeNS(window->monitor, &window->videoMode); - const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); - const NSRect frame = NSMakeRect(bounds.origin.x, - _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), - bounds.size.width, - bounds.size.height); - - [window->ns.object setFrame:frame display:YES]; - - _glfwInputMonitorWindow(window->monitor, window); -} - -// Remove the window and restore the original video mode -// -static void releaseMonitor(_GLFWwindow* window) -{ - if (window->monitor->window != window) - return; - - _glfwInputMonitorWindow(window->monitor, NULL); - _glfwRestoreVideoModeNS(window->monitor); -} - -// Translates macOS key modifiers into GLFW ones -// -static int translateFlags(NSUInteger flags) -{ - int mods = 0; - - if (flags & NSEventModifierFlagShift) - mods |= GLFW_MOD_SHIFT; - if (flags & NSEventModifierFlagControl) - mods |= GLFW_MOD_CONTROL; - if (flags & NSEventModifierFlagOption) - mods |= GLFW_MOD_ALT; - if (flags & NSEventModifierFlagCommand) - mods |= GLFW_MOD_SUPER; - if (flags & NSEventModifierFlagCapsLock) - mods |= GLFW_MOD_CAPS_LOCK; - - return mods; -} - -// Translates a macOS keycode to a GLFW keycode -// -static int translateKey(unsigned int key) -{ - if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) - return GLFW_KEY_UNKNOWN; - - return _glfw.ns.keycodes[key]; -} - -// Translate a GLFW keycode to a Cocoa modifier flag -// -static NSUInteger translateKeyToModifierFlag(int key) -{ - switch (key) - { - case GLFW_KEY_LEFT_SHIFT: - case GLFW_KEY_RIGHT_SHIFT: - return NSEventModifierFlagShift; - case GLFW_KEY_LEFT_CONTROL: - case GLFW_KEY_RIGHT_CONTROL: - return NSEventModifierFlagControl; - case GLFW_KEY_LEFT_ALT: - case GLFW_KEY_RIGHT_ALT: - return NSEventModifierFlagOption; - case GLFW_KEY_LEFT_SUPER: - case GLFW_KEY_RIGHT_SUPER: - return NSEventModifierFlagCommand; - case GLFW_KEY_CAPS_LOCK: - return NSEventModifierFlagCapsLock; - } - - return 0; -} - -// Defines a constant for empty ranges in NSTextInputClient -// -static const NSRange kEmptyRange = { NSNotFound, 0 }; - - -//------------------------------------------------------------------------ -// Delegate for window related notifications -//------------------------------------------------------------------------ - -@interface GLFWWindowDelegate : NSObject -{ - _GLFWwindow* window; -} - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - -@end - -@implementation GLFWWindowDelegate - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow -{ - self = [super init]; - if (self != nil) - window = initWindow; - - return self; -} - -- (BOOL)windowShouldClose:(id)sender -{ - _glfwInputWindowCloseRequest(window); - return NO; -} - -- (void)windowDidResize:(NSNotification *)notification -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - const int maximized = [window->ns.object isZoomed]; - if (window->ns.maximized != maximized) - { - window->ns.maximized = maximized; - _glfwInputWindowMaximize(window, maximized); - } - - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (fbRect.size.width != window->ns.fbWidth || - fbRect.size.height != window->ns.fbHeight) - { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); - } - - if (contentRect.size.width != window->ns.width || - contentRect.size.height != window->ns.height) - { - window->ns.width = contentRect.size.width; - window->ns.height = contentRect.size.height; - _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); - } -} - -- (void)windowDidMove:(NSNotification *)notification -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - int x, y; - _glfwPlatformGetWindowPos(window, &x, &y); - _glfwInputWindowPos(window, x, y); -} - -- (void)windowDidMiniaturize:(NSNotification *)notification -{ - if (window->monitor) - releaseMonitor(window); - - _glfwInputWindowIconify(window, GLFW_TRUE); -} - -- (void)windowDidDeminiaturize:(NSNotification *)notification -{ - if (window->monitor) - acquireMonitor(window); - - _glfwInputWindowIconify(window, GLFW_FALSE); -} - -- (void)windowDidBecomeKey:(NSNotification *)notification -{ - if (_glfw.ns.disabledCursorWindow == window) - _glfwCenterCursorInContentArea(window); - - _glfwInputWindowFocus(window, GLFW_TRUE); - updateCursorMode(window); -} - -- (void)windowDidResignKey:(NSNotification *)notification -{ - if (window->monitor && window->autoIconify) - _glfwPlatformIconifyWindow(window); - - _glfwInputWindowFocus(window, GLFW_FALSE); -} - -- (void)windowDidChangeOcclusionState:(NSNotification* )notification -{ - if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) - window->ns.occluded = GLFW_FALSE; - else - window->ns.occluded = GLFW_TRUE; -} - -@end - - -//------------------------------------------------------------------------ -// Content view class for the GLFW window -//------------------------------------------------------------------------ - -@interface GLFWContentView : NSView -{ - _GLFWwindow* window; - NSTrackingArea* trackingArea; - NSMutableAttributedString* markedText; -} - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - -@end - -@implementation GLFWContentView - -- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow -{ - self = [super init]; - if (self != nil) - { - window = initWindow; - trackingArea = nil; - markedText = [[NSMutableAttributedString alloc] init]; - - [self updateTrackingAreas]; - // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available - // on 10.7 without having been deprecated yet - [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]]; - } - - return self; -} - -- (void)dealloc -{ - [trackingArea release]; - [markedText release]; - [super dealloc]; -} - -- (BOOL)isOpaque -{ - return [window->ns.object isOpaque]; -} - -- (BOOL)canBecomeKeyView -{ - return YES; -} - -- (BOOL)acceptsFirstResponder -{ - return YES; -} - -- (BOOL)wantsUpdateLayer -{ - return YES; -} - -- (void)updateLayer -{ - if (window->context.client != GLFW_NO_API) - [window->context.nsgl.object update]; - - _glfwInputWindowDamage(window); -} - -- (void)cursorUpdate:(NSEvent *)event -{ - updateCursorImage(window); -} - -- (BOOL)acceptsFirstMouse:(NSEvent *)event -{ - return YES; -} - -- (void)mouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_LEFT, - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)mouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_LEFT, - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)mouseMoved:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_DISABLED) - { - const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; - const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; - - _glfwInputCursorPos(window, - window->virtualCursorPosX + dx, - window->virtualCursorPosY + dy); - } - else - { - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [event locationInWindow]; - - _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); - } - - window->ns.cursorWarpDeltaX = 0; - window->ns.cursorWarpDeltaY = 0; -} - -- (void)rightMouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_RIGHT, - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)rightMouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)rightMouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - GLFW_MOUSE_BUTTON_RIGHT, - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)otherMouseDown:(NSEvent *)event -{ - _glfwInputMouseClick(window, - (int) [event buttonNumber], - GLFW_PRESS, - translateFlags([event modifierFlags])); -} - -- (void)otherMouseDragged:(NSEvent *)event -{ - [self mouseMoved:event]; -} - -- (void)otherMouseUp:(NSEvent *)event -{ - _glfwInputMouseClick(window, - (int) [event buttonNumber], - GLFW_RELEASE, - translateFlags([event modifierFlags])); -} - -- (void)mouseExited:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_HIDDEN) - showCursor(window); - - _glfwInputCursorEnter(window, GLFW_FALSE); -} - -- (void)mouseEntered:(NSEvent *)event -{ - if (window->cursorMode == GLFW_CURSOR_HIDDEN) - hideCursor(window); - - _glfwInputCursorEnter(window, GLFW_TRUE); -} - -- (void)viewDidChangeBackingProperties -{ - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (fbRect.size.width != window->ns.fbWidth || - fbRect.size.height != window->ns.fbHeight) - { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); - } - - const float xscale = fbRect.size.width / contentRect.size.width; - const float yscale = fbRect.size.height / contentRect.size.height; - - if (xscale != window->ns.xscale || yscale != window->ns.yscale) - { - window->ns.xscale = xscale; - window->ns.yscale = yscale; - _glfwInputWindowContentScale(window, xscale, yscale); - - if (window->ns.retina && window->ns.layer) - [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; - } -} - -- (void)drawRect:(NSRect)rect -{ - _glfwInputWindowDamage(window); -} - -- (void)updateTrackingAreas -{ - if (trackingArea != nil) - { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingEnabledDuringMouseDrag | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect | - NSTrackingAssumeInside; - - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -- (void)keyDown:(NSEvent *)event -{ - const int key = translateKey([event keyCode]); - const int mods = translateFlags([event modifierFlags]); - - _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); - - [self interpretKeyEvents:@[event]]; -} - -- (void)flagsChanged:(NSEvent *)event -{ - int action; - const unsigned int modifierFlags = - [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; - const int key = translateKey([event keyCode]); - const int mods = translateFlags(modifierFlags); - const NSUInteger keyFlag = translateKeyToModifierFlag(key); - - if (keyFlag & modifierFlags) - { - if (window->keys[key] == GLFW_PRESS) - action = GLFW_RELEASE; - else - action = GLFW_PRESS; - } - else - action = GLFW_RELEASE; - - _glfwInputKey(window, key, [event keyCode], action, mods); -} - -- (void)keyUp:(NSEvent *)event -{ - const int key = translateKey([event keyCode]); - const int mods = translateFlags([event modifierFlags]); - _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); -} - -- (void)scrollWheel:(NSEvent *)event -{ - double deltaX = [event scrollingDeltaX]; - double deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) - { - deltaX *= 0.1; - deltaY *= 0.1; - } - - if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) - _glfwInputScroll(window, deltaX, deltaY); -} - -- (NSDragOperation)draggingEntered:(id )sender -{ - // HACK: We don't know what to say here because we don't know what the - // application wants to do with the paths - return NSDragOperationGeneric; -} - -- (BOOL)performDragOperation:(id )sender -{ - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [sender draggingLocation]; - _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); - - NSPasteboard* pasteboard = [sender draggingPasteboard]; - NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; - NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] - options:options]; - const NSUInteger count = [urls count]; - if (count) - { - char** paths = calloc(count, sizeof(char*)); - - for (NSUInteger i = 0; i < count; i++) - paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]); - - _glfwInputDrop(window, (int) count, (const char**) paths); - - for (NSUInteger i = 0; i < count; i++) - free(paths[i]); - free(paths); - } - - 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 -{ - [markedText release]; - if ([string isKindOfClass:[NSAttributedString class]]) - markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; - else - markedText = [[NSMutableAttributedString alloc] initWithString:string]; -} - -- (void)unmarkText -{ - [[markedText mutableString] setString:@""]; -} - -- (NSArray*)validAttributesForMarkedText -{ - return [NSArray array]; -} - -- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range - actualRange:(NSRangePointer)actualRange -{ - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)point -{ - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)range - actualRange:(NSRangePointer)actualRange -{ - const NSRect frame = [window->ns.view frame]; - return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); -} - -- (void)insertText:(id)string replacementRange:(NSRange)replacementRange -{ - NSString* characters; - NSEvent* event = [NSApp currentEvent]; - const int mods = translateFlags([event modifierFlags]); - const int plain = !(mods & GLFW_MOD_SUPER); - - if ([string isKindOfClass:[NSAttributedString class]]) - characters = [string string]; - else - characters = (NSString*) string; - - NSRange range = NSMakeRange(0, [characters length]); - while (range.length) - { - uint32_t codepoint = 0; - - if ([characters getBytes:&codepoint - maxLength:sizeof(codepoint) - usedLength:NULL - encoding:NSUTF32StringEncoding - options:0 - range:range - remainingRange:&range]) - { - if (codepoint >= 0xf700 && codepoint <= 0xf7ff) - continue; - - _glfwInputChar(window, codepoint, mods, plain); - } - } -} - -- (void)doCommandBySelector:(SEL)selector -{ -} - -@end - - -//------------------------------------------------------------------------ -// GLFW window class -//------------------------------------------------------------------------ - -@interface GLFWWindow : NSWindow {} -@end - -@implementation GLFWWindow - -- (BOOL)canBecomeKeyWindow -{ - // Required for NSWindowStyleMaskBorderless windows - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - return YES; -} - -@end - - -// Create the Cocoa window -// -static GLFWbool createNativeWindow(_GLFWwindow* window, - const _GLFWwndconfig* wndconfig, - const _GLFWfbconfig* fbconfig) -{ - window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; - if (window->ns.delegate == nil) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create window delegate"); - return GLFW_FALSE; - } - - NSRect contentRect; - - if (window->monitor) - { - GLFWvidmode mode; - int xpos, ypos; - - _glfwPlatformGetVideoMode(window->monitor, &mode); - _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); - - contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); - } - else - contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); - - window->ns.object = [[GLFWWindow alloc] - initWithContentRect:contentRect - styleMask:getStyleMask(window) - backing:NSBackingStoreBuffered - defer:NO]; - - if (window->ns.object == nil) - { - _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); - return GLFW_FALSE; - } - - if (window->monitor) - [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; - else - { - [(NSWindow*) window->ns.object center]; - _glfw.ns.cascadePoint = - NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: - NSPointFromCGPoint(_glfw.ns.cascadePoint)]); - - if (wndconfig->resizable) - { - const NSWindowCollectionBehavior behavior = - NSWindowCollectionBehaviorFullScreenPrimary | - NSWindowCollectionBehaviorManaged; - [window->ns.object setCollectionBehavior:behavior]; - } - - if (wndconfig->floating) - [window->ns.object setLevel:NSFloatingWindowLevel]; - - if (wndconfig->maximized) - [window->ns.object zoom:nil]; - } - - if (strlen(wndconfig->ns.frameName)) - [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; - - window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; - window->ns.retina = wndconfig->ns.retina; - - if (fbconfig->transparent) - { - [window->ns.object setOpaque:NO]; - [window->ns.object setHasShadow:NO]; - [window->ns.object setBackgroundColor:[NSColor clearColor]]; - } - - [window->ns.object setContentView:window->ns.view]; - [window->ns.object makeFirstResponder:window->ns.view]; - [window->ns.object setTitle:@(wndconfig->title)]; - [window->ns.object setDelegate:window->ns.delegate]; - [window->ns.object setAcceptsMouseMovedEvents:YES]; - [window->ns.object setRestorable:NO]; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 - if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)]) - [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed]; -#endif - - _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); - _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); - - return GLFW_TRUE; -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW internal API ////// -////////////////////////////////////////////////////////////////////////// - -// Transforms a y-coordinate between the CG display and NS screen spaces -// -float _glfwTransformYNS(float y) -{ - return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW platform API ////// -////////////////////////////////////////////////////////////////////////// - -int _glfwPlatformCreateWindow(_GLFWwindow* window, - const _GLFWwndconfig* wndconfig, - const _GLFWctxconfig* ctxconfig, - const _GLFWfbconfig* fbconfig) -{ - @autoreleasepool { - - if (!createNativeWindow(window, wndconfig, fbconfig)) - return GLFW_FALSE; - - if (ctxconfig->client != GLFW_NO_API) - { - if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) - { - if (!_glfwInitNSGL()) - return GLFW_FALSE; - if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) - { - // EGL implementation on macOS use CALayer* EGLNativeWindowType so we - // need to get the layer for EGL window surface creation. - [window->ns.view setWantsLayer:YES]; - window->ns.layer = [window->ns.view layer]; - - if (!_glfwInitEGL()) - return GLFW_FALSE; - if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) - { - if (!_glfwInitOSMesa()) - return GLFW_FALSE; - if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) - return GLFW_FALSE; - } - } - - if (window->monitor) - { - _glfwPlatformShowWindow(window); - _glfwPlatformFocusWindow(window); - acquireMonitor(window); - } - - return GLFW_TRUE; - - } // autoreleasepool -} - -void _glfwPlatformDestroyWindow(_GLFWwindow* window) -{ - @autoreleasepool { - - if (_glfw.ns.disabledCursorWindow == window) - _glfw.ns.disabledCursorWindow = NULL; - - [window->ns.object orderOut:nil]; - - if (window->monitor) - releaseMonitor(window); - - if (window->context.destroy) - window->context.destroy(window); - - [window->ns.object setDelegate:nil]; - [window->ns.delegate release]; - window->ns.delegate = nil; - - [window->ns.view release]; - window->ns.view = nil; - - [window->ns.object close]; - window->ns.object = nil; - - // HACK: Allow Cocoa to catch up before returning - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) -{ - return NULL; -} - -void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) -{ - @autoreleasepool { - NSString* string = @(title); - [window->ns.object setTitle:string]; - // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it - // if the window lacks NSWindowStyleMaskTitled - [window->ns.object setMiniwindowTitle:string]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowIcon(_GLFWwindow* window, - int count, const GLFWimage* images) -{ - _glfwInputError(GLFW_FEATURE_UNAVAILABLE, - "Cocoa: Regular windows do not have icons on macOS"); -} - -void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) -{ - @autoreleasepool { - - const NSRect contentRect = - [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; - - if (xpos) - *xpos = contentRect.origin.x; - if (ypos) - *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); - - } // autoreleasepool -} - -void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); - const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; - [window->ns.object setFrameOrigin:frameRect.origin]; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - - if (width) - *width = contentRect.size.width; - if (height) - *height = contentRect.size.height; - - } // autoreleasepool -} - -void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) -{ - @autoreleasepool { - - if (window->monitor) - { - if (window->monitor->window == window) - acquireMonitor(window); - } - else - { - NSRect contentRect = - [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; - contentRect.origin.y += contentRect.size.height - height; - contentRect.size = NSMakeSize(width, height); - [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] - display:YES]; - } - - } // autoreleasepool -} - -void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, - int minwidth, int minheight, - int maxwidth, int maxheight) -{ - @autoreleasepool { - - 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)]; - - } // autoreleasepool -} - -void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) -{ - @autoreleasepool { - if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) - [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; - else - [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; - } // autoreleasepool -} - -void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; - - if (width) - *width = (int) fbRect.size.width; - if (height) - *height = (int) fbRect.size.height; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, - int* left, int* top, - int* right, int* bottom) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; - - if (left) - *left = contentRect.origin.x - frameRect.origin.x; - if (top) - *top = frameRect.origin.y + frameRect.size.height - - contentRect.origin.y - contentRect.size.height; - if (right) - *right = frameRect.origin.x + frameRect.size.width - - contentRect.origin.x - contentRect.size.width; - if (bottom) - *bottom = contentRect.origin.y - frameRect.origin.y; - - } // autoreleasepool -} - -void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, - float* xscale, float* yscale) -{ - @autoreleasepool { - - const NSRect points = [window->ns.view frame]; - const NSRect pixels = [window->ns.view convertRectToBacking:points]; - - if (xscale) - *xscale = (float) (pixels.size.width / points.size.width); - if (yscale) - *yscale = (float) (pixels.size.height / points.size.height); - - } // autoreleasepool -} - -void _glfwPlatformIconifyWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object miniaturize:nil]; - } // autoreleasepool -} - -void _glfwPlatformRestoreWindow(_GLFWwindow* window) -{ - @autoreleasepool { - if ([window->ns.object isMiniaturized]) - [window->ns.object deminiaturize:nil]; - else if ([window->ns.object isZoomed]) - [window->ns.object zoom:nil]; - } // autoreleasepool -} - -void _glfwPlatformMaximizeWindow(_GLFWwindow* window) -{ - @autoreleasepool { - if (![window->ns.object isZoomed]) - [window->ns.object zoom:nil]; - } // autoreleasepool -} - -void _glfwPlatformShowWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object orderFront:nil]; - } // autoreleasepool -} - -void _glfwPlatformHideWindow(_GLFWwindow* window) -{ - @autoreleasepool { - [window->ns.object orderOut:nil]; - } // autoreleasepool -} - -void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) -{ - @autoreleasepool { - [NSApp requestUserAttention:NSInformationalRequest]; - } // autoreleasepool -} - -void _glfwPlatformFocusWindow(_GLFWwindow* window) -{ - @autoreleasepool { - // Make us the active application - // HACK: This is here to prevent applications using only hidden windows from - // being activated, but should probably not be done every time any - // window is shown - [NSApp activateIgnoringOtherApps:YES]; - [window->ns.object makeKeyAndOrderFront:nil]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, - _GLFWmonitor* monitor, - int xpos, int ypos, - int width, int height, - int refreshRate) -{ - @autoreleasepool { - - if (window->monitor == monitor) - { - if (monitor) - { - if (monitor->window == window) - acquireMonitor(window); - } - else - { - const NSRect contentRect = - NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); - const NSRect frameRect = - [window->ns.object frameRectForContentRect:contentRect - styleMask:getStyleMask(window)]; - - [window->ns.object setFrame:frameRect display:YES]; - } - - return; - } - - if (window->monitor) - releaseMonitor(window); - - _glfwInputWindowMonitor(window, monitor); - - // HACK: Allow the state cached in Cocoa to catch up to reality - // TODO: Solve this in a less terrible way - _glfwPlatformPollEvents(); - - const NSUInteger styleMask = getStyleMask(window); - [window->ns.object setStyleMask:styleMask]; - // HACK: Changing the style mask can cause the first responder to be cleared - [window->ns.object makeFirstResponder:window->ns.view]; - - if (window->monitor) - { - [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; - [window->ns.object setHasShadow:NO]; - - acquireMonitor(window); - } - else - { - NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), - width, height); - NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect - styleMask:styleMask]; - [window->ns.object setFrame:frameRect display:YES]; - - if (window->numer != GLFW_DONT_CARE && - window->denom != GLFW_DONT_CARE) - { - [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, - window->denom)]; - } - - if (window->minwidth != GLFW_DONT_CARE && - window->minheight != GLFW_DONT_CARE) - { - [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, - window->minheight)]; - } - - if (window->maxwidth != GLFW_DONT_CARE && - window->maxheight != GLFW_DONT_CARE) - { - [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, - window->maxheight)]; - } - - if (window->floating) - [window->ns.object setLevel:NSFloatingWindowLevel]; - else - [window->ns.object setLevel:NSNormalWindowLevel]; - - [window->ns.object setHasShadow:YES]; - // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window - // title property but the miniwindow title property is unaffected - [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; - } - - } // autoreleasepool -} - -int _glfwPlatformWindowFocused(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isKeyWindow]; - } // autoreleasepool -} - -int _glfwPlatformWindowIconified(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isMiniaturized]; - } // autoreleasepool -} - -int _glfwPlatformWindowVisible(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isVisible]; - } // autoreleasepool -} - -int _glfwPlatformWindowMaximized(_GLFWwindow* window) -{ - @autoreleasepool { - return [window->ns.object isZoomed]; - } // autoreleasepool -} - -int _glfwPlatformWindowHovered(_GLFWwindow* window) -{ - @autoreleasepool { - - const NSPoint point = [NSEvent mouseLocation]; - - if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != - [window->ns.object windowNumber]) - { - return GLFW_FALSE; - } - - return NSMouseInRect(point, - [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); - - } // autoreleasepool -} - -int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) -{ - @autoreleasepool { - return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setStyleMask:getStyleMask(window)]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setStyleMask:getStyleMask(window)]; - [window->ns.object makeFirstResponder:window->ns.view]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - if (enabled) - [window->ns.object setLevel:NSFloatingWindowLevel]; - else - [window->ns.object setLevel:NSNormalWindowLevel]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) -{ - @autoreleasepool { - [window->ns.object setIgnoresMouseEvents:enabled]; - } -} - -float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) -{ - @autoreleasepool { - return (float) [window->ns.object alphaValue]; - } // autoreleasepool -} - -void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) -{ - @autoreleasepool { - [window->ns.object setAlphaValue:opacity]; - } // autoreleasepool -} - -void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) -{ - _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, - "Cocoa: Raw mouse motion not yet implemented"); -} - -GLFWbool _glfwPlatformRawMouseMotionSupported(void) -{ - return GLFW_FALSE; -} - -void _glfwPlatformPollEvents(void) -{ - @autoreleasepool { - - for (;;) - { - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event == nil) - break; - - [NSApp sendEvent:event]; - } - - } // autoreleasepool -} - -void _glfwPlatformWaitEvents(void) -{ - @autoreleasepool { - - // I wanted to pass NO to dequeue:, and rely on PollEvents to - // dequeue and send. For reasons not at all clear to me, passing - // NO to dequeue: causes this method never to return. - NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantFuture] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -void _glfwPlatformWaitEventsTimeout(double timeout) -{ - @autoreleasepool { - - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; - NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:date - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event) - [NSApp sendEvent:event]; - - _glfwPlatformPollEvents(); - - } // autoreleasepool -} - -void _glfwPlatformPostEmptyEvent(void) -{ - @autoreleasepool { - - 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]; - - } // autoreleasepool -} - -void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) -{ - @autoreleasepool { - - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - - if (xpos) - *xpos = pos.x; - if (ypos) - *ypos = contentRect.size.height - pos.y; - - } // autoreleasepool -} - -void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) -{ - @autoreleasepool { - - updateCursorImage(window); - - const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 - const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; - - window->ns.cursorWarpDeltaX += x - pos.x; - window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; - - if (window->monitor) - { - CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, - CGPointMake(x, y)); - } - else - { - const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); - const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; - const NSPoint globalPoint = globalRect.origin; - - CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, - _glfwTransformYNS(globalPoint.y))); - } - - } // autoreleasepool -} - -void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) -{ - @autoreleasepool { - if (_glfwPlatformWindowFocused(window)) - updateCursorMode(window); - } // autoreleasepool -} - -const char* _glfwPlatformGetScancodeName(int scancode) -{ - @autoreleasepool { - - if (scancode < 0 || scancode > 0xff || - _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) - { - _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode"); - return NULL; - } - - const int key = _glfw.ns.keycodes[scancode]; - - UInt32 deadKeyState = 0; - UniChar characters[4]; - UniCharCount characterCount = 0; - - if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], - scancode, - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &deadKeyState, - sizeof(characters) / sizeof(characters[0]), - &characterCount, - characters) != noErr) - { - return NULL; - } - - if (!characterCount) - return NULL; - - CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - characters, - characterCount, - kCFAllocatorNull); - CFStringGetCString(string, - _glfw.ns.keynames[key], - sizeof(_glfw.ns.keynames[key]), - kCFStringEncodingUTF8); - CFRelease(string); - - return _glfw.ns.keynames[key]; - - } // autoreleasepool -} - -int _glfwPlatformGetKeyScancode(int key) -{ - return _glfw.ns.scancodes[key]; -} - -int _glfwPlatformCreateCursor(_GLFWcursor* cursor, - const GLFWimage* image, - int xhot, int yhot) -{ - @autoreleasepool { - - NSImage* native; - NSBitmapImageRep* rep; - - rep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL - pixelsWide:image->width - pixelsHigh:image->height - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSCalibratedRGBColorSpace - bitmapFormat:NSBitmapFormatAlphaNonpremultiplied - bytesPerRow:image->width * 4 - bitsPerPixel:32]; - - if (rep == nil) - return GLFW_FALSE; - - memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); - - native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; - [native addRepresentation:rep]; - - cursor->ns.object = [[NSCursor alloc] initWithImage:native - hotSpot:NSMakePoint(xhot, yhot)]; - - [native release]; - [rep release]; - - if (cursor->ns.object == nil) - return GLFW_FALSE; - - return GLFW_TRUE; - - } // autoreleasepool -} - -int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) -{ - @autoreleasepool { - - SEL cursorSelector = NULL; - - // HACK: Try to use a private message - switch (shape) - { - case GLFW_RESIZE_EW_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor"); - break; - case GLFW_RESIZE_NS_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor"); - break; - case GLFW_RESIZE_NWSE_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor"); - break; - case GLFW_RESIZE_NESW_CURSOR: - cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor"); - break; - } - - if (cursorSelector && [NSCursor respondsToSelector:cursorSelector]) - { - id object = [NSCursor performSelector:cursorSelector]; - if ([object isKindOfClass:[NSCursor class]]) - cursor->ns.object = object; - } - - if (!cursor->ns.object) - { - switch (shape) - { - case GLFW_ARROW_CURSOR: - cursor->ns.object = [NSCursor arrowCursor]; - break; - case GLFW_IBEAM_CURSOR: - cursor->ns.object = [NSCursor IBeamCursor]; - break; - case GLFW_CROSSHAIR_CURSOR: - cursor->ns.object = [NSCursor crosshairCursor]; - break; - case GLFW_POINTING_HAND_CURSOR: - cursor->ns.object = [NSCursor pointingHandCursor]; - break; - case GLFW_RESIZE_EW_CURSOR: - cursor->ns.object = [NSCursor resizeLeftRightCursor]; - break; - case GLFW_RESIZE_NS_CURSOR: - cursor->ns.object = [NSCursor resizeUpDownCursor]; - break; - case GLFW_RESIZE_ALL_CURSOR: - cursor->ns.object = [NSCursor closedHandCursor]; - break; - case GLFW_NOT_ALLOWED_CURSOR: - cursor->ns.object = [NSCursor operationNotAllowedCursor]; - break; - } - } - - if (!cursor->ns.object) - { - _glfwInputError(GLFW_CURSOR_UNAVAILABLE, - "Cocoa: Standard cursor shape unavailable"); - return GLFW_FALSE; - } - - [cursor->ns.object retain]; - return GLFW_TRUE; - - } // autoreleasepool -} - -void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) -{ - @autoreleasepool { - if (cursor->ns.object) - [(NSCursor*) cursor->ns.object release]; - } // autoreleasepool -} - -void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) -{ - @autoreleasepool { - if (cursorInContentArea(window)) - updateCursorImage(window); - } // autoreleasepool -} - -void _glfwPlatformSetClipboardString(const char* string) -{ - @autoreleasepool { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; - [pasteboard setString:@(string) forType:NSPasteboardTypeString]; - } // autoreleasepool -} - -const char* _glfwPlatformGetClipboardString(void) -{ - @autoreleasepool { - - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - - if (![[pasteboard types] containsObject:NSPasteboardTypeString]) - { - _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "Cocoa: Failed to retrieve string from pasteboard"); - return NULL; - } - - NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; - if (!object) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to retrieve object from pasteboard"); - return NULL; - } - - free(_glfw.ns.clipboardString); - _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); - - return _glfw.ns.clipboardString; - - } // autoreleasepool -} - -EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) -{ - if (_glfw.egl.ANGLE_platform_angle) - { - int type = 0; - - if (_glfw.egl.ANGLE_platform_angle_opengl) - { - if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) - type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; - } - - if (_glfw.egl.ANGLE_platform_angle_metal) - { - if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) - type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; - } - - if (type) - { - *attribs = calloc(3, sizeof(EGLint)); - (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; - (*attribs)[1] = type; - (*attribs)[2] = EGL_NONE; - return EGL_PLATFORM_ANGLE_ANGLE; - } - } - - return 0; -} - -EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) -{ - return EGL_DEFAULT_DISPLAY; -} - -EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) -{ - return window->ns.layer; -} - -void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) -{ - if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) - { - extensions[0] = "VK_KHR_surface"; - extensions[1] = "VK_EXT_metal_surface"; - } - else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) - { - extensions[0] = "VK_KHR_surface"; - extensions[1] = "VK_MVK_macos_surface"; - } -} - -int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, - VkPhysicalDevice device, - uint32_t queuefamily) -{ - return GLFW_TRUE; -} - -VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, - _GLFWwindow* window, - const VkAllocationCallbacks* allocator, - VkSurfaceKHR* surface) -{ - @autoreleasepool { - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 - // HACK: Dynamically load Core Animation to avoid adding an extra - // dependency for the majority who don't use MoltenVK - NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; - if (!bundle) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to find QuartzCore.framework"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - // NOTE: Create the layer here as makeBackingLayer should not return nil - window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; - if (!window->ns.layer) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create layer for view"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - if (window->ns.retina) - [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; - - [window->ns.view setLayer:window->ns.layer]; - [window->ns.view setWantsLayer:YES]; - - VkResult err; - - if (_glfw.vk.EXT_metal_surface) - { - VkMetalSurfaceCreateInfoEXT sci; - - PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; - vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) - vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); - if (!vkCreateMetalSurfaceEXT) - { - _glfwInputError(GLFW_API_UNAVAILABLE, - "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - memset(&sci, 0, sizeof(sci)); - sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; - sci.pLayer = window->ns.layer; - - err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); - } - else - { - VkMacOSSurfaceCreateInfoMVK sci; - - PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; - vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) - vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); - if (!vkCreateMacOSSurfaceMVK) - { - _glfwInputError(GLFW_API_UNAVAILABLE, - "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - - memset(&sci, 0, sizeof(sci)); - sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - sci.pView = window->ns.view; - - err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); - } - - if (err) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to create Vulkan surface: %s", - _glfwGetVulkanResultString(err)); - } - - return err; -#else - return VK_ERROR_EXTENSION_NOT_PRESENT; -#endif - - } // autoreleasepool -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW native API ////// -////////////////////////////////////////////////////////////////////////// - -GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) -{ - _GLFWwindow* window = (_GLFWwindow*) handle; - _GLFW_REQUIRE_INIT_OR_RETURN(nil); - return window->ns.object; -} - +//======================================================================== +// GLFW 3.4 macOS - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2009-2019 Camilla LΓΆwy +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// It is fine to use C99 in this file because it will not be built with VS +//======================================================================== + +#include "internal.h" + +#include +#include + +// Returns the style mask corresponding to the window settings +// +static NSUInteger getStyleMask(_GLFWwindow* window) +{ + NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; + + if (window->monitor || !window->decorated) + styleMask |= NSWindowStyleMaskBorderless; + else + { + styleMask |= NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable; + + if (window->resizable) + styleMask |= NSWindowStyleMaskResizable; + } + + return styleMask; +} + +// Returns whether the cursor is in the content area of the specified window +// +static GLFWbool cursorInContentArea(_GLFWwindow* window) +{ + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; +} + +// Hides the cursor if not already hidden +// +static void hideCursor(_GLFWwindow* window) +{ + if (!_glfw.ns.cursorHidden) + { + [NSCursor hide]; + _glfw.ns.cursorHidden = GLFW_TRUE; + } +} + +// Shows the cursor if not already shown +// +static void showCursor(_GLFWwindow* window) +{ + if (_glfw.ns.cursorHidden) + { + [NSCursor unhide]; + _glfw.ns.cursorHidden = GLFW_FALSE; + } +} + +// Updates the cursor image according to its cursor mode +// +static void updateCursorImage(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_NORMAL) + { + showCursor(window); + + if (window->cursor) + [(NSCursor*) window->cursor->ns.object set]; + else + [[NSCursor arrowCursor] set]; + } + else + hideCursor(window); +} + +// Apply chosen cursor mode to a focused window +// +static void updateCursorMode(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + _glfw.ns.disabledCursorWindow = window; + _glfwPlatformGetCursorPos(window, + &_glfw.ns.restoreCursorPosX, + &_glfw.ns.restoreCursorPosY); + _glfwCenterCursorInContentArea(window); + CGAssociateMouseAndMouseCursorPosition(false); + } + else if (_glfw.ns.disabledCursorWindow == window) + { + _glfw.ns.disabledCursorWindow = NULL; + CGAssociateMouseAndMouseCursorPosition(true); + _glfwPlatformSetCursorPos(window, + _glfw.ns.restoreCursorPosX, + _glfw.ns.restoreCursorPosY); + } + + if (cursorInContentArea(window)) + updateCursorImage(window); +} + +// Make the specified window and its video mode active on its monitor +// +static void acquireMonitor(_GLFWwindow* window) +{ + _glfwSetVideoModeNS(window->monitor, &window->videoMode); + const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); + const NSRect frame = NSMakeRect(bounds.origin.x, + _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), + bounds.size.width, + bounds.size.height); + + [window->ns.object setFrame:frame display:YES]; + + _glfwInputMonitorWindow(window->monitor, window); +} + +// Remove the window and restore the original video mode +// +static void releaseMonitor(_GLFWwindow* window) +{ + if (window->monitor->window != window) + return; + + _glfwInputMonitorWindow(window->monitor, NULL); + _glfwRestoreVideoModeNS(window->monitor); +} + +// Translates macOS key modifiers into GLFW ones +// +static int translateFlags(NSUInteger flags) +{ + int mods = 0; + + if (flags & NSEventModifierFlagShift) + mods |= GLFW_MOD_SHIFT; + if (flags & NSEventModifierFlagControl) + mods |= GLFW_MOD_CONTROL; + if (flags & NSEventModifierFlagOption) + mods |= GLFW_MOD_ALT; + if (flags & NSEventModifierFlagCommand) + mods |= GLFW_MOD_SUPER; + if (flags & NSEventModifierFlagCapsLock) + mods |= GLFW_MOD_CAPS_LOCK; + + return mods; +} + +// Translates a macOS keycode to a GLFW keycode +// +static int translateKey(unsigned int key) +{ + if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) + return GLFW_KEY_UNKNOWN; + + return _glfw.ns.keycodes[key]; +} + +// Translate a GLFW keycode to a Cocoa modifier flag +// +static NSUInteger translateKeyToModifierFlag(int key) +{ + switch (key) + { + case GLFW_KEY_LEFT_SHIFT: + case GLFW_KEY_RIGHT_SHIFT: + return NSEventModifierFlagShift; + case GLFW_KEY_LEFT_CONTROL: + case GLFW_KEY_RIGHT_CONTROL: + return NSEventModifierFlagControl; + case GLFW_KEY_LEFT_ALT: + case GLFW_KEY_RIGHT_ALT: + return NSEventModifierFlagOption; + case GLFW_KEY_LEFT_SUPER: + case GLFW_KEY_RIGHT_SUPER: + return NSEventModifierFlagCommand; + case GLFW_KEY_CAPS_LOCK: + return NSEventModifierFlagCapsLock; + } + + return 0; +} + +// Defines a constant for empty ranges in NSTextInputClient +// +static const NSRange kEmptyRange = { NSNotFound, 0 }; + + +//------------------------------------------------------------------------ +// Delegate for window related notifications +//------------------------------------------------------------------------ + +@interface GLFWWindowDelegate : NSObject +{ + _GLFWwindow* window; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWWindowDelegate + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + window = initWindow; + + return self; +} + +- (BOOL)windowShouldClose:(id)sender +{ + _glfwInputWindowCloseRequest(window); + return NO; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + const int maximized = [window->ns.object isZoomed]; + if (window->ns.maximized != maximized) + { + window->ns.maximized = maximized; + _glfwInputWindowMaximize(window, maximized); + } + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } + + if (contentRect.size.width != window->ns.width || + contentRect.size.height != window->ns.height) + { + window->ns.width = contentRect.size.width; + window->ns.height = contentRect.size.height; + _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); + } +} + +- (void)windowDidMove:(NSNotification *)notification +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + int x, y; + _glfwPlatformGetWindowPos(window, &x, &y); + _glfwInputWindowPos(window, x, y); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification +{ + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowIconify(window, GLFW_TRUE); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification +{ + if (window->monitor) + acquireMonitor(window); + + _glfwInputWindowIconify(window, GLFW_FALSE); +} + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + _glfwInputWindowFocus(window, GLFW_TRUE); + updateCursorMode(window); +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + if (window->monitor && window->autoIconify) + _glfwPlatformIconifyWindow(window); + + _glfwInputWindowFocus(window, GLFW_FALSE); +} + +- (void)windowDidChangeOcclusionState:(NSNotification* )notification +{ + if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) + window->ns.occluded = GLFW_FALSE; + else + window->ns.occluded = GLFW_TRUE; +} + +@end + + +//------------------------------------------------------------------------ +// Content view class for the GLFW window +//------------------------------------------------------------------------ + +@interface GLFWContentView : NSView +{ + _GLFWwindow* window; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWContentView + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + { + window = initWindow; + trackingArea = nil; + markedText = [[NSMutableAttributedString alloc] init]; + + [self updateTrackingAreas]; + // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available + // on 10.7 without having been deprecated yet + [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]]; + } + + return self; +} + +- (void)dealloc +{ + [trackingArea release]; + [markedText release]; + [super dealloc]; +} + +- (BOOL)isOpaque +{ + return [window->ns.object isOpaque]; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (void)updateLayer +{ + if (window->context.client != GLFW_NO_API) + [window->context.nsgl.object update]; + + _glfwInputWindowDamage(window); +} + +- (void)cursorUpdate:(NSEvent *)event +{ + updateCursorImage(window); +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)event +{ + return YES; +} + +- (void)mouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseMoved:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; + const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; + + _glfwInputCursorPos(window, + window->virtualCursorPosX + dx, + window->virtualCursorPosY + dy); + } + else + { + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [event locationInWindow]; + + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + } + + window->ns.cursorWarpDeltaX = 0; + window->ns.cursorWarpDeltaY = 0; +} + +- (void)rightMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseExited:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + showCursor(window); + + _glfwInputCursorEnter(window, GLFW_FALSE); +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + hideCursor(window); + + _glfwInputCursorEnter(window, GLFW_TRUE); +} + +- (void)viewDidChangeBackingProperties +{ + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } + + const float xscale = fbRect.size.width / contentRect.size.width; + const float yscale = fbRect.size.height / contentRect.size.height; + + if (xscale != window->ns.xscale || yscale != window->ns.yscale) + { + window->ns.xscale = xscale; + window->ns.yscale = yscale; + _glfwInputWindowContentScale(window, xscale, yscale); + + if (window->ns.retina && window->ns.layer) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + } +} + +- (void)drawRect:(NSRect)rect +{ + _glfwInputWindowDamage(window); +} + +- (void)updateTrackingAreas +{ + if (trackingArea != nil) + { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:options + owner:self + userInfo:nil]; + + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (void)keyDown:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); + + [self interpretKeyEvents:@[event]]; +} + +- (void)flagsChanged:(NSEvent *)event +{ + int action; + const unsigned int modifierFlags = + [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + const int key = translateKey([event keyCode]); + const int mods = translateFlags(modifierFlags); + const NSUInteger keyFlag = translateKeyToModifierFlag(key); + + if (keyFlag & modifierFlags) + { + if (window->keys[key] == GLFW_PRESS) + action = GLFW_RELEASE; + else + action = GLFW_PRESS; + } + else + action = GLFW_RELEASE; + + _glfwInputKey(window, key, [event keyCode], action, mods); +} + +- (void)keyUp:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); +} + +- (void)scrollWheel:(NSEvent *)event +{ + double deltaX = [event scrollingDeltaX]; + double deltaY = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) + { + deltaX *= 0.1; + deltaY *= 0.1; + } + + if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) + _glfwInputScroll(window, deltaX, deltaY); +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + // HACK: We don't know what to say here because we don't know what the + // application wants to do with the paths + return NSDragOperationGeneric; +} + +- (BOOL)performDragOperation:(id )sender +{ + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [sender draggingLocation]; + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + + NSPasteboard* pasteboard = [sender draggingPasteboard]; + NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; + NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] + options:options]; + const NSUInteger count = [urls count]; + if (count) + { + char** paths = calloc(count, sizeof(char*)); + + for (NSUInteger i = 0; i < count; i++) + paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]); + + _glfwInputDrop(window, (int) count, (const char**) paths); + + for (NSUInteger i = 0; i < count; i++) + free(paths[i]); + free(paths); + } + + 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 +{ + [markedText release]; + if ([string isKindOfClass:[NSAttributedString class]]) + markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; + else + markedText = [[NSMutableAttributedString alloc] initWithString:string]; +} + +- (void)unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*)validAttributesForMarkedText +{ + return [NSArray array]; +} + +- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + const NSRect frame = [window->ns.view frame]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + NSString* characters; + NSEvent* event = [NSApp currentEvent]; + const int mods = translateFlags([event modifierFlags]); + const int plain = !(mods & GLFW_MOD_SUPER); + + if ([string isKindOfClass:[NSAttributedString class]]) + characters = [string string]; + else + characters = (NSString*) string; + + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) + { + uint32_t codepoint = 0; + + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if (codepoint >= 0xf700 && codepoint <= 0xf7ff) + continue; + + _glfwInputChar(window, codepoint, mods, plain); + } + } +} + +- (void)doCommandBySelector:(SEL)selector +{ +} + +@end + + +//------------------------------------------------------------------------ +// GLFW window class +//------------------------------------------------------------------------ + +@interface GLFWWindow : NSWindow {} +@end + +@implementation GLFWWindow + +- (BOOL)canBecomeKeyWindow +{ + // Required for NSWindowStyleMaskBorderless windows + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +@end + + +// Create the Cocoa window +// +static GLFWbool createNativeWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWfbconfig* fbconfig) +{ + window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; + if (window->ns.delegate == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create window delegate"); + return GLFW_FALSE; + } + + NSRect contentRect; + + if (window->monitor) + { + GLFWvidmode mode; + int xpos, ypos; + + _glfwPlatformGetVideoMode(window->monitor, &mode); + _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); + + contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); + } + else + contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); + + window->ns.object = [[GLFWWindow alloc] + initWithContentRect:contentRect + styleMask:getStyleMask(window) + backing:NSBackingStoreBuffered + defer:NO]; + + if (window->ns.object == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); + return GLFW_FALSE; + } + + if (window->monitor) + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + else + { + [(NSWindow*) window->ns.object center]; + _glfw.ns.cascadePoint = + NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: + NSPointFromCGPoint(_glfw.ns.cascadePoint)]); + + if (wndconfig->resizable) + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | + NSWindowCollectionBehaviorManaged; + [window->ns.object setCollectionBehavior:behavior]; + } + + if (wndconfig->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + + if (wndconfig->maximized) + [window->ns.object zoom:nil]; + } + + if (strlen(wndconfig->ns.frameName)) + [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; + + window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; + window->ns.retina = wndconfig->ns.retina; + + if (fbconfig->transparent) + { + [window->ns.object setOpaque:NO]; + [window->ns.object setHasShadow:NO]; + [window->ns.object setBackgroundColor:[NSColor clearColor]]; + } + + [window->ns.object setContentView:window->ns.view]; + [window->ns.object makeFirstResponder:window->ns.view]; + [window->ns.object setTitle:@(wndconfig->title)]; + [window->ns.object setDelegate:window->ns.delegate]; + [window->ns.object setAcceptsMouseMovedEvents:YES]; + [window->ns.object setRestorable:NO]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)]) + [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed]; +#endif + + _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); + _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); + + return GLFW_TRUE; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +// Transforms a y-coordinate between the CG display and NS screen spaces +// +float _glfwTransformYNS(float y) +{ + return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +int _glfwPlatformCreateWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig) +{ + @autoreleasepool { + + if (!createNativeWindow(window, wndconfig, fbconfig)) + return GLFW_FALSE; + + if (ctxconfig->client != GLFW_NO_API) + { + if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) + { + if (!_glfwInitNSGL()) + return GLFW_FALSE; + if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) + { + // EGL implementation on macOS use CALayer* EGLNativeWindowType so we + // need to get the layer for EGL window surface creation. + [window->ns.view setWantsLayer:YES]; + window->ns.layer = [window->ns.view layer]; + + if (!_glfwInitEGL()) + return GLFW_FALSE; + if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) + { + if (!_glfwInitOSMesa()) + return GLFW_FALSE; + if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + } + + if (window->monitor) + { + _glfwPlatformShowWindow(window); + _glfwPlatformFocusWindow(window); + acquireMonitor(window); + } + + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + + if (_glfw.ns.disabledCursorWindow == window) + _glfw.ns.disabledCursorWindow = NULL; + + [window->ns.object orderOut:nil]; + + if (window->monitor) + releaseMonitor(window); + + if (window->context.destroy) + window->context.destroy(window); + + [window->ns.object setDelegate:nil]; + [window->ns.delegate release]; + window->ns.delegate = nil; + + [window->ns.view release]; + window->ns.view = nil; + + [window->ns.object close]; + window->ns.object = nil; + + // HACK: Allow Cocoa to catch up before returning + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +char* _glfwPlatformGetWindowTitle(_GLFWwindow* window) +{ + return NULL; +} + +void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) +{ + @autoreleasepool { + NSString* string = @(title); + [window->ns.object setTitle:string]; + // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it + // if the window lacks NSWindowStyleMaskTitled + [window->ns.object setMiniwindowTitle:string]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Cocoa: Regular windows do not have icons on macOS"); +} + +void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + + if (xpos) + *xpos = contentRect.origin.x; + if (ypos) + *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); + + } // autoreleasepool +} + +void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); + const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; + [window->ns.object setFrameOrigin:frameRect.origin]; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + + if (width) + *width = contentRect.size.width; + if (height) + *height = contentRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) +{ + @autoreleasepool { + + if (window->monitor) + { + if (window->monitor->window == window) + acquireMonitor(window); + } + else + { + NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + contentRect.origin.y += contentRect.size.height - height; + contentRect.size = NSMakeSize(width, height); + [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] + display:YES]; + } + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + @autoreleasepool { + + 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)]; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + @autoreleasepool { + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; + else + [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; + } // autoreleasepool +} + +void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (width) + *width = (int) fbRect.size.width; + if (height) + *height = (int) fbRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, + int* left, int* top, + int* right, int* bottom) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; + + if (left) + *left = contentRect.origin.x - frameRect.origin.x; + if (top) + *top = frameRect.origin.y + frameRect.size.height - + contentRect.origin.y - contentRect.size.height; + if (right) + *right = frameRect.origin.x + frameRect.size.width - + contentRect.origin.x - contentRect.size.width; + if (bottom) + *bottom = contentRect.origin.y - frameRect.origin.y; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, + float* xscale, float* yscale) +{ + @autoreleasepool { + + const NSRect points = [window->ns.view frame]; + const NSRect pixels = [window->ns.view convertRectToBacking:points]; + + if (xscale) + *xscale = (float) (pixels.size.width / points.size.width); + if (yscale) + *yscale = (float) (pixels.size.height / points.size.height); + + } // autoreleasepool +} + +void _glfwPlatformIconifyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object miniaturize:nil]; + } // autoreleasepool +} + +void _glfwPlatformRestoreWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if ([window->ns.object isMiniaturized]) + [window->ns.object deminiaturize:nil]; + else if ([window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformMaximizeWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if (![window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformShowWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformHideWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderOut:nil]; + } // autoreleasepool +} + +void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) +{ + @autoreleasepool { + [NSApp requestUserAttention:NSInformationalRequest]; + } // autoreleasepool +} + +void _glfwPlatformFocusWindow(_GLFWwindow* window) +{ + @autoreleasepool { + // Make us the active application + // HACK: This is here to prevent applications using only hidden windows from + // being activated, but should probably not be done every time any + // window is shown + [NSApp activateIgnoringOtherApps:YES]; + [window->ns.object makeKeyAndOrderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, + _GLFWmonitor* monitor, + int xpos, int ypos, + int width, int height, + int refreshRate) +{ + @autoreleasepool { + + if (window->monitor == monitor) + { + if (monitor) + { + if (monitor->window == window) + acquireMonitor(window); + } + else + { + const NSRect contentRect = + NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); + const NSRect frameRect = + [window->ns.object frameRectForContentRect:contentRect + styleMask:getStyleMask(window)]; + + [window->ns.object setFrame:frameRect display:YES]; + } + + return; + } + + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowMonitor(window, monitor); + + // HACK: Allow the state cached in Cocoa to catch up to reality + // TODO: Solve this in a less terrible way + _glfwPlatformPollEvents(); + + const NSUInteger styleMask = getStyleMask(window); + [window->ns.object setStyleMask:styleMask]; + // HACK: Changing the style mask can cause the first responder to be cleared + [window->ns.object makeFirstResponder:window->ns.view]; + + if (window->monitor) + { + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + [window->ns.object setHasShadow:NO]; + + acquireMonitor(window); + } + else + { + NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), + width, height); + NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect + styleMask:styleMask]; + [window->ns.object setFrame:frameRect display:YES]; + + if (window->numer != GLFW_DONT_CARE && + window->denom != GLFW_DONT_CARE) + { + [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, + window->denom)]; + } + + if (window->minwidth != GLFW_DONT_CARE && + window->minheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, + window->minheight)]; + } + + if (window->maxwidth != GLFW_DONT_CARE && + window->maxheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, + window->maxheight)]; + } + + if (window->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + + [window->ns.object setHasShadow:YES]; + // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window + // title property but the miniwindow title property is unaffected + [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; + } + + } // autoreleasepool +} + +int _glfwPlatformWindowFocused(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isKeyWindow]; + } // autoreleasepool +} + +int _glfwPlatformWindowIconified(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isMiniaturized]; + } // autoreleasepool +} + +int _glfwPlatformWindowVisible(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isVisible]; + } // autoreleasepool +} + +int _glfwPlatformWindowMaximized(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isZoomed]; + } // autoreleasepool +} + +int _glfwPlatformWindowHovered(_GLFWwindow* window) +{ + @autoreleasepool { + + const NSPoint point = [NSEvent mouseLocation]; + + if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != + [window->ns.object windowNumber]) + { + return GLFW_FALSE; + } + + return NSMouseInRect(point, + [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); + + } // autoreleasepool +} + +int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) +{ + @autoreleasepool { + return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setStyleMask:getStyleMask(window)]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setStyleMask:getStyleMask(window)]; + [window->ns.object makeFirstResponder:window->ns.view]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + if (enabled) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setIgnoresMouseEvents:enabled]; + } +} + +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + @autoreleasepool { + return (float) [window->ns.object alphaValue]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + @autoreleasepool { + [window->ns.object setAlphaValue:opacity]; + } // autoreleasepool +} + +void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Cocoa: Raw mouse motion not yet implemented"); +} + +GLFWbool _glfwPlatformRawMouseMotionSupported(void) +{ + return GLFW_FALSE; +} + +void _glfwPlatformPollEvents(void) +{ + @autoreleasepool { + + for (;;) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event == nil) + break; + + [NSApp sendEvent:event]; + } + + } // autoreleasepool +} + +void _glfwPlatformWaitEvents(void) +{ + @autoreleasepool { + + // I wanted to pass NO to dequeue:, and rely on PollEvents to + // dequeue and send. For reasons not at all clear to me, passing + // NO to dequeue: causes this method never to return. + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + @autoreleasepool { + + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformPostEmptyEvent(void) +{ + @autoreleasepool { + + 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]; + + } // autoreleasepool +} + +void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + if (xpos) + *xpos = pos.x; + if (ypos) + *ypos = contentRect.size.height - pos.y; + + } // autoreleasepool +} + +void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) +{ + @autoreleasepool { + + updateCursorImage(window); + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + window->ns.cursorWarpDeltaX += x - pos.x; + window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; + + if (window->monitor) + { + CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, + CGPointMake(x, y)); + } + else + { + const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); + const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; + const NSPoint globalPoint = globalRect.origin; + + CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, + _glfwTransformYNS(globalPoint.y))); + } + + } // autoreleasepool +} + +void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) +{ + @autoreleasepool { + if (_glfwPlatformWindowFocused(window)) + updateCursorMode(window); + } // autoreleasepool +} + +const char* _glfwPlatformGetScancodeName(int scancode) +{ + @autoreleasepool { + + if (scancode < 0 || scancode > 0xff || + _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode"); + return NULL; + } + + const int key = _glfw.ns.keycodes[scancode]; + + UInt32 deadKeyState = 0; + UniChar characters[4]; + UniCharCount characterCount = 0; + + if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], + scancode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &deadKeyState, + sizeof(characters) / sizeof(characters[0]), + &characterCount, + characters) != noErr) + { + return NULL; + } + + if (!characterCount) + return NULL; + + CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + characters, + characterCount, + kCFAllocatorNull); + CFStringGetCString(string, + _glfw.ns.keynames[key], + sizeof(_glfw.ns.keynames[key]), + kCFStringEncodingUTF8); + CFRelease(string); + + return _glfw.ns.keynames[key]; + + } // autoreleasepool +} + +int _glfwPlatformGetKeyScancode(int key) +{ + return _glfw.ns.scancodes[key]; +} + +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, + const GLFWimage* image, + int xhot, int yhot) +{ + @autoreleasepool { + + NSImage* native; + NSBitmapImageRep* rep; + + rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:image->width + pixelsHigh:image->height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:NSBitmapFormatAlphaNonpremultiplied + bytesPerRow:image->width * 4 + bitsPerPixel:32]; + + if (rep == nil) + return GLFW_FALSE; + + memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); + + native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; + [native addRepresentation:rep]; + + cursor->ns.object = [[NSCursor alloc] initWithImage:native + hotSpot:NSMakePoint(xhot, yhot)]; + + [native release]; + [rep release]; + + if (cursor->ns.object == nil) + return GLFW_FALSE; + + return GLFW_TRUE; + + } // autoreleasepool +} + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) +{ + @autoreleasepool { + + SEL cursorSelector = NULL; + + // HACK: Try to use a private message + switch (shape) + { + case GLFW_RESIZE_EW_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor"); + break; + case GLFW_RESIZE_NS_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor"); + break; + case GLFW_RESIZE_NWSE_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor"); + break; + case GLFW_RESIZE_NESW_CURSOR: + cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor"); + break; + } + + if (cursorSelector && [NSCursor respondsToSelector:cursorSelector]) + { + id object = [NSCursor performSelector:cursorSelector]; + if ([object isKindOfClass:[NSCursor class]]) + cursor->ns.object = object; + } + + if (!cursor->ns.object) + { + switch (shape) + { + case GLFW_ARROW_CURSOR: + cursor->ns.object = [NSCursor arrowCursor]; + break; + case GLFW_IBEAM_CURSOR: + cursor->ns.object = [NSCursor IBeamCursor]; + break; + case GLFW_CROSSHAIR_CURSOR: + cursor->ns.object = [NSCursor crosshairCursor]; + break; + case GLFW_POINTING_HAND_CURSOR: + cursor->ns.object = [NSCursor pointingHandCursor]; + break; + case GLFW_RESIZE_EW_CURSOR: + cursor->ns.object = [NSCursor resizeLeftRightCursor]; + break; + case GLFW_RESIZE_NS_CURSOR: + cursor->ns.object = [NSCursor resizeUpDownCursor]; + break; + case GLFW_RESIZE_ALL_CURSOR: + cursor->ns.object = [NSCursor closedHandCursor]; + break; + case GLFW_NOT_ALLOWED_CURSOR: + cursor->ns.object = [NSCursor operationNotAllowedCursor]; + break; + } + } + + if (!cursor->ns.object) + { + _glfwInputError(GLFW_CURSOR_UNAVAILABLE, + "Cocoa: Standard cursor shape unavailable"); + return GLFW_FALSE; + } + + [cursor->ns.object retain]; + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursor->ns.object) + [(NSCursor*) cursor->ns.object release]; + } // autoreleasepool +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursorInContentArea(window)) + updateCursorImage(window); + } // autoreleasepool +} + +void _glfwPlatformSetClipboardString(const char* string) +{ + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(string) forType:NSPasteboardTypeString]; + } // autoreleasepool +} + +const char* _glfwPlatformGetClipboardString(void) +{ + @autoreleasepool { + + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, + "Cocoa: Failed to retrieve string from pasteboard"); + return NULL; + } + + NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; + if (!object) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to retrieve object from pasteboard"); + return NULL; + } + + free(_glfw.ns.clipboardString); + _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); + + return _glfw.ns.clipboardString; + + } // autoreleasepool +} + +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.ANGLE_platform_angle) + { + int type = 0; + + if (_glfw.egl.ANGLE_platform_angle_opengl) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_metal) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) + type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; + } + + if (type) + { + *attribs = calloc(3, sizeof(EGLint)); + (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + (*attribs)[1] = type; + (*attribs)[2] = EGL_NONE; + return EGL_PLATFORM_ANGLE_ANGLE; + } + } + + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + return window->ns.layer; +} + +void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) +{ + if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_EXT_metal_surface"; + } + else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_MVK_macos_surface"; + } +} + +int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, + VkPhysicalDevice device, + uint32_t queuefamily) +{ + return GLFW_TRUE; +} + +VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, + _GLFWwindow* window, + const VkAllocationCallbacks* allocator, + VkSurfaceKHR* surface) +{ + @autoreleasepool { + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 + // HACK: Dynamically load Core Animation to avoid adding an extra + // dependency for the majority who don't use MoltenVK + NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; + if (!bundle) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to find QuartzCore.framework"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + // NOTE: Create the layer here as makeBackingLayer should not return nil + window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; + if (!window->ns.layer) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create layer for view"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + if (window->ns.retina) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + + [window->ns.view setLayer:window->ns.layer]; + [window->ns.view setWantsLayer:YES]; + + VkResult err; + + if (_glfw.vk.EXT_metal_surface) + { + VkMetalSurfaceCreateInfoEXT sci; + + PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; + vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) + vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); + if (!vkCreateMetalSurfaceEXT) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + sci.pLayer = window->ns.layer; + + err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); + } + else + { + VkMacOSSurfaceCreateInfoMVK sci; + + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; + vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) + vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); + if (!vkCreateMacOSSurfaceMVK) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + sci.pView = window->ns.view; + + err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); + } + + if (err) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create Vulkan surface: %s", + _glfwGetVulkanResultString(err)); + } + + return err; +#else + return VK_ERROR_EXTENSION_NOT_PRESENT; +#endif + + } // autoreleasepool +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT_OR_RETURN(nil); + return window->ns.object; +} + diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt new file mode 100644 index 00000000..09a0a8f1 --- /dev/null +++ b/testing/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +project(test + VERSION 1.0 + LANGUAGES CXX) + +add_executable(test + test.cpp +) + +find_package(OpenGL REQUIRED) + +set(GLFW_BUILD_DOCS OFF) +set(USE_MSVC_RUNTIME_LIBRARY_DLL OFF) +add_subdirectory(D:/src/glfw ${CMAKE_BINARY_DIR}/glfw-build) + +target_include_directories(test PUBLIC D:/src/glfw/include ${OPENGL_INCLUDE_DIRS}) +target_link_libraries(test glfw ${OPENGL_LIBRARIES}) \ No newline at end of file diff --git a/testing/test.cpp b/testing/test.cpp new file mode 100644 index 00000000..e12ef718 --- /dev/null +++ b/testing/test.cpp @@ -0,0 +1,48 @@ +#include + +#include + +void printWindowTitle(GLFWwindow *window) +{ + std::cout << "The window title should be '" << glfwGetWindowTitle(window) << "'.\n"; +} + +void windowShow(GLFWwindow *window) +{ + printWindowTitle(window); + while (!glfwWindowShouldClose(window)) + { + glfwWaitEvents(); + } + glfwSetWindowShouldClose(window, GLFW_FALSE); +} + +int main() +{ + if (!glfwInit()) + { + std::cerr << "Could not initialise glfw.\n"; + return -1; + } + + GLFWwindow *window = glfwCreateWindow(800, 600, "Initial title", NULL, NULL); + windowShow(window); + + glfwSetWindowTitle(window, ""); + windowShow(window); + + glfwSetWindowTitle(window, "Potato's are cool"); + windowShow(window); + + glfwSetWindowTitle(window, u8"πŸ˜€ πŸ˜ƒ πŸ˜„ 😁"); + windowShow(window); + + glfwDestroyWindow(window); + + window = glfwCreateWindow(800, 600, "", NULL, NULL); + windowShow(window); + + glfwTerminate(); + + return 0; +} \ No newline at end of file From d1c2a28a415309f8fc0552d50b620e4819ff7909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Mon, 7 Jun 2021 22:21:23 +0200 Subject: [PATCH 14/16] Migrate from Travis CI to GitHub Actions The old Travis CI .org service has been throttled for a while now and is said to be shutting down completely next week. This migrates the Travis CI build jobs to GitHub Actions, with minor changes. - The trailing whitespace detection has been removed for now. - The libegl1-mesa-dev dependency for Wayland has been removed as it appears to no longer be necessary for building. --- .github/workflows/build.yml | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d87ae8d1..74b28e4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,18 +2,18 @@ name: Build on: pull_request: push: - branches: [ ci, master, latest, 3.3-stable ] + branches: [ ci, master, 3.3-stable ] permissions: statuses: write contents: read +env: + CC: clang + CFLAGS: -Werror jobs: build-linux-x11-clang: name: X11 (Linux, Clang) runs-on: ubuntu-latest - env: - CC: clang - CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -34,9 +34,6 @@ jobs: build-linux-wayland-clang: name: Wayland (Linux, Clang) runs-on: ubuntu-latest - env: - CC: clang - CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -57,9 +54,6 @@ jobs: build-linux-null-clang: name: Null (Linux, Clang) runs-on: ubuntu-latest - env: - CC: clang - CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -81,7 +75,6 @@ jobs: name: Cocoa (macOS, Clang) runs-on: macos-latest env: - CFLAGS: -Werror MACOSX_DEPLOYMENT_TARGET: 10.8 steps: - uses: actions/checkout@v2 @@ -96,21 +89,3 @@ jobs: - name: Build shared library run: cmake --build build-shared --parallel - build-windows-win32-vs2019: - name: Win32 (Windows, VS2019) - runs-on: windows-latest - env: - CFLAGS: /WX - steps: - - uses: actions/checkout@v2 - - - name: Configure static library - run: cmake -S . -B build-static -G "Visual Studio 16 2019" - - name: Build static library - run: cmake --build build-static --parallel - - - name: Configure shared library - run: cmake -S . -B build-shared -G "Visual Studio 16 2019" -D BUILD_SHARED_LIBS=ON - - name: Build shared library - run: cmake --build build-shared --parallel - From c65763bbb4dfca93818d7f40c82267ddaaca5ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Wed, 9 Jun 2021 16:44:45 +0200 Subject: [PATCH 15/16] Move VS 2019 builds from AppVeyor to GH Actions GitHub runners unfortunately do not come with MinGW or VS 2010 pre-installed, so moving those builds will be more involved. MinGW-w64 is not a good replacement for MinGW as it is far more complete. This gives at least some feedback for all supported platforms via the GitHub Actions system. --- .github/workflows/build.yml | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74b28e4a..f3058c0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,14 +6,14 @@ on: permissions: statuses: write contents: read -env: - CC: clang - CFLAGS: -Werror jobs: build-linux-x11-clang: name: X11 (Linux, Clang) runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -34,6 +34,9 @@ jobs: build-linux-wayland-clang: name: Wayland (Linux, Clang) runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -54,6 +57,9 @@ jobs: build-linux-null-clang: name: Null (Linux, Clang) runs-on: ubuntu-latest + env: + CC: clang + CFLAGS: -Werror steps: - uses: actions/checkout@v2 - name: Install dependencies @@ -75,6 +81,7 @@ jobs: name: Cocoa (macOS, Clang) runs-on: macos-latest env: + CFLAGS: -Werror MACOSX_DEPLOYMENT_TARGET: 10.8 steps: - uses: actions/checkout@v2 @@ -89,3 +96,21 @@ jobs: - name: Build shared library run: cmake --build build-shared --parallel + build-windows-win32-vs2019: + name: Win32 (Windows, VS2019) + runs-on: windows-latest + env: + CFLAGS: /WX + steps: + - uses: actions/checkout@v2 + + - name: Configure static library + run: cmake -S . -B build-static -G "Visual Studio 16 2019" + - name: Build static library + run: cmake --build build-static --parallel + + - name: Configure shared library + run: cmake -S . -B build-shared -G "Visual Studio 16 2019" -D BUILD_SHARED_LIBS=ON + - name: Build shared library + run: cmake --build build-shared --parallel + From bd5c843084c77a8f9279bd18857901ce7e97354b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Wed, 9 Jun 2021 16:48:34 +0200 Subject: [PATCH 16/16] Add 'latest' branch to all CI builds With 3.4, the tip of the 'latest' branch will be a merge commit for 3.3-stable and the main branch, and not simply a fast-foward to an already tested commit. This sets up a tiny additional safety net before that merge. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3058c0d..d87ae8d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build on: pull_request: push: - branches: [ ci, master, 3.3-stable ] + branches: [ ci, master, latest, 3.3-stable ] permissions: statuses: write contents: read