From 21d1e350a3b22ba690553f8371714fd9e896c7c8 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 29 Oct 2020 13:35:22 +0100 Subject: Move implementation source files to a conventional src directory I think this attempt to be optionally header-only was misguided, particularly installing source code to the system include path. Typically anyone vendoring code just includes the repository and builds from there anyway. This commit moves all the implementation code to a typical src directory (Don't Be Weird). I still think there is some value in simple "inline" deployment, but that would be better achieved another way, like producing a single-file amalgamation that builds anywhere, ala sqlite. --- include/pugl/detail/.clang-tidy | 12 - include/pugl/detail/implementation.c | 488 ------------ include/pugl/detail/implementation.h | 86 -- include/pugl/detail/mac.h | 60 -- include/pugl/detail/mac.m | 1449 --------------------------------- include/pugl/detail/mac_cairo.m | 165 ---- include/pugl/detail/mac_gl.m | 202 ----- include/pugl/detail/mac_stub.m | 96 --- include/pugl/detail/mac_vulkan.m | 216 ----- include/pugl/detail/stub.h | 75 -- include/pugl/detail/types.h | 118 --- include/pugl/detail/win.c | 1139 -------------------------- include/pugl/detail/win.h | 151 ---- include/pugl/detail/win_cairo.c | 177 ----- include/pugl/detail/win_gl.c | 323 -------- include/pugl/detail/win_stub.c | 80 -- include/pugl/detail/win_vulkan.c | 130 --- include/pugl/detail/x11.c | 1348 ------------------------------- include/pugl/detail/x11.h | 81 -- include/pugl/detail/x11_cairo.c | 170 ---- include/pugl/detail/x11_gl.c | 239 ------ include/pugl/detail/x11_stub.c | 57 -- include/pugl/detail/x11_vulkan.c | 134 ---- src/.clang-tidy | 12 + src/implementation.c | 489 ++++++++++++ src/implementation.h | 87 ++ src/mac.h | 60 ++ src/mac.m | 1450 ++++++++++++++++++++++++++++++++++ src/mac_cairo.m | 166 ++++ src/mac_gl.m | 203 +++++ src/mac_stub.m | 97 +++ src/mac_vulkan.m | 217 +++++ src/stub.h | 75 ++ src/types.h | 118 +++ src/win.c | 1140 ++++++++++++++++++++++++++ src/win.h | 151 ++++ src/win_cairo.c | 178 +++++ src/win_gl.c | 324 ++++++++ src/win_stub.c | 80 ++ src/win_vulkan.c | 131 +++ src/x11.c | 1349 +++++++++++++++++++++++++++++++ src/x11.h | 82 ++ src/x11_cairo.c | 171 ++++ src/x11_gl.c | 240 ++++++ src/x11_stub.c | 58 ++ src/x11_vulkan.c | 135 ++++ wscript | 49 +- 47 files changed, 7032 insertions(+), 7026 deletions(-) delete mode 100644 include/pugl/detail/.clang-tidy delete mode 100644 include/pugl/detail/implementation.c delete mode 100644 include/pugl/detail/implementation.h delete mode 100644 include/pugl/detail/mac.h delete mode 100644 include/pugl/detail/mac.m delete mode 100644 include/pugl/detail/mac_cairo.m delete mode 100644 include/pugl/detail/mac_gl.m delete mode 100644 include/pugl/detail/mac_stub.m delete mode 100644 include/pugl/detail/mac_vulkan.m delete mode 100644 include/pugl/detail/stub.h delete mode 100644 include/pugl/detail/types.h delete mode 100644 include/pugl/detail/win.c delete mode 100644 include/pugl/detail/win.h delete mode 100644 include/pugl/detail/win_cairo.c delete mode 100644 include/pugl/detail/win_gl.c delete mode 100644 include/pugl/detail/win_stub.c delete mode 100644 include/pugl/detail/win_vulkan.c delete mode 100644 include/pugl/detail/x11.c delete mode 100644 include/pugl/detail/x11.h delete mode 100644 include/pugl/detail/x11_cairo.c delete mode 100644 include/pugl/detail/x11_gl.c delete mode 100644 include/pugl/detail/x11_stub.c delete mode 100644 include/pugl/detail/x11_vulkan.c create mode 100644 src/.clang-tidy create mode 100644 src/implementation.c create mode 100644 src/implementation.h create mode 100644 src/mac.h create mode 100644 src/mac.m create mode 100644 src/mac_cairo.m create mode 100644 src/mac_gl.m create mode 100644 src/mac_stub.m create mode 100644 src/mac_vulkan.m create mode 100644 src/stub.h create mode 100644 src/types.h create mode 100644 src/win.c create mode 100644 src/win.h create mode 100644 src/win_cairo.c create mode 100644 src/win_gl.c create mode 100644 src/win_stub.c create mode 100644 src/win_vulkan.c create mode 100644 src/x11.c create mode 100644 src/x11.h create mode 100644 src/x11_cairo.c create mode 100644 src/x11_gl.c create mode 100644 src/x11_stub.c create mode 100644 src/x11_vulkan.c diff --git a/include/pugl/detail/.clang-tidy b/include/pugl/detail/.clang-tidy deleted file mode 100644 index 7bc36f7..0000000 --- a/include/pugl/detail/.clang-tidy +++ /dev/null @@ -1,12 +0,0 @@ -Checks: > - *, - -*-uppercase-literal-suffix, - -*magic-numbers, - -bugprone-suspicious-string-compare, - -cert-flp30-c, - -clang-analyzer-security.FloatLoopCounter, - -hicpp-multiway-paths-covered, - -hicpp-signed-bitwise, - -readability-else-after-return, -FormatStyle: file -HeaderFilterRegex: 'pugl/.*' diff --git a/include/pugl/detail/implementation.c b/include/pugl/detail/implementation.c deleted file mode 100644 index 4f0776a..0000000 --- a/include/pugl/detail/implementation.c +++ /dev/null @@ -1,488 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file implementation.c - @brief Platform-independent implementation. -*/ - -#include "pugl/detail/implementation.h" -#include "pugl/pugl.h" - -#include -#include -#include -#include -#include - -static const char* -puglLogLevelPrefix(const PuglLogLevel level) -{ - switch (level) { - case PUGL_LOG_LEVEL_ERR: - return "error: "; - case PUGL_LOG_LEVEL_WARNING: - return "warning: "; - case PUGL_LOG_LEVEL_INFO: - case PUGL_LOG_LEVEL_DEBUG: - return ""; - } - - return ""; -} - -static void -puglDefaultLogFunc(PuglWorld* PUGL_UNUSED(world), - PuglLogLevel level, - const char* msg) -{ - fprintf(stderr, "%s%s", puglLogLevelPrefix(level), msg); -} - -const char* -puglStrerror(const PuglStatus status) -{ - // clang-format off - switch (status) { - case PUGL_SUCCESS: return "Success"; - case PUGL_FAILURE: return "Non-fatal failure"; - case PUGL_UNKNOWN_ERROR: return "Unknown system error"; - case PUGL_BAD_BACKEND: return "Invalid or missing backend"; - case PUGL_BAD_CONFIGURATION: return "Invalid view configuration"; - case PUGL_BAD_PARAMETER: return "Invalid parameter"; - case PUGL_BACKEND_FAILED: return "Backend initialisation failed"; - case PUGL_REGISTRATION_FAILED: return "Class registration failed"; - case PUGL_REALIZE_FAILED: return "View creation failed"; - case PUGL_SET_FORMAT_FAILED: return "Failed to set pixel format"; - case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context"; - case PUGL_UNSUPPORTED_TYPE: return "Unsupported data type"; - } - // clang-format on - - return "Unknown error"; -} - -void -puglSetString(char** dest, const char* string) -{ - if (*dest != string) { - const size_t len = strlen(string); - - *dest = (char*)realloc(*dest, len + 1); - strncpy(*dest, string, len + 1); - } -} - -void -puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) -{ - if (data) { - dest->len = len; - dest->data = realloc(dest->data, len + 1); - memcpy(dest->data, data, len); - ((char*)dest->data)[len] = 0; - } else { - dest->len = 0; - dest->data = NULL; - } -} - -static void -puglSetDefaultHints(PuglHints hints) -{ - hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE; - hints[PUGL_CONTEXT_VERSION_MAJOR] = 2; - hints[PUGL_CONTEXT_VERSION_MINOR] = 0; - hints[PUGL_RED_BITS] = 8; - hints[PUGL_GREEN_BITS] = 8; - hints[PUGL_BLUE_BITS] = 8; - hints[PUGL_ALPHA_BITS] = 8; - hints[PUGL_DEPTH_BITS] = 0; - hints[PUGL_STENCIL_BITS] = 0; - hints[PUGL_SAMPLES] = 0; - hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE; - hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE; - hints[PUGL_RESIZABLE] = PUGL_FALSE; - hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE; - hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE; -} - -PuglWorld* -puglNewWorld(PuglWorldType type, PuglWorldFlags flags) -{ - PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld)); - if (!world || !(world->impl = puglInitWorldInternals(type, flags))) { - free(world); - return NULL; - } - - world->startTime = puglGetTime(world); - world->logFunc = puglDefaultLogFunc; - world->logLevel = PUGL_LOG_LEVEL_INFO; - - puglSetString(&world->className, "Pugl"); - - return world; -} - -void -puglFreeWorld(PuglWorld* const world) -{ - puglFreeWorldInternals(world); - free(world->className); - free(world->views); - free(world); -} - -void -puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle) -{ - world->handle = handle; -} - -PuglWorldHandle -puglGetWorldHandle(PuglWorld* world) -{ - return world->handle; -} - -PuglStatus -puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc) -{ - world->logFunc = logFunc; - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetLogLevel(PuglWorld* world, PuglLogLevel level) -{ - world->logLevel = level; - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetClassName(PuglWorld* const world, const char* const name) -{ - puglSetString(&world->className, name); - return PUGL_SUCCESS; -} - -PuglView* -puglNewView(PuglWorld* const world) -{ - PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); - if (!view || !(view->impl = puglInitViewInternals())) { - free(view); - return NULL; - } - - view->world = world; - view->minWidth = 1; - view->minHeight = 1; - - puglSetDefaultHints(view->hints); - - // Add to world view list - ++world->numViews; - world->views = (PuglView**)realloc(world->views, - world->numViews * sizeof(PuglView*)); - - world->views[world->numViews - 1] = view; - - return view; -} - -void -puglFreeView(PuglView* view) -{ - puglDispatchSimpleEvent(view, PUGL_DESTROY); - - // Remove from world view list - PuglWorld* world = view->world; - for (size_t i = 0; i < world->numViews; ++i) { - if (world->views[i] == view) { - if (i == world->numViews - 1) { - world->views[i] = NULL; - } else { - memmove(world->views + i, world->views + i + 1, - sizeof(PuglView*) * (world->numViews - i - 1)); - world->views[world->numViews - 1] = NULL; - } - --world->numViews; - } - } - - free(view->title); - free(view->clipboard.data); - puglFreeViewInternals(view); - free(view); -} - -PuglWorld* -puglGetWorld(PuglView* view) -{ - return view->world; -} - -PuglStatus -puglSetViewHint(PuglView* view, PuglViewHint hint, int value) -{ - if (value == PUGL_DONT_CARE) { - switch (hint) { - case PUGL_USE_COMPAT_PROFILE: - case PUGL_USE_DEBUG_CONTEXT: - case PUGL_CONTEXT_VERSION_MAJOR: - case PUGL_CONTEXT_VERSION_MINOR: - case PUGL_SWAP_INTERVAL: - return PUGL_BAD_PARAMETER; - default: - break; - } - } - - if (hint < PUGL_NUM_VIEW_HINTS) { - view->hints[hint] = value; - return PUGL_SUCCESS; - } - - return PUGL_BAD_PARAMETER; -} - -int -puglGetViewHint(const PuglView* view, PuglViewHint hint) -{ - if (hint < PUGL_NUM_VIEW_HINTS) { - return view->hints[hint]; - } - - return PUGL_DONT_CARE; -} - -PuglStatus -puglSetParentWindow(PuglView* view, PuglNativeView parent) -{ - view->parent = parent; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetBackend(PuglView* view, const PuglBackend* backend) -{ - view->backend = backend; - return PUGL_SUCCESS; -} - -void -puglSetHandle(PuglView* view, PuglHandle handle) -{ - view->handle = handle; -} - -PuglHandle -puglGetHandle(PuglView* view) -{ - return view->handle; -} - -bool -puglGetVisible(const PuglView* view) -{ - return view->visible; -} - -PuglRect -puglGetFrame(const PuglView* view) -{ - return view->frame; -} - -void* -puglGetContext(PuglView* view) -{ - return view->backend->getContext(view); -} - -#ifndef PUGL_DISABLE_DEPRECATED - -PuglStatus -puglPollEvents(PuglWorld* world, double timeout) -{ - return puglUpdate(world, timeout); -} - -PuglStatus -puglDispatchEvents(PuglWorld* world) -{ - return puglUpdate(world, 0.0); -} - -#endif - -PuglStatus -puglEnterContext(PuglView* view) -{ - return view->backend->enter(view, NULL); -} - -PuglStatus -puglLeaveContext(PuglView* view) -{ - return view->backend->leave(view, NULL); -} - -PuglStatus -puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc) -{ - view->eventFunc = eventFunc; - return PUGL_SUCCESS; -} - -/// Return the code point for buf, or the replacement character on error -uint32_t -puglDecodeUTF8(const uint8_t* buf) -{ -#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0) - - // http://en.wikipedia.org/wiki/UTF-8 - - if (buf[0] < 0x80) { - return buf[0]; - } else if (buf[0] < 0xC2) { - return 0xFFFD; - } else if (buf[0] < 0xE0) { - FAIL_IF((buf[1] & 0xC0u) != 0x80); - return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u; - } else if (buf[0] < 0xF0) { - FAIL_IF((buf[1] & 0xC0u) != 0x80); - FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); - FAIL_IF((buf[2] & 0xC0u) != 0x80); - return ((uint32_t)buf[0] << 12u) + // - ((uint32_t)buf[1] << 6u) + // - ((uint32_t)buf[2] - 0xE2080u); - } else if (buf[0] < 0xF5) { - FAIL_IF((buf[1] & 0xC0u) != 0x80); - FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); - FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); - FAIL_IF((buf[2] & 0xC0u) != 0x80u); - FAIL_IF((buf[3] & 0xC0u) != 0x80u); - return (((uint32_t)buf[0] << 18u) + // - ((uint32_t)buf[1] << 12u) + // - ((uint32_t)buf[2] << 6u) + // - ((uint32_t)buf[3] - 0x3C82080u)); - } - return 0xFFFD; -} - -static inline bool -puglMustConfigure(PuglView* view, const PuglEventConfigure* configure) -{ - return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure)); -} - -void -puglDispatchSimpleEvent(PuglView* view, const PuglEventType type) -{ - assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP || - type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE || - type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE); - - const PuglEvent event = {{type, 0}}; - puglDispatchEvent(view, &event); -} - -void -puglDispatchEventInContext(PuglView* view, const PuglEvent* event) -{ - if (event->type == PUGL_CONFIGURE) { - view->frame.x = event->configure.x; - view->frame.y = event->configure.y; - view->frame.width = event->configure.width; - view->frame.height = event->configure.height; - - if (puglMustConfigure(view, &event->configure)) { - view->eventFunc(view, event); - view->lastConfigure = event->configure; - } - } else if (event->type == PUGL_EXPOSE) { - if (event->expose.width > 0 && event->expose.height > 0) { - view->eventFunc(view, event); - } - } else { - view->eventFunc(view, event); - } -} - -void -puglDispatchEvent(PuglView* view, const PuglEvent* event) -{ - switch (event->type) { - case PUGL_NOTHING: - break; - case PUGL_CREATE: - case PUGL_DESTROY: - view->backend->enter(view, NULL); - view->eventFunc(view, event); - view->backend->leave(view, NULL); - break; - case PUGL_CONFIGURE: - if (puglMustConfigure(view, &event->configure)) { - view->backend->enter(view, NULL); - puglDispatchEventInContext(view, event); - view->backend->leave(view, NULL); - } - break; - case PUGL_EXPOSE: - view->backend->enter(view, &event->expose); - puglDispatchEventInContext(view, event); - view->backend->leave(view, &event->expose); - break; - default: - view->eventFunc(view, event); - } -} - -const void* -puglGetInternalClipboard(const PuglView* const view, - const char** const type, - size_t* const len) -{ - if (len) { - *len = view->clipboard.len; - } - - if (type) { - *type = "text/plain"; - } - - return view->clipboard.data; -} - -PuglStatus -puglSetInternalClipboard(PuglView* const view, - const char* const type, - const void* const data, - const size_t len) -{ - if (type && strcmp(type, "text/plain")) { - return PUGL_UNSUPPORTED_TYPE; - } - - puglSetBlob(&view->clipboard, data, len); - return PUGL_SUCCESS; -} - diff --git a/include/pugl/detail/implementation.h b/include/pugl/detail/implementation.h deleted file mode 100644 index c40a2bb..0000000 --- a/include/pugl/detail/implementation.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file implementation.h - @brief Shared declarations for implementation. -*/ - -#ifndef PUGL_DETAIL_IMPLEMENTATION_H -#define PUGL_DETAIL_IMPLEMENTATION_H - -#include "pugl/detail/types.h" -#include "pugl/pugl.h" - -#include -#include - -PUGL_BEGIN_DECLS - -/// Set `blob` to `data` with length `len`, reallocating if necessary -void -puglSetBlob(PuglBlob* dest, const void* data, size_t len); - -/// Reallocate and set `*dest` to `string` -void -puglSetString(char** dest, const char* string); - -/// Allocate and initialise world internals (implemented once per platform) -PuglWorldInternals* -puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags); - -/// Destroy and free world internals (implemented once per platform) -void -puglFreeWorldInternals(PuglWorld* world); - -/// Allocate and initialise view internals (implemented once per platform) -PuglInternals* -puglInitViewInternals(void); - -/// Destroy and free view internals (implemented once per platform) -void -puglFreeViewInternals(PuglView* view); - -/// Return the Unicode code point for `buf` or the replacement character -uint32_t -puglDecodeUTF8(const uint8_t* buf); - -/// Dispatch an event with a simple `type` to `view` -void -puglDispatchSimpleEvent(PuglView* view, PuglEventType type); - -/// Dispatch `event` to `view` while already in the graphics context -void -puglDispatchEventInContext(PuglView* view, const PuglEvent* event); - -/// Dispatch `event` to `view`, entering graphics context if necessary -void -puglDispatchEvent(PuglView* view, const PuglEvent* event); - -/// Set internal (stored in view) clipboard contents -const void* -puglGetInternalClipboard(const PuglView* view, const char** type, size_t* len); - -/// Set internal (stored in view) clipboard contents -PuglStatus -puglSetInternalClipboard(PuglView* view, - const char* type, - const void* data, - size_t len); - -PUGL_END_DECLS - -#endif // PUGL_DETAIL_IMPLEMENTATION_H diff --git a/include/pugl/detail/mac.h b/include/pugl/detail/mac.h deleted file mode 100644 index d31eb91..0000000 --- a/include/pugl/detail/mac.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - Copyright 2017 Hanspeter Portner - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac.h - @brief Shared definitions for MacOS implementation. -*/ - -#ifndef PUGL_DETAIL_MAC_H -#define PUGL_DETAIL_MAC_H - -#include "pugl/pugl.h" - -#import - -#include - -@interface PuglWrapperView : NSView - -- (void)dispatchExpose:(NSRect)rect; -- (void)setReshaped; - -@end - -@interface PuglWindow : NSWindow - -- (void)setPuglview:(PuglView*)view; - -@end - -struct PuglWorldInternalsImpl { - NSApplication* app; - NSAutoreleasePool* autoreleasePool; -}; - -struct PuglInternalsImpl { - NSApplication* app; - PuglWrapperView* wrapperView; - NSView* drawView; - NSCursor* cursor; - PuglWindow* window; - uint32_t mods; - bool mouseTracked; -}; - -#endif // PUGL_DETAIL_MAC_H diff --git a/include/pugl/detail/mac.m b/include/pugl/detail/mac.m deleted file mode 100644 index a807761..0000000 --- a/include/pugl/detail/mac.m +++ /dev/null @@ -1,1449 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - Copyright 2017 Hanspeter Portner - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac.m - @brief MacOS implementation. -*/ - -#define GL_SILENCE_DEPRECATION 1 - -#include "pugl/detail/mac.h" - -#include "pugl/detail/implementation.h" -#include "pugl/pugl.h" - -#import - -#include - -#include - -#ifndef __MAC_10_10 -typedef NSUInteger NSEventModifierFlags; -#endif - -#ifndef __MAC_10_12 -typedef NSUInteger NSWindowStyleMask; -#endif - -static NSRect -rectToScreen(NSScreen* screen, NSRect rect) -{ - const double screenHeight = [screen frame].size.height; - - rect.origin.y = screenHeight - rect.origin.y - rect.size.height; - return rect; -} - -static NSScreen* -viewScreen(PuglView* view) -{ - return view->impl->window ? [view->impl->window screen] : [NSScreen mainScreen]; -} - -static NSRect -nsRectToPoints(PuglView* view, const NSRect rect) -{ - const double scaleFactor = [viewScreen(view) backingScaleFactor]; - - return NSMakeRect(rect.origin.x / scaleFactor, - rect.origin.y / scaleFactor, - rect.size.width / scaleFactor, - rect.size.height / scaleFactor); -} - -static NSRect -nsRectFromPoints(PuglView* view, const NSRect rect) -{ - const double scaleFactor = [viewScreen(view) backingScaleFactor]; - - return NSMakeRect(rect.origin.x * scaleFactor, - rect.origin.y * scaleFactor, - rect.size.width * scaleFactor, - rect.size.height * scaleFactor); -} - -static NSPoint -nsPointFromPoints(PuglView* view, const NSPoint point) -{ - const double scaleFactor = [viewScreen(view) backingScaleFactor]; - - return NSMakePoint(point.x * scaleFactor, point.y * scaleFactor); -} - -static NSRect -rectToNsRect(const PuglRect rect) -{ - return NSMakeRect(rect.x, rect.y, rect.width, rect.height); -} - -static NSSize -sizePoints(PuglView* view, const double width, const double height) -{ - const double scaleFactor = [viewScreen(view) backingScaleFactor]; - - return NSMakeSize(width / scaleFactor, height / scaleFactor); -} - -static void -updateViewRect(PuglView* view) -{ - NSWindow* const window = view->impl->window; - if (window) { - const NSRect screenFramePt = [[NSScreen mainScreen] frame]; - const NSRect screenFramePx = nsRectFromPoints(view, screenFramePt); - const NSRect framePt = [window frame]; - const NSRect contentPt = [window contentRectForFrameRect:framePt]; - const NSRect contentPx = nsRectFromPoints(view, contentPt); - const double screenHeight = screenFramePx.size.height; - - view->frame.x = contentPx.origin.x; - view->frame.y = screenHeight - contentPx.origin.y - contentPx.size.height; - view->frame.width = contentPx.size.width; - view->frame.height = contentPx.size.height; - } -} - -@implementation PuglWindow -{ -@public - PuglView* puglview; -} - -- (id)initWithContentRect:(NSRect)contentRect - styleMask:(NSWindowStyleMask)aStyle - backing:(NSBackingStoreType)bufferingType - defer:(BOOL)flag -{ - (void)flag; - - NSWindow* result = [super initWithContentRect:contentRect - styleMask:aStyle - backing:bufferingType - defer:NO]; - - [result setAcceptsMouseMovedEvents:YES]; - return (PuglWindow*)result; -} - -- (void)setPuglview:(PuglView*)view -{ - puglview = view; - - [self - setContentSize:sizePoints(view, view->frame.width, view->frame.height)]; -} - -- (BOOL)canBecomeKeyWindow -{ - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - return YES; -} - -- (void)setIsVisible:(BOOL)flag -{ - if (flag && !puglview->visible) { - const PuglEventConfigure ev = { - PUGL_CONFIGURE, - 0, - puglview->frame.x, - puglview->frame.y, - puglview->frame.width, - puglview->frame.height, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - puglDispatchSimpleEvent(puglview, PUGL_MAP); - } else if (!flag && puglview->visible) { - puglDispatchSimpleEvent(puglview, PUGL_UNMAP); - } - - puglview->visible = flag; - - [super setIsVisible:flag]; -} - -@end - -@implementation PuglWrapperView -{ -@public - PuglView* puglview; - NSTrackingArea* trackingArea; - NSMutableAttributedString* markedText; - NSMutableDictionary* userTimers; - bool reshaped; -} - -- (void)dispatchExpose:(NSRect)rect -{ - const double scaleFactor = [[NSScreen mainScreen] backingScaleFactor]; - - if (reshaped) { - updateViewRect(puglview); - - const PuglEventConfigure ev = { - PUGL_CONFIGURE, - 0, - puglview->frame.x, - puglview->frame.y, - puglview->frame.width, - puglview->frame.height, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - reshaped = false; - } - - if (![[puglview->impl->drawView window] isVisible]) { - return; - } - - const PuglEventExpose ev = { - PUGL_EXPOSE, - 0, - rect.origin.x * scaleFactor, - rect.origin.y * scaleFactor, - rect.size.width * scaleFactor, - rect.size.height * scaleFactor, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (NSSize)intrinsicContentSize -{ - if (puglview->defaultWidth || puglview->defaultHeight) { - return sizePoints(puglview, - puglview->defaultWidth, - puglview->defaultHeight); - } - - return NSMakeSize(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric); -} - -- (BOOL)isFlipped -{ - return YES; -} - -- (BOOL)acceptsFirstResponder -{ - return YES; -} - -- (void)setReshaped -{ - reshaped = true; -} - -static uint32_t -getModifiers(const NSEvent* const ev) -{ - const NSEventModifierFlags modifierFlags = [ev modifierFlags]; - - return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) | - ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) | - ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) | - ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0)); -} - -static PuglKey -keySymToSpecial(const NSEvent* const ev) -{ - NSString* chars = [ev charactersIgnoringModifiers]; - if ([chars length] == 1) { - switch ([chars characterAtIndex:0]) { - case NSF1FunctionKey: return PUGL_KEY_F1; - case NSF2FunctionKey: return PUGL_KEY_F2; - case NSF3FunctionKey: return PUGL_KEY_F3; - case NSF4FunctionKey: return PUGL_KEY_F4; - case NSF5FunctionKey: return PUGL_KEY_F5; - case NSF6FunctionKey: return PUGL_KEY_F6; - case NSF7FunctionKey: return PUGL_KEY_F7; - case NSF8FunctionKey: return PUGL_KEY_F8; - case NSF9FunctionKey: return PUGL_KEY_F9; - case NSF10FunctionKey: return PUGL_KEY_F10; - case NSF11FunctionKey: return PUGL_KEY_F11; - case NSF12FunctionKey: return PUGL_KEY_F12; - case NSDeleteCharacter: return PUGL_KEY_BACKSPACE; - case NSDeleteFunctionKey: return PUGL_KEY_DELETE; - case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT; - case NSUpArrowFunctionKey: return PUGL_KEY_UP; - case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT; - case NSDownArrowFunctionKey: return PUGL_KEY_DOWN; - case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP; - case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN; - case NSHomeFunctionKey: return PUGL_KEY_HOME; - case NSEndFunctionKey: return PUGL_KEY_END; - case NSInsertFunctionKey: return PUGL_KEY_INSERT; - case NSMenuFunctionKey: return PUGL_KEY_MENU; - case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK; - case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK; - case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN; - case NSPauseFunctionKey: return PUGL_KEY_PAUSE; - } - // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged] - } - return (PuglKey)0; -} - -- (void)updateTrackingAreas -{ - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - const int opts = (NSTrackingMouseEnteredAndExited | - NSTrackingMouseMoved | - NSTrackingActiveAlways); - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:opts - owner:self - userInfo:nil]; - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -- (NSPoint)eventLocation:(NSEvent*)event -{ - return nsPointFromPoints(puglview, - [self convertPoint:[event locationInWindow] - fromView:nil]); -} - -static void -handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) -{ - const NSPoint wloc = [view eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventCrossing ev = { - type, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - PUGL_CROSSING_NORMAL, - }; - - puglDispatchEvent(view->puglview, (const PuglEvent*)&ev); -} - -- (void)mouseEntered:(NSEvent*)event -{ - handleCrossing(self, event, PUGL_POINTER_IN); - [puglview->impl->cursor set]; - puglview->impl->mouseTracked = true; -} - -- (void)mouseExited:(NSEvent*)event -{ - [[NSCursor arrowCursor] set]; - handleCrossing(self, event, PUGL_POINTER_OUT); - puglview->impl->mouseTracked = false; -} - -- (void)cursorUpdate:(NSEvent*)event -{ - (void)event; - [puglview->impl->cursor set]; -} - -- (void)mouseMoved:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventMotion ev = { - PUGL_MOTION, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void)mouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void)rightMouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void)otherMouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void)mouseDown:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventButton ev = { - PUGL_BUTTON_PRESS, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - (uint32_t)[event buttonNumber] + 1, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void)mouseUp:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventButton ev = { - PUGL_BUTTON_RELEASE, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - (uint32_t)[event buttonNumber] + 1, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void)rightMouseDown:(NSEvent*)event -{ - [self mouseDown: event]; -} - -- (void)rightMouseUp:(NSEvent*)event -{ - [self mouseUp: event]; -} - -- (void)otherMouseDown:(NSEvent*)event -{ - [self mouseDown: event]; -} - -- (void)otherMouseUp:(NSEvent*)event -{ - [self mouseUp: event]; -} - -- (void)scrollWheel:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const double dx = [event scrollingDeltaX]; - const double dy = [event scrollingDeltaY]; - const PuglScrollDirection dir = - ((dx == 0.0 && dy > 0.0) - ? PUGL_SCROLL_UP - : ((dx == 0.0 && dy < 0.0) - ? PUGL_SCROLL_DOWN - : ((dy == 0.0 && dx > 0.0) - ? PUGL_SCROLL_RIGHT - : ((dy == 0.0 && dx < 0.0) ? PUGL_SCROLL_LEFT - : PUGL_SCROLL_SMOOTH)))); - - const PuglEventScroll ev = { - PUGL_SCROLL, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event hasPreciseScrollingDeltas] ? PUGL_SCROLL_SMOOTH : dir, - dx, - dy, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void)keyDown:(NSEvent*)event -{ - if (puglview->hints[PUGL_IGNORE_KEY_REPEAT] && [event isARepeat]) { - return; - } - - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglKey spec = keySymToSpecial(event); - const NSString* chars = [event charactersIgnoringModifiers]; - const char* str = [[chars lowercaseString] UTF8String]; - const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); - - const PuglEventKey ev = { - PUGL_KEY_PRESS, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - (code != 0xFFFD) ? code : 0, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - - if (!spec) { - [self interpretKeyEvents:@[event]]; - } -} - -- (void)keyUp:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglKey spec = keySymToSpecial(event); - const NSString* chars = [event charactersIgnoringModifiers]; - const char* str = [[chars lowercaseString] UTF8String]; - const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); - - const PuglEventKey ev = { - PUGL_KEY_RELEASE, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - (code != 0xFFFD) ? code : 0, - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (BOOL)hasMarkedText -{ - return [markedText length] > 0; -} - -- (NSRange)markedRange -{ - return (([markedText length] > 0) - ? NSMakeRange(0, [markedText length] - 1) - : NSMakeRange(NSNotFound, 0)); -} - -- (NSRange)selectedRange -{ - return NSMakeRange(NSNotFound, 0); -} - -- (void)setMarkedText:(id)string - selectedRange:(NSRange)selected - replacementRange:(NSRange)replacement -{ - (void)selected; - (void)replacement; - [markedText release]; - markedText = ( - [(NSObject*)string isKindOfClass:[NSAttributedString class]] - ? [[NSMutableAttributedString alloc] initWithAttributedString:string] - : [[NSMutableAttributedString alloc] initWithString:string]); -} - -- (void)unmarkText -{ - [[markedText mutableString] setString:@""]; -} - -- (NSArray*)validAttributesForMarkedText -{ - return @[]; -} - -- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range - actualRange: - (NSRangePointer)actual -{ - (void)range; - (void)actual; - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)point -{ - (void)point; - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)range - actualRange:(NSRangePointer)actual -{ - (void)range; - (void)actual; - - const NSRect frame = [self bounds]; - return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); -} - -- (void)doCommandBySelector:(SEL)selector -{ - (void)selector; -} - -- (void)insertText:(id)string replacementRange:(NSRange)replacement -{ - (void)replacement; - - NSEvent* const event = [NSApp currentEvent]; - NSString* const characters = - ([(NSObject*)string isKindOfClass:[NSAttributedString class]] - ? [(NSAttributedString*)string string] - : (NSString*)string); - - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - for (size_t i = 0; i < [characters length]; ++i) { - const uint32_t code = [characters characterAtIndex:i]; - char utf8[8] = {0}; - NSUInteger len = 0; - - [characters getBytes:utf8 - maxLength:sizeof(utf8) - usedLength:&len - encoding:NSUTF8StringEncoding - options:0 - range:NSMakeRange(i, i + 1) - remainingRange:nil]; - - PuglEventText ev = { - PUGL_TEXT, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - code, - { 0, 0, 0, 0, 0, 0, 0, 0 }, - }; - - memcpy(ev.string, utf8, len); - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - } -} - -- (void)flagsChanged:(NSEvent*)event -{ - const uint32_t mods = getModifiers(event); - PuglEventType type = PUGL_NOTHING; - PuglKey special = (PuglKey)0; - - if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) { - type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_SHIFT; - } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) { - type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_CTRL; - } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) { - type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_ALT; - } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) { - type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_SUPER; - } - - if (special != 0) { - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - PuglEventKey ev = { - type, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - mods, - [event keyCode], - special - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - } - - puglview->impl->mods = mods; -} - -- (BOOL)preservesContentInLiveResize -{ - return NO; -} - -- (void)viewWillStartLiveResize -{ - puglDispatchSimpleEvent(puglview, PUGL_LOOP_ENTER); -} - -- (void)viewWillDraw -{ - puglDispatchSimpleEvent(puglview, PUGL_UPDATE); - [super viewWillDraw]; -} - -- (void)resizeTick -{ - puglPostRedisplay(puglview); -} - -- (void)timerTick:(NSTimer*)userTimer -{ - const NSNumber* userInfo = userTimer.userInfo; - const PuglEventTimer ev = {PUGL_TIMER, 0, userInfo.unsignedLongValue}; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void)viewDidEndLiveResize -{ - puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE); -} - -@end - -@interface PuglWindowDelegate : NSObject - -- (instancetype)initWithPuglWindow:(PuglWindow*)window; - -@end - -@implementation PuglWindowDelegate -{ - PuglWindow* window; -} - -- (instancetype)initWithPuglWindow:(PuglWindow*)puglWindow -{ - if ((self = [super init])) { - window = puglWindow; - } - - return self; -} - -- (BOOL)windowShouldClose:(id)sender -{ - (void)sender; - - puglDispatchSimpleEvent(window->puglview, PUGL_CLOSE); - return YES; -} - -- (void)windowDidMove:(NSNotification*)notification -{ - (void)notification; - - updateViewRect(window->puglview); -} - -- (void)windowDidBecomeKey:(NSNotification*)notification -{ - (void)notification; - - PuglEvent ev = {{PUGL_FOCUS_IN, 0}}; - ev.focus.mode = PUGL_CROSSING_NORMAL; - puglDispatchEvent(window->puglview, &ev); -} - -- (void)windowDidResignKey:(NSNotification*)notification -{ - (void)notification; - - PuglEvent ev = {{PUGL_FOCUS_OUT, 0}}; - ev.focus.mode = PUGL_CROSSING_NORMAL; - puglDispatchEvent(window->puglview, &ev); -} - -@end - -PuglWorldInternals* -puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags)) -{ - PuglWorldInternals* impl = (PuglWorldInternals*)calloc( - 1, sizeof(PuglWorldInternals)); - - impl->app = [NSApplication sharedApplication]; - - if (type == PUGL_PROGRAM) { - impl->autoreleasePool = [NSAutoreleasePool new]; - } - - return impl; -} - -void -puglFreeWorldInternals(PuglWorld* world) -{ - if (world->impl->autoreleasePool) { - [world->impl->autoreleasePool drain]; - } - - free(world->impl); -} - -void* -puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) -{ - return NULL; -} - -PuglInternals* -puglInitViewInternals(void) -{ - PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); - - impl->cursor = [NSCursor arrowCursor]; - - return impl; -} - -static NSLayoutConstraint* -puglConstraint(id item, NSLayoutAttribute attribute, float constant) -{ - return [NSLayoutConstraint - constraintWithItem: item - attribute: attribute - relatedBy: NSLayoutRelationGreaterThanOrEqual - toItem: nil - attribute: NSLayoutAttributeNotAnAttribute - multiplier: 1.0 - constant: (CGFloat)constant]; -} - -PuglStatus -puglRealize(PuglView* view) -{ - PuglInternals* impl = view->impl; - if (impl->wrapperView) { - return PUGL_FAILURE; - } - - const NSScreen* const screen = [NSScreen mainScreen]; - const double scaleFactor = [screen backingScaleFactor]; - - // Getting depth from the display mode seems tedious, just set usual values - if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_RED_BITS] = 8; - } - if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_BLUE_BITS] = 8; - } - if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_GREEN_BITS] = 8; - } - if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_ALPHA_BITS] = 8; - } - - CGDirectDisplayID displayId = CGMainDisplayID(); - CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId); - - // Try to get refresh rate from mode (usually fails) - view->hints[PUGL_REFRESH_RATE] = (int)CGDisplayModeGetRefreshRate(mode); - - CGDisplayModeRelease(mode); - if (view->hints[PUGL_REFRESH_RATE] == 0) { - // Get refresh rate from a display link - // TODO: Keep and actually use the display link for something? - CVDisplayLinkRef link; - CVDisplayLinkCreateWithCGDisplay(displayId, &link); - - const CVTime p = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); - const double r = p.timeScale / (double)p.timeValue; - view->hints[PUGL_REFRESH_RATE] = (int)lrint(r); - - CVDisplayLinkRelease(link); - } - - if (view->frame.width == 0.0 && view->frame.height == 0.0) { - if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { - return PUGL_BAD_CONFIGURATION; - } - - const double screenWidthPx = [screen frame].size.width * scaleFactor; - const double screenHeightPx = [screen frame].size.height * scaleFactor; - - view->frame.width = view->defaultWidth; - view->frame.height = view->defaultHeight; - view->frame.x = screenWidthPx / 2.0 - view->frame.width / 2.0; - view->frame.y = screenHeightPx / 2.0 - view->frame.height / 2.0; - } - - const NSRect framePx = rectToNsRect(view->frame); - const NSRect framePt = NSMakeRect(framePx.origin.x / scaleFactor, - framePx.origin.y / scaleFactor, - framePx.size.width / scaleFactor, - framePx.size.height / scaleFactor); - - // Create wrapper view to handle input - impl->wrapperView = [PuglWrapperView alloc]; - impl->wrapperView->puglview = view; - impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init]; - impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init]; - [impl->wrapperView setAutoresizesSubviews:YES]; - [impl->wrapperView initWithFrame:framePt]; - [impl->wrapperView addConstraint: - puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->minWidth)]; - [impl->wrapperView addConstraint: - puglConstraint(impl->wrapperView, NSLayoutAttributeHeight, view->minHeight)]; - - // Create draw view to be rendered to - PuglStatus st = PUGL_SUCCESS; - if ((st = view->backend->configure(view)) || - (st = view->backend->create(view))) { - return st; - } - - // Add draw view to wrapper view - [impl->wrapperView addSubview:impl->drawView]; - [impl->wrapperView setHidden:NO]; - [impl->drawView setHidden:NO]; - - if (view->parent) { - NSView* pview = (NSView*)view->parent; - [pview addSubview:impl->wrapperView]; - [impl->drawView setHidden:NO]; - [[impl->drawView window] makeFirstResponder:impl->wrapperView]; - } else { - unsigned style = (NSClosableWindowMask | - NSTitledWindowMask | - NSMiniaturizableWindowMask ); - if (view->hints[PUGL_RESIZABLE]) { - style |= NSResizableWindowMask; - } - - PuglWindow* window = [[[PuglWindow alloc] - initWithContentRect:rectToScreen([NSScreen mainScreen], framePt) - styleMask:style - backing:NSBackingStoreBuffered - defer:NO - ] retain]; - [window setPuglview:view]; - - if (view->title) { - NSString* titleString = [[NSString alloc] - initWithBytes:view->title - length:strlen(view->title) - encoding:NSUTF8StringEncoding]; - - [window setTitle:titleString]; - } - - if (view->minWidth || view->minHeight) { - [window setContentMinSize:sizePoints(view, - view->minWidth, - view->minHeight)]; - } - impl->window = window; - - ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc] - initWithPuglWindow:window]; - - if (view->minAspectX && view->minAspectY) { - [window setContentAspectRatio:sizePoints(view, - view->minAspectX, - view->minAspectY)]; - } - - puglSetFrame(view, view->frame); - - [window setContentView:impl->wrapperView]; - [view->world->impl->app activateIgnoringOtherApps:YES]; - [window makeFirstResponder:impl->wrapperView]; - [window makeKeyAndOrderFront:window]; - [impl->window setIsVisible:NO]; - } - - [impl->wrapperView updateTrackingAreas]; - - puglDispatchSimpleEvent(view, PUGL_CREATE); - - return PUGL_SUCCESS; -} - -PuglStatus -puglShowWindow(PuglView* view) -{ - if (![view->impl->window isVisible]) { - [view->impl->window setIsVisible:YES]; - [view->impl->drawView setNeedsDisplay: YES]; - updateViewRect(view); - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglHideWindow(PuglView* view) -{ - [view->impl->window setIsVisible:NO]; - return PUGL_SUCCESS; -} - -void -puglFreeViewInternals(PuglView* view) -{ - if (view) { - if (view->backend) { - view->backend->destroy(view); - } - - if (view->impl) { - [view->impl->wrapperView removeFromSuperview]; - view->impl->wrapperView->puglview = NULL; - if (view->impl->window) { - [view->impl->window close]; - } - [view->impl->wrapperView release]; - if (view->impl->window) { - [view->impl->window release]; - } - free(view->impl); - } - } -} - -PuglStatus -puglGrabFocus(PuglView* view) -{ - NSWindow* window = [view->impl->wrapperView window]; - - [window makeKeyWindow]; - [window makeFirstResponder:view->impl->wrapperView]; - return PUGL_SUCCESS; -} - -bool -puglHasFocus(const PuglView* view) -{ - PuglInternals* const impl = view->impl; - - return ([[impl->wrapperView window] isKeyWindow] && - [[impl->wrapperView window] firstResponder] == impl->wrapperView); -} - -PuglStatus -puglRequestAttention(PuglView* view) -{ - if (![view->impl->window isKeyWindow]) { - [view->world->impl->app requestUserAttention:NSInformationalRequest]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglStartTimer(PuglView* view, uintptr_t id, double timeout) -{ - puglStopTimer(view, id); - - NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; - - NSTimer* timer = [NSTimer timerWithTimeInterval:timeout - target:view->impl->wrapperView - selector:@selector(timerTick:) - userInfo:idNumber - repeats:YES]; - - [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; - - view->impl->wrapperView->userTimers[idNumber] = timer; - - return PUGL_SUCCESS; -} - -PuglStatus -puglStopTimer(PuglView* view, uintptr_t id) -{ - NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; - NSTimer* timer = view->impl->wrapperView->userTimers[idNumber]; - - if (timer) { - [view->impl->wrapperView->userTimers removeObjectForKey:timer]; - [timer invalidate]; - return PUGL_SUCCESS; - } - - return PUGL_UNKNOWN_ERROR; -} - -PuglStatus -puglSendEvent(PuglView* view, const PuglEvent* event) -{ - if (event->type == PUGL_CLIENT) { - PuglWrapperView* wrapper = view->impl->wrapperView; - const NSWindow* window = [wrapper window]; - const NSRect rect = [wrapper frame]; - const NSPoint center = {NSMidX(rect), NSMidY(rect)}; - - NSEvent* nsevent = [NSEvent - otherEventWithType:NSApplicationDefined - location:center - modifierFlags:0 - timestamp:[[NSProcessInfo processInfo] systemUptime] - windowNumber:window.windowNumber - context:nil - subtype:PUGL_CLIENT - data1:(NSInteger)event->client.data1 - data2:(NSInteger)event->client.data2]; - - [view->world->impl->app postEvent:nsevent atStart:false]; - return PUGL_SUCCESS; - } - - return PUGL_UNSUPPORTED_TYPE; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglWaitForEvent(PuglView* view) -{ - return puglPollEvents(view->world, -1.0); -} -#endif - -static void -dispatchClientEvent(PuglWorld* world, NSEvent* ev) -{ - NSWindow* win = [ev window]; - NSPoint loc = [ev locationInWindow]; - for (size_t i = 0; i < world->numViews; ++i) { - PuglView* view = world->views[i]; - PuglWrapperView* wrapper = view->impl->wrapperView; - if ([wrapper window] == win && NSPointInRect(loc, [wrapper frame])) { - const PuglEventClient event = {PUGL_CLIENT, - 0, - (uintptr_t)[ev data1], - (uintptr_t)[ev data2]}; - - puglDispatchEvent(view, (const PuglEvent*)&event); - } - } -} - -PuglStatus -puglUpdate(PuglWorld* world, const double timeout) -{ - NSDate* date = ((timeout < 0) - ? [NSDate distantFuture] - : [NSDate dateWithTimeIntervalSinceNow:timeout]); - - for (NSEvent* ev = NULL; - (ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask - untilDate:date - inMode:NSDefaultRunLoopMode - dequeue:YES]);) { - - if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) { - dispatchClientEvent(world, ev); - } - - [world->impl->app sendEvent: ev]; - - if (timeout < 0) { - // Now that we've waited and got an event, set the date to now to - // avoid looping forever - date = [NSDate date]; - } - } - - for (size_t i = 0; i < world->numViews; ++i) { - PuglView* const view = world->views[i]; - - if ([[view->impl->drawView window] isVisible]) { - puglDispatchSimpleEvent(view, PUGL_UPDATE); - } - - [view->impl->drawView displayIfNeeded]; - } - - return PUGL_SUCCESS; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglProcessEvents(PuglView* view) -{ - return puglDispatchEvents(view->world); -} -#endif - -double -puglGetTime(const PuglWorld* world) -{ - return (mach_absolute_time() / 1e9) - world->startTime; -} - -PuglStatus -puglPostRedisplay(PuglView* view) -{ - [view->impl->drawView setNeedsDisplay: YES]; - return PUGL_SUCCESS; -} - -PuglStatus -puglPostRedisplayRect(PuglView* view, const PuglRect rect) -{ - const NSRect rectPx = rectToNsRect(rect); - - [view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)]; - - return PUGL_SUCCESS; -} - -PuglNativeView -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeView)view->impl->wrapperView; -} - -PuglStatus -puglSetWindowTitle(PuglView* view, const char* title) -{ - puglSetString(&view->title, title); - - if (view->impl->window) { - NSString* titleString = [[NSString alloc] - initWithBytes:title - length:strlen(title) - encoding:NSUTF8StringEncoding]; - - [view->impl->window setTitle:titleString]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetFrame(PuglView* view, const PuglRect frame) -{ - PuglInternals* const impl = view->impl; - - // Update view frame to exactly the requested frame in Pugl coordinates - view->frame = frame; - - const NSRect framePx = rectToNsRect(frame); - const NSRect framePt = nsRectToPoints(view, framePx); - if (impl->window) { - // Resize window to fit new content rect - const NSRect screenPt = rectToScreen(viewScreen(view), framePt); - const NSRect winFrame = [impl->window frameRectForContentRect:screenPt]; - - [impl->window setFrame:winFrame display:NO]; - } - - // Resize views - const NSRect sizePx = NSMakeRect(0, 0, frame.width, frame.height); - const NSRect sizePt = [impl->drawView convertRectFromBacking:sizePx]; - - [impl->wrapperView setFrame:(impl->window ? sizePt : framePt)]; - [impl->drawView setFrame:sizePt]; - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetDefaultSize(PuglView* const view, const int width, const int height) -{ - view->defaultWidth = width; - view->defaultHeight = height; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetMinSize(PuglView* const view, const int width, const int height) -{ - view->minWidth = width; - view->minHeight = height; - - if (view->impl->window && (view->minWidth || view->minHeight)) { - [view->impl->window setContentMinSize:sizePoints(view, - view->minWidth, - view->minHeight)]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetMaxSize(PuglView* const view, const int width, const int height) -{ - view->maxWidth = width; - view->maxHeight = height; - - if (view->impl->window && (view->maxWidth || view->maxHeight)) { - [view->impl->window setContentMaxSize:sizePoints(view, - view->maxWidth, - view->maxHeight)]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetAspectRatio(PuglView* const view, - const int minX, - const int minY, - const int maxX, - const int maxY) -{ - view->minAspectX = minX; - view->minAspectY = minY; - view->maxAspectX = maxX; - view->maxAspectY = maxY; - - if (view->impl->window && view->minAspectX && view->minAspectY) { - [view->impl->window setContentAspectRatio:sizePoints(view, - view->minAspectX, - view->minAspectY)]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetTransientFor(PuglView* view, PuglNativeView parent) -{ - view->transientParent = parent; - - if (view->impl->window) { - NSWindow* parentWindow = [(NSView*)parent window]; - if (parentWindow) { - [parentWindow addChildWindow:view->impl->window - ordered:NSWindowAbove]; - return PUGL_SUCCESS; - } - } - - return PUGL_FAILURE; -} - -const void* -puglGetClipboard(PuglView* const view, - const char** const type, - size_t* const len) -{ - NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; - - if ([[pasteboard types] containsObject:NSStringPboardType]) { - const NSString* str = [pasteboard stringForType:NSStringPboardType]; - const char* utf8 = [str UTF8String]; - - puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1); - } - - return puglGetInternalClipboard(view, type, len); -} - -static NSCursor* -puglGetNsCursor(const PuglCursor cursor) -{ - switch (cursor) { - case PUGL_CURSOR_ARROW: - return [NSCursor arrowCursor]; - case PUGL_CURSOR_CARET: - return [NSCursor IBeamCursor]; - case PUGL_CURSOR_CROSSHAIR: - return [NSCursor crosshairCursor]; - case PUGL_CURSOR_HAND: - return [NSCursor pointingHandCursor]; - case PUGL_CURSOR_NO: - return [NSCursor operationNotAllowedCursor]; - case PUGL_CURSOR_LEFT_RIGHT: - return [NSCursor resizeLeftRightCursor]; - case PUGL_CURSOR_UP_DOWN: - return [NSCursor resizeUpDownCursor]; - } - - return NULL; -} - -PuglStatus -puglSetCursor(PuglView* view, PuglCursor cursor) -{ - PuglInternals* const impl = view->impl; - NSCursor* const cur = puglGetNsCursor(cursor); - if (!cur) { - return PUGL_FAILURE; - } - - impl->cursor = cur; - - if (impl->mouseTracked) { - [cur set]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetClipboard(PuglView* const view, - const char* const type, - const void* const data, - const size_t len) -{ - NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; - const char* const str = (const char*)data; - - PuglStatus st = puglSetInternalClipboard(view, type, data, len); - if (st) { - return st; - } - - NSString* nsString = [NSString stringWithUTF8String:str]; - if (nsString) { - [pasteboard - declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] - owner:nil]; - - [pasteboard setString:nsString forType:NSStringPboardType]; - - return PUGL_SUCCESS; - } - - return PUGL_UNKNOWN_ERROR; -} diff --git a/include/pugl/detail/mac_cairo.m b/include/pugl/detail/mac_cairo.m deleted file mode 100644 index a8c05ec..0000000 --- a/include/pugl/detail/mac_cairo.m +++ /dev/null @@ -1,165 +0,0 @@ -/* - Copyright 2019-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac_cairo.m - @brief Cairo graphics backend for MacOS. -*/ - -#include "pugl/detail/implementation.h" -#include "pugl/detail/mac.h" -#include "pugl/detail/stub.h" -#include "pugl/pugl_cairo.h" - -#include - -#import - -#include - -@interface PuglCairoView : NSView -@end - -@implementation PuglCairoView -{ -@public - PuglView* puglview; - cairo_surface_t* surface; - cairo_t* cr; -} - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - - return self; -} - -- (void)resizeWithOldSuperviewSize:(NSSize)oldSize -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - - [super resizeWithOldSuperviewSize:oldSize]; - [wrapper setReshaped]; -} - -- (void)drawRect:(NSRect)rect -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - [wrapper dispatchExpose:rect]; -} - -@end - -static PuglStatus -puglMacCairoCreate(PuglView* view) -{ - PuglInternals* impl = view->impl; - PuglCairoView* drawView = [PuglCairoView alloc]; - - drawView->puglview = view; - [drawView initWithFrame:[impl->wrapperView bounds]]; - if (view->hints[PUGL_RESIZABLE]) { - [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - } else { - [drawView setAutoresizingMask:NSViewNotSizable]; - } - - impl->drawView = drawView; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacCairoDestroy(PuglView* view) -{ - PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; - - [drawView removeFromSuperview]; - [drawView release]; - - view->impl->drawView = nil; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose) -{ - PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; - if (!expose) { - return PUGL_SUCCESS; - } - - assert(!drawView->surface); - assert(!drawView->cr); - - const double scale = 1.0 / [[NSScreen mainScreen] backingScaleFactor]; - CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; - const CGSize sizePx = {view->frame.width, view->frame.height}; - const CGSize sizePt = CGContextConvertSizeToUserSpace(context, sizePx); - - // Convert coordinates to standard Cairo space - CGContextTranslateCTM(context, 0.0, -sizePt.height); - CGContextScaleCTM(context, scale, -scale); - - drawView->surface = cairo_quartz_surface_create_for_cg_context( - context, (unsigned)sizePx.width, (unsigned)sizePx.height); - - drawView->cr = cairo_create(drawView->surface); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacCairoLeave(PuglView* view, const PuglEventExpose* expose) -{ - PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; - if (!expose) { - return PUGL_SUCCESS; - } - - assert(drawView->surface); - assert(drawView->cr); - - CGContextRef context = cairo_quartz_surface_get_cg_context(drawView->surface); - - cairo_destroy(drawView->cr); - cairo_surface_destroy(drawView->surface); - drawView->cr = NULL; - drawView->surface = NULL; - - CGContextFlush(context); - - return PUGL_SUCCESS; -} - -static void* -puglMacCairoGetContext(PuglView* view) -{ - return ((PuglCairoView*)view->impl->drawView)->cr; -} - -const PuglBackend* -puglCairoBackend(void) -{ - static const PuglBackend backend = {puglStubConfigure, - puglMacCairoCreate, - puglMacCairoDestroy, - puglMacCairoEnter, - puglMacCairoLeave, - puglMacCairoGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/mac_gl.m b/include/pugl/detail/mac_gl.m deleted file mode 100644 index bca790d..0000000 --- a/include/pugl/detail/mac_gl.m +++ /dev/null @@ -1,202 +0,0 @@ -/* - Copyright 2019-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac_gl.m - @brief OpenGL graphics backend for MacOS. -*/ - -#include "pugl/detail/implementation.h" -#include "pugl/detail/mac.h" -#include "pugl/detail/stub.h" -#include "pugl/pugl_gl.h" - -#ifndef __MAC_10_10 -# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core -#endif - -@interface PuglOpenGLView : NSOpenGLView -@end - -@implementation PuglOpenGLView -{ -@public - PuglView* puglview; -} - -- (id)initWithFrame:(NSRect)frame -{ - const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE]; - const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES]; - const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR]; - const unsigned profile = ((compat || major < 3) - ? NSOpenGLProfileVersionLegacy - : (major >= 4 - ? NSOpenGLProfileVersion4_1Core - : NSOpenGLProfileVersion3_2Core)); - - // Set attributes to default if they are unset - // (There is no GLX_DONT_CARE equivalent on MacOS) - if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { - puglview->hints[PUGL_DEPTH_BITS] = 0; - } - if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { - puglview->hints[PUGL_STENCIL_BITS] = 0; - } - if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { - puglview->hints[PUGL_SAMPLES] = 1; - } - if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { - puglview->hints[PUGL_DOUBLE_BUFFER] = 1; - } - if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { - puglview->hints[PUGL_SWAP_INTERVAL] = 1; - } - - const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] + - puglview->hints[PUGL_BLUE_BITS] + - puglview->hints[PUGL_GREEN_BITS] + - puglview->hints[PUGL_ALPHA_BITS]); - - NSOpenGLPixelFormatAttribute pixelAttribs[17] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAAccelerated, - NSOpenGLPFAOpenGLProfile, profile, - NSOpenGLPFAColorSize, colorSize, - NSOpenGLPFADepthSize, (unsigned)puglview->hints[PUGL_DEPTH_BITS], - NSOpenGLPFAStencilSize, (unsigned)puglview->hints[PUGL_STENCIL_BITS], - NSOpenGLPFAMultisample, samples ? 1 : 0, - NSOpenGLPFASampleBuffers, samples ? 1 : 0, - NSOpenGLPFASamples, samples, - 0}; - - NSOpenGLPixelFormat* pixelFormat = [ - [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs]; - - if (pixelFormat) { - self = [super initWithFrame:frame pixelFormat:pixelFormat]; - [pixelFormat release]; - } else { - self = [super initWithFrame:frame]; - } - - [self setWantsBestResolutionOpenGLSurface:YES]; - - if (self) { - [[self openGLContext] makeCurrentContext]; - [self reshape]; - } - return self; -} - -- (void)reshape -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - - [super reshape]; - [wrapper setReshaped]; -} - -- (void)drawRect:(NSRect)rect -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - [wrapper dispatchExpose:rect]; -} - -@end - -static PuglStatus -puglMacGlCreate(PuglView* view) -{ - PuglInternals* impl = view->impl; - PuglOpenGLView* drawView = [PuglOpenGLView alloc]; - - drawView->puglview = view; - [drawView initWithFrame:[impl->wrapperView bounds]]; - if (view->hints[PUGL_RESIZABLE]) { - [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - } else { - [drawView setAutoresizingMask:NSViewNotSizable]; - } - - impl->drawView = drawView; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacGlDestroy(PuglView* view) -{ - PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; - - [drawView removeFromSuperview]; - [drawView release]; - - view->impl->drawView = nil; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacGlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose)) -{ - PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; - - [[drawView openGLContext] makeCurrentContext]; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacGlLeave(PuglView* view, const PuglEventExpose* expose) -{ - PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; - - if (expose) { - [[drawView openGLContext] flushBuffer]; - } - - [NSOpenGLContext clearCurrentContext]; - - return PUGL_SUCCESS; -} - -PuglGlFunc -puglGetProcAddress(const char *name) -{ - CFBundleRef framework = - CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - - CFStringRef symbol = CFStringCreateWithCString( - kCFAllocatorDefault, name, kCFStringEncodingASCII); - - PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName( - framework, symbol); - - CFRelease(symbol); - - return func; -} - -const PuglBackend* -puglGlBackend(void) -{ - static const PuglBackend backend = {puglStubConfigure, - puglMacGlCreate, - puglMacGlDestroy, - puglMacGlEnter, - puglMacGlLeave, - puglStubGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/mac_stub.m b/include/pugl/detail/mac_stub.m deleted file mode 100644 index e6aa895..0000000 --- a/include/pugl/detail/mac_stub.m +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright 2019-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac_stub.m - @brief Stub graphics backend for MacOS. -*/ - -#include "pugl/detail/implementation.h" -#include "pugl/detail/mac.h" -#include "pugl/detail/stub.h" -#include "pugl/pugl_stub.h" - -#import - -@interface PuglStubView : NSView -@end - -@implementation PuglStubView -{ -@public - PuglView* puglview; -} - -- (void)resizeWithOldSuperviewSize:(NSSize)oldSize -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - - [super resizeWithOldSuperviewSize:oldSize]; - [wrapper setReshaped]; -} - -- (void)drawRect:(NSRect)rect -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - - [wrapper dispatchExpose:rect]; -} - -@end - -static PuglStatus -puglMacStubCreate(PuglView* view) -{ - PuglInternals* impl = view->impl; - PuglStubView* drawView = [PuglStubView alloc]; - - drawView->puglview = view; - [drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)]; - if (view->hints[PUGL_RESIZABLE]) { - [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - } else { - [drawView setAutoresizingMask:NSViewNotSizable]; - } - - impl->drawView = drawView; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacStubDestroy(PuglView* view) -{ - PuglStubView* const drawView = (PuglStubView*)view->impl->drawView; - - [drawView removeFromSuperview]; - [drawView release]; - - view->impl->drawView = nil; - return PUGL_SUCCESS; -} - -const PuglBackend* -puglStubBackend(void) -{ - static const PuglBackend backend = {puglStubConfigure, - puglMacStubCreate, - puglMacStubDestroy, - puglStubEnter, - puglStubLeave, - puglStubGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/mac_vulkan.m b/include/pugl/detail/mac_vulkan.m deleted file mode 100644 index 813e0ae..0000000 --- a/include/pugl/detail/mac_vulkan.m +++ /dev/null @@ -1,216 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file mac_vulkan.m Vulkan graphics backend for MacOS. -*/ - -#define VK_NO_PROTOTYPES 1 - -#include "pugl/detail/implementation.h" -#include "pugl/detail/mac.h" -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/pugl.h" -#include "pugl/pugl_stub.h" -#include "pugl/pugl_vulkan.h" - -#include -#include - -#import -#import - -#include - -#include -#include - -@interface PuglVulkanView : NSView - -@end - -@implementation PuglVulkanView -{ -@public - PuglView* puglview; -} - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - - if (self) { - self.wantsLayer = YES; - self.layerContentsRedrawPolicy = - NSViewLayerContentsRedrawOnSetNeedsDisplay; - } - - return self; -} - -- (CALayer*)makeBackingLayer -{ - CAMetalLayer* layer = [CAMetalLayer layer]; - [layer setDelegate:self]; - return layer; -} - -- (void)setFrameSize:(NSSize)newSize -{ - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - - [super setFrameSize:newSize]; - [wrapper setReshaped]; - - self.layer.frame = self.bounds; -} - -- (void)displayLayer:(CALayer*)layer -{ - (void)layer; - PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; - [wrapper dispatchExpose:[self bounds]]; -} - -@end - -static PuglStatus -puglMacVulkanCreate(PuglView* view) -{ - PuglInternals* impl = view->impl; - PuglVulkanView* drawView = [PuglVulkanView alloc]; - const NSRect rect = NSMakeRect(0, 0, view->frame.width, view->frame.height); - - drawView->puglview = view; - [drawView initWithFrame:rect]; - if (view->hints[PUGL_RESIZABLE]) { - [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - } else { - [drawView setAutoresizingMask:NSViewNotSizable]; - } - - impl->drawView = drawView; - return PUGL_SUCCESS; -} - -static PuglStatus -puglMacVulkanDestroy(PuglView* view) -{ - PuglVulkanView* const drawView = (PuglVulkanView*)view->impl->drawView; - - [drawView removeFromSuperview]; - [drawView release]; - - view->impl->drawView = nil; - return PUGL_SUCCESS; -} - -struct PuglVulkanLoaderImpl { - void* libvulkan; - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; -}; - -PuglVulkanLoader* -puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) -{ - PuglVulkanLoader* loader = (PuglVulkanLoader*) - calloc(1, sizeof(PuglVulkanLoader)); - if (!loader) { - return NULL; - } - - if (!(loader->libvulkan = dlopen("libvulkan.dylib", RTLD_LAZY))) { - free(loader); - return NULL; - } - - loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) - dlsym(loader->libvulkan, "vkGetInstanceProcAddr"); - - loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr) - dlsym(loader->libvulkan, "vkGetDeviceProcAddr"); - - return loader; -} - -void -puglFreeVulkanLoader(PuglVulkanLoader* loader) -{ - if (loader) { - dlclose(loader->libvulkan); - free(loader); - } -} - -PFN_vkGetInstanceProcAddr -puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetInstanceProcAddr; -} - -PFN_vkGetDeviceProcAddr -puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetDeviceProcAddr; -} - -const PuglBackend* -puglVulkanBackend(void) -{ - static const PuglBackend backend = {puglStubConfigure, - puglMacVulkanCreate, - puglMacVulkanDestroy, - puglStubEnter, - puglStubLeave, - puglStubGetContext}; - - return &backend; -} - -const char* const* -puglGetInstanceExtensions(uint32_t* const count) -{ - static const char* const extensions[] = {"VK_KHR_surface", - "VK_MVK_macos_surface"}; - - *count = 2; - return extensions; -} - -VkResult -puglCreateSurface(const PuglVulkanLoader* const loader, - PuglView* const view, - VkInstance instance, - const VkAllocationCallbacks* const allocator, - VkSurfaceKHR* const surface) -{ - PuglInternals* const impl = view->impl; - - PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = - (PFN_vkCreateMacOSSurfaceMVK)puglGetInstanceProcAddrFunc( - loader)(instance, "vkCreateMacOSSurfaceMVK"); - - const VkMacOSSurfaceCreateInfoMVK info = { - VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, - NULL, - 0, - impl->drawView, - }; - - return vkCreateMacOSSurfaceMVK(instance, &info, allocator, surface); -} diff --git a/include/pugl/detail/stub.h b/include/pugl/detail/stub.h deleted file mode 100644 index acd3181..0000000 --- a/include/pugl/detail/stub.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file stub.h - @brief Definition of generic stub backend functions. -*/ - -#ifndef PUGL_DETAIL_STUB_H -#define PUGL_DETAIL_STUB_H - -#include "pugl/pugl.h" - -PUGL_BEGIN_DECLS - -static inline PuglStatus -puglStubConfigure(PuglView* view) -{ - (void)view; - return PUGL_SUCCESS; -} - -static inline PuglStatus -puglStubCreate(PuglView* view) -{ - (void)view; - return PUGL_SUCCESS; -} - -static inline PuglStatus -puglStubDestroy(PuglView* view) -{ - (void)view; - return PUGL_SUCCESS; -} - -static inline PuglStatus -puglStubEnter(PuglView* view, const PuglEventExpose* expose) -{ - (void)view; - (void)expose; - return PUGL_SUCCESS; -} - -static inline PuglStatus -puglStubLeave(PuglView* view, const PuglEventExpose* expose) -{ - (void)view; - (void)expose; - return PUGL_SUCCESS; -} - -static inline void* -puglStubGetContext(PuglView* view) -{ - (void)view; - return NULL; -} - -PUGL_END_DECLS - -#endif // PUGL_DETAIL_STUB_H diff --git a/include/pugl/detail/types.h b/include/pugl/detail/types.h deleted file mode 100644 index e18eb87..0000000 --- a/include/pugl/detail/types.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file types.h - @brief Shared internal type definitions. -*/ - -#ifndef PUGL_DETAIL_TYPES_H -#define PUGL_DETAIL_TYPES_H - -#include "pugl/pugl.h" - -#include -#include -#include - -// Unused parameter macro to suppresses warnings and make it impossible to use -#if defined(__cplusplus) -# define PUGL_UNUSED(name) -#elif defined(__GNUC__) || defined(__clang__) -# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__)) -#else -# define PUGL_UNUSED(name) name -#endif - -/// Platform-specific world internals -typedef struct PuglWorldInternalsImpl PuglWorldInternals; - -/// Platform-specific view internals -typedef struct PuglInternalsImpl PuglInternals; - -/// View hints -typedef int PuglHints[PUGL_NUM_VIEW_HINTS]; - -/// Blob of arbitrary data -typedef struct { - void* data; ///< Dynamically allocated data - size_t len; ///< Length of data in bytes -} PuglBlob; - -/// Cross-platform view definition -struct PuglViewImpl { - PuglWorld* world; - const PuglBackend* backend; - PuglInternals* impl; - PuglHandle handle; - PuglEventFunc eventFunc; - char* title; - PuglBlob clipboard; - PuglNativeView parent; - uintptr_t transientParent; - PuglRect frame; - PuglEventConfigure lastConfigure; - PuglHints hints; - int defaultWidth; - int defaultHeight; - int minWidth; - int minHeight; - int maxWidth; - int maxHeight; - int minAspectX; - int minAspectY; - int maxAspectX; - int maxAspectY; - bool visible; -}; - -/// Cross-platform world definition -struct PuglWorldImpl { - PuglWorldInternals* impl; - PuglWorldHandle handle; - PuglLogFunc logFunc; - char* className; - double startTime; - size_t numViews; - PuglView** views; - PuglLogLevel logLevel; -}; - -/// Opaque surface used by graphics backend -typedef void PuglSurface; - -/// Graphics backend interface -struct PuglBackendImpl { - /// Get visual information from display and setup view as necessary - PuglStatus (*configure)(PuglView*); - - /// Create surface and drawing context - PuglStatus (*create)(PuglView*); - - /// Destroy surface and drawing context - PuglStatus (*destroy)(PuglView*); - - /// Enter drawing context, for drawing if expose is non-null - PuglStatus (*enter)(PuglView*, const PuglEventExpose*); - - /// Leave drawing context, after drawing if expose is non-null - PuglStatus (*leave)(PuglView*, const PuglEventExpose*); - - /// Return the puglGetContext() handle for the application, if any - void* (*getContext)(PuglView*); -}; - -#endif // PUGL_DETAIL_TYPES_H diff --git a/include/pugl/detail/win.c b/include/pugl/detail/win.c deleted file mode 100644 index 237ebde..0000000 --- a/include/pugl/detail/win.c +++ /dev/null @@ -1,1139 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file win.c - @brief Windows implementation. -*/ - -#include "pugl/detail/win.h" - -#include "pugl/detail/implementation.h" -#include "pugl/detail/stub.h" -#include "pugl/pugl.h" -#include "pugl/pugl_stub.h" - -#include -#include - -#include -#include -#include -#include -#include - -#ifndef WM_MOUSEWHEEL -# define WM_MOUSEWHEEL 0x020A -#endif -#ifndef WM_MOUSEHWHEEL -# define WM_MOUSEHWHEEL 0x020E -#endif -#ifndef WHEEL_DELTA -# define WHEEL_DELTA 120 -#endif -#ifndef GWLP_USERDATA -# define GWLP_USERDATA (-21) -#endif - -#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) -#define PUGL_LOCAL_MARK_MSG (WM_USER + 51) -#define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52) -#define PUGL_USER_TIMER_MIN 9470 - -typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - -static wchar_t* -puglUtf8ToWideChar(const char* const utf8) -{ - const int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); - if (len > 0) { - wchar_t* result = (wchar_t*)calloc((size_t)len, sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, len); - return result; - } - - return NULL; -} - -static char* -puglWideCharToUtf8(const wchar_t* const wstr, size_t* len) -{ - int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); - if (n > 0) { - char* result = (char*)calloc((size_t)n, sizeof(char)); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, n, NULL, NULL); - *len = (size_t)n; - return result; - } - - return NULL; -} - -static bool -puglRegisterWindowClass(const char* name) -{ - WNDCLASSEX wc = { 0 }; - if (GetClassInfoEx(GetModuleHandle(NULL), name, &wc)) { - return true; // Already registered - } - - wc.cbSize = sizeof(wc); - wc.style = CS_OWNDC; - wc.lpfnWndProc = wndProc; - wc.hInstance = GetModuleHandle(NULL); - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc.lpszClassName = name; - - return RegisterClassEx(&wc); -} - -PuglWorldInternals* -puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type), - PuglWorldFlags PUGL_UNUSED(flags)) -{ - PuglWorldInternals* impl = (PuglWorldInternals*)calloc( - 1, sizeof(PuglWorldInternals)); - if (!impl) { - return NULL; - } - - HMODULE user32 = LoadLibrary("user32.dll"); - if (user32) { - PFN_SetProcessDPIAware SetProcessDPIAware = - (PFN_SetProcessDPIAware)GetProcAddress( - user32, "SetProcessDPIAware"); - if (SetProcessDPIAware) { - SetProcessDPIAware(); - } - - FreeLibrary(user32); - } - - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - impl->timerFrequency = (double)frequency.QuadPart; - - return impl; -} - -void* -puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) -{ - return GetModuleHandle(NULL); -} - -PuglInternals* -puglInitViewInternals(void) -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -static PuglStatus -puglPollWinEvents(PuglWorld* world, const double timeout) -{ - (void)world; - - if (timeout < 0) { - WaitMessage(); - } else { - MsgWaitForMultipleObjects( - 0, NULL, FALSE, (DWORD)(timeout * 1e3), QS_ALLEVENTS); - } - return PUGL_SUCCESS; -} - -PuglStatus -puglRealize(PuglView* view) -{ - PuglInternals* impl = view->impl; - if (impl->hwnd) { - return PUGL_FAILURE; - } - - // Getting depth from the display mode seems tedious, just set usual values - if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_RED_BITS] = 8; - } - if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_BLUE_BITS] = 8; - } - if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_GREEN_BITS] = 8; - } - if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_ALPHA_BITS] = 8; - } - - // Get refresh rate for resize draw timer - DEVMODEA devMode = {0}; - EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode); - view->hints[PUGL_REFRESH_RATE] = (int)devMode.dmDisplayFrequency; - - // Register window class if necessary - if (!puglRegisterWindowClass(view->world->className)) { - return PUGL_REGISTRATION_FAILED; - } - - if (!view->backend || !view->backend->configure) { - return PUGL_BAD_BACKEND; - } - - PuglStatus st = PUGL_SUCCESS; - if ((st = view->backend->configure(view)) || - (st = view->backend->create(view))) { - return st; - } - - if (view->title) { - puglSetWindowTitle(view, view->title); - } - - view->impl->cursor = LoadCursor(NULL, IDC_ARROW); - - puglSetFrame(view, view->frame); - SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); - - puglDispatchSimpleEvent(view, PUGL_CREATE); - - return PUGL_SUCCESS; -} - -PuglStatus -puglShowWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - ShowWindow(impl->hwnd, SW_SHOWNORMAL); - SetFocus(impl->hwnd); - return PUGL_SUCCESS; -} - -PuglStatus -puglHideWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - ShowWindow(impl->hwnd, SW_HIDE); - return PUGL_SUCCESS; -} - -void -puglFreeViewInternals(PuglView* view) -{ - if (view) { - if (view->backend) { - view->backend->destroy(view); - } - - ReleaseDC(view->impl->hwnd, view->impl->hdc); - DestroyWindow(view->impl->hwnd); - free(view->impl); - } -} - -void -puglFreeWorldInternals(PuglWorld* world) -{ - UnregisterClass(world->className, NULL); - free(world->impl); -} - -static PuglKey -keySymToSpecial(WPARAM sym) -{ - // clang-format off - switch (sym) { - case VK_F1: return PUGL_KEY_F1; - case VK_F2: return PUGL_KEY_F2; - case VK_F3: return PUGL_KEY_F3; - case VK_F4: return PUGL_KEY_F4; - case VK_F5: return PUGL_KEY_F5; - case VK_F6: return PUGL_KEY_F6; - case VK_F7: return PUGL_KEY_F7; - case VK_F8: return PUGL_KEY_F8; - case VK_F9: return PUGL_KEY_F9; - case VK_F10: return PUGL_KEY_F10; - case VK_F11: return PUGL_KEY_F11; - case VK_F12: return PUGL_KEY_F12; - case VK_BACK: return PUGL_KEY_BACKSPACE; - case VK_DELETE: return PUGL_KEY_DELETE; - case VK_LEFT: return PUGL_KEY_LEFT; - case VK_UP: return PUGL_KEY_UP; - case VK_RIGHT: return PUGL_KEY_RIGHT; - case VK_DOWN: return PUGL_KEY_DOWN; - case VK_PRIOR: return PUGL_KEY_PAGE_UP; - case VK_NEXT: return PUGL_KEY_PAGE_DOWN; - case VK_HOME: return PUGL_KEY_HOME; - case VK_END: return PUGL_KEY_END; - case VK_INSERT: return PUGL_KEY_INSERT; - case VK_SHIFT: - case VK_LSHIFT: return PUGL_KEY_SHIFT_L; - case VK_RSHIFT: return PUGL_KEY_SHIFT_R; - case VK_CONTROL: - case VK_LCONTROL: return PUGL_KEY_CTRL_L; - case VK_RCONTROL: return PUGL_KEY_CTRL_R; - case VK_MENU: - case VK_LMENU: return PUGL_KEY_ALT_L; - case VK_RMENU: return PUGL_KEY_ALT_R; - case VK_LWIN: return PUGL_KEY_SUPER_L; - case VK_RWIN: return PUGL_KEY_SUPER_R; - case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK; - case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK; - case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK; - case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN; - case VK_PAUSE: return PUGL_KEY_PAUSE; - } - // clang-format on - - return (PuglKey)0; -} - -static uint32_t -getModifiers(void) -{ - // clang-format off - return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) | - ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) | - ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) | - ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) | - ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u)); - // clang-format on -} - -static void -initMouseEvent(PuglEvent* event, - PuglView* view, - int button, - bool press, - LPARAM lParam) -{ - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ClientToScreen(view->impl->hwnd, &pt); - - if (press) { - SetCapture(view->impl->hwnd); - } else { - ReleaseCapture(); - } - - event->button.time = GetMessageTime() / 1e3; - event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; - event->button.x = GET_X_LPARAM(lParam); - event->button.y = GET_Y_LPARAM(lParam); - event->button.xRoot = pt.x; - event->button.yRoot = pt.y; - event->button.state = getModifiers(); - event->button.button = (uint32_t)button; -} - -static void -initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam) -{ - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(view->impl->hwnd, &pt); - - event->scroll.time = GetMessageTime() / 1e3; - event->scroll.type = PUGL_SCROLL; - event->scroll.x = pt.x; - event->scroll.y = pt.y; - event->scroll.xRoot = GET_X_LPARAM(lParam); - event->scroll.yRoot = GET_Y_LPARAM(lParam); - event->scroll.state = getModifiers(); - event->scroll.dx = 0; - event->scroll.dy = 0; -} - -/// Return the code point for buf, or the replacement character on error -static uint32_t -puglDecodeUTF16(const wchar_t* buf, const int len) -{ - const uint32_t c0 = buf[0]; - const uint32_t c1 = buf[0]; - if (c0 >= 0xD800 && c0 < 0xDC00) { - if (len < 2) { - return 0xFFFD; // Surrogate, but length is only 1 - } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) { - return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000; - } - - return 0xFFFD; // Unpaired surrogates - } - - return c0; -} - -static void -initKeyEvent(PuglEventKey* event, - PuglView* view, - bool press, - WPARAM wParam, - LPARAM lParam) -{ - POINT rpos = { 0, 0 }; - GetCursorPos(&rpos); - - POINT cpos = { rpos.x, rpos.y }; - ScreenToClient(view->impl->hwnd, &rpos); - - const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16); - const unsigned vkey = ((wParam == VK_SHIFT) - ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX) - : (unsigned)wParam); - - const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); - const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); - const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1; - const bool ext = lParam & 0x01000000; - - event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - event->time = GetMessageTime() / 1e3; - event->state = getModifiers(); - event->xRoot = rpos.x; - event->yRoot = rpos.y; - event->x = cpos.x; - event->y = cpos.y; - event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16); - event->key = 0; - - const PuglKey special = keySymToSpecial(vkey); - if (special) { - if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) { - event->key = (uint32_t)special + 1u; // Right hand key - } else { - event->key = (uint32_t)special; - } - } else if (!dead) { - // Translate unshifted key - BYTE keyboardState[256] = {0}; - wchar_t buf[5] = {0}; - - event->key = puglDecodeUTF16( - buf, ToUnicode(vkey, vcode, keyboardState, buf, 4, 1 << 2)); - } -} - -static void -initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam) -{ - const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF }; - - initKeyEvent(&event->key, view, true, wParam, lParam); - event->type = PUGL_TEXT; - event->text.character = puglDecodeUTF16(utf16, 2); - - if (!WideCharToMultiByte( - CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) { - memset(event->text.string, 0, 8); - } -} - -static bool -ignoreKeyEvent(PuglView* view, LPARAM lParam) -{ - return view->hints[PUGL_IGNORE_KEY_REPEAT] && (lParam & (1 << 30)); -} - -static RECT -handleConfigure(PuglView* view, PuglEvent* event) -{ - RECT rect; - GetClientRect(view->impl->hwnd, &rect); - MapWindowPoints(view->impl->hwnd, - view->parent ? (HWND)view->parent : HWND_DESKTOP, - (LPPOINT)&rect, - 2); - - const LONG width = rect.right - rect.left; - const LONG height = rect.bottom - rect.top; - - view->frame.x = rect.left; - view->frame.y = rect.top; - - event->configure.type = PUGL_CONFIGURE; - event->configure.x = view->frame.x; - event->configure.y = view->frame.y; - event->configure.width = width; - event->configure.height = height; - - if (view->frame.width != width || view->frame.height != height) { - view->frame.width = width; - view->frame.height = height; - } - - return rect; -} - -static void -handleCrossing(PuglView* view, const PuglEventType type, POINT pos) -{ - POINT root_pos = pos; - ClientToScreen(view->impl->hwnd, &root_pos); - - const PuglEventCrossing ev = { - type, - 0, - GetMessageTime() / 1e3, - (double)pos.x, - (double)pos.y, - (double)root_pos.x, - (double)root_pos.y, - getModifiers(), - PUGL_CROSSING_NORMAL, - }; - - puglDispatchEvent(view, (const PuglEvent*)&ev); -} - -static void -constrainAspect(const PuglView* const view, - RECT* const size, - const WPARAM wParam) -{ - const float minA = (float)view->minAspectX / (float)view->minAspectY; - const float maxA = (float)view->maxAspectX / (float)view->maxAspectY; - const float w = (float)(size->right - size->left); - const float h = (float)(size->bottom - size->top); - const float a = w / h; - - switch (wParam) { - case WMSZ_TOP: - size->top = (a < minA ? (LONG)((float)size->bottom - w * minA) : - a > maxA ? (LONG)((float)size->bottom - w * maxA) : - size->top); - break; - case WMSZ_TOPRIGHT: - case WMSZ_RIGHT: - case WMSZ_BOTTOMRIGHT: - size->right = (a < minA ? (LONG)((float)size->left + h * minA) : - a > maxA ? (LONG)((float)size->left + h * maxA) : - size->right); - break; - case WMSZ_BOTTOM: - size->bottom = (a < minA ? (LONG)((float)size->top + w * minA) : - a > maxA ? (LONG)((float)size->top + w * maxA) : - size->bottom); - break; - case WMSZ_BOTTOMLEFT: - case WMSZ_LEFT: - case WMSZ_TOPLEFT: - size->left = (a < minA ? (LONG)((float)size->right - h * minA) : - a > maxA ? (LONG)((float)size->right - h * maxA) : - size->left); - break; - } -} - -static LRESULT -handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) -{ - PuglEvent event = {{PUGL_NOTHING, 0}}; - RECT rect = {0, 0, 0, 0}; - POINT pt = {0, 0}; - MINMAXINFO* mmi = NULL; - void* dummy_ptr = NULL; - - if (InSendMessageEx(dummy_ptr)) { - event.any.flags |= PUGL_IS_SEND_EVENT; - } - - switch (message) { - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT) { - SetCursor(view->impl->cursor); - } else { - return DefWindowProc(view->impl->hwnd, message, wParam, lParam); - } - break; - case WM_SHOWWINDOW: - if (wParam) { - handleConfigure(view, &event); - puglDispatchEvent(view, &event); - event.type = PUGL_NOTHING; - - RedrawWindow(view->impl->hwnd, NULL, NULL, - RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); - } - - if ((bool)wParam != view->visible) { - view->visible = wParam; - event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; - } - break; - case WM_SIZE: - handleConfigure(view, &event); - InvalidateRect(view->impl->hwnd, NULL, false); - break; - case WM_SIZING: - if (view->minAspectX) { - constrainAspect(view, (RECT*)lParam, wParam); - return TRUE; - } - break; - case WM_ENTERSIZEMOVE: - case WM_ENTERMENULOOP: - puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER); - break; - case WM_TIMER: - if (wParam >= PUGL_USER_TIMER_MIN) { - PuglEvent ev = {{PUGL_TIMER, 0}}; - ev.timer.id = wParam - PUGL_USER_TIMER_MIN; - puglDispatchEvent(view, &ev); - } - break; - case WM_EXITSIZEMOVE: - case WM_EXITMENULOOP: - puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE); - break; - case WM_GETMINMAXINFO: - mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = view->minWidth; - mmi->ptMinTrackSize.y = view->minHeight; - if (view->maxWidth > 0 && view->maxHeight > 0) { - mmi->ptMaxTrackSize.x = view->maxWidth; - mmi->ptMaxTrackSize.y = view->maxHeight; - } - break; - case WM_PAINT: - GetUpdateRect(view->impl->hwnd, &rect, false); - event.expose.type = PUGL_EXPOSE; - event.expose.x = rect.left; - event.expose.y = rect.top; - event.expose.width = rect.right - rect.left; - event.expose.height = rect.bottom - rect.top; - break; - case WM_ERASEBKGND: - return true; - case WM_MOUSEMOVE: - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - - if (!view->impl->mouseTracked) { - TRACKMOUSEEVENT tme = {0}; - - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = view->impl->hwnd; - TrackMouseEvent(&tme); - - handleCrossing(view, PUGL_POINTER_IN, pt); - view->impl->mouseTracked = true; - } - - ClientToScreen(view->impl->hwnd, &pt); - event.motion.type = PUGL_MOTION; - event.motion.time = GetMessageTime() / 1e3; - event.motion.x = GET_X_LPARAM(lParam); - event.motion.y = GET_Y_LPARAM(lParam); - event.motion.xRoot = pt.x; - event.motion.yRoot = pt.y; - event.motion.state = getModifiers(); - break; - case WM_MOUSELEAVE: - GetCursorPos(&pt); - ScreenToClient(view->impl->hwnd, &pt); - handleCrossing(view, PUGL_POINTER_OUT, pt); - view->impl->mouseTracked = false; - break; - case WM_LBUTTONDOWN: - initMouseEvent(&event, view, 1, true, lParam); - break; - case WM_MBUTTONDOWN: - initMouseEvent(&event, view, 2, true, lParam); - break; - case WM_RBUTTONDOWN: - initMouseEvent(&event, view, 3, true, lParam); - break; - case WM_LBUTTONUP: - initMouseEvent(&event, view, 1, false, lParam); - break; - case WM_MBUTTONUP: - initMouseEvent(&event, view, 2, false, lParam); - break; - case WM_RBUTTONUP: - initMouseEvent(&event, view, 3, false, lParam); - break; - case WM_MOUSEWHEEL: - initScrollEvent(&event, view, lParam); - event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA; - event.scroll.direction = (event.scroll.dy > 0 - ? PUGL_SCROLL_UP - : PUGL_SCROLL_DOWN); - break; - case WM_MOUSEHWHEEL: - initScrollEvent(&event, view, lParam); - event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA; - event.scroll.direction = (event.scroll.dx > 0 - ? PUGL_SCROLL_RIGHT - : PUGL_SCROLL_LEFT); - break; - case WM_KEYDOWN: - if (!ignoreKeyEvent(view, lParam)) { - initKeyEvent(&event.key, view, true, wParam, lParam); - } - break; - case WM_KEYUP: - initKeyEvent(&event.key, view, false, wParam, lParam); - break; - case WM_CHAR: - initCharEvent(&event, view, wParam, lParam); - break; - case WM_SETFOCUS: - event.type = PUGL_FOCUS_IN; - break; - case WM_KILLFOCUS: - event.type = PUGL_FOCUS_OUT; - break; - case WM_SYSKEYDOWN: - initKeyEvent(&event.key, view, true, wParam, lParam); - break; - case WM_SYSKEYUP: - initKeyEvent(&event.key, view, false, wParam, lParam); - break; - case WM_SYSCHAR: - return TRUE; - case PUGL_LOCAL_CLIENT_MSG: - event.client.type = PUGL_CLIENT; - event.client.data1 = (uintptr_t)wParam; - event.client.data2 = (uintptr_t)lParam; - break; - case WM_QUIT: - case PUGL_LOCAL_CLOSE_MSG: - event.any.type = PUGL_CLOSE; - break; - default: - return DefWindowProc(view->impl->hwnd, message, wParam, lParam); - } - - puglDispatchEvent(view, &event); - - return 0; -} - -PuglStatus -puglGrabFocus(PuglView* view) -{ - SetFocus(view->impl->hwnd); - return PUGL_SUCCESS; -} - -bool -puglHasFocus(const PuglView* view) -{ - return GetFocus() == view->impl->hwnd; -} - -PuglStatus -puglRequestAttention(PuglView* view) -{ - FLASHWINFO info = {sizeof(FLASHWINFO), - view->impl->hwnd, - FLASHW_ALL|FLASHW_TIMERNOFG, - 1, - 0}; - - FlashWindowEx(&info); - - return PUGL_SUCCESS; -} - -PuglStatus -puglStartTimer(PuglView* view, uintptr_t id, double timeout) -{ - const UINT msec = (UINT)floor(timeout * 1000.0); - - return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL) - ? PUGL_SUCCESS - : PUGL_UNKNOWN_ERROR); -} - -PuglStatus -puglStopTimer(PuglView* view, uintptr_t id) -{ - return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id) - ? PUGL_SUCCESS - : PUGL_UNKNOWN_ERROR); -} - -PuglStatus -puglSendEvent(PuglView* view, const PuglEvent* event) -{ - if (event->type == PUGL_CLIENT) { - PostMessage(view->impl->hwnd, - PUGL_LOCAL_CLIENT_MSG, - (WPARAM)event->client.data1, - (LPARAM)event->client.data2); - - return PUGL_SUCCESS; - } - - return PUGL_UNSUPPORTED_TYPE; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglWaitForEvent(PuglView* PUGL_UNUSED(view)) -{ - WaitMessage(); - return PUGL_SUCCESS; -} -#endif - -static PuglStatus -puglDispatchViewEvents(PuglView* view) -{ - /* Windows has no facility to process only currently queued messages, which - causes the event loop to run forever in cases like mouse movement where - the queue is constantly being filled with new messages. To work around - this, we post a message to ourselves before starting, record its time - when it is received, then break the loop on the first message that was - created afterwards. */ - - long markTime = 0; - MSG msg; - while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { - if (msg.message == PUGL_LOCAL_MARK_MSG) { - markTime = GetMessageTime(); - } else { - TranslateMessage(&msg); - DispatchMessage(&msg); - if (markTime != 0 && GetMessageTime() > markTime) { - break; - } - } - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglDispatchWinEvents(PuglWorld* world) -{ - for (size_t i = 0; i < world->numViews; ++i) { - PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0); - } - - for (size_t i = 0; i < world->numViews; ++i) { - puglDispatchViewEvents(world->views[i]); - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglUpdate(PuglWorld* world, double timeout) -{ - const double startTime = puglGetTime(world); - PuglStatus st = PUGL_SUCCESS; - - if (timeout < 0.0) { - st = puglPollWinEvents(world, timeout); - st = st ? st : puglDispatchWinEvents(world); - } else if (timeout == 0.0) { - st = puglDispatchWinEvents(world); - } else { - const double endTime = startTime + timeout - 0.001; - for (double t = startTime; t < endTime; t = puglGetTime(world)) { - if ((st = puglPollWinEvents(world, endTime - t)) || - (st = puglDispatchWinEvents(world))) { - break; - } - } - } - - for (size_t i = 0; i < world->numViews; ++i) { - if (world->views[i]->visible) { - puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE); - } - - UpdateWindow(world->views[i]->impl->hwnd); - } - - return st; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglProcessEvents(PuglView* view) -{ - return puglUpdate(view->world, 0.0); -} -#endif - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - - switch (message) { - case WM_CREATE: - PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); - return 0; - case WM_CLOSE: - PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); - return 0; - case WM_DESTROY: - return 0; - default: - if (view && hwnd == view->impl->hwnd) { - return handleMessage(view, message, wParam, lParam); - } else { - return DefWindowProc(hwnd, message, wParam, lParam); - } - } -} - -double -puglGetTime(const PuglWorld* world) -{ - LARGE_INTEGER count; - QueryPerformanceCounter(&count); - return ((double)count.QuadPart / world->impl->timerFrequency - - world->startTime); -} - -PuglStatus -puglPostRedisplay(PuglView* view) -{ - InvalidateRect(view->impl->hwnd, NULL, false); - return PUGL_SUCCESS; -} - -PuglStatus -puglPostRedisplayRect(PuglView* view, const PuglRect rect) -{ - const RECT r = {(long)floor(rect.x), - (long)floor(rect.y), - (long)ceil(rect.x + rect.width), - (long)ceil(rect.y + rect.height)}; - - InvalidateRect(view->impl->hwnd, &r, false); - - return PUGL_SUCCESS; -} - -PuglNativeView -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeView)view->impl->hwnd; -} - -PuglStatus -puglSetWindowTitle(PuglView* view, const char* title) -{ - puglSetString(&view->title, title); - - if (view->impl->hwnd) { - wchar_t* wtitle = puglUtf8ToWideChar(title); - if (wtitle) { - SetWindowTextW(view->impl->hwnd, wtitle); - free(wtitle); - } - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetFrame(PuglView* view, const PuglRect frame) -{ - view->frame = frame; - - if (view->impl->hwnd) { - RECT rect = { (long)frame.x, - (long)frame.y, - (long)frame.x + (long)frame.width, - (long)frame.y + (long)frame.height }; - - AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), - FALSE, - puglWinGetWindowExFlags(view)); - - if (!SetWindowPos(view->impl->hwnd, - HWND_TOP, - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER)) { - return PUGL_UNKNOWN_ERROR; - } - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetDefaultSize(PuglView* const view, const int width, const int height) -{ - view->defaultWidth = width; - view->defaultHeight = height; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetMinSize(PuglView* const view, const int width, const int height) -{ - view->minWidth = width; - view->minHeight = height; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetMaxSize(PuglView* const view, const int width, const int height) -{ - view->maxWidth = width; - view->maxHeight = height; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetAspectRatio(PuglView* const view, - const int minX, - const int minY, - const int maxX, - const int maxY) -{ - view->minAspectX = minX; - view->minAspectY = minY; - view->maxAspectX = maxX; - view->maxAspectY = maxY; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetTransientFor(PuglView* view, PuglNativeView parent) -{ - if (view->parent) { - return PUGL_FAILURE; - } - - view->transientParent = parent; - - if (view->impl->hwnd) { - SetWindowLongPtr(view->impl->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent); - return GetLastError() == NO_ERROR ? PUGL_SUCCESS : PUGL_FAILURE; - } - - return PUGL_SUCCESS; -} - -const void* -puglGetClipboard(PuglView* const view, - const char** const type, - size_t* const len) -{ - PuglInternals* const impl = view->impl; - - if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || - !OpenClipboard(impl->hwnd)) { - return NULL; - } - - HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); - wchar_t* wstr = mem ? (wchar_t*)GlobalLock(mem) : NULL; - if (!wstr) { - CloseClipboard(); - return NULL; - } - - free(view->clipboard.data); - view->clipboard.data = puglWideCharToUtf8(wstr, &view->clipboard.len); - GlobalUnlock(mem); - CloseClipboard(); - - return puglGetInternalClipboard(view, type, len); -} - -PuglStatus -puglSetClipboard(PuglView* const view, - const char* const type, - const void* const data, - const size_t len) -{ - PuglInternals* const impl = view->impl; - - PuglStatus st = puglSetInternalClipboard(view, type, data, len); - if (st) { - return st; - } else if (!OpenClipboard(impl->hwnd)) { - return PUGL_UNKNOWN_ERROR; - } - - // Measure string and allocate global memory for clipboard - const char* str = (const char*)data; - const int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, - (size_t)(wlen + 1) * sizeof(wchar_t)); - if (!mem) { - CloseClipboard(); - return PUGL_UNKNOWN_ERROR; - } - - // Lock global memory - wchar_t* wstr = (wchar_t*)GlobalLock(mem); - if (!wstr) { - GlobalFree(mem); - CloseClipboard(); - return PUGL_UNKNOWN_ERROR; - } - - // Convert string into global memory and set it as clipboard data - MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, wlen); - wstr[wlen] = 0; - GlobalUnlock(mem); - SetClipboardData(CF_UNICODETEXT, mem); - CloseClipboard(); - return PUGL_SUCCESS; -} - -static const char* const cursor_ids[] = { - IDC_ARROW, // ARROW - IDC_IBEAM, // CARET - IDC_CROSS, // CROSSHAIR - IDC_HAND, // HAND - IDC_NO, // NO - IDC_SIZEWE, // LEFT_RIGHT - IDC_SIZENS, // UP_DOWN -}; - -PuglStatus -puglSetCursor(PuglView* view, PuglCursor cursor) -{ - PuglInternals* const impl = view->impl; - const unsigned index = (unsigned)cursor; - const unsigned count = sizeof(cursor_ids) / sizeof(cursor_ids[0]); - - if (index >= count) { - return PUGL_BAD_PARAMETER; - } - - const HCURSOR cur = LoadCursor(NULL, cursor_ids[index]); - if (!cur) { - return PUGL_FAILURE; - } - - impl->cursor = cur; - if (impl->mouseTracked) { - SetCursor(cur); - } - - return PUGL_SUCCESS; -} diff --git a/include/pugl/detail/win.h b/include/pugl/detail/win.h deleted file mode 100644 index 4c0a3c0..0000000 --- a/include/pugl/detail/win.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file win.h - @brief Shared definitions for Windows implementation. -*/ - -#ifndef PUGL_DETAIL_WIN_H -#define PUGL_DETAIL_WIN_H - -#include "pugl/detail/implementation.h" - -#include - -#include - -typedef PIXELFORMATDESCRIPTOR PuglWinPFD; - -struct PuglWorldInternalsImpl { - double timerFrequency; -}; - -struct PuglInternalsImpl { - PuglWinPFD pfd; - int pfId; - HWND hwnd; - HCURSOR cursor; - HDC hdc; - PuglSurface* surface; - bool flashing; - bool mouseTracked; -}; - -static inline PuglWinPFD -puglWinGetPixelFormatDescriptor(const PuglHints hints) -{ - const int rgbBits = (hints[PUGL_RED_BITS] + // - hints[PUGL_GREEN_BITS] + // - hints[PUGL_BLUE_BITS]); - - const DWORD dwFlags = hints[PUGL_DOUBLE_BUFFER] ? PFD_DOUBLEBUFFER : 0u; - - PuglWinPFD pfd; - ZeroMemory(&pfd, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | dwFlags; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = (BYTE)rgbBits; - pfd.cRedBits = (BYTE)hints[PUGL_RED_BITS]; - pfd.cGreenBits = (BYTE)hints[PUGL_GREEN_BITS]; - pfd.cBlueBits = (BYTE)hints[PUGL_BLUE_BITS]; - pfd.cAlphaBits = (BYTE)hints[PUGL_ALPHA_BITS]; - pfd.cDepthBits = (BYTE)hints[PUGL_DEPTH_BITS]; - pfd.cStencilBits = (BYTE)hints[PUGL_STENCIL_BITS]; - pfd.iLayerType = PFD_MAIN_PLANE; - return pfd; -} - -static inline unsigned -puglWinGetWindowFlags(const PuglView* const view) -{ - const bool resizable = view->hints[PUGL_RESIZABLE]; - const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0u; - - return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | - (view->parent - ? WS_CHILD - : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags))); -} - -static inline unsigned -puglWinGetWindowExFlags(const PuglView* const view) -{ - return WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW); -} - -static inline PuglStatus -puglWinCreateWindow(PuglView* const view, - const char* const title, - HWND* const hwnd, - HDC* const hdc) -{ - const char* className = (const char*)view->world->className; - const unsigned winFlags = puglWinGetWindowFlags(view); - const unsigned winExFlags = puglWinGetWindowExFlags(view); - - if (view->frame.width == 0.0 && view->frame.height == 0.0) { - if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { - return PUGL_BAD_CONFIGURATION; - } - - RECT desktopRect; - GetClientRect(GetDesktopWindow(), &desktopRect); - - const int screenWidth = desktopRect.right - desktopRect.left; - const int screenHeight = desktopRect.bottom - desktopRect.top; - - view->frame.width = view->defaultWidth; - view->frame.height = view->defaultHeight; - view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0; - view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0; - } - - // The meaning of "parent" depends on the window type (WS_CHILD) - PuglNativeView parent = view->parent ? view->parent : view->transientParent; - - // Calculate total window size to accommodate requested view size - RECT wr = { (long)view->frame.x, (long)view->frame.y, - (long)view->frame.width, (long)view->frame.height }; - AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags); - - // Create window and get drawing context - if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags, - CW_USEDEFAULT, CW_USEDEFAULT, - wr.right-wr.left, wr.bottom-wr.top, - (HWND)parent, NULL, NULL, NULL))) { - return PUGL_REALIZE_FAILED; - } else if (!(*hdc = GetDC(*hwnd))) { - DestroyWindow(*hwnd); - *hwnd = NULL; - return PUGL_REALIZE_FAILED; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglWinStubConfigure(PuglView* view); - -PuglStatus -puglWinStubEnter(PuglView* view, const PuglEventExpose* expose); - -PuglStatus -puglWinStubLeave(PuglView* view, const PuglEventExpose* expose); - -#endif // PUGL_DETAIL_WIN_H diff --git a/include/pugl/detail/win_cairo.c b/include/pugl/detail/win_cairo.c deleted file mode 100644 index 866569c..0000000 --- a/include/pugl/detail/win_cairo.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file win_cairo.c - @brief Cairo graphics backend for Windows. -*/ - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/win.h" -#include "pugl/pugl_cairo.h" - -#include -#include - -#include - -typedef struct { - cairo_surface_t* surface; - cairo_t* cr; - HDC drawDc; - HBITMAP drawBitmap; -} PuglWinCairoSurface; - -static PuglStatus -puglWinCairoCreateDrawContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - surface->drawDc = CreateCompatibleDC(impl->hdc); - surface->drawBitmap = CreateCompatibleBitmap( - impl->hdc, (int)view->frame.width, (int)view->frame.height); - - DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinCairoDestroyDrawContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - DeleteDC(surface->drawDc); - DeleteObject(surface->drawBitmap); - - surface->drawDc = NULL; - surface->drawBitmap = NULL; - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinCairoConfigure(PuglView* view) -{ - const PuglStatus st = puglWinStubConfigure(view); - - if (!st) { - view->impl->surface = (PuglWinCairoSurface*)calloc( - 1, sizeof(PuglWinCairoSurface)); - } - - return st; -} - -static void -puglWinCairoClose(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - cairo_surface_destroy(surface->surface); - - surface->surface = NULL; -} - -static PuglStatus -puglWinCairoOpen(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || - cairo_surface_status(surface->surface) || - !(surface->cr = cairo_create(surface->surface)) || - cairo_status(surface->cr)) { - return PUGL_CREATE_CONTEXT_FAILED; - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinCairoDestroy(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - puglWinCairoClose(view); - puglWinCairoDestroyDrawContext(view); - free(surface); - impl->surface = NULL; - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinCairoEnter(PuglView* view, const PuglEventExpose* expose) -{ - PuglStatus st = PUGL_SUCCESS; - - if (expose && - !(st = puglWinCairoCreateDrawContext(view)) && - !(st = puglWinCairoOpen(view))) { - PAINTSTRUCT ps; - BeginPaint(view->impl->hwnd, &ps); - } - - return st; -} - -static PuglStatus -puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - if (expose) { - cairo_surface_flush(surface->surface); - BitBlt(impl->hdc, - 0, 0, (int)view->frame.width, (int)view->frame.height, - surface->drawDc, 0, 0, SRCCOPY); - - puglWinCairoClose(view); - puglWinCairoDestroyDrawContext(view); - - PAINTSTRUCT ps; - EndPaint(view->impl->hwnd, &ps); - } - - return PUGL_SUCCESS; -} - -static void* -puglWinCairoGetContext(PuglView* view) -{ - return ((PuglWinCairoSurface*)view->impl->surface)->cr; -} - -const PuglBackend* -puglCairoBackend() -{ - static const PuglBackend backend = {puglWinCairoConfigure, - puglStubCreate, - puglWinCairoDestroy, - puglWinCairoEnter, - puglWinCairoLeave, - puglWinCairoGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/win_gl.c b/include/pugl/detail/win_gl.c deleted file mode 100644 index 4f71d76..0000000 --- a/include/pugl/detail/win_gl.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file win_gl.c - @brief OpenGL graphics backend for Windows. -*/ - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/win.h" -#include "pugl/pugl_gl.h" - -#include - -#include - -#include -#include - -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_FULL_ACCELERATION_ARB 0x2027 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_SAMPLE_BUFFERS_ARB 0x2041 -#define WGL_SAMPLES_ARB 0x2042 - -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 - -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 - -typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*); -typedef BOOL (*WglSwapInterval)(int); -typedef BOOL (*WglChoosePixelFormat)( - HDC, const int*, const FLOAT*, UINT, int*, UINT*); - -typedef struct { - WglChoosePixelFormat wglChoosePixelFormat; - WglCreateContextAttribs wglCreateContextAttribs; - WglSwapInterval wglSwapInterval; -} PuglWinGlProcs; - -typedef struct { - PuglWinGlProcs procs; - HGLRC hglrc; -} PuglWinGlSurface; - -// Struct to manage the fake window used during configuration -typedef struct { - HWND hwnd; - HDC hdc; -} PuglFakeWindow; - -static PuglStatus -puglWinError(PuglFakeWindow* fakeWin, const PuglStatus status) -{ - if (fakeWin->hwnd) { - ReleaseDC(fakeWin->hwnd, fakeWin->hdc); - DestroyWindow(fakeWin->hwnd); - } - - return status; -} - -static PuglWinGlProcs puglWinGlGetProcs(void) -{ - const PuglWinGlProcs procs = { - (WglChoosePixelFormat)( - wglGetProcAddress("wglChoosePixelFormatARB")), - (WglCreateContextAttribs)( - wglGetProcAddress("wglCreateContextAttribsARB")), - (WglSwapInterval)( - wglGetProcAddress("wglSwapIntervalEXT")) - }; - - return procs; -} - -static PuglStatus -puglWinGlConfigure(PuglView* view) -{ - PuglInternals* impl = view->impl; - - // Set attributes to default if they are unset - // (There is no GLX_DONT_CARE equivalent on Windows) - if (view->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_DEPTH_BITS] = 0; - } - if (view->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { - view->hints[PUGL_STENCIL_BITS] = 0; - } - if (view->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { - view->hints[PUGL_SAMPLES] = 1; - } - if (view->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { - view->hints[PUGL_DOUBLE_BUFFER] = 1; - } - if (view->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { - view->hints[PUGL_SWAP_INTERVAL] = 1; - } - - // clang-format off - const int pixelAttrs[] = { - WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, - WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - WGL_SUPPORT_OPENGL_ARB, GL_TRUE, - WGL_DOUBLE_BUFFER_ARB, view->hints[PUGL_DOUBLE_BUFFER], - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLES] ? 1 : 0, - WGL_SAMPLES_ARB, view->hints[PUGL_SAMPLES], - WGL_RED_BITS_ARB, view->hints[PUGL_RED_BITS], - WGL_GREEN_BITS_ARB, view->hints[PUGL_GREEN_BITS], - WGL_BLUE_BITS_ARB, view->hints[PUGL_BLUE_BITS], - WGL_ALPHA_BITS_ARB, view->hints[PUGL_ALPHA_BITS], - WGL_DEPTH_BITS_ARB, view->hints[PUGL_DEPTH_BITS], - WGL_STENCIL_BITS_ARB, view->hints[PUGL_STENCIL_BITS], - 0, - }; - // clang-format on - - PuglWinGlSurface* const surface = - (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface)); - impl->surface = surface; - - // Create fake window for getting at GL context - PuglStatus st = PUGL_SUCCESS; - PuglFakeWindow fakeWin = {0, 0}; - static const char* title = "Pugl Configuration"; - if ((st = puglWinCreateWindow(view, title, &fakeWin.hwnd, &fakeWin.hdc))) { - return puglWinError(&fakeWin, st); - } - - // Set pixel format for fake window - const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(view->hints); - const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd); - if (!fakePfId || !SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) { - return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); - } - - // Create fake GL context to get at the functions we need - HGLRC fakeRc = wglCreateContext(fakeWin.hdc); - if (!fakeRc) { - return puglWinError(&fakeWin, PUGL_CREATE_CONTEXT_FAILED); - } - - // Enter fake context and get extension functions - wglMakeCurrent(fakeWin.hdc, fakeRc); - surface->procs = puglWinGlGetProcs(); - - if (surface->procs.wglChoosePixelFormat) { - // Choose pixel format based on attributes - UINT numFormats = 0; - if (!surface->procs.wglChoosePixelFormat( - fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { - return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); - } - - DescribePixelFormat( - impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd); - } else { - // Modern extensions not available, use basic pixel format - impl->pfd = fakePfd; - impl->pfId = fakePfId; - } - - // Dispose of fake window and context - wglMakeCurrent(NULL, NULL); - wglDeleteContext(fakeRc); - ReleaseDC(fakeWin.hwnd, fakeWin.hdc); - DestroyWindow(fakeWin.hwnd); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinGlCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface; - PuglStatus st = PUGL_SUCCESS; - - const int contextAttribs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, - view->hints[PUGL_CONTEXT_VERSION_MAJOR], - - WGL_CONTEXT_MINOR_VERSION_ARB, - view->hints[PUGL_CONTEXT_VERSION_MINOR], - - WGL_CONTEXT_FLAGS_ARB, - (view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), - - WGL_CONTEXT_PROFILE_MASK_ARB, - (view->hints[PUGL_USE_COMPAT_PROFILE] - ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB - : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), - - 0}; - - // Create real window with desired pixel format - if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { - return st; - } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { - ReleaseDC(impl->hwnd, impl->hdc); - DestroyWindow(impl->hwnd); - impl->hwnd = NULL; - impl->hdc = NULL; - return PUGL_SET_FORMAT_FAILED; - } - - // Create GL context - if (surface->procs.wglCreateContextAttribs && - !(surface->hglrc = surface->procs.wglCreateContextAttribs( - impl->hdc, 0, contextAttribs))) { - return PUGL_CREATE_CONTEXT_FAILED; - } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) { - return PUGL_CREATE_CONTEXT_FAILED; - } - - // Enter context and set swap interval - wglMakeCurrent(impl->hdc, surface->hglrc); - const int swapInterval = view->hints[PUGL_SWAP_INTERVAL]; - if (surface->procs.wglSwapInterval && swapInterval != PUGL_DONT_CARE) { - surface->procs.wglSwapInterval(swapInterval); - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinGlDestroy(PuglView* view) -{ - PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; - if (surface) { - wglMakeCurrent(NULL, NULL); - wglDeleteContext(surface->hglrc); - free(surface); - view->impl->surface = NULL; - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinGlEnter(PuglView* view, const PuglEventExpose* expose) -{ - PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; - - wglMakeCurrent(view->impl->hdc, surface->hglrc); - - if (expose) { - PAINTSTRUCT ps; - BeginPaint(view->impl->hwnd, &ps); - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglWinGlLeave(PuglView* view, const PuglEventExpose* expose) -{ - if (expose) { - PAINTSTRUCT ps; - EndPaint(view->impl->hwnd, &ps); - SwapBuffers(view->impl->hdc); - } - - wglMakeCurrent(NULL, NULL); - return PUGL_SUCCESS; -} - -PuglGlFunc -puglGetProcAddress(const char* name) -{ - const PuglGlFunc func = (PuglGlFunc)wglGetProcAddress(name); - - /* Windows has the annoying property that wglGetProcAddress returns NULL - for functions from OpenGL 1.1, so we fall back to pulling them directly - from opengl32.dll */ - - return func - ? func - : (PuglGlFunc)GetProcAddress(GetModuleHandle("opengl32.dll"), name); -} - -const PuglBackend* -puglGlBackend(void) -{ - static const PuglBackend backend = {puglWinGlConfigure, - puglWinGlCreate, - puglWinGlDestroy, - puglWinGlEnter, - puglWinGlLeave, - puglStubGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/win_stub.c b/include/pugl/detail/win_stub.c deleted file mode 100644 index ab9e6aa..0000000 --- a/include/pugl/detail/win_stub.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "pugl/pugl_stub.h" - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/win.h" - -PuglStatus -puglWinStubConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglStatus st = PUGL_SUCCESS; - - if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { - return st; - } - - impl->pfd = puglWinGetPixelFormatDescriptor(view->hints); - impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd); - - if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { - ReleaseDC(impl->hwnd, impl->hdc); - DestroyWindow(impl->hwnd); - impl->hwnd = NULL; - impl->hdc = NULL; - return PUGL_SET_FORMAT_FAILED; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglWinStubEnter(PuglView* view, const PuglEventExpose* expose) -{ - if (expose) { - PAINTSTRUCT ps; - BeginPaint(view->impl->hwnd, &ps); - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglWinStubLeave(PuglView* view, const PuglEventExpose* expose) -{ - if (expose) { - PAINTSTRUCT ps; - EndPaint(view->impl->hwnd, &ps); - } - - return PUGL_SUCCESS; -} - -const PuglBackend* -puglStubBackend(void) -{ - static const PuglBackend backend = {puglWinStubConfigure, - puglStubCreate, - puglStubDestroy, - puglWinStubEnter, - puglWinStubLeave, - puglStubGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/win_vulkan.c b/include/pugl/detail/win_vulkan.c deleted file mode 100644 index cfe3613..0000000 --- a/include/pugl/detail/win_vulkan.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file win_vulkan.c Vulkan graphics backend for Windows. -*/ - -#define VK_NO_PROTOTYPES 1 - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/win.h" -#include "pugl/pugl_stub.h" -#include "pugl/pugl_vulkan.h" - -#include -#include - -#include - -struct PuglVulkanLoaderImpl -{ - HMODULE libvulkan; - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; -}; - -PuglVulkanLoader* -puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) -{ - PuglVulkanLoader* loader = - (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); - if (!loader) { - return NULL; - } - - if (!(loader->libvulkan = LoadLibrary("vulkan-1.dll"))) { - free(loader); - return NULL; - } - - loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)GetProcAddress( - loader->libvulkan, "vkGetInstanceProcAddr"); - - loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)GetProcAddress( - loader->libvulkan, "vkGetDeviceProcAddr"); - - return loader; -} - -void -puglFreeVulkanLoader(PuglVulkanLoader* loader) -{ - if (loader) { - FreeLibrary(loader->libvulkan); - free(loader); - } -} - -PFN_vkGetInstanceProcAddr -puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetInstanceProcAddr; -} - -PFN_vkGetDeviceProcAddr -puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetDeviceProcAddr; -} - -const PuglBackend* -puglVulkanBackend() -{ - static const PuglBackend backend = {puglWinStubConfigure, - puglStubCreate, - puglStubDestroy, - puglWinStubEnter, - puglWinStubLeave, - puglStubGetContext}; - - return &backend; -} - -const char* const* -puglGetInstanceExtensions(uint32_t* const count) -{ - static const char* const extensions[] = {"VK_KHR_surface", - "VK_KHR_win32_surface"}; - - *count = 2; - return extensions; -} - -VkResult -puglCreateSurface(const PuglVulkanLoader* const loader, - PuglView* const view, - VkInstance instance, - const VkAllocationCallbacks* const pAllocator, - VkSurfaceKHR* const pSurface) -{ - PuglInternals* const impl = view->impl; - - PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = - (PFN_vkCreateWin32SurfaceKHR)puglGetInstanceProcAddrFunc(loader)( - instance, "vkCreateWin32SurfaceKHR"); - - const VkWin32SurfaceCreateInfoKHR createInfo = { - VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, - NULL, - 0, - GetModuleHandle(NULL), - impl->hwnd, - }; - - return vkCreateWin32SurfaceKHR(instance, &createInfo, pAllocator, pSurface); -} diff --git a/include/pugl/detail/x11.c b/include/pugl/detail/x11.c deleted file mode 100644 index fd76606..0000000 --- a/include/pugl/detail/x11.c +++ /dev/null @@ -1,1348 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - Copyright 2013 Robin Gareus - Copyright 2011-2012 Ben Loftis, Harrison Consoles - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file x11.c - @brief X11 implementation. -*/ - -#define _POSIX_C_SOURCE 199309L - -#include "pugl/detail/x11.h" - -#include "pugl/detail/implementation.h" -#include "pugl/detail/types.h" -#include "pugl/pugl.h" - -#include -#include -#include -#include -#include - -#ifdef HAVE_XRANDR -# include -#endif - -#ifdef HAVE_XSYNC -# include -# include -#endif - -#ifdef HAVE_XCURSOR -# include -# include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifndef MIN -# define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#ifndef MAX -# define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - -enum WmClientStateMessageAction { - WM_STATE_REMOVE, - WM_STATE_ADD, - WM_STATE_TOGGLE -}; - -static const long eventMask = - (ExposureMask | StructureNotifyMask | - VisibilityChangeMask | FocusChangeMask | - EnterWindowMask | LeaveWindowMask | PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); - -static bool -puglInitXSync(PuglWorldInternals* impl) -{ -#ifdef HAVE_XSYNC - int syncMajor = 0; - int syncMinor = 0; - int errorBase = 0; - XSyncSystemCounter* counters = NULL; - int numCounters = 0; - - if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) && - XSyncInitialize(impl->display, &syncMajor, &syncMinor) && - (counters = XSyncListSystemCounters(impl->display, &numCounters))) { - - for (int n = 0; n < numCounters; ++n) { - if (!strcmp(counters[n].name, "SERVERTIME")) { - impl->serverTimeCounter = counters[n].counter; - impl->syncSupported = true; - break; - } - } - - XSyncFreeSystemCounterList(counters); - } -#else - (void)impl; -#endif - - return false; -} - -PuglWorldInternals* -puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags) -{ - if (type == PUGL_PROGRAM && (flags & PUGL_WORLD_THREADS)) { - XInitThreads(); - } - - Display* display = XOpenDisplay(NULL); - if (!display) { - return NULL; - } - - PuglWorldInternals* impl = (PuglWorldInternals*)calloc( - 1, sizeof(PuglWorldInternals)); - - impl->display = display; - - // Intern the various atoms we will need - impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0); - impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0); - impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); - impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); - impl->atoms.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 0); - impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0); - impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); - impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = - XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); - - // Open input method - XSetLocaleModifiers(""); - if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) { - XSetLocaleModifiers("@im="); - impl->xim = XOpenIM(display, NULL, NULL, NULL); - } - - puglInitXSync(impl); - XFlush(display); - - return impl; -} - -void* -puglGetNativeWorld(PuglWorld* world) -{ - return world->impl->display; -} - -PuglInternals* -puglInitViewInternals(void) -{ - PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); - -#ifdef HAVE_XCURSOR - impl->cursorShape = XC_arrow; -#endif - - return impl; -} - -static PuglStatus -puglPollX11Socket(PuglWorld* world, const double timeout) -{ - if (XPending(world->impl->display) > 0) { - return PUGL_SUCCESS; - } - - const int fd = ConnectionNumber(world->impl->display); - const int nfds = fd + 1; - int ret = 0; - fd_set fds; - FD_ZERO(&fds); // NOLINT - FD_SET(fd, &fds); - - if (timeout < 0.0) { - ret = select(nfds, &fds, NULL, NULL, NULL); - } else { - const long sec = (long)timeout; - const long usec = (long)((timeout - (double)sec) * 1e6); - struct timeval tv = {sec, usec}; - ret = select(nfds, &fds, NULL, NULL, &tv); - } - - return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS; -} - -static PuglView* -puglFindView(PuglWorld* world, const Window window) -{ - for (size_t i = 0; i < world->numViews; ++i) { - if (world->views[i]->impl->win == window) { - return world->views[i]; - } - } - - return NULL; -} - -static PuglStatus -updateSizeHints(const PuglView* view) -{ - if (!view->impl->win) { - return PUGL_SUCCESS; - } - - Display* display = view->world->impl->display; - XSizeHints sizeHints = {0}; - - if (!view->hints[PUGL_RESIZABLE]) { - sizeHints.flags = PBaseSize | PMinSize | PMaxSize; - sizeHints.base_width = (int)view->frame.width; - sizeHints.base_height = (int)view->frame.height; - sizeHints.min_width = (int)view->frame.width; - sizeHints.min_height = (int)view->frame.height; - sizeHints.max_width = (int)view->frame.width; - sizeHints.max_height = (int)view->frame.height; - } else { - if (view->defaultWidth || view->defaultHeight) { - sizeHints.flags = PBaseSize; - sizeHints.base_width = view->defaultWidth; - sizeHints.base_height = view->defaultHeight; - } - if (view->minWidth || view->minHeight) { - sizeHints.flags = PMinSize; - sizeHints.min_width = view->minWidth; - sizeHints.min_height = view->minHeight; - } - if (view->maxWidth || view->maxHeight) { - sizeHints.flags = PMaxSize; - sizeHints.max_width = view->maxWidth; - sizeHints.max_height = view->maxHeight; - } - if (view->minAspectX) { - sizeHints.flags |= PAspect; - sizeHints.min_aspect.x = view->minAspectX; - sizeHints.min_aspect.y = view->minAspectY; - sizeHints.max_aspect.x = view->maxAspectX; - sizeHints.max_aspect.y = view->maxAspectY; - } - } - - XSetNormalHints(display, view->impl->win, &sizeHints); - return PUGL_SUCCESS; -} - -#ifdef HAVE_XCURSOR -static PuglStatus -puglDefineCursorShape(PuglView* view, unsigned shape) -{ - PuglInternals* const impl = view->impl; - PuglWorld* const world = view->world; - Display* const display = world->impl->display; - const Cursor cur = XcursorShapeLoadCursor(display, shape); - - if (cur) { - XDefineCursor(display, impl->win, cur); - XFreeCursor(display, cur); - return PUGL_SUCCESS; - } - - return PUGL_FAILURE; -} -#endif - -PuglStatus -puglRealize(PuglView* view) -{ - PuglInternals* const impl = view->impl; - if (impl->win) { - return PUGL_FAILURE; - } - - PuglWorld* const world = view->world; - PuglX11Atoms* const atoms = &view->world->impl->atoms; - Display* const display = world->impl->display; - - impl->display = display; - impl->screen = DefaultScreen(display); - - if (!view->backend || !view->backend->configure) { - return PUGL_BAD_BACKEND; - } else if (view->frame.width == 0.0 && view->frame.height == 0.0) { - if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { - return PUGL_BAD_CONFIGURATION; - } - - const int screenWidth = DisplayWidth(display, impl->screen); - const int screenHeight = DisplayHeight(display, impl->screen); - - view->frame.width = view->defaultWidth; - view->frame.height = view->defaultHeight; - view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0; - view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0; - } - - PuglStatus st = view->backend->configure(view); - if (st || !impl->vi) { - view->backend->destroy(view); - return st ? st : PUGL_BACKEND_FAILED; - } - - Window xParent = view->parent ? (Window)view->parent - : RootWindow(display, impl->screen); - - Colormap cmap = XCreateColormap( - display, xParent, impl->vi->visual, AllocNone); - - XSetWindowAttributes attr = {0}; - attr.colormap = cmap; - attr.event_mask = eventMask; - - const Window win = impl->win = XCreateWindow( - display, xParent, - (int)view->frame.x, (int)view->frame.y, - (unsigned)view->frame.width, (unsigned)view->frame.height, - 0, impl->vi->depth, InputOutput, - impl->vi->visual, CWColormap | CWEventMask, &attr); - - if ((st = view->backend->create(view))) { - return st; - } - -#ifdef HAVE_XRANDR - // Set refresh rate hint to the real refresh rate - XRRScreenConfiguration* conf = XRRGetScreenInfo(display, xParent); - short current_rate = XRRConfigCurrentRate(conf); - - view->hints[PUGL_REFRESH_RATE] = current_rate; - XRRFreeScreenConfigInfo(conf); -#endif - - updateSizeHints(view); - - XClassHint classHint = { world->className, world->className }; - XSetClassHint(display, win, &classHint); - - if (view->title) { - puglSetWindowTitle(view, view->title); - } - - if (!view->parent) { - XSetWMProtocols(display, win, &atoms->WM_DELETE_WINDOW, 1); - } - - if (view->transientParent) { - XSetTransientForHint(display, win, (Window)(view->transientParent)); - } - - // Create input context - const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing; - if (!(impl->xic = XCreateIC(world->impl->xim, - XNInputStyle, im_style, - XNClientWindow, win, - XNFocusWindow, win, - NULL))) { - view->world->logFunc(view->world, - PUGL_LOG_LEVEL_WARNING, - "XCreateID failed\n"); - } - -#ifdef HAVE_XCURSOR - puglDefineCursorShape(view, impl->cursorShape); -#endif - - puglDispatchSimpleEvent(view, PUGL_CREATE); - - return PUGL_SUCCESS; -} - -PuglStatus -puglShowWindow(PuglView* view) -{ - PuglStatus st = PUGL_SUCCESS; - - if (!view->impl->win) { - if ((st = puglRealize(view))) { - return st; - } - } - - XMapRaised(view->impl->display, view->impl->win); - puglPostRedisplay(view); - - return st; -} - -PuglStatus -puglHideWindow(PuglView* view) -{ - XUnmapWindow(view->impl->display, view->impl->win); - return PUGL_SUCCESS; -} - -void -puglFreeViewInternals(PuglView* view) -{ - if (view && view->impl) { - if (view->impl->xic) { - XDestroyIC(view->impl->xic); - } - if (view->backend) { - view->backend->destroy(view); - } - if (view->impl->display) { - XDestroyWindow(view->impl->display, view->impl->win); - } - XFree(view->impl->vi); - free(view->impl); - } -} - -void -puglFreeWorldInternals(PuglWorld* world) -{ - if (world->impl->xim) { - XCloseIM(world->impl->xim); - } - XCloseDisplay(world->impl->display); - free(world->impl->timers); - free(world->impl); -} - -static PuglKey -keySymToSpecial(KeySym sym) -{ - switch (sym) { - case XK_F1: return PUGL_KEY_F1; - case XK_F2: return PUGL_KEY_F2; - case XK_F3: return PUGL_KEY_F3; - case XK_F4: return PUGL_KEY_F4; - case XK_F5: return PUGL_KEY_F5; - case XK_F6: return PUGL_KEY_F6; - case XK_F7: return PUGL_KEY_F7; - case XK_F8: return PUGL_KEY_F8; - case XK_F9: return PUGL_KEY_F9; - case XK_F10: return PUGL_KEY_F10; - case XK_F11: return PUGL_KEY_F11; - case XK_F12: return PUGL_KEY_F12; - case XK_Left: return PUGL_KEY_LEFT; - case XK_Up: return PUGL_KEY_UP; - case XK_Right: return PUGL_KEY_RIGHT; - case XK_Down: return PUGL_KEY_DOWN; - case XK_Page_Up: return PUGL_KEY_PAGE_UP; - case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; - case XK_Home: return PUGL_KEY_HOME; - case XK_End: return PUGL_KEY_END; - case XK_Insert: return PUGL_KEY_INSERT; - case XK_Shift_L: return PUGL_KEY_SHIFT_L; - case XK_Shift_R: return PUGL_KEY_SHIFT_R; - case XK_Control_L: return PUGL_KEY_CTRL_L; - case XK_Control_R: return PUGL_KEY_CTRL_R; - case XK_Alt_L: return PUGL_KEY_ALT_L; - case XK_ISO_Level3_Shift: - case XK_Alt_R: return PUGL_KEY_ALT_R; - case XK_Super_L: return PUGL_KEY_SUPER_L; - case XK_Super_R: return PUGL_KEY_SUPER_R; - case XK_Menu: return PUGL_KEY_MENU; - case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK; - case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK; - case XK_Num_Lock: return PUGL_KEY_NUM_LOCK; - case XK_Print: return PUGL_KEY_PRINT_SCREEN; - case XK_Pause: return PUGL_KEY_PAUSE; - default: break; - } - return (PuglKey)0; -} - -static int -lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym) -{ - Status status = 0; - -#ifdef X_HAVE_UTF8_STRING - const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status); -#else - const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status); -#endif - - return status == XBufferOverflow ? 0 : n; -} - -static void -translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) -{ - const unsigned state = xevent->xkey.state; - const bool filter = XFilterEvent(xevent, None); - - event->key.keycode = xevent->xkey.keycode; - xevent->xkey.state = 0; - - // Lookup unshifted key - char ustr[8] = {0}; - KeySym sym = 0; - const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL); - const PuglKey special = keySymToSpecial(sym); - - event->key.key = ((special || ufound <= 0) - ? special - : puglDecodeUTF8((const uint8_t*)ustr)); - - if (xevent->type == KeyPress && !filter && !special) { - // Lookup shifted key for possible text event - xevent->xkey.state = state; - - char sstr[8] = {0}; - const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym); - if (sfound > 0) { - // Dispatch key event now - puglDispatchEvent(view, event); - - // "Return" a text event in its place - event->text.type = PUGL_TEXT; - event->text.character = puglDecodeUTF8((const uint8_t*)sstr); - memcpy(event->text.string, sstr, sizeof(sstr)); - } - } -} - -static uint32_t -translateModifiers(const unsigned xstate) -{ - return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0u) | - ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0u) | - ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0u) | - ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u)); -} - -static PuglEvent -translateEvent(PuglView* view, XEvent xevent) -{ - const PuglX11Atoms* atoms = &view->world->impl->atoms; - - PuglEvent event = {{PUGL_NOTHING, 0}}; - event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0; - - switch (xevent.type) { - case ClientMessage: - if (xevent.xclient.message_type == atoms->WM_PROTOCOLS) { - const Atom protocol = (Atom)xevent.xclient.data.l[0]; - if (protocol == atoms->WM_DELETE_WINDOW) { - event.type = PUGL_CLOSE; - } - } else if (xevent.xclient.message_type == atoms->PUGL_CLIENT_MSG) { - event.type = PUGL_CLIENT; - event.client.data1 = (uintptr_t)xevent.xclient.data.l[0]; - event.client.data2 = (uintptr_t)xevent.xclient.data.l[1]; - } - break; - case VisibilityNotify: - view->visible = xevent.xvisibility.state != VisibilityFullyObscured; - break; - case MapNotify: - event.type = PUGL_MAP; - break; - case UnmapNotify: - event.type = PUGL_UNMAP; - view->visible = false; - break; - case ConfigureNotify: - event.type = PUGL_CONFIGURE; - event.configure.x = xevent.xconfigure.x; - event.configure.y = xevent.xconfigure.y; - event.configure.width = xevent.xconfigure.width; - event.configure.height = xevent.xconfigure.height; - break; - case Expose: - event.type = PUGL_EXPOSE; - event.expose.x = xevent.xexpose.x; - event.expose.y = xevent.xexpose.y; - event.expose.width = xevent.xexpose.width; - event.expose.height = xevent.xexpose.height; - break; - case MotionNotify: - event.type = PUGL_MOTION; - event.motion.time = (double)xevent.xmotion.time / 1e3; - event.motion.x = xevent.xmotion.x; - event.motion.y = xevent.xmotion.y; - event.motion.xRoot = xevent.xmotion.x_root; - event.motion.yRoot = xevent.xmotion.y_root; - event.motion.state = translateModifiers(xevent.xmotion.state); - if (xevent.xmotion.is_hint == NotifyHint) { - event.motion.flags |= PUGL_IS_HINT; - } - break; - case ButtonPress: - if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) { - event.type = PUGL_SCROLL; - event.scroll.time = (double)xevent.xbutton.time / 1e3; - event.scroll.x = xevent.xbutton.x; - event.scroll.y = xevent.xbutton.y; - event.scroll.xRoot = xevent.xbutton.x_root; - event.scroll.yRoot = xevent.xbutton.y_root; - event.scroll.state = translateModifiers(xevent.xbutton.state); - event.scroll.dx = 0.0; - event.scroll.dy = 0.0; - switch (xevent.xbutton.button) { - case 4: - event.scroll.dy = 1.0; - event.scroll.direction = PUGL_SCROLL_UP; - break; - case 5: - event.scroll.dy = -1.0; - event.scroll.direction = PUGL_SCROLL_DOWN; - break; - case 6: - event.scroll.dx = -1.0; - event.scroll.direction = PUGL_SCROLL_LEFT; - break; - case 7: - event.scroll.dx = 1.0; - event.scroll.direction = PUGL_SCROLL_RIGHT; - break; - } - // fallthru - } - // fallthru - case ButtonRelease: - if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) { - event.button.type = ((xevent.type == ButtonPress) - ? PUGL_BUTTON_PRESS - : PUGL_BUTTON_RELEASE); - event.button.time = (double)xevent.xbutton.time / 1e3; - event.button.x = xevent.xbutton.x; - event.button.y = xevent.xbutton.y; - event.button.xRoot = xevent.xbutton.x_root; - event.button.yRoot = xevent.xbutton.y_root; - event.button.state = translateModifiers(xevent.xbutton.state); - event.button.button = xevent.xbutton.button; - } - break; - case KeyPress: - case KeyRelease: - event.type = ((xevent.type == KeyPress) - ? PUGL_KEY_PRESS - : PUGL_KEY_RELEASE); - event.key.time = (double)xevent.xkey.time / 1e3; - event.key.x = xevent.xkey.x; - event.key.y = xevent.xkey.y; - event.key.xRoot = xevent.xkey.x_root; - event.key.yRoot = xevent.xkey.y_root; - event.key.state = translateModifiers(xevent.xkey.state); - translateKey(view, &xevent, &event); - break; - case EnterNotify: - case LeaveNotify: - event.type = ((xevent.type == EnterNotify) - ? PUGL_POINTER_IN - : PUGL_POINTER_OUT); - event.crossing.time = (double)xevent.xcrossing.time / 1e3; - event.crossing.x = xevent.xcrossing.x; - event.crossing.y = xevent.xcrossing.y; - event.crossing.xRoot = xevent.xcrossing.x_root; - event.crossing.yRoot = xevent.xcrossing.y_root; - event.crossing.state = translateModifiers(xevent.xcrossing.state); - event.crossing.mode = PUGL_CROSSING_NORMAL; - if (xevent.xcrossing.mode == NotifyGrab) { - event.crossing.mode = PUGL_CROSSING_GRAB; - } else if (xevent.xcrossing.mode == NotifyUngrab) { - event.crossing.mode = PUGL_CROSSING_UNGRAB; - } - break; - - case FocusIn: - case FocusOut: - event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT; - event.focus.mode = PUGL_CROSSING_NORMAL; - if (xevent.xfocus.mode == NotifyGrab) { - event.focus.mode = PUGL_CROSSING_GRAB; - } else if (xevent.xfocus.mode == NotifyUngrab) { - event.focus.mode = PUGL_CROSSING_UNGRAB; - } - break; - - default: - break; - } - - return event; -} - -PuglStatus -puglGrabFocus(PuglView* view) -{ - XSetInputFocus( - view->impl->display, view->impl->win, RevertToNone, CurrentTime); - return PUGL_SUCCESS; -} - -bool -puglHasFocus(const PuglView* view) -{ - int revertTo = 0; - Window focusedWindow = 0; - XGetInputFocus(view->impl->display, &focusedWindow, &revertTo); - return focusedWindow == view->impl->win; -} - -PuglStatus -puglRequestAttention(PuglView* view) -{ - PuglInternals* const impl = view->impl; - const PuglX11Atoms* const atoms = &view->world->impl->atoms; - XEvent event = {0}; - - event.type = ClientMessage; - event.xclient.window = impl->win; - event.xclient.format = 32; - event.xclient.message_type = atoms->NET_WM_STATE; - event.xclient.data.l[0] = WM_STATE_ADD; - event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION; - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 1; - event.xclient.data.l[4] = 0; - - const Window root = RootWindow(impl->display, impl->screen); - XSendEvent(impl->display, - root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &event); - - return PUGL_SUCCESS; -} - -PuglStatus -puglStartTimer(PuglView* view, uintptr_t id, double timeout) -{ -#ifdef HAVE_XSYNC - if (view->world->impl->syncSupported) { - XSyncValue value; - XSyncIntToValue(&value, (int)floor(timeout * 1000.0)); - - PuglWorldInternals* w = view->world->impl; - Display* const display = w->display; - const XSyncCounter counter = w->serverTimeCounter; - const XSyncTestType type = XSyncPositiveTransition; - const XSyncTrigger trigger = {counter, XSyncRelative, value, type}; - XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive}; - const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr); - const PuglTimer timer = {alarm, view, id}; - - if (alarm != None) { - for (size_t i = 0; i < w->numTimers; ++i) { - if (w->timers[i].view == view && w->timers[i].id == id) { - // Replace existing timer - XSyncDestroyAlarm(w->display, w->timers[i].alarm); - w->timers[i] = timer; - return PUGL_SUCCESS; - } - } - - // Add new timer - const size_t size = ++w->numTimers * sizeof(timer); - w->timers = (PuglTimer*)realloc(w->timers, size); - w->timers[w->numTimers - 1] = timer; - return PUGL_SUCCESS; - } - } -#else - (void)view; - (void)id; - (void)timeout; -#endif - - return PUGL_FAILURE; -} - -PuglStatus -puglStopTimer(PuglView* view, uintptr_t id) -{ -#ifdef HAVE_XSYNC - PuglWorldInternals* w = view->world->impl; - - for (size_t i = 0; i < w->numTimers; ++i) { - if (w->timers[i].view == view && w->timers[i].id == id) { - XSyncDestroyAlarm(w->display, w->timers[i].alarm); - - if (i == w->numTimers - 1) { - memset(&w->timers[i], 0, sizeof(PuglTimer)); - } else { - memmove(w->timers + i, - w->timers + i + 1, - sizeof(PuglTimer) * (w->numTimers - i - 1)); - - memset(&w->timers[i], 0, sizeof(PuglTimer)); - } - - --w->numTimers; - return PUGL_SUCCESS; - } - } -#else - (void)view; - (void)id; -#endif - - return PUGL_FAILURE; -} - -static XEvent -puglEventToX(PuglView* view, const PuglEvent* event) -{ - XEvent xev = {0}; - xev.xany.send_event = True; - - switch (event->type) { - case PUGL_EXPOSE: { - const double x = floor(event->expose.x); - const double y = floor(event->expose.y); - const double w = ceil(event->expose.x + event->expose.width) - x; - const double h = ceil(event->expose.y + event->expose.height) - y; - - xev.xexpose.type = Expose; - xev.xexpose.serial = 0; - xev.xexpose.display = view->impl->display; - xev.xexpose.window = view->impl->win; - xev.xexpose.x = (int)x; - xev.xexpose.y = (int)y; - xev.xexpose.width = (int)w; - xev.xexpose.height = (int)h; - break; - } - - case PUGL_CLIENT: - xev.xclient.type = ClientMessage; - xev.xclient.serial = 0; - xev.xclient.send_event = True; - xev.xclient.display = view->impl->display; - xev.xclient.window = view->impl->win; - xev.xclient.message_type = view->world->impl->atoms.PUGL_CLIENT_MSG; - xev.xclient.format = 32; - xev.xclient.data.l[0] = (long)event->client.data1; - xev.xclient.data.l[1] = (long)event->client.data2; - break; - - default: - break; - } - - return xev; -} - -PuglStatus -puglSendEvent(PuglView* view, const PuglEvent* event) -{ - XEvent xev = puglEventToX(view, event); - - if (xev.type) { - if (XSendEvent(view->impl->display, view->impl->win, False, 0, &xev)) { - return PUGL_SUCCESS; - } else { - return PUGL_UNKNOWN_ERROR; - } - } - - return PUGL_UNSUPPORTED_TYPE; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglWaitForEvent(PuglView* view) -{ - XEvent xevent; - XPeekEvent(view->impl->display, &xevent); - return PUGL_SUCCESS; -} -#endif - -static void -mergeExposeEvents(PuglEvent* dst, const PuglEvent* src) -{ - if (!dst->type) { - *dst = *src; - } else { - const double max_x = MAX(dst->expose.x + dst->expose.width, - src->expose.x + src->expose.width); - const double max_y = MAX(dst->expose.y + dst->expose.height, - src->expose.y + src->expose.height); - - dst->expose.x = MIN(dst->expose.x, src->expose.x); - dst->expose.y = MIN(dst->expose.y, src->expose.y); - dst->expose.width = max_x - dst->expose.x; - dst->expose.height = max_y - dst->expose.y; - } -} - -static void -handleSelectionNotify(const PuglWorld* world, PuglView* view) -{ - uint8_t* str = NULL; - Atom type = 0; - int fmt = 0; - unsigned long len = 0; - unsigned long left = 0; - - XGetWindowProperty(world->impl->display, - view->impl->win, - XA_PRIMARY, - 0, - 0x1FFFFFFF, - False, - AnyPropertyType, - &type, - &fmt, - &len, - &left, - &str); - - if (str && fmt == 8 && type == world->impl->atoms.UTF8_STRING && - left == 0) { - puglSetBlob(&view->clipboard, str, len); - } - - XFree(str); -} - -static void -handleSelectionRequest(const PuglWorld* world, - PuglView* view, - const XSelectionRequestEvent* request) -{ - XSelectionEvent note = {SelectionNotify, - request->serial, - False, - world->impl->display, - request->requestor, - request->selection, - request->target, - None, - request->time}; - - const char* type = NULL; - size_t len = 0; - const void* data = puglGetInternalClipboard(view, &type, &len); - if (data && request->selection == world->impl->atoms.CLIPBOARD && - request->target == world->impl->atoms.UTF8_STRING) { - note.property = request->property; - XChangeProperty(world->impl->display, - note.requestor, - note.property, - note.target, - 8, - PropModeReplace, - (const uint8_t*)data, - (int)len); - } else { - note.property = None; - } - - XSendEvent(world->impl->display, note.requestor, True, 0, (XEvent*)¬e); -} - -/// Flush pending configure and expose events for all views -static void -flushExposures(PuglWorld* world) -{ - for (size_t i = 0; i < world->numViews; ++i) { - PuglView* const view = world->views[i]; - - if (view->visible) { - puglDispatchSimpleEvent(view, PUGL_UPDATE); - } - - const PuglEvent configure = view->impl->pendingConfigure; - const PuglEvent expose = view->impl->pendingExpose; - - view->impl->pendingConfigure.type = PUGL_NOTHING; - view->impl->pendingExpose.type = PUGL_NOTHING; - - if (configure.type || expose.type) { - view->backend->enter(view, expose.type ? &expose.expose : NULL); - puglDispatchEventInContext(view, &configure); - puglDispatchEventInContext(view, &expose); - view->backend->leave(view, expose.type ? &expose.expose : NULL); - } - } -} - -static bool -handleTimerEvent(PuglWorld* world, XEvent xevent) -{ -#ifdef HAVE_XSYNC - if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) { - XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent); - - for (size_t i = 0; i < world->impl->numTimers; ++i) { - if (world->impl->timers[i].alarm == notify->alarm) { - PuglEvent event = {{PUGL_TIMER, 0}}; - event.timer.id = world->impl->timers[i].id; - puglDispatchEvent(world->impl->timers[i].view, - (const PuglEvent*)&event); - } - } - - return true; - } -#else - (void)world; - (void)xevent; -#endif - - return false; -} - -static PuglStatus -puglDispatchX11Events(PuglWorld* world) -{ - const PuglX11Atoms* const atoms = &world->impl->atoms; - - // Flush output to the server once at the start - Display* display = world->impl->display; - XFlush(display); - - // Process all queued events (without further flushing) - while (XEventsQueued(display, QueuedAfterReading) > 0) { - XEvent xevent; - XNextEvent(display, &xevent); - - if (handleTimerEvent(world, xevent)) { - continue; - } - - PuglView* view = puglFindView(world, xevent.xany.window); - if (!view) { - continue; - } - - // Handle special events - PuglInternals* const impl = view->impl; - if (xevent.type == KeyRelease && view->hints[PUGL_IGNORE_KEY_REPEAT]) { - XEvent next; - if (XCheckTypedWindowEvent(display, impl->win, KeyPress, &next) && - next.type == KeyPress && - next.xkey.time == xevent.xkey.time && - next.xkey.keycode == xevent.xkey.keycode) { - continue; - } - } else if (xevent.type == FocusIn) { - XSetICFocus(impl->xic); - } else if (xevent.type == FocusOut) { - XUnsetICFocus(impl->xic); - } else if (xevent.type == SelectionClear) { - puglSetBlob(&view->clipboard, NULL, 0); - } else if (xevent.type == SelectionNotify && - xevent.xselection.selection == atoms->CLIPBOARD && - xevent.xselection.target == atoms->UTF8_STRING && - xevent.xselection.property == XA_PRIMARY) { - handleSelectionNotify(world, view); - } else if (xevent.type == SelectionRequest) { - handleSelectionRequest(world, view, &xevent.xselectionrequest); - } - - // Translate X11 event to Pugl event - const PuglEvent event = translateEvent(view, xevent); - - if (event.type == PUGL_EXPOSE) { - // Expand expose event to be dispatched after loop - mergeExposeEvents(&view->impl->pendingExpose, &event); - } else if (event.type == PUGL_CONFIGURE) { - // Expand configure event to be dispatched after loop - view->impl->pendingConfigure = event; - view->frame.x = event.configure.x; - view->frame.y = event.configure.y; - view->frame.width = event.configure.width; - view->frame.height = event.configure.height; - } else if (event.type == PUGL_MAP && view->parent) { - XWindowAttributes attrs; - XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); - - const PuglEventConfigure configure = { - PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height}; - - puglDispatchEvent(view, (const PuglEvent*)&configure); - puglDispatchEvent(view, &event); - } else { - // Dispatch event to application immediately - puglDispatchEvent(view, &event); - } - } - - return PUGL_SUCCESS; -} - -#ifndef PUGL_DISABLE_DEPRECATED -PuglStatus -puglProcessEvents(PuglView* view) -{ - return puglUpdate(view->world, 0.0); -} -#endif - -PuglStatus -puglUpdate(PuglWorld* world, double timeout) -{ - const double startTime = puglGetTime(world); - PuglStatus st = PUGL_SUCCESS; - - world->impl->dispatchingEvents = true; - - if (timeout < 0.0) { - st = puglPollX11Socket(world, timeout); - st = st ? st : puglDispatchX11Events(world); - } else if (timeout <= 0.001) { - st = puglDispatchX11Events(world); - } else { - const double endTime = startTime + timeout - 0.001; - for (double t = startTime; t < endTime; t = puglGetTime(world)) { - if ((st = puglPollX11Socket(world, endTime - t)) || - (st = puglDispatchX11Events(world))) { - break; - } - } - } - - flushExposures(world); - - world->impl->dispatchingEvents = false; - - return st; -} - -double -puglGetTime(const PuglWorld* world) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) - - world->startTime; -} - -PuglStatus -puglPostRedisplay(PuglView* view) -{ - const PuglRect rect = { 0, 0, view->frame.width, view->frame.height }; - - return puglPostRedisplayRect(view, rect); -} - -PuglStatus -puglPostRedisplayRect(PuglView* view, PuglRect rect) -{ - const PuglEventExpose event = { - PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height - }; - - if (view->world->impl->dispatchingEvents) { - // Currently dispatching events, add/expand expose for the loop end - mergeExposeEvents(&view->impl->pendingExpose, (const PuglEvent*)&event); - } else if (view->visible) { - // Not dispatching events, send an X expose so we wake up next time - return puglSendEvent(view, (const PuglEvent*)&event); - } - - return PUGL_SUCCESS; -} - -PuglNativeView -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeView)view->impl->win; -} - -PuglStatus -puglSetWindowTitle(PuglView* view, const char* title) -{ - Display* display = view->world->impl->display; - const PuglX11Atoms* const atoms = &view->world->impl->atoms; - - puglSetString(&view->title, title); - - if (view->impl->win) { - XStoreName(display, view->impl->win, title); - XChangeProperty(display, view->impl->win, atoms->NET_WM_NAME, - atoms->UTF8_STRING, 8, PropModeReplace, - (const uint8_t*)title, (int)strlen(title)); - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglSetFrame(PuglView* view, const PuglRect frame) -{ - if (view->impl->win) { - if (!XMoveResizeWindow(view->world->impl->display, - view->impl->win, - (int)frame.x, - (int)frame.y, - (unsigned)frame.width, - (unsigned)frame.height)) { - return PUGL_UNKNOWN_ERROR; - } - } - - view->frame = frame; - return PUGL_SUCCESS; -} - -PuglStatus -puglSetDefaultSize(PuglView* const view, const int width, const int height) -{ - view->defaultWidth = width; - view->defaultHeight = height; - return updateSizeHints(view); -} - -PuglStatus -puglSetMinSize(PuglView* const view, const int width, const int height) -{ - view->minWidth = width; - view->minHeight = height; - return updateSizeHints(view); -} - -PuglStatus -puglSetMaxSize(PuglView* const view, const int width, const int height) -{ - view->minWidth = width; - view->minHeight = height; - return updateSizeHints(view); -} - -PuglStatus -puglSetAspectRatio(PuglView* const view, - const int minX, - const int minY, - const int maxX, - const int maxY) -{ - view->minAspectX = minX; - view->minAspectY = minY; - view->maxAspectX = maxX; - view->maxAspectY = maxY; - - return updateSizeHints(view); -} - -PuglStatus -puglSetTransientFor(PuglView* view, PuglNativeView parent) -{ - Display* display = view->world->impl->display; - - view->transientParent = parent; - - if (view->impl->win) { - XSetTransientForHint(display, view->impl->win, - (Window)view->transientParent); - } - - return PUGL_SUCCESS; -} - -const void* -puglGetClipboard(PuglView* const view, - const char** const type, - size_t* const len) -{ - PuglInternals* const impl = view->impl; - const PuglX11Atoms* const atoms = &view->world->impl->atoms; - - const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD); - if (owner != None && owner != impl->win) { - // Clear internal selection - puglSetBlob(&view->clipboard, NULL, 0); - - // Request selection from the owner - XConvertSelection(impl->display, - atoms->CLIPBOARD, - atoms->UTF8_STRING, - XA_PRIMARY, - impl->win, - CurrentTime); - - // Run event loop until data is received - while (!view->clipboard.data) { - puglUpdate(view->world, -1.0); - } - } - - return puglGetInternalClipboard(view, type, len); -} - -PuglStatus -puglSetClipboard(PuglView* const view, - const char* const type, - const void* const data, - const size_t len) -{ - PuglInternals* const impl = view->impl; - const PuglX11Atoms* const atoms = &view->world->impl->atoms; - - PuglStatus st = puglSetInternalClipboard(view, type, data, len); - if (st) { - return st; - } - - XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime); - return PUGL_SUCCESS; -} - -#ifdef HAVE_XCURSOR -static const unsigned cursor_nums[] = { - XC_arrow, // ARROW - XC_xterm, // CARET - XC_crosshair, // CROSSHAIR - XC_hand2, // HAND - XC_pirate, // NO - XC_sb_h_double_arrow, // LEFT_RIGHT - XC_sb_v_double_arrow, // UP_DOWN -}; -#endif - -PuglStatus -puglSetCursor(PuglView* view, PuglCursor cursor) -{ -#ifdef HAVE_XCURSOR - PuglInternals* const impl = view->impl; - const unsigned index = (unsigned)cursor; - const unsigned count = sizeof(cursor_nums) / sizeof(cursor_nums[0]); - if (index >= count) { - return PUGL_BAD_PARAMETER; - } - - const unsigned shape = cursor_nums[index]; - if (!impl->win || impl->cursorShape == shape) { - return PUGL_SUCCESS; - } - - impl->cursorShape = cursor_nums[index]; - - return puglDefineCursorShape(view, impl->cursorShape); -#else - (void)view; - (void)cursor; - return PUGL_FAILURE; -#endif -} diff --git a/include/pugl/detail/x11.h b/include/pugl/detail/x11.h deleted file mode 100644 index 9788191..0000000 --- a/include/pugl/detail/x11.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file x11.h - @brief Shared definitions for X11 implementation. -*/ - -#ifndef PUGL_DETAIL_X11_H -#define PUGL_DETAIL_X11_H - -#include "pugl/detail/types.h" -#include "pugl/pugl.h" - -#include -#include -#include - -#include -#include -#include - -typedef struct { - Atom CLIPBOARD; - Atom UTF8_STRING; - Atom WM_PROTOCOLS; - Atom WM_DELETE_WINDOW; - Atom PUGL_CLIENT_MSG; - Atom NET_WM_NAME; - Atom NET_WM_STATE; - Atom NET_WM_STATE_DEMANDS_ATTENTION; -} PuglX11Atoms; - -typedef struct { - XID alarm; - PuglView* view; - uintptr_t id; -} PuglTimer; - -struct PuglWorldInternalsImpl { - Display* display; - PuglX11Atoms atoms; - XIM xim; - PuglTimer* timers; - size_t numTimers; - XID serverTimeCounter; - int syncEventBase; - bool syncSupported; - bool dispatchingEvents; -}; - -struct PuglInternalsImpl { - Display* display; - XVisualInfo* vi; - Window win; - XIC xic; - PuglSurface* surface; - PuglEvent pendingConfigure; - PuglEvent pendingExpose; - int screen; -#ifdef HAVE_XCURSOR - unsigned cursorShape; -#endif -}; - -PuglStatus puglX11StubConfigure(PuglView* view); - -#endif // PUGL_DETAIL_X11_H diff --git a/include/pugl/detail/x11_cairo.c b/include/pugl/detail/x11_cairo.c deleted file mode 100644 index 0112c4e..0000000 --- a/include/pugl/detail/x11_cairo.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file x11_cairo.c - @brief Cairo graphics backend for X11. -*/ - -#include "pugl/detail/types.h" -#include "pugl/detail/x11.h" -#include "pugl/pugl.h" -#include "pugl/pugl_cairo.h" - -#include -#include -#include - -#include - -typedef struct { - cairo_surface_t* back; - cairo_surface_t* front; - cairo_t* cr; -} PuglX11CairoSurface; - -static void -puglX11CairoClose(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - cairo_surface_destroy(surface->front); - cairo_surface_destroy(surface->back); - surface->front = surface->back = NULL; -} - -static PuglStatus -puglX11CairoOpen(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - surface->back = cairo_xlib_surface_create(impl->display, - impl->win, - impl->vi->visual, - (int)view->frame.width, - (int)view->frame.height); - - surface->front = cairo_surface_create_similar( - surface->back, - cairo_surface_get_content(surface->back), - (int)view->frame.width, - (int)view->frame.height); - - if (cairo_surface_status(surface->back) || - cairo_surface_status(surface->front)) { - puglX11CairoClose(view); - return PUGL_CREATE_CONTEXT_FAILED; - } - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11CairoCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - - impl->surface = (cairo_surface_t*)calloc(1, sizeof(PuglX11CairoSurface)); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11CairoDestroy(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - puglX11CairoClose(view); - free(surface); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11CairoEnter(PuglView* view, const PuglEventExpose* expose) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - PuglStatus st = PUGL_SUCCESS; - - if (expose && !(st = puglX11CairoOpen(view))) { - surface->cr = cairo_create(surface->front); - - if (cairo_status(surface->cr)) { - st = PUGL_CREATE_CONTEXT_FAILED; - } - } - - return st; -} - -static PuglStatus -puglX11CairoLeave(PuglView* view, const PuglEventExpose* expose) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - if (expose) { - // Destroy front context and create a new one for drawing to the back - cairo_destroy(surface->cr); - surface->cr = cairo_create(surface->back); - - // Clip to expose region - cairo_rectangle(surface->cr, - expose->x, - expose->y, - expose->width, - expose->height); - cairo_clip(surface->cr); - - // Paint front onto back - cairo_set_source_surface(surface->cr, surface->front, 0, 0); - cairo_paint(surface->cr); - - // Flush to X and close everything - cairo_destroy(surface->cr); - cairo_surface_flush(surface->back); - puglX11CairoClose(view); - surface->cr = NULL; - } - - return PUGL_SUCCESS; -} - -static void* -puglX11CairoGetContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - return surface->cr; -} - -const PuglBackend* -puglCairoBackend(void) -{ - static const PuglBackend backend = {puglX11StubConfigure, - puglX11CairoCreate, - puglX11CairoDestroy, - puglX11CairoEnter, - puglX11CairoLeave, - puglX11CairoGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/x11_gl.c b/include/pugl/detail/x11_gl.c deleted file mode 100644 index d89a350..0000000 --- a/include/pugl/detail/x11_gl.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file x11_gl.c - @brief OpenGL graphics backend for X11. -*/ - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/x11.h" -#include "pugl/pugl.h" -#include "pugl/pugl_gl.h" - -#include -#include -#include -#include - -#include -#include -#include - -typedef struct { - GLXFBConfig fb_config; - GLXContext ctx; -} PuglX11GlSurface; - -static int -puglX11GlHintValue(const int value) -{ - return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value; -} - -static int -puglX11GlGetAttrib(Display* const display, - GLXFBConfig fb_config, - const int attrib) -{ - int value = 0; - glXGetFBConfigAttrib(display, fb_config, attrib, &value); - return value; -} - -static PuglStatus -puglX11GlConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - const int screen = impl->screen; - Display* const display = impl->display; - - PuglX11GlSurface* const surface = - (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface)); - impl->surface = surface; - - const int attrs[] = { - GLX_X_RENDERABLE, True, - GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]), - GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]), - GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]), - GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]), - GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]), - GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]), - GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]), - GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), - None - }; - - int n_fbc = 0; - GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc); - if (n_fbc <= 0) { - return PUGL_CREATE_CONTEXT_FAILED; - } - - surface->fb_config = fbc[0]; - impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]); - - view->hints[PUGL_RED_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_RED_SIZE); - view->hints[PUGL_GREEN_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_GREEN_SIZE); - view->hints[PUGL_BLUE_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_BLUE_SIZE); - view->hints[PUGL_ALPHA_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_ALPHA_SIZE); - view->hints[PUGL_DEPTH_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_DEPTH_SIZE); - view->hints[PUGL_STENCIL_BITS] = puglX11GlGetAttrib( - display, fbc[0], GLX_STENCIL_SIZE); - view->hints[PUGL_SAMPLES] = puglX11GlGetAttrib( - display, fbc[0], GLX_SAMPLES); - view->hints[PUGL_DOUBLE_BUFFER] = puglX11GlGetAttrib( - display, fbc[0], GLX_DOUBLEBUFFER); - - char msg[256]; - - snprintf( - msg, - sizeof(msg), - "Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d DOUBLE=%d SAMPLES=%d\n", - impl->vi->visualid, - puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER), - puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES)); - - view->world->logFunc(view->world, PUGL_LOG_LEVEL_INFO, msg); - - XFree(fbc); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose)) -{ - PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; - glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx); - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11GlLeave(PuglView* view, const PuglEventExpose* expose) -{ - if (expose && view->hints[PUGL_DOUBLE_BUFFER]) { - glXSwapBuffers(view->impl->display, view->impl->win); - } - - glXMakeCurrent(view->impl->display, None, NULL); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11GlCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface; - Display* const display = impl->display; - GLXFBConfig fb_config = surface->fb_config; - - const int ctx_attrs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MAJOR], - GLX_CONTEXT_MINOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MINOR], - GLX_CONTEXT_FLAGS_ARB, (view->hints[PUGL_USE_DEBUG_CONTEXT] - ? GLX_CONTEXT_DEBUG_BIT_ARB - : 0), - GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints[PUGL_USE_COMPAT_PROFILE] - ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB - : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), - 0}; - - PFNGLXCREATECONTEXTATTRIBSARBPROC create_context = - (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress( - (const uint8_t*)"glXCreateContextAttribsARB"); - - PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = - (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress( - (const uint8_t*)"glXSwapIntervalEXT"); - - surface->ctx = create_context(display, fb_config, 0, True, ctx_attrs); - if (!surface->ctx) { - surface->ctx = - glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); - } - - if (!surface->ctx) { - return PUGL_CREATE_CONTEXT_FAILED; - } - - const int swapInterval = view->hints[PUGL_SWAP_INTERVAL]; - if (glXSwapIntervalEXT && swapInterval != PUGL_DONT_CARE) { - puglX11GlEnter(view, NULL); - glXSwapIntervalEXT(display, impl->win, swapInterval); - puglX11GlLeave(view, NULL); - } - - glXGetConfig(impl->display, - impl->vi, - GLX_DOUBLEBUFFER, - &view->hints[PUGL_DOUBLE_BUFFER]); - - glXQueryDrawable(display, - impl->win, - GLX_SWAP_INTERVAL_EXT, - (unsigned int*)&view->hints[PUGL_SWAP_INTERVAL]); - - return PUGL_SUCCESS; -} - -static PuglStatus -puglX11GlDestroy(PuglView* view) -{ - PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; - if (surface) { - glXDestroyContext(view->impl->display, surface->ctx); - free(surface); - view->impl->surface = NULL; - } - return PUGL_SUCCESS; -} - -PuglGlFunc -puglGetProcAddress(const char* name) -{ - return glXGetProcAddress((const uint8_t*)name); -} - -const PuglBackend* -puglGlBackend(void) -{ - static const PuglBackend backend = {puglX11GlConfigure, - puglX11GlCreate, - puglX11GlDestroy, - puglX11GlEnter, - puglX11GlLeave, - puglStubGetContext}; - - return &backend; -} diff --git a/include/pugl/detail/x11_stub.c b/include/pugl/detail/x11_stub.c deleted file mode 100644 index 093066b..0000000 --- a/include/pugl/detail/x11_stub.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "pugl/pugl_stub.h" - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/x11.h" -#include "pugl/pugl.h" - -#include - -PuglStatus -puglX11StubConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - XVisualInfo pat = {0}; - int n = 0; - - pat.screen = impl->screen; - impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); - - view->hints[PUGL_RED_BITS] = impl->vi->bits_per_rgb; - view->hints[PUGL_GREEN_BITS] = impl->vi->bits_per_rgb; - view->hints[PUGL_BLUE_BITS] = impl->vi->bits_per_rgb; - view->hints[PUGL_ALPHA_BITS] = 0; - - return PUGL_SUCCESS; -} - -const PuglBackend* -puglStubBackend(void) -{ - static const PuglBackend backend = { - puglX11StubConfigure, - puglStubCreate, - puglStubDestroy, - puglStubEnter, - puglStubLeave, - puglStubGetContext, - }; - - return &backend; -} diff --git a/include/pugl/detail/x11_vulkan.c b/include/pugl/detail/x11_vulkan.c deleted file mode 100644 index 0bd1532..0000000 --- a/include/pugl/detail/x11_vulkan.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright 2012-2020 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file x11_vulkan.c Vulkan graphics backend for X11. -*/ - -#define VK_NO_PROTOTYPES 1 - -#include "pugl/detail/stub.h" -#include "pugl/detail/types.h" -#include "pugl/detail/x11.h" -#include "pugl/pugl.h" -#include "pugl/pugl_vulkan.h" - -#include -#include - -#include - -#include -#include - -struct PuglVulkanLoaderImpl -{ - void* libvulkan; - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; -}; - -PuglVulkanLoader* -puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) -{ - PuglVulkanLoader* loader = - (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); - if (!loader) { - return NULL; - } - - if (!(loader->libvulkan = dlopen("libvulkan.so", RTLD_LAZY))) { - free(loader); - return NULL; - } - - loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym( - loader->libvulkan, "vkGetInstanceProcAddr"); - - loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)dlsym( - loader->libvulkan, "vkGetDeviceProcAddr"); - - return loader; -} - -void -puglFreeVulkanLoader(PuglVulkanLoader* loader) -{ - if (loader) { - dlclose(loader->libvulkan); - free(loader); - } -} - -PFN_vkGetInstanceProcAddr -puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetInstanceProcAddr; -} - -PFN_vkGetDeviceProcAddr -puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) -{ - return loader->vkGetDeviceProcAddr; -} - -const PuglBackend* -puglVulkanBackend(void) -{ - static const PuglBackend backend = {puglX11StubConfigure, - puglStubCreate, - puglStubDestroy, - puglStubEnter, - puglStubLeave, - puglStubGetContext}; - - return &backend; -} - -const char* const* -puglGetInstanceExtensions(uint32_t* const count) -{ - static const char* const extensions[] = {"VK_KHR_surface", - "VK_KHR_xlib_surface"}; - - *count = 2; - return extensions; -} - -VkResult -puglCreateSurface(const PuglVulkanLoader* const loader, - PuglView* const view, - VkInstance instance, - const VkAllocationCallbacks* const allocator, - VkSurfaceKHR* const surface) -{ - PuglInternals* const impl = view->impl; - PuglWorldInternals* world_impl = view->world->impl; - - PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = - (PFN_vkCreateXlibSurfaceKHR)puglGetInstanceProcAddrFunc(loader)( - instance, "vkCreateXlibSurfaceKHR"); - - const VkXlibSurfaceCreateInfoKHR info = { - VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - NULL, - 0, - world_impl->display, - impl->win, - }; - - return vkCreateXlibSurfaceKHR(instance, &info, allocator, surface); -} diff --git a/src/.clang-tidy b/src/.clang-tidy new file mode 100644 index 0000000..7bc36f7 --- /dev/null +++ b/src/.clang-tidy @@ -0,0 +1,12 @@ +Checks: > + *, + -*-uppercase-literal-suffix, + -*magic-numbers, + -bugprone-suspicious-string-compare, + -cert-flp30-c, + -clang-analyzer-security.FloatLoopCounter, + -hicpp-multiway-paths-covered, + -hicpp-signed-bitwise, + -readability-else-after-return, +FormatStyle: file +HeaderFilterRegex: 'pugl/.*' diff --git a/src/implementation.c b/src/implementation.c new file mode 100644 index 0000000..74c6aea --- /dev/null +++ b/src/implementation.c @@ -0,0 +1,489 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file implementation.c + @brief Platform-independent implementation. +*/ + +#include "implementation.h" + +#include "pugl/pugl.h" + +#include +#include +#include +#include +#include + +static const char* +puglLogLevelPrefix(const PuglLogLevel level) +{ + switch (level) { + case PUGL_LOG_LEVEL_ERR: + return "error: "; + case PUGL_LOG_LEVEL_WARNING: + return "warning: "; + case PUGL_LOG_LEVEL_INFO: + case PUGL_LOG_LEVEL_DEBUG: + return ""; + } + + return ""; +} + +static void +puglDefaultLogFunc(PuglWorld* PUGL_UNUSED(world), + PuglLogLevel level, + const char* msg) +{ + fprintf(stderr, "%s%s", puglLogLevelPrefix(level), msg); +} + +const char* +puglStrerror(const PuglStatus status) +{ + // clang-format off + switch (status) { + case PUGL_SUCCESS: return "Success"; + case PUGL_FAILURE: return "Non-fatal failure"; + case PUGL_UNKNOWN_ERROR: return "Unknown system error"; + case PUGL_BAD_BACKEND: return "Invalid or missing backend"; + case PUGL_BAD_CONFIGURATION: return "Invalid view configuration"; + case PUGL_BAD_PARAMETER: return "Invalid parameter"; + case PUGL_BACKEND_FAILED: return "Backend initialisation failed"; + case PUGL_REGISTRATION_FAILED: return "Class registration failed"; + case PUGL_REALIZE_FAILED: return "View creation failed"; + case PUGL_SET_FORMAT_FAILED: return "Failed to set pixel format"; + case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context"; + case PUGL_UNSUPPORTED_TYPE: return "Unsupported data type"; + } + // clang-format on + + return "Unknown error"; +} + +void +puglSetString(char** dest, const char* string) +{ + if (*dest != string) { + const size_t len = strlen(string); + + *dest = (char*)realloc(*dest, len + 1); + strncpy(*dest, string, len + 1); + } +} + +void +puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) +{ + if (data) { + dest->len = len; + dest->data = realloc(dest->data, len + 1); + memcpy(dest->data, data, len); + ((char*)dest->data)[len] = 0; + } else { + dest->len = 0; + dest->data = NULL; + } +} + +static void +puglSetDefaultHints(PuglHints hints) +{ + hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE; + hints[PUGL_CONTEXT_VERSION_MAJOR] = 2; + hints[PUGL_CONTEXT_VERSION_MINOR] = 0; + hints[PUGL_RED_BITS] = 8; + hints[PUGL_GREEN_BITS] = 8; + hints[PUGL_BLUE_BITS] = 8; + hints[PUGL_ALPHA_BITS] = 8; + hints[PUGL_DEPTH_BITS] = 0; + hints[PUGL_STENCIL_BITS] = 0; + hints[PUGL_SAMPLES] = 0; + hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE; + hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE; + hints[PUGL_RESIZABLE] = PUGL_FALSE; + hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE; + hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE; +} + +PuglWorld* +puglNewWorld(PuglWorldType type, PuglWorldFlags flags) +{ + PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld)); + if (!world || !(world->impl = puglInitWorldInternals(type, flags))) { + free(world); + return NULL; + } + + world->startTime = puglGetTime(world); + world->logFunc = puglDefaultLogFunc; + world->logLevel = PUGL_LOG_LEVEL_INFO; + + puglSetString(&world->className, "Pugl"); + + return world; +} + +void +puglFreeWorld(PuglWorld* const world) +{ + puglFreeWorldInternals(world); + free(world->className); + free(world->views); + free(world); +} + +void +puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle) +{ + world->handle = handle; +} + +PuglWorldHandle +puglGetWorldHandle(PuglWorld* world) +{ + return world->handle; +} + +PuglStatus +puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc) +{ + world->logFunc = logFunc; + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetLogLevel(PuglWorld* world, PuglLogLevel level) +{ + world->logLevel = level; + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetClassName(PuglWorld* const world, const char* const name) +{ + puglSetString(&world->className, name); + return PUGL_SUCCESS; +} + +PuglView* +puglNewView(PuglWorld* const world) +{ + PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); + if (!view || !(view->impl = puglInitViewInternals())) { + free(view); + return NULL; + } + + view->world = world; + view->minWidth = 1; + view->minHeight = 1; + + puglSetDefaultHints(view->hints); + + // Add to world view list + ++world->numViews; + world->views = (PuglView**)realloc(world->views, + world->numViews * sizeof(PuglView*)); + + world->views[world->numViews - 1] = view; + + return view; +} + +void +puglFreeView(PuglView* view) +{ + puglDispatchSimpleEvent(view, PUGL_DESTROY); + + // Remove from world view list + PuglWorld* world = view->world; + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i] == view) { + if (i == world->numViews - 1) { + world->views[i] = NULL; + } else { + memmove(world->views + i, world->views + i + 1, + sizeof(PuglView*) * (world->numViews - i - 1)); + world->views[world->numViews - 1] = NULL; + } + --world->numViews; + } + } + + free(view->title); + free(view->clipboard.data); + puglFreeViewInternals(view); + free(view); +} + +PuglWorld* +puglGetWorld(PuglView* view) +{ + return view->world; +} + +PuglStatus +puglSetViewHint(PuglView* view, PuglViewHint hint, int value) +{ + if (value == PUGL_DONT_CARE) { + switch (hint) { + case PUGL_USE_COMPAT_PROFILE: + case PUGL_USE_DEBUG_CONTEXT: + case PUGL_CONTEXT_VERSION_MAJOR: + case PUGL_CONTEXT_VERSION_MINOR: + case PUGL_SWAP_INTERVAL: + return PUGL_BAD_PARAMETER; + default: + break; + } + } + + if (hint < PUGL_NUM_VIEW_HINTS) { + view->hints[hint] = value; + return PUGL_SUCCESS; + } + + return PUGL_BAD_PARAMETER; +} + +int +puglGetViewHint(const PuglView* view, PuglViewHint hint) +{ + if (hint < PUGL_NUM_VIEW_HINTS) { + return view->hints[hint]; + } + + return PUGL_DONT_CARE; +} + +PuglStatus +puglSetParentWindow(PuglView* view, PuglNativeView parent) +{ + view->parent = parent; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetBackend(PuglView* view, const PuglBackend* backend) +{ + view->backend = backend; + return PUGL_SUCCESS; +} + +void +puglSetHandle(PuglView* view, PuglHandle handle) +{ + view->handle = handle; +} + +PuglHandle +puglGetHandle(PuglView* view) +{ + return view->handle; +} + +bool +puglGetVisible(const PuglView* view) +{ + return view->visible; +} + +PuglRect +puglGetFrame(const PuglView* view) +{ + return view->frame; +} + +void* +puglGetContext(PuglView* view) +{ + return view->backend->getContext(view); +} + +#ifndef PUGL_DISABLE_DEPRECATED + +PuglStatus +puglPollEvents(PuglWorld* world, double timeout) +{ + return puglUpdate(world, timeout); +} + +PuglStatus +puglDispatchEvents(PuglWorld* world) +{ + return puglUpdate(world, 0.0); +} + +#endif + +PuglStatus +puglEnterContext(PuglView* view) +{ + return view->backend->enter(view, NULL); +} + +PuglStatus +puglLeaveContext(PuglView* view) +{ + return view->backend->leave(view, NULL); +} + +PuglStatus +puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc) +{ + view->eventFunc = eventFunc; + return PUGL_SUCCESS; +} + +/// Return the code point for buf, or the replacement character on error +uint32_t +puglDecodeUTF8(const uint8_t* buf) +{ +#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0) + + // http://en.wikipedia.org/wiki/UTF-8 + + if (buf[0] < 0x80) { + return buf[0]; + } else if (buf[0] < 0xC2) { + return 0xFFFD; + } else if (buf[0] < 0xE0) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u; + } else if (buf[0] < 0xF0) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); + FAIL_IF((buf[2] & 0xC0u) != 0x80); + return ((uint32_t)buf[0] << 12u) + // + ((uint32_t)buf[1] << 6u) + // + ((uint32_t)buf[2] - 0xE2080u); + } else if (buf[0] < 0xF5) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); + FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); + FAIL_IF((buf[2] & 0xC0u) != 0x80u); + FAIL_IF((buf[3] & 0xC0u) != 0x80u); + return (((uint32_t)buf[0] << 18u) + // + ((uint32_t)buf[1] << 12u) + // + ((uint32_t)buf[2] << 6u) + // + ((uint32_t)buf[3] - 0x3C82080u)); + } + return 0xFFFD; +} + +static inline bool +puglMustConfigure(PuglView* view, const PuglEventConfigure* configure) +{ + return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure)); +} + +void +puglDispatchSimpleEvent(PuglView* view, const PuglEventType type) +{ + assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP || + type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE || + type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE); + + const PuglEvent event = {{type, 0}}; + puglDispatchEvent(view, &event); +} + +void +puglDispatchEventInContext(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CONFIGURE) { + view->frame.x = event->configure.x; + view->frame.y = event->configure.y; + view->frame.width = event->configure.width; + view->frame.height = event->configure.height; + + if (puglMustConfigure(view, &event->configure)) { + view->eventFunc(view, event); + view->lastConfigure = event->configure; + } + } else if (event->type == PUGL_EXPOSE) { + if (event->expose.width > 0 && event->expose.height > 0) { + view->eventFunc(view, event); + } + } else { + view->eventFunc(view, event); + } +} + +void +puglDispatchEvent(PuglView* view, const PuglEvent* event) +{ + switch (event->type) { + case PUGL_NOTHING: + break; + case PUGL_CREATE: + case PUGL_DESTROY: + view->backend->enter(view, NULL); + view->eventFunc(view, event); + view->backend->leave(view, NULL); + break; + case PUGL_CONFIGURE: + if (puglMustConfigure(view, &event->configure)) { + view->backend->enter(view, NULL); + puglDispatchEventInContext(view, event); + view->backend->leave(view, NULL); + } + break; + case PUGL_EXPOSE: + view->backend->enter(view, &event->expose); + puglDispatchEventInContext(view, event); + view->backend->leave(view, &event->expose); + break; + default: + view->eventFunc(view, event); + } +} + +const void* +puglGetInternalClipboard(const PuglView* const view, + const char** const type, + size_t* const len) +{ + if (len) { + *len = view->clipboard.len; + } + + if (type) { + *type = "text/plain"; + } + + return view->clipboard.data; +} + +PuglStatus +puglSetInternalClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + if (type && strcmp(type, "text/plain")) { + return PUGL_UNSUPPORTED_TYPE; + } + + puglSetBlob(&view->clipboard, data, len); + return PUGL_SUCCESS; +} + diff --git a/src/implementation.h b/src/implementation.h new file mode 100644 index 0000000..3a8b6ca --- /dev/null +++ b/src/implementation.h @@ -0,0 +1,87 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file implementation.h + @brief Shared declarations for implementation. +*/ + +#ifndef PUGL_DETAIL_IMPLEMENTATION_H +#define PUGL_DETAIL_IMPLEMENTATION_H + +#include "types.h" + +#include "pugl/pugl.h" + +#include +#include + +PUGL_BEGIN_DECLS + +/// Set `blob` to `data` with length `len`, reallocating if necessary +void +puglSetBlob(PuglBlob* dest, const void* data, size_t len); + +/// Reallocate and set `*dest` to `string` +void +puglSetString(char** dest, const char* string); + +/// Allocate and initialise world internals (implemented once per platform) +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags); + +/// Destroy and free world internals (implemented once per platform) +void +puglFreeWorldInternals(PuglWorld* world); + +/// Allocate and initialise view internals (implemented once per platform) +PuglInternals* +puglInitViewInternals(void); + +/// Destroy and free view internals (implemented once per platform) +void +puglFreeViewInternals(PuglView* view); + +/// Return the Unicode code point for `buf` or the replacement character +uint32_t +puglDecodeUTF8(const uint8_t* buf); + +/// Dispatch an event with a simple `type` to `view` +void +puglDispatchSimpleEvent(PuglView* view, PuglEventType type); + +/// Dispatch `event` to `view` while already in the graphics context +void +puglDispatchEventInContext(PuglView* view, const PuglEvent* event); + +/// Dispatch `event` to `view`, entering graphics context if necessary +void +puglDispatchEvent(PuglView* view, const PuglEvent* event); + +/// Set internal (stored in view) clipboard contents +const void* +puglGetInternalClipboard(const PuglView* view, const char** type, size_t* len); + +/// Set internal (stored in view) clipboard contents +PuglStatus +puglSetInternalClipboard(PuglView* view, + const char* type, + const void* data, + size_t len); + +PUGL_END_DECLS + +#endif // PUGL_DETAIL_IMPLEMENTATION_H diff --git a/src/mac.h b/src/mac.h new file mode 100644 index 0000000..d31eb91 --- /dev/null +++ b/src/mac.h @@ -0,0 +1,60 @@ +/* + Copyright 2012-2020 David Robillard + Copyright 2017 Hanspeter Portner + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac.h + @brief Shared definitions for MacOS implementation. +*/ + +#ifndef PUGL_DETAIL_MAC_H +#define PUGL_DETAIL_MAC_H + +#include "pugl/pugl.h" + +#import + +#include + +@interface PuglWrapperView : NSView + +- (void)dispatchExpose:(NSRect)rect; +- (void)setReshaped; + +@end + +@interface PuglWindow : NSWindow + +- (void)setPuglview:(PuglView*)view; + +@end + +struct PuglWorldInternalsImpl { + NSApplication* app; + NSAutoreleasePool* autoreleasePool; +}; + +struct PuglInternalsImpl { + NSApplication* app; + PuglWrapperView* wrapperView; + NSView* drawView; + NSCursor* cursor; + PuglWindow* window; + uint32_t mods; + bool mouseTracked; +}; + +#endif // PUGL_DETAIL_MAC_H diff --git a/src/mac.m b/src/mac.m new file mode 100644 index 0000000..3d1fba2 --- /dev/null +++ b/src/mac.m @@ -0,0 +1,1450 @@ +/* + Copyright 2012-2020 David Robillard + Copyright 2017 Hanspeter Portner + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac.m + @brief MacOS implementation. +*/ + +#define GL_SILENCE_DEPRECATION 1 + +#include "mac.h" + +#include "implementation.h" + +#include "pugl/pugl.h" + +#import + +#include + +#include + +#ifndef __MAC_10_10 +typedef NSUInteger NSEventModifierFlags; +#endif + +#ifndef __MAC_10_12 +typedef NSUInteger NSWindowStyleMask; +#endif + +static NSRect +rectToScreen(NSScreen* screen, NSRect rect) +{ + const double screenHeight = [screen frame].size.height; + + rect.origin.y = screenHeight - rect.origin.y - rect.size.height; + return rect; +} + +static NSScreen* +viewScreen(PuglView* view) +{ + return view->impl->window ? [view->impl->window screen] : [NSScreen mainScreen]; +} + +static NSRect +nsRectToPoints(PuglView* view, const NSRect rect) +{ + const double scaleFactor = [viewScreen(view) backingScaleFactor]; + + return NSMakeRect(rect.origin.x / scaleFactor, + rect.origin.y / scaleFactor, + rect.size.width / scaleFactor, + rect.size.height / scaleFactor); +} + +static NSRect +nsRectFromPoints(PuglView* view, const NSRect rect) +{ + const double scaleFactor = [viewScreen(view) backingScaleFactor]; + + return NSMakeRect(rect.origin.x * scaleFactor, + rect.origin.y * scaleFactor, + rect.size.width * scaleFactor, + rect.size.height * scaleFactor); +} + +static NSPoint +nsPointFromPoints(PuglView* view, const NSPoint point) +{ + const double scaleFactor = [viewScreen(view) backingScaleFactor]; + + return NSMakePoint(point.x * scaleFactor, point.y * scaleFactor); +} + +static NSRect +rectToNsRect(const PuglRect rect) +{ + return NSMakeRect(rect.x, rect.y, rect.width, rect.height); +} + +static NSSize +sizePoints(PuglView* view, const double width, const double height) +{ + const double scaleFactor = [viewScreen(view) backingScaleFactor]; + + return NSMakeSize(width / scaleFactor, height / scaleFactor); +} + +static void +updateViewRect(PuglView* view) +{ + NSWindow* const window = view->impl->window; + if (window) { + const NSRect screenFramePt = [[NSScreen mainScreen] frame]; + const NSRect screenFramePx = nsRectFromPoints(view, screenFramePt); + const NSRect framePt = [window frame]; + const NSRect contentPt = [window contentRectForFrameRect:framePt]; + const NSRect contentPx = nsRectFromPoints(view, contentPt); + const double screenHeight = screenFramePx.size.height; + + view->frame.x = contentPx.origin.x; + view->frame.y = screenHeight - contentPx.origin.y - contentPx.size.height; + view->frame.width = contentPx.size.width; + view->frame.height = contentPx.size.height; + } +} + +@implementation PuglWindow +{ +@public + PuglView* puglview; +} + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)aStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + (void)flag; + + NSWindow* result = [super initWithContentRect:contentRect + styleMask:aStyle + backing:bufferingType + defer:NO]; + + [result setAcceptsMouseMovedEvents:YES]; + return (PuglWindow*)result; +} + +- (void)setPuglview:(PuglView*)view +{ + puglview = view; + + [self + setContentSize:sizePoints(view, view->frame.width, view->frame.height)]; +} + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +- (void)setIsVisible:(BOOL)flag +{ + if (flag && !puglview->visible) { + const PuglEventConfigure ev = { + PUGL_CONFIGURE, + 0, + puglview->frame.x, + puglview->frame.y, + puglview->frame.width, + puglview->frame.height, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + puglDispatchSimpleEvent(puglview, PUGL_MAP); + } else if (!flag && puglview->visible) { + puglDispatchSimpleEvent(puglview, PUGL_UNMAP); + } + + puglview->visible = flag; + + [super setIsVisible:flag]; +} + +@end + +@implementation PuglWrapperView +{ +@public + PuglView* puglview; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; + NSMutableDictionary* userTimers; + bool reshaped; +} + +- (void)dispatchExpose:(NSRect)rect +{ + const double scaleFactor = [[NSScreen mainScreen] backingScaleFactor]; + + if (reshaped) { + updateViewRect(puglview); + + const PuglEventConfigure ev = { + PUGL_CONFIGURE, + 0, + puglview->frame.x, + puglview->frame.y, + puglview->frame.width, + puglview->frame.height, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + reshaped = false; + } + + if (![[puglview->impl->drawView window] isVisible]) { + return; + } + + const PuglEventExpose ev = { + PUGL_EXPOSE, + 0, + rect.origin.x * scaleFactor, + rect.origin.y * scaleFactor, + rect.size.width * scaleFactor, + rect.size.height * scaleFactor, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (NSSize)intrinsicContentSize +{ + if (puglview->defaultWidth || puglview->defaultHeight) { + return sizePoints(puglview, + puglview->defaultWidth, + puglview->defaultHeight); + } + + return NSMakeSize(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric); +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)setReshaped +{ + reshaped = true; +} + +static uint32_t +getModifiers(const NSEvent* const ev) +{ + const NSEventModifierFlags modifierFlags = [ev modifierFlags]; + + return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) | + ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) | + ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) | + ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0)); +} + +static PuglKey +keySymToSpecial(const NSEvent* const ev) +{ + NSString* chars = [ev charactersIgnoringModifiers]; + if ([chars length] == 1) { + switch ([chars characterAtIndex:0]) { + case NSF1FunctionKey: return PUGL_KEY_F1; + case NSF2FunctionKey: return PUGL_KEY_F2; + case NSF3FunctionKey: return PUGL_KEY_F3; + case NSF4FunctionKey: return PUGL_KEY_F4; + case NSF5FunctionKey: return PUGL_KEY_F5; + case NSF6FunctionKey: return PUGL_KEY_F6; + case NSF7FunctionKey: return PUGL_KEY_F7; + case NSF8FunctionKey: return PUGL_KEY_F8; + case NSF9FunctionKey: return PUGL_KEY_F9; + case NSF10FunctionKey: return PUGL_KEY_F10; + case NSF11FunctionKey: return PUGL_KEY_F11; + case NSF12FunctionKey: return PUGL_KEY_F12; + case NSDeleteCharacter: return PUGL_KEY_BACKSPACE; + case NSDeleteFunctionKey: return PUGL_KEY_DELETE; + case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT; + case NSUpArrowFunctionKey: return PUGL_KEY_UP; + case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT; + case NSDownArrowFunctionKey: return PUGL_KEY_DOWN; + case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP; + case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN; + case NSHomeFunctionKey: return PUGL_KEY_HOME; + case NSEndFunctionKey: return PUGL_KEY_END; + case NSInsertFunctionKey: return PUGL_KEY_INSERT; + case NSMenuFunctionKey: return PUGL_KEY_MENU; + case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK; + case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK; + case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN; + case NSPauseFunctionKey: return PUGL_KEY_PAUSE; + } + // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged] + } + return (PuglKey)0; +} + +- (void)updateTrackingAreas +{ + if (trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const int opts = (NSTrackingMouseEnteredAndExited | + NSTrackingMouseMoved | + NSTrackingActiveAlways); + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (NSPoint)eventLocation:(NSEvent*)event +{ + return nsPointFromPoints(puglview, + [self convertPoint:[event locationInWindow] + fromView:nil]); +} + +static void +handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) +{ + const NSPoint wloc = [view eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventCrossing ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + PUGL_CROSSING_NORMAL, + }; + + puglDispatchEvent(view->puglview, (const PuglEvent*)&ev); +} + +- (void)mouseEntered:(NSEvent*)event +{ + handleCrossing(self, event, PUGL_POINTER_IN); + [puglview->impl->cursor set]; + puglview->impl->mouseTracked = true; +} + +- (void)mouseExited:(NSEvent*)event +{ + [[NSCursor arrowCursor] set]; + handleCrossing(self, event, PUGL_POINTER_OUT); + puglview->impl->mouseTracked = false; +} + +- (void)cursorUpdate:(NSEvent*)event +{ + (void)event; + [puglview->impl->cursor set]; +} + +- (void)mouseMoved:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventMotion ev = { + PUGL_MOTION, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void)mouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void)rightMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void)otherMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void)mouseDown:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void)mouseUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void)rightMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void)rightMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void)otherMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void)otherMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void)scrollWheel:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const double dx = [event scrollingDeltaX]; + const double dy = [event scrollingDeltaY]; + const PuglScrollDirection dir = + ((dx == 0.0 && dy > 0.0) + ? PUGL_SCROLL_UP + : ((dx == 0.0 && dy < 0.0) + ? PUGL_SCROLL_DOWN + : ((dy == 0.0 && dx > 0.0) + ? PUGL_SCROLL_RIGHT + : ((dy == 0.0 && dx < 0.0) ? PUGL_SCROLL_LEFT + : PUGL_SCROLL_SMOOTH)))); + + const PuglEventScroll ev = { + PUGL_SCROLL, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event hasPreciseScrollingDeltas] ? PUGL_SCROLL_SMOOTH : dir, + dx, + dy, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void)keyDown:(NSEvent*)event +{ + if (puglview->hints[PUGL_IGNORE_KEY_REPEAT] && [event isARepeat]) { + return; + } + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + + if (!spec) { + [self interpretKeyEvents:@[event]]; + } +} + +- (void)keyUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (BOOL)hasMarkedText +{ + return [markedText length] > 0; +} + +- (NSRange)markedRange +{ + return (([markedText length] > 0) + ? NSMakeRange(0, [markedText length] - 1) + : NSMakeRange(NSNotFound, 0)); +} + +- (NSRange)selectedRange +{ + return NSMakeRange(NSNotFound, 0); +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selected + replacementRange:(NSRange)replacement +{ + (void)selected; + (void)replacement; + [markedText release]; + markedText = ( + [(NSObject*)string isKindOfClass:[NSAttributedString class]] + ? [[NSMutableAttributedString alloc] initWithAttributedString:string] + : [[NSMutableAttributedString alloc] initWithString:string]); +} + +- (void)unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*)validAttributesForMarkedText +{ + return @[]; +} + +- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range + actualRange: + (NSRangePointer)actual +{ + (void)range; + (void)actual; + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + (void)point; + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actual +{ + (void)range; + (void)actual; + + const NSRect frame = [self bounds]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void)doCommandBySelector:(SEL)selector +{ + (void)selector; +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacement +{ + (void)replacement; + + NSEvent* const event = [NSApp currentEvent]; + NSString* const characters = + ([(NSObject*)string isKindOfClass:[NSAttributedString class]] + ? [(NSAttributedString*)string string] + : (NSString*)string); + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + for (size_t i = 0; i < [characters length]; ++i) { + const uint32_t code = [characters characterAtIndex:i]; + char utf8[8] = {0}; + NSUInteger len = 0; + + [characters getBytes:utf8 + maxLength:sizeof(utf8) + usedLength:&len + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(i, i + 1) + remainingRange:nil]; + + PuglEventText ev = { + PUGL_TEXT, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + code, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + + memcpy(ev.string, utf8, len); + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } +} + +- (void)flagsChanged:(NSEvent*)event +{ + const uint32_t mods = getModifiers(event); + PuglEventType type = PUGL_NOTHING; + PuglKey special = (PuglKey)0; + + if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) { + type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SHIFT; + } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) { + type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_CTRL; + } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) { + type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_ALT; + } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) { + type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SUPER; + } + + if (special != 0) { + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + PuglEventKey ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + mods, + [event keyCode], + special + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } + + puglview->impl->mods = mods; +} + +- (BOOL)preservesContentInLiveResize +{ + return NO; +} + +- (void)viewWillStartLiveResize +{ + puglDispatchSimpleEvent(puglview, PUGL_LOOP_ENTER); +} + +- (void)viewWillDraw +{ + puglDispatchSimpleEvent(puglview, PUGL_UPDATE); + [super viewWillDraw]; +} + +- (void)resizeTick +{ + puglPostRedisplay(puglview); +} + +- (void)timerTick:(NSTimer*)userTimer +{ + const NSNumber* userInfo = userTimer.userInfo; + const PuglEventTimer ev = {PUGL_TIMER, 0, userInfo.unsignedLongValue}; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void)viewDidEndLiveResize +{ + puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE); +} + +@end + +@interface PuglWindowDelegate : NSObject + +- (instancetype)initWithPuglWindow:(PuglWindow*)window; + +@end + +@implementation PuglWindowDelegate +{ + PuglWindow* window; +} + +- (instancetype)initWithPuglWindow:(PuglWindow*)puglWindow +{ + if ((self = [super init])) { + window = puglWindow; + } + + return self; +} + +- (BOOL)windowShouldClose:(id)sender +{ + (void)sender; + + puglDispatchSimpleEvent(window->puglview, PUGL_CLOSE); + return YES; +} + +- (void)windowDidMove:(NSNotification*)notification +{ + (void)notification; + + updateViewRect(window->puglview); +} + +- (void)windowDidBecomeKey:(NSNotification*)notification +{ + (void)notification; + + PuglEvent ev = {{PUGL_FOCUS_IN, 0}}; + ev.focus.mode = PUGL_CROSSING_NORMAL; + puglDispatchEvent(window->puglview, &ev); +} + +- (void)windowDidResignKey:(NSNotification*)notification +{ + (void)notification; + + PuglEvent ev = {{PUGL_FOCUS_OUT, 0}}; + ev.focus.mode = PUGL_CROSSING_NORMAL; + puglDispatchEvent(window->puglview, &ev); +} + +@end + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags)) +{ + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + + impl->app = [NSApplication sharedApplication]; + + if (type == PUGL_PROGRAM) { + impl->autoreleasePool = [NSAutoreleasePool new]; + } + + return impl; +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + if (world->impl->autoreleasePool) { + [world->impl->autoreleasePool drain]; + } + + free(world->impl); +} + +void* +puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) +{ + return NULL; +} + +PuglInternals* +puglInitViewInternals(void) +{ + PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); + + impl->cursor = [NSCursor arrowCursor]; + + return impl; +} + +static NSLayoutConstraint* +puglConstraint(id item, NSLayoutAttribute attribute, float constant) +{ + return [NSLayoutConstraint + constraintWithItem: item + attribute: attribute + relatedBy: NSLayoutRelationGreaterThanOrEqual + toItem: nil + attribute: NSLayoutAttributeNotAnAttribute + multiplier: 1.0 + constant: (CGFloat)constant]; +} + +PuglStatus +puglRealize(PuglView* view) +{ + PuglInternals* impl = view->impl; + if (impl->wrapperView) { + return PUGL_FAILURE; + } + + const NSScreen* const screen = [NSScreen mainScreen]; + const double scaleFactor = [screen backingScaleFactor]; + + // Getting depth from the display mode seems tedious, just set usual values + if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_RED_BITS] = 8; + } + if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_BLUE_BITS] = 8; + } + if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_GREEN_BITS] = 8; + } + if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_ALPHA_BITS] = 8; + } + + CGDirectDisplayID displayId = CGMainDisplayID(); + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId); + + // Try to get refresh rate from mode (usually fails) + view->hints[PUGL_REFRESH_RATE] = (int)CGDisplayModeGetRefreshRate(mode); + + CGDisplayModeRelease(mode); + if (view->hints[PUGL_REFRESH_RATE] == 0) { + // Get refresh rate from a display link + // TODO: Keep and actually use the display link for something? + CVDisplayLinkRef link; + CVDisplayLinkCreateWithCGDisplay(displayId, &link); + + const CVTime p = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); + const double r = p.timeScale / (double)p.timeValue; + view->hints[PUGL_REFRESH_RATE] = (int)lrint(r); + + CVDisplayLinkRelease(link); + } + + if (view->frame.width == 0.0 && view->frame.height == 0.0) { + if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { + return PUGL_BAD_CONFIGURATION; + } + + const double screenWidthPx = [screen frame].size.width * scaleFactor; + const double screenHeightPx = [screen frame].size.height * scaleFactor; + + view->frame.width = view->defaultWidth; + view->frame.height = view->defaultHeight; + view->frame.x = screenWidthPx / 2.0 - view->frame.width / 2.0; + view->frame.y = screenHeightPx / 2.0 - view->frame.height / 2.0; + } + + const NSRect framePx = rectToNsRect(view->frame); + const NSRect framePt = NSMakeRect(framePx.origin.x / scaleFactor, + framePx.origin.y / scaleFactor, + framePx.size.width / scaleFactor, + framePx.size.height / scaleFactor); + + // Create wrapper view to handle input + impl->wrapperView = [PuglWrapperView alloc]; + impl->wrapperView->puglview = view; + impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init]; + impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init]; + [impl->wrapperView setAutoresizesSubviews:YES]; + [impl->wrapperView initWithFrame:framePt]; + [impl->wrapperView addConstraint: + puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->minWidth)]; + [impl->wrapperView addConstraint: + puglConstraint(impl->wrapperView, NSLayoutAttributeHeight, view->minHeight)]; + + // Create draw view to be rendered to + PuglStatus st = PUGL_SUCCESS; + if ((st = view->backend->configure(view)) || + (st = view->backend->create(view))) { + return st; + } + + // Add draw view to wrapper view + [impl->wrapperView addSubview:impl->drawView]; + [impl->wrapperView setHidden:NO]; + [impl->drawView setHidden:NO]; + + if (view->parent) { + NSView* pview = (NSView*)view->parent; + [pview addSubview:impl->wrapperView]; + [impl->drawView setHidden:NO]; + [[impl->drawView window] makeFirstResponder:impl->wrapperView]; + } else { + unsigned style = (NSClosableWindowMask | + NSTitledWindowMask | + NSMiniaturizableWindowMask ); + if (view->hints[PUGL_RESIZABLE]) { + style |= NSResizableWindowMask; + } + + PuglWindow* window = [[[PuglWindow alloc] + initWithContentRect:rectToScreen([NSScreen mainScreen], framePt) + styleMask:style + backing:NSBackingStoreBuffered + defer:NO + ] retain]; + [window setPuglview:view]; + + if (view->title) { + NSString* titleString = [[NSString alloc] + initWithBytes:view->title + length:strlen(view->title) + encoding:NSUTF8StringEncoding]; + + [window setTitle:titleString]; + } + + if (view->minWidth || view->minHeight) { + [window setContentMinSize:sizePoints(view, + view->minWidth, + view->minHeight)]; + } + impl->window = window; + + ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc] + initWithPuglWindow:window]; + + if (view->minAspectX && view->minAspectY) { + [window setContentAspectRatio:sizePoints(view, + view->minAspectX, + view->minAspectY)]; + } + + puglSetFrame(view, view->frame); + + [window setContentView:impl->wrapperView]; + [view->world->impl->app activateIgnoringOtherApps:YES]; + [window makeFirstResponder:impl->wrapperView]; + [window makeKeyAndOrderFront:window]; + [impl->window setIsVisible:NO]; + } + + [impl->wrapperView updateTrackingAreas]; + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + if (![view->impl->window isVisible]) { + [view->impl->window setIsVisible:YES]; + [view->impl->drawView setNeedsDisplay: YES]; + updateViewRect(view); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + [view->impl->window setIsVisible:NO]; + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view) { + if (view->backend) { + view->backend->destroy(view); + } + + if (view->impl) { + [view->impl->wrapperView removeFromSuperview]; + view->impl->wrapperView->puglview = NULL; + if (view->impl->window) { + [view->impl->window close]; + } + [view->impl->wrapperView release]; + if (view->impl->window) { + [view->impl->window release]; + } + free(view->impl); + } + } +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + NSWindow* window = [view->impl->wrapperView window]; + + [window makeKeyWindow]; + [window makeFirstResponder:view->impl->wrapperView]; + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + PuglInternals* const impl = view->impl; + + return ([[impl->wrapperView window] isKeyWindow] && + [[impl->wrapperView window] firstResponder] == impl->wrapperView); +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + if (![view->impl->window isKeyWindow]) { + [view->world->impl->app requestUserAttention:NSInformationalRequest]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + puglStopTimer(view, id); + + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + + NSTimer* timer = [NSTimer timerWithTimeInterval:timeout + target:view->impl->wrapperView + selector:@selector(timerTick:) + userInfo:idNumber + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; + + view->impl->wrapperView->userTimers[idNumber] = timer; + + return PUGL_SUCCESS; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + NSTimer* timer = view->impl->wrapperView->userTimers[idNumber]; + + if (timer) { + [view->impl->wrapperView->userTimers removeObjectForKey:timer]; + [timer invalidate]; + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; +} + +PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CLIENT) { + PuglWrapperView* wrapper = view->impl->wrapperView; + const NSWindow* window = [wrapper window]; + const NSRect rect = [wrapper frame]; + const NSPoint center = {NSMidX(rect), NSMidY(rect)}; + + NSEvent* nsevent = [NSEvent + otherEventWithType:NSApplicationDefined + location:center + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:window.windowNumber + context:nil + subtype:PUGL_CLIENT + data1:(NSInteger)event->client.data1 + data2:(NSInteger)event->client.data2]; + + [view->world->impl->app postEvent:nsevent atStart:false]; + return PUGL_SUCCESS; + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* view) +{ + return puglPollEvents(view->world, -1.0); +} +#endif + +static void +dispatchClientEvent(PuglWorld* world, NSEvent* ev) +{ + NSWindow* win = [ev window]; + NSPoint loc = [ev locationInWindow]; + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* view = world->views[i]; + PuglWrapperView* wrapper = view->impl->wrapperView; + if ([wrapper window] == win && NSPointInRect(loc, [wrapper frame])) { + const PuglEventClient event = {PUGL_CLIENT, + 0, + (uintptr_t)[ev data1], + (uintptr_t)[ev data2]}; + + puglDispatchEvent(view, (const PuglEvent*)&event); + } + } +} + +PuglStatus +puglUpdate(PuglWorld* world, const double timeout) +{ + NSDate* date = ((timeout < 0) + ? [NSDate distantFuture] + : [NSDate dateWithTimeIntervalSinceNow:timeout]); + + for (NSEvent* ev = NULL; + (ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]);) { + + if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) { + dispatchClientEvent(world, ev); + } + + [world->impl->app sendEvent: ev]; + + if (timeout < 0) { + // Now that we've waited and got an event, set the date to now to + // avoid looping forever + date = [NSDate date]; + } + } + + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* const view = world->views[i]; + + if ([[view->impl->drawView window] isVisible]) { + puglDispatchSimpleEvent(view, PUGL_UPDATE); + } + + [view->impl->drawView displayIfNeeded]; + } + + return PUGL_SUCCESS; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglDispatchEvents(view->world); +} +#endif + +double +puglGetTime(const PuglWorld* world) +{ + return (mach_absolute_time() / 1e9) - world->startTime; +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + [view->impl->drawView setNeedsDisplay: YES]; + return PUGL_SUCCESS; +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, const PuglRect rect) +{ + const NSRect rectPx = rectToNsRect(rect); + + [view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)]; + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->wrapperView; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + puglSetString(&view->title, title); + + if (view->impl->window) { + NSString* titleString = [[NSString alloc] + initWithBytes:title + length:strlen(title) + encoding:NSUTF8StringEncoding]; + + [view->impl->window setTitle:titleString]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + PuglInternals* const impl = view->impl; + + // Update view frame to exactly the requested frame in Pugl coordinates + view->frame = frame; + + const NSRect framePx = rectToNsRect(frame); + const NSRect framePt = nsRectToPoints(view, framePx); + if (impl->window) { + // Resize window to fit new content rect + const NSRect screenPt = rectToScreen(viewScreen(view), framePt); + const NSRect winFrame = [impl->window frameRectForContentRect:screenPt]; + + [impl->window setFrame:winFrame display:NO]; + } + + // Resize views + const NSRect sizePx = NSMakeRect(0, 0, frame.width, frame.height); + const NSRect sizePt = [impl->drawView convertRectFromBacking:sizePx]; + + [impl->wrapperView setFrame:(impl->window ? sizePt : framePt)]; + [impl->drawView setFrame:sizePt]; + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetDefaultSize(PuglView* const view, const int width, const int height) +{ + view->defaultWidth = width; + view->defaultHeight = height; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetMinSize(PuglView* const view, const int width, const int height) +{ + view->minWidth = width; + view->minHeight = height; + + if (view->impl->window && (view->minWidth || view->minHeight)) { + [view->impl->window setContentMinSize:sizePoints(view, + view->minWidth, + view->minHeight)]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetMaxSize(PuglView* const view, const int width, const int height) +{ + view->maxWidth = width; + view->maxHeight = height; + + if (view->impl->window && (view->maxWidth || view->maxHeight)) { + [view->impl->window setContentMaxSize:sizePoints(view, + view->maxWidth, + view->maxHeight)]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetAspectRatio(PuglView* const view, + const int minX, + const int minY, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + + if (view->impl->window && view->minAspectX && view->minAspectY) { + [view->impl->window setContentAspectRatio:sizePoints(view, + view->minAspectX, + view->minAspectY)]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetTransientFor(PuglView* view, PuglNativeView parent) +{ + view->transientParent = parent; + + if (view->impl->window) { + NSWindow* parentWindow = [(NSView*)parent window]; + if (parentWindow) { + [parentWindow addChildWindow:view->impl->window + ordered:NSWindowAbove]; + return PUGL_SUCCESS; + } + } + + return PUGL_FAILURE; +} + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + + if ([[pasteboard types] containsObject:NSStringPboardType]) { + const NSString* str = [pasteboard stringForType:NSStringPboardType]; + const char* utf8 = [str UTF8String]; + + puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1); + } + + return puglGetInternalClipboard(view, type, len); +} + +static NSCursor* +puglGetNsCursor(const PuglCursor cursor) +{ + switch (cursor) { + case PUGL_CURSOR_ARROW: + return [NSCursor arrowCursor]; + case PUGL_CURSOR_CARET: + return [NSCursor IBeamCursor]; + case PUGL_CURSOR_CROSSHAIR: + return [NSCursor crosshairCursor]; + case PUGL_CURSOR_HAND: + return [NSCursor pointingHandCursor]; + case PUGL_CURSOR_NO: + return [NSCursor operationNotAllowedCursor]; + case PUGL_CURSOR_LEFT_RIGHT: + return [NSCursor resizeLeftRightCursor]; + case PUGL_CURSOR_UP_DOWN: + return [NSCursor resizeUpDownCursor]; + } + + return NULL; +} + +PuglStatus +puglSetCursor(PuglView* view, PuglCursor cursor) +{ + PuglInternals* const impl = view->impl; + NSCursor* const cur = puglGetNsCursor(cursor); + if (!cur) { + return PUGL_FAILURE; + } + + impl->cursor = cur; + + if (impl->mouseTracked) { + [cur set]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + const char* const str = (const char*)data; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + NSString* nsString = [NSString stringWithUTF8String:str]; + if (nsString) { + [pasteboard + declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] + owner:nil]; + + [pasteboard setString:nsString forType:NSStringPboardType]; + + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; +} diff --git a/src/mac_cairo.m b/src/mac_cairo.m new file mode 100644 index 0000000..dd8fd36 --- /dev/null +++ b/src/mac_cairo.m @@ -0,0 +1,166 @@ +/* + Copyright 2019-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac_cairo.m + @brief Cairo graphics backend for MacOS. +*/ + +#include "implementation.h" +#include "mac.h" +#include "stub.h" + +#include "pugl/pugl_cairo.h" + +#include + +#import + +#include + +@interface PuglCairoView : NSView +@end + +@implementation PuglCairoView +{ +@public + PuglView* puglview; + cairo_surface_t* surface; + cairo_t* cr; +} + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + + return self; +} + +- (void)resizeWithOldSuperviewSize:(NSSize)oldSize +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super resizeWithOldSuperviewSize:oldSize]; + [wrapper setReshaped]; +} + +- (void)drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacCairoCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglCairoView* drawView = [PuglCairoView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:[impl->wrapperView bounds]]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoDestroy(PuglView* view) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + if (!expose) { + return PUGL_SUCCESS; + } + + assert(!drawView->surface); + assert(!drawView->cr); + + const double scale = 1.0 / [[NSScreen mainScreen] backingScaleFactor]; + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + const CGSize sizePx = {view->frame.width, view->frame.height}; + const CGSize sizePt = CGContextConvertSizeToUserSpace(context, sizePx); + + // Convert coordinates to standard Cairo space + CGContextTranslateCTM(context, 0.0, -sizePt.height); + CGContextScaleCTM(context, scale, -scale); + + drawView->surface = cairo_quartz_surface_create_for_cg_context( + context, (unsigned)sizePx.width, (unsigned)sizePx.height); + + drawView->cr = cairo_create(drawView->surface); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + if (!expose) { + return PUGL_SUCCESS; + } + + assert(drawView->surface); + assert(drawView->cr); + + CGContextRef context = cairo_quartz_surface_get_cg_context(drawView->surface); + + cairo_destroy(drawView->cr); + cairo_surface_destroy(drawView->surface); + drawView->cr = NULL; + drawView->surface = NULL; + + CGContextFlush(context); + + return PUGL_SUCCESS; +} + +static void* +puglMacCairoGetContext(PuglView* view) +{ + return ((PuglCairoView*)view->impl->drawView)->cr; +} + +const PuglBackend* +puglCairoBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacCairoCreate, + puglMacCairoDestroy, + puglMacCairoEnter, + puglMacCairoLeave, + puglMacCairoGetContext}; + + return &backend; +} diff --git a/src/mac_gl.m b/src/mac_gl.m new file mode 100644 index 0000000..038b942 --- /dev/null +++ b/src/mac_gl.m @@ -0,0 +1,203 @@ +/* + Copyright 2019-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac_gl.m + @brief OpenGL graphics backend for MacOS. +*/ + +#include "implementation.h" +#include "mac.h" +#include "stub.h" + +#include "pugl/pugl_gl.h" + +#ifndef __MAC_10_10 +# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core +#endif + +@interface PuglOpenGLView : NSOpenGLView +@end + +@implementation PuglOpenGLView +{ +@public + PuglView* puglview; +} + +- (id)initWithFrame:(NSRect)frame +{ + const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE]; + const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES]; + const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR]; + const unsigned profile = ((compat || major < 3) + ? NSOpenGLProfileVersionLegacy + : (major >= 4 + ? NSOpenGLProfileVersion4_1Core + : NSOpenGLProfileVersion3_2Core)); + + // Set attributes to default if they are unset + // (There is no GLX_DONT_CARE equivalent on MacOS) + if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { + puglview->hints[PUGL_DEPTH_BITS] = 0; + } + if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { + puglview->hints[PUGL_STENCIL_BITS] = 0; + } + if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { + puglview->hints[PUGL_SAMPLES] = 1; + } + if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { + puglview->hints[PUGL_DOUBLE_BUFFER] = 1; + } + if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { + puglview->hints[PUGL_SWAP_INTERVAL] = 1; + } + + const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] + + puglview->hints[PUGL_BLUE_BITS] + + puglview->hints[PUGL_GREEN_BITS] + + puglview->hints[PUGL_ALPHA_BITS]); + + NSOpenGLPixelFormatAttribute pixelAttribs[17] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAccelerated, + NSOpenGLPFAOpenGLProfile, profile, + NSOpenGLPFAColorSize, colorSize, + NSOpenGLPFADepthSize, (unsigned)puglview->hints[PUGL_DEPTH_BITS], + NSOpenGLPFAStencilSize, (unsigned)puglview->hints[PUGL_STENCIL_BITS], + NSOpenGLPFAMultisample, samples ? 1 : 0, + NSOpenGLPFASampleBuffers, samples ? 1 : 0, + NSOpenGLPFASamples, samples, + 0}; + + NSOpenGLPixelFormat* pixelFormat = [ + [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs]; + + if (pixelFormat) { + self = [super initWithFrame:frame pixelFormat:pixelFormat]; + [pixelFormat release]; + } else { + self = [super initWithFrame:frame]; + } + + [self setWantsBestResolutionOpenGLSurface:YES]; + + if (self) { + [[self openGLContext] makeCurrentContext]; + [self reshape]; + } + return self; +} + +- (void)reshape +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super reshape]; + [wrapper setReshaped]; +} + +- (void)drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacGlCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglOpenGLView* drawView = [PuglOpenGLView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:[impl->wrapperView bounds]]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlDestroy(PuglView* view) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose)) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + [[drawView openGLContext] makeCurrentContext]; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + if (expose) { + [[drawView openGLContext] flushBuffer]; + } + + [NSOpenGLContext clearCurrentContext]; + + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char *name) +{ + CFBundleRef framework = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + + CFStringRef symbol = CFStringCreateWithCString( + kCFAllocatorDefault, name, kCFStringEncodingASCII); + + PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName( + framework, symbol); + + CFRelease(symbol); + + return func; +} + +const PuglBackend* +puglGlBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacGlCreate, + puglMacGlDestroy, + puglMacGlEnter, + puglMacGlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/src/mac_stub.m b/src/mac_stub.m new file mode 100644 index 0000000..b4d9058 --- /dev/null +++ b/src/mac_stub.m @@ -0,0 +1,97 @@ +/* + Copyright 2019-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac_stub.m + @brief Stub graphics backend for MacOS. +*/ + +#include "implementation.h" +#include "mac.h" +#include "stub.h" + +#include "pugl/pugl_stub.h" + +#import + +@interface PuglStubView : NSView +@end + +@implementation PuglStubView +{ +@public + PuglView* puglview; +} + +- (void)resizeWithOldSuperviewSize:(NSSize)oldSize +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super resizeWithOldSuperviewSize:oldSize]; + [wrapper setReshaped]; +} + +- (void)drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacStubCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglStubView* drawView = [PuglStubView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacStubDestroy(PuglView* view) +{ + PuglStubView* const drawView = (PuglStubView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacStubCreate, + puglMacStubDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/src/mac_vulkan.m b/src/mac_vulkan.m new file mode 100644 index 0000000..90d349b --- /dev/null +++ b/src/mac_vulkan.m @@ -0,0 +1,217 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac_vulkan.m Vulkan graphics backend for MacOS. +*/ + +#define VK_NO_PROTOTYPES 1 + +#include "implementation.h" +#include "mac.h" +#include "stub.h" +#include "types.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_stub.h" +#include "pugl/pugl_vulkan.h" + +#include +#include + +#import +#import + +#include + +#include +#include + +@interface PuglVulkanView : NSView + +@end + +@implementation PuglVulkanView +{ +@public + PuglView* puglview; +} + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + self.wantsLayer = YES; + self.layerContentsRedrawPolicy = + NSViewLayerContentsRedrawOnSetNeedsDisplay; + } + + return self; +} + +- (CALayer*)makeBackingLayer +{ + CAMetalLayer* layer = [CAMetalLayer layer]; + [layer setDelegate:self]; + return layer; +} + +- (void)setFrameSize:(NSSize)newSize +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super setFrameSize:newSize]; + [wrapper setReshaped]; + + self.layer.frame = self.bounds; +} + +- (void)displayLayer:(CALayer*)layer +{ + (void)layer; + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + [wrapper dispatchExpose:[self bounds]]; +} + +@end + +static PuglStatus +puglMacVulkanCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglVulkanView* drawView = [PuglVulkanView alloc]; + const NSRect rect = NSMakeRect(0, 0, view->frame.width, view->frame.height); + + drawView->puglview = view; + [drawView initWithFrame:rect]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacVulkanDestroy(PuglView* view) +{ + PuglVulkanView* const drawView = (PuglVulkanView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +struct PuglVulkanLoaderImpl { + void* libvulkan; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; +}; + +PuglVulkanLoader* +puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) +{ + PuglVulkanLoader* loader = (PuglVulkanLoader*) + calloc(1, sizeof(PuglVulkanLoader)); + if (!loader) { + return NULL; + } + + if (!(loader->libvulkan = dlopen("libvulkan.dylib", RTLD_LAZY))) { + free(loader); + return NULL; + } + + loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) + dlsym(loader->libvulkan, "vkGetInstanceProcAddr"); + + loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr) + dlsym(loader->libvulkan, "vkGetDeviceProcAddr"); + + return loader; +} + +void +puglFreeVulkanLoader(PuglVulkanLoader* loader) +{ + if (loader) { + dlclose(loader->libvulkan); + free(loader); + } +} + +PFN_vkGetInstanceProcAddr +puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetInstanceProcAddr; +} + +PFN_vkGetDeviceProcAddr +puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetDeviceProcAddr; +} + +const PuglBackend* +puglVulkanBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacVulkanCreate, + puglMacVulkanDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext}; + + return &backend; +} + +const char* const* +puglGetInstanceExtensions(uint32_t* const count) +{ + static const char* const extensions[] = {"VK_KHR_surface", + "VK_MVK_macos_surface"}; + + *count = 2; + return extensions; +} + +VkResult +puglCreateSurface(const PuglVulkanLoader* const loader, + PuglView* const view, + VkInstance instance, + const VkAllocationCallbacks* const allocator, + VkSurfaceKHR* const surface) +{ + PuglInternals* const impl = view->impl; + + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = + (PFN_vkCreateMacOSSurfaceMVK)puglGetInstanceProcAddrFunc( + loader)(instance, "vkCreateMacOSSurfaceMVK"); + + const VkMacOSSurfaceCreateInfoMVK info = { + VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, + NULL, + 0, + impl->drawView, + }; + + return vkCreateMacOSSurfaceMVK(instance, &info, allocator, surface); +} diff --git a/src/stub.h b/src/stub.h new file mode 100644 index 0000000..acd3181 --- /dev/null +++ b/src/stub.h @@ -0,0 +1,75 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file stub.h + @brief Definition of generic stub backend functions. +*/ + +#ifndef PUGL_DETAIL_STUB_H +#define PUGL_DETAIL_STUB_H + +#include "pugl/pugl.h" + +PUGL_BEGIN_DECLS + +static inline PuglStatus +puglStubConfigure(PuglView* view) +{ + (void)view; + return PUGL_SUCCESS; +} + +static inline PuglStatus +puglStubCreate(PuglView* view) +{ + (void)view; + return PUGL_SUCCESS; +} + +static inline PuglStatus +puglStubDestroy(PuglView* view) +{ + (void)view; + return PUGL_SUCCESS; +} + +static inline PuglStatus +puglStubEnter(PuglView* view, const PuglEventExpose* expose) +{ + (void)view; + (void)expose; + return PUGL_SUCCESS; +} + +static inline PuglStatus +puglStubLeave(PuglView* view, const PuglEventExpose* expose) +{ + (void)view; + (void)expose; + return PUGL_SUCCESS; +} + +static inline void* +puglStubGetContext(PuglView* view) +{ + (void)view; + return NULL; +} + +PUGL_END_DECLS + +#endif // PUGL_DETAIL_STUB_H diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..e18eb87 --- /dev/null +++ b/src/types.h @@ -0,0 +1,118 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file types.h + @brief Shared internal type definitions. +*/ + +#ifndef PUGL_DETAIL_TYPES_H +#define PUGL_DETAIL_TYPES_H + +#include "pugl/pugl.h" + +#include +#include +#include + +// Unused parameter macro to suppresses warnings and make it impossible to use +#if defined(__cplusplus) +# define PUGL_UNUSED(name) +#elif defined(__GNUC__) || defined(__clang__) +# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__)) +#else +# define PUGL_UNUSED(name) name +#endif + +/// Platform-specific world internals +typedef struct PuglWorldInternalsImpl PuglWorldInternals; + +/// Platform-specific view internals +typedef struct PuglInternalsImpl PuglInternals; + +/// View hints +typedef int PuglHints[PUGL_NUM_VIEW_HINTS]; + +/// Blob of arbitrary data +typedef struct { + void* data; ///< Dynamically allocated data + size_t len; ///< Length of data in bytes +} PuglBlob; + +/// Cross-platform view definition +struct PuglViewImpl { + PuglWorld* world; + const PuglBackend* backend; + PuglInternals* impl; + PuglHandle handle; + PuglEventFunc eventFunc; + char* title; + PuglBlob clipboard; + PuglNativeView parent; + uintptr_t transientParent; + PuglRect frame; + PuglEventConfigure lastConfigure; + PuglHints hints; + int defaultWidth; + int defaultHeight; + int minWidth; + int minHeight; + int maxWidth; + int maxHeight; + int minAspectX; + int minAspectY; + int maxAspectX; + int maxAspectY; + bool visible; +}; + +/// Cross-platform world definition +struct PuglWorldImpl { + PuglWorldInternals* impl; + PuglWorldHandle handle; + PuglLogFunc logFunc; + char* className; + double startTime; + size_t numViews; + PuglView** views; + PuglLogLevel logLevel; +}; + +/// Opaque surface used by graphics backend +typedef void PuglSurface; + +/// Graphics backend interface +struct PuglBackendImpl { + /// Get visual information from display and setup view as necessary + PuglStatus (*configure)(PuglView*); + + /// Create surface and drawing context + PuglStatus (*create)(PuglView*); + + /// Destroy surface and drawing context + PuglStatus (*destroy)(PuglView*); + + /// Enter drawing context, for drawing if expose is non-null + PuglStatus (*enter)(PuglView*, const PuglEventExpose*); + + /// Leave drawing context, after drawing if expose is non-null + PuglStatus (*leave)(PuglView*, const PuglEventExpose*); + + /// Return the puglGetContext() handle for the application, if any + void* (*getContext)(PuglView*); +}; + +#endif // PUGL_DETAIL_TYPES_H diff --git a/src/win.c b/src/win.c new file mode 100644 index 0000000..0578749 --- /dev/null +++ b/src/win.c @@ -0,0 +1,1140 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.c + @brief Windows implementation. +*/ + +#include "win.h" + +#include "implementation.h" +#include "stub.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_stub.h" + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef WM_MOUSEWHEEL +# define WM_MOUSEWHEEL 0x020A +#endif +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef WHEEL_DELTA +# define WHEEL_DELTA 120 +#endif +#ifndef GWLP_USERDATA +# define GWLP_USERDATA (-21) +#endif + +#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) +#define PUGL_LOCAL_MARK_MSG (WM_USER + 51) +#define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52) +#define PUGL_USER_TIMER_MIN 9470 + +typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +static wchar_t* +puglUtf8ToWideChar(const char* const utf8) +{ + const int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (len > 0) { + wchar_t* result = (wchar_t*)calloc((size_t)len, sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, len); + return result; + } + + return NULL; +} + +static char* +puglWideCharToUtf8(const wchar_t* const wstr, size_t* len) +{ + int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (n > 0) { + char* result = (char*)calloc((size_t)n, sizeof(char)); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, n, NULL, NULL); + *len = (size_t)n; + return result; + } + + return NULL; +} + +static bool +puglRegisterWindowClass(const char* name) +{ + WNDCLASSEX wc = { 0 }; + if (GetClassInfoEx(GetModuleHandle(NULL), name, &wc)) { + return true; // Already registered + } + + wc.cbSize = sizeof(wc); + wc.style = CS_OWNDC; + wc.lpfnWndProc = wndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wc.lpszClassName = name; + + return RegisterClassEx(&wc); +} + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type), + PuglWorldFlags PUGL_UNUSED(flags)) +{ + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + if (!impl) { + return NULL; + } + + HMODULE user32 = LoadLibrary("user32.dll"); + if (user32) { + PFN_SetProcessDPIAware SetProcessDPIAware = + (PFN_SetProcessDPIAware)GetProcAddress( + user32, "SetProcessDPIAware"); + if (SetProcessDPIAware) { + SetProcessDPIAware(); + } + + FreeLibrary(user32); + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + impl->timerFrequency = (double)frequency.QuadPart; + + return impl; +} + +void* +puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) +{ + return GetModuleHandle(NULL); +} + +PuglInternals* +puglInitViewInternals(void) +{ + return (PuglInternals*)calloc(1, sizeof(PuglInternals)); +} + +static PuglStatus +puglPollWinEvents(PuglWorld* world, const double timeout) +{ + (void)world; + + if (timeout < 0) { + WaitMessage(); + } else { + MsgWaitForMultipleObjects( + 0, NULL, FALSE, (DWORD)(timeout * 1e3), QS_ALLEVENTS); + } + return PUGL_SUCCESS; +} + +PuglStatus +puglRealize(PuglView* view) +{ + PuglInternals* impl = view->impl; + if (impl->hwnd) { + return PUGL_FAILURE; + } + + // Getting depth from the display mode seems tedious, just set usual values + if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_RED_BITS] = 8; + } + if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_BLUE_BITS] = 8; + } + if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_GREEN_BITS] = 8; + } + if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_ALPHA_BITS] = 8; + } + + // Get refresh rate for resize draw timer + DEVMODEA devMode = {0}; + EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode); + view->hints[PUGL_REFRESH_RATE] = (int)devMode.dmDisplayFrequency; + + // Register window class if necessary + if (!puglRegisterWindowClass(view->world->className)) { + return PUGL_REGISTRATION_FAILED; + } + + if (!view->backend || !view->backend->configure) { + return PUGL_BAD_BACKEND; + } + + PuglStatus st = PUGL_SUCCESS; + if ((st = view->backend->configure(view)) || + (st = view->backend->create(view))) { + return st; + } + + if (view->title) { + puglSetWindowTitle(view, view->title); + } + + view->impl->cursor = LoadCursor(NULL, IDC_ARROW); + + puglSetFrame(view, view->frame); + SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_SHOWNORMAL); + SetFocus(impl->hwnd); + return PUGL_SUCCESS; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_HIDE); + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view) { + if (view->backend) { + view->backend->destroy(view); + } + + ReleaseDC(view->impl->hwnd, view->impl->hdc); + DestroyWindow(view->impl->hwnd); + free(view->impl); + } +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + UnregisterClass(world->className, NULL); + free(world->impl); +} + +static PuglKey +keySymToSpecial(WPARAM sym) +{ + // clang-format off + switch (sym) { + case VK_F1: return PUGL_KEY_F1; + case VK_F2: return PUGL_KEY_F2; + case VK_F3: return PUGL_KEY_F3; + case VK_F4: return PUGL_KEY_F4; + case VK_F5: return PUGL_KEY_F5; + case VK_F6: return PUGL_KEY_F6; + case VK_F7: return PUGL_KEY_F7; + case VK_F8: return PUGL_KEY_F8; + case VK_F9: return PUGL_KEY_F9; + case VK_F10: return PUGL_KEY_F10; + case VK_F11: return PUGL_KEY_F11; + case VK_F12: return PUGL_KEY_F12; + case VK_BACK: return PUGL_KEY_BACKSPACE; + case VK_DELETE: return PUGL_KEY_DELETE; + case VK_LEFT: return PUGL_KEY_LEFT; + case VK_UP: return PUGL_KEY_UP; + case VK_RIGHT: return PUGL_KEY_RIGHT; + case VK_DOWN: return PUGL_KEY_DOWN; + case VK_PRIOR: return PUGL_KEY_PAGE_UP; + case VK_NEXT: return PUGL_KEY_PAGE_DOWN; + case VK_HOME: return PUGL_KEY_HOME; + case VK_END: return PUGL_KEY_END; + case VK_INSERT: return PUGL_KEY_INSERT; + case VK_SHIFT: + case VK_LSHIFT: return PUGL_KEY_SHIFT_L; + case VK_RSHIFT: return PUGL_KEY_SHIFT_R; + case VK_CONTROL: + case VK_LCONTROL: return PUGL_KEY_CTRL_L; + case VK_RCONTROL: return PUGL_KEY_CTRL_R; + case VK_MENU: + case VK_LMENU: return PUGL_KEY_ALT_L; + case VK_RMENU: return PUGL_KEY_ALT_R; + case VK_LWIN: return PUGL_KEY_SUPER_L; + case VK_RWIN: return PUGL_KEY_SUPER_R; + case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK; + case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK; + case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK; + case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN; + case VK_PAUSE: return PUGL_KEY_PAUSE; + } + // clang-format on + + return (PuglKey)0; +} + +static uint32_t +getModifiers(void) +{ + // clang-format off + return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) | + ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) | + ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) | + ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) | + ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u)); + // clang-format on +} + +static void +initMouseEvent(PuglEvent* event, + PuglView* view, + int button, + bool press, + LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ClientToScreen(view->impl->hwnd, &pt); + + if (press) { + SetCapture(view->impl->hwnd); + } else { + ReleaseCapture(); + } + + event->button.time = GetMessageTime() / 1e3; + event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; + event->button.x = GET_X_LPARAM(lParam); + event->button.y = GET_Y_LPARAM(lParam); + event->button.xRoot = pt.x; + event->button.yRoot = pt.y; + event->button.state = getModifiers(); + event->button.button = (uint32_t)button; +} + +static void +initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(view->impl->hwnd, &pt); + + event->scroll.time = GetMessageTime() / 1e3; + event->scroll.type = PUGL_SCROLL; + event->scroll.x = pt.x; + event->scroll.y = pt.y; + event->scroll.xRoot = GET_X_LPARAM(lParam); + event->scroll.yRoot = GET_Y_LPARAM(lParam); + event->scroll.state = getModifiers(); + event->scroll.dx = 0; + event->scroll.dy = 0; +} + +/// Return the code point for buf, or the replacement character on error +static uint32_t +puglDecodeUTF16(const wchar_t* buf, const int len) +{ + const uint32_t c0 = buf[0]; + const uint32_t c1 = buf[0]; + if (c0 >= 0xD800 && c0 < 0xDC00) { + if (len < 2) { + return 0xFFFD; // Surrogate, but length is only 1 + } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) { + return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000; + } + + return 0xFFFD; // Unpaired surrogates + } + + return c0; +} + +static void +initKeyEvent(PuglEventKey* event, + PuglView* view, + bool press, + WPARAM wParam, + LPARAM lParam) +{ + POINT rpos = { 0, 0 }; + GetCursorPos(&rpos); + + POINT cpos = { rpos.x, rpos.y }; + ScreenToClient(view->impl->hwnd, &rpos); + + const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16); + const unsigned vkey = ((wParam == VK_SHIFT) + ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX) + : (unsigned)wParam); + + const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); + const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); + const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1; + const bool ext = lParam & 0x01000000; + + event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + event->time = GetMessageTime() / 1e3; + event->state = getModifiers(); + event->xRoot = rpos.x; + event->yRoot = rpos.y; + event->x = cpos.x; + event->y = cpos.y; + event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16); + event->key = 0; + + const PuglKey special = keySymToSpecial(vkey); + if (special) { + if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) { + event->key = (uint32_t)special + 1u; // Right hand key + } else { + event->key = (uint32_t)special; + } + } else if (!dead) { + // Translate unshifted key + BYTE keyboardState[256] = {0}; + wchar_t buf[5] = {0}; + + event->key = puglDecodeUTF16( + buf, ToUnicode(vkey, vcode, keyboardState, buf, 4, 1 << 2)); + } +} + +static void +initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam) +{ + const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF }; + + initKeyEvent(&event->key, view, true, wParam, lParam); + event->type = PUGL_TEXT; + event->text.character = puglDecodeUTF16(utf16, 2); + + if (!WideCharToMultiByte( + CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) { + memset(event->text.string, 0, 8); + } +} + +static bool +ignoreKeyEvent(PuglView* view, LPARAM lParam) +{ + return view->hints[PUGL_IGNORE_KEY_REPEAT] && (lParam & (1 << 30)); +} + +static RECT +handleConfigure(PuglView* view, PuglEvent* event) +{ + RECT rect; + GetClientRect(view->impl->hwnd, &rect); + MapWindowPoints(view->impl->hwnd, + view->parent ? (HWND)view->parent : HWND_DESKTOP, + (LPPOINT)&rect, + 2); + + const LONG width = rect.right - rect.left; + const LONG height = rect.bottom - rect.top; + + view->frame.x = rect.left; + view->frame.y = rect.top; + + event->configure.type = PUGL_CONFIGURE; + event->configure.x = view->frame.x; + event->configure.y = view->frame.y; + event->configure.width = width; + event->configure.height = height; + + if (view->frame.width != width || view->frame.height != height) { + view->frame.width = width; + view->frame.height = height; + } + + return rect; +} + +static void +handleCrossing(PuglView* view, const PuglEventType type, POINT pos) +{ + POINT root_pos = pos; + ClientToScreen(view->impl->hwnd, &root_pos); + + const PuglEventCrossing ev = { + type, + 0, + GetMessageTime() / 1e3, + (double)pos.x, + (double)pos.y, + (double)root_pos.x, + (double)root_pos.y, + getModifiers(), + PUGL_CROSSING_NORMAL, + }; + + puglDispatchEvent(view, (const PuglEvent*)&ev); +} + +static void +constrainAspect(const PuglView* const view, + RECT* const size, + const WPARAM wParam) +{ + const float minA = (float)view->minAspectX / (float)view->minAspectY; + const float maxA = (float)view->maxAspectX / (float)view->maxAspectY; + const float w = (float)(size->right - size->left); + const float h = (float)(size->bottom - size->top); + const float a = w / h; + + switch (wParam) { + case WMSZ_TOP: + size->top = (a < minA ? (LONG)((float)size->bottom - w * minA) : + a > maxA ? (LONG)((float)size->bottom - w * maxA) : + size->top); + break; + case WMSZ_TOPRIGHT: + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + size->right = (a < minA ? (LONG)((float)size->left + h * minA) : + a > maxA ? (LONG)((float)size->left + h * maxA) : + size->right); + break; + case WMSZ_BOTTOM: + size->bottom = (a < minA ? (LONG)((float)size->top + w * minA) : + a > maxA ? (LONG)((float)size->top + w * maxA) : + size->bottom); + break; + case WMSZ_BOTTOMLEFT: + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + size->left = (a < minA ? (LONG)((float)size->right - h * minA) : + a > maxA ? (LONG)((float)size->right - h * maxA) : + size->left); + break; + } +} + +static LRESULT +handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglEvent event = {{PUGL_NOTHING, 0}}; + RECT rect = {0, 0, 0, 0}; + POINT pt = {0, 0}; + MINMAXINFO* mmi = NULL; + void* dummy_ptr = NULL; + + if (InSendMessageEx(dummy_ptr)) { + event.any.flags |= PUGL_IS_SEND_EVENT; + } + + switch (message) { + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) { + SetCursor(view->impl->cursor); + } else { + return DefWindowProc(view->impl->hwnd, message, wParam, lParam); + } + break; + case WM_SHOWWINDOW: + if (wParam) { + handleConfigure(view, &event); + puglDispatchEvent(view, &event); + event.type = PUGL_NOTHING; + + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); + } + + if ((bool)wParam != view->visible) { + view->visible = wParam; + event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; + } + break; + case WM_SIZE: + handleConfigure(view, &event); + InvalidateRect(view->impl->hwnd, NULL, false); + break; + case WM_SIZING: + if (view->minAspectX) { + constrainAspect(view, (RECT*)lParam, wParam); + return TRUE; + } + break; + case WM_ENTERSIZEMOVE: + case WM_ENTERMENULOOP: + puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER); + break; + case WM_TIMER: + if (wParam >= PUGL_USER_TIMER_MIN) { + PuglEvent ev = {{PUGL_TIMER, 0}}; + ev.timer.id = wParam - PUGL_USER_TIMER_MIN; + puglDispatchEvent(view, &ev); + } + break; + case WM_EXITSIZEMOVE: + case WM_EXITMENULOOP: + puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE); + break; + case WM_GETMINMAXINFO: + mmi = (MINMAXINFO*)lParam; + mmi->ptMinTrackSize.x = view->minWidth; + mmi->ptMinTrackSize.y = view->minHeight; + if (view->maxWidth > 0 && view->maxHeight > 0) { + mmi->ptMaxTrackSize.x = view->maxWidth; + mmi->ptMaxTrackSize.y = view->maxHeight; + } + break; + case WM_PAINT: + GetUpdateRect(view->impl->hwnd, &rect, false); + event.expose.type = PUGL_EXPOSE; + event.expose.x = rect.left; + event.expose.y = rect.top; + event.expose.width = rect.right - rect.left; + event.expose.height = rect.bottom - rect.top; + break; + case WM_ERASEBKGND: + return true; + case WM_MOUSEMOVE: + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + + if (!view->impl->mouseTracked) { + TRACKMOUSEEVENT tme = {0}; + + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = view->impl->hwnd; + TrackMouseEvent(&tme); + + handleCrossing(view, PUGL_POINTER_IN, pt); + view->impl->mouseTracked = true; + } + + ClientToScreen(view->impl->hwnd, &pt); + event.motion.type = PUGL_MOTION; + event.motion.time = GetMessageTime() / 1e3; + event.motion.x = GET_X_LPARAM(lParam); + event.motion.y = GET_Y_LPARAM(lParam); + event.motion.xRoot = pt.x; + event.motion.yRoot = pt.y; + event.motion.state = getModifiers(); + break; + case WM_MOUSELEAVE: + GetCursorPos(&pt); + ScreenToClient(view->impl->hwnd, &pt); + handleCrossing(view, PUGL_POINTER_OUT, pt); + view->impl->mouseTracked = false; + break; + case WM_LBUTTONDOWN: + initMouseEvent(&event, view, 1, true, lParam); + break; + case WM_MBUTTONDOWN: + initMouseEvent(&event, view, 2, true, lParam); + break; + case WM_RBUTTONDOWN: + initMouseEvent(&event, view, 3, true, lParam); + break; + case WM_LBUTTONUP: + initMouseEvent(&event, view, 1, false, lParam); + break; + case WM_MBUTTONUP: + initMouseEvent(&event, view, 2, false, lParam); + break; + case WM_RBUTTONUP: + initMouseEvent(&event, view, 3, false, lParam); + break; + case WM_MOUSEWHEEL: + initScrollEvent(&event, view, lParam); + event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA; + event.scroll.direction = (event.scroll.dy > 0 + ? PUGL_SCROLL_UP + : PUGL_SCROLL_DOWN); + break; + case WM_MOUSEHWHEEL: + initScrollEvent(&event, view, lParam); + event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA; + event.scroll.direction = (event.scroll.dx > 0 + ? PUGL_SCROLL_RIGHT + : PUGL_SCROLL_LEFT); + break; + case WM_KEYDOWN: + if (!ignoreKeyEvent(view, lParam)) { + initKeyEvent(&event.key, view, true, wParam, lParam); + } + break; + case WM_KEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_CHAR: + initCharEvent(&event, view, wParam, lParam); + break; + case WM_SETFOCUS: + event.type = PUGL_FOCUS_IN; + break; + case WM_KILLFOCUS: + event.type = PUGL_FOCUS_OUT; + break; + case WM_SYSKEYDOWN: + initKeyEvent(&event.key, view, true, wParam, lParam); + break; + case WM_SYSKEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_SYSCHAR: + return TRUE; + case PUGL_LOCAL_CLIENT_MSG: + event.client.type = PUGL_CLIENT; + event.client.data1 = (uintptr_t)wParam; + event.client.data2 = (uintptr_t)lParam; + break; + case WM_QUIT: + case PUGL_LOCAL_CLOSE_MSG: + event.any.type = PUGL_CLOSE; + break; + default: + return DefWindowProc(view->impl->hwnd, message, wParam, lParam); + } + + puglDispatchEvent(view, &event); + + return 0; +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + SetFocus(view->impl->hwnd); + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + return GetFocus() == view->impl->hwnd; +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + FLASHWINFO info = {sizeof(FLASHWINFO), + view->impl->hwnd, + FLASHW_ALL|FLASHW_TIMERNOFG, + 1, + 0}; + + FlashWindowEx(&info); + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + const UINT msec = (UINT)floor(timeout * 1000.0); + + return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CLIENT) { + PostMessage(view->impl->hwnd, + PUGL_LOCAL_CLIENT_MSG, + (WPARAM)event->client.data1, + (LPARAM)event->client.data2); + + return PUGL_SUCCESS; + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* PUGL_UNUSED(view)) +{ + WaitMessage(); + return PUGL_SUCCESS; +} +#endif + +static PuglStatus +puglDispatchViewEvents(PuglView* view) +{ + /* Windows has no facility to process only currently queued messages, which + causes the event loop to run forever in cases like mouse movement where + the queue is constantly being filled with new messages. To work around + this, we post a message to ourselves before starting, record its time + when it is received, then break the loop on the first message that was + created afterwards. */ + + long markTime = 0; + MSG msg; + while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { + if (msg.message == PUGL_LOCAL_MARK_MSG) { + markTime = GetMessageTime(); + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + if (markTime != 0 && GetMessageTime() > markTime) { + break; + } + } + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglDispatchWinEvents(PuglWorld* world) +{ + for (size_t i = 0; i < world->numViews; ++i) { + PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0); + } + + for (size_t i = 0; i < world->numViews; ++i) { + puglDispatchViewEvents(world->views[i]); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglUpdate(PuglWorld* world, double timeout) +{ + const double startTime = puglGetTime(world); + PuglStatus st = PUGL_SUCCESS; + + if (timeout < 0.0) { + st = puglPollWinEvents(world, timeout); + st = st ? st : puglDispatchWinEvents(world); + } else if (timeout == 0.0) { + st = puglDispatchWinEvents(world); + } else { + const double endTime = startTime + timeout - 0.001; + for (double t = startTime; t < endTime; t = puglGetTime(world)) { + if ((st = puglPollWinEvents(world, endTime - t)) || + (st = puglDispatchWinEvents(world))) { + break; + } + } + } + + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i]->visible) { + puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE); + } + + UpdateWindow(world->views[i]->impl->hwnd); + } + + return st; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglUpdate(view->world, 0.0); +} +#endif + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (message) { + case WM_CREATE: + PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); + return 0; + case WM_CLOSE: + PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); + return 0; + case WM_DESTROY: + return 0; + default: + if (view && hwnd == view->impl->hwnd) { + return handleMessage(view, message, wParam, lParam); + } else { + return DefWindowProc(hwnd, message, wParam, lParam); + } + } +} + +double +puglGetTime(const PuglWorld* world) +{ + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return ((double)count.QuadPart / world->impl->timerFrequency - + world->startTime); +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + InvalidateRect(view->impl->hwnd, NULL, false); + return PUGL_SUCCESS; +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, const PuglRect rect) +{ + const RECT r = {(long)floor(rect.x), + (long)floor(rect.y), + (long)ceil(rect.x + rect.width), + (long)ceil(rect.y + rect.height)}; + + InvalidateRect(view->impl->hwnd, &r, false); + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->hwnd; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + puglSetString(&view->title, title); + + if (view->impl->hwnd) { + wchar_t* wtitle = puglUtf8ToWideChar(title); + if (wtitle) { + SetWindowTextW(view->impl->hwnd, wtitle); + free(wtitle); + } + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + view->frame = frame; + + if (view->impl->hwnd) { + RECT rect = { (long)frame.x, + (long)frame.y, + (long)frame.x + (long)frame.width, + (long)frame.y + (long)frame.height }; + + AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), + FALSE, + puglWinGetWindowExFlags(view)); + + if (!SetWindowPos(view->impl->hwnd, + HWND_TOP, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER)) { + return PUGL_UNKNOWN_ERROR; + } + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetDefaultSize(PuglView* const view, const int width, const int height) +{ + view->defaultWidth = width; + view->defaultHeight = height; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetMinSize(PuglView* const view, const int width, const int height) +{ + view->minWidth = width; + view->minHeight = height; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetMaxSize(PuglView* const view, const int width, const int height) +{ + view->maxWidth = width; + view->maxHeight = height; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetAspectRatio(PuglView* const view, + const int minX, + const int minY, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetTransientFor(PuglView* view, PuglNativeView parent) +{ + if (view->parent) { + return PUGL_FAILURE; + } + + view->transientParent = parent; + + if (view->impl->hwnd) { + SetWindowLongPtr(view->impl->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent); + return GetLastError() == NO_ERROR ? PUGL_SUCCESS : PUGL_FAILURE; + } + + return PUGL_SUCCESS; +} + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + + if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || + !OpenClipboard(impl->hwnd)) { + return NULL; + } + + HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); + wchar_t* wstr = mem ? (wchar_t*)GlobalLock(mem) : NULL; + if (!wstr) { + CloseClipboard(); + return NULL; + } + + free(view->clipboard.data); + view->clipboard.data = puglWideCharToUtf8(wstr, &view->clipboard.len); + GlobalUnlock(mem); + CloseClipboard(); + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } else if (!OpenClipboard(impl->hwnd)) { + return PUGL_UNKNOWN_ERROR; + } + + // Measure string and allocate global memory for clipboard + const char* str = (const char*)data; + const int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, + (size_t)(wlen + 1) * sizeof(wchar_t)); + if (!mem) { + CloseClipboard(); + return PUGL_UNKNOWN_ERROR; + } + + // Lock global memory + wchar_t* wstr = (wchar_t*)GlobalLock(mem); + if (!wstr) { + GlobalFree(mem); + CloseClipboard(); + return PUGL_UNKNOWN_ERROR; + } + + // Convert string into global memory and set it as clipboard data + MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, wlen); + wstr[wlen] = 0; + GlobalUnlock(mem); + SetClipboardData(CF_UNICODETEXT, mem); + CloseClipboard(); + return PUGL_SUCCESS; +} + +static const char* const cursor_ids[] = { + IDC_ARROW, // ARROW + IDC_IBEAM, // CARET + IDC_CROSS, // CROSSHAIR + IDC_HAND, // HAND + IDC_NO, // NO + IDC_SIZEWE, // LEFT_RIGHT + IDC_SIZENS, // UP_DOWN +}; + +PuglStatus +puglSetCursor(PuglView* view, PuglCursor cursor) +{ + PuglInternals* const impl = view->impl; + const unsigned index = (unsigned)cursor; + const unsigned count = sizeof(cursor_ids) / sizeof(cursor_ids[0]); + + if (index >= count) { + return PUGL_BAD_PARAMETER; + } + + const HCURSOR cur = LoadCursor(NULL, cursor_ids[index]); + if (!cur) { + return PUGL_FAILURE; + } + + impl->cursor = cur; + if (impl->mouseTracked) { + SetCursor(cur); + } + + return PUGL_SUCCESS; +} diff --git a/src/win.h b/src/win.h new file mode 100644 index 0000000..bd85cb1 --- /dev/null +++ b/src/win.h @@ -0,0 +1,151 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.h + @brief Shared definitions for Windows implementation. +*/ + +#ifndef PUGL_DETAIL_WIN_H +#define PUGL_DETAIL_WIN_H + +#include "implementation.h" + +#include + +#include + +typedef PIXELFORMATDESCRIPTOR PuglWinPFD; + +struct PuglWorldInternalsImpl { + double timerFrequency; +}; + +struct PuglInternalsImpl { + PuglWinPFD pfd; + int pfId; + HWND hwnd; + HCURSOR cursor; + HDC hdc; + PuglSurface* surface; + bool flashing; + bool mouseTracked; +}; + +static inline PuglWinPFD +puglWinGetPixelFormatDescriptor(const PuglHints hints) +{ + const int rgbBits = (hints[PUGL_RED_BITS] + // + hints[PUGL_GREEN_BITS] + // + hints[PUGL_BLUE_BITS]); + + const DWORD dwFlags = hints[PUGL_DOUBLE_BUFFER] ? PFD_DOUBLEBUFFER : 0u; + + PuglWinPFD pfd; + ZeroMemory(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | dwFlags; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = (BYTE)rgbBits; + pfd.cRedBits = (BYTE)hints[PUGL_RED_BITS]; + pfd.cGreenBits = (BYTE)hints[PUGL_GREEN_BITS]; + pfd.cBlueBits = (BYTE)hints[PUGL_BLUE_BITS]; + pfd.cAlphaBits = (BYTE)hints[PUGL_ALPHA_BITS]; + pfd.cDepthBits = (BYTE)hints[PUGL_DEPTH_BITS]; + pfd.cStencilBits = (BYTE)hints[PUGL_STENCIL_BITS]; + pfd.iLayerType = PFD_MAIN_PLANE; + return pfd; +} + +static inline unsigned +puglWinGetWindowFlags(const PuglView* const view) +{ + const bool resizable = view->hints[PUGL_RESIZABLE]; + const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0u; + + return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | + (view->parent + ? WS_CHILD + : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags))); +} + +static inline unsigned +puglWinGetWindowExFlags(const PuglView* const view) +{ + return WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW); +} + +static inline PuglStatus +puglWinCreateWindow(PuglView* const view, + const char* const title, + HWND* const hwnd, + HDC* const hdc) +{ + const char* className = (const char*)view->world->className; + const unsigned winFlags = puglWinGetWindowFlags(view); + const unsigned winExFlags = puglWinGetWindowExFlags(view); + + if (view->frame.width == 0.0 && view->frame.height == 0.0) { + if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { + return PUGL_BAD_CONFIGURATION; + } + + RECT desktopRect; + GetClientRect(GetDesktopWindow(), &desktopRect); + + const int screenWidth = desktopRect.right - desktopRect.left; + const int screenHeight = desktopRect.bottom - desktopRect.top; + + view->frame.width = view->defaultWidth; + view->frame.height = view->defaultHeight; + view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0; + view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0; + } + + // The meaning of "parent" depends on the window type (WS_CHILD) + PuglNativeView parent = view->parent ? view->parent : view->transientParent; + + // Calculate total window size to accommodate requested view size + RECT wr = { (long)view->frame.x, (long)view->frame.y, + (long)view->frame.width, (long)view->frame.height }; + AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags); + + // Create window and get drawing context + if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right-wr.left, wr.bottom-wr.top, + (HWND)parent, NULL, NULL, NULL))) { + return PUGL_REALIZE_FAILED; + } else if (!(*hdc = GetDC(*hwnd))) { + DestroyWindow(*hwnd); + *hwnd = NULL; + return PUGL_REALIZE_FAILED; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubConfigure(PuglView* view); + +PuglStatus +puglWinStubEnter(PuglView* view, const PuglEventExpose* expose); + +PuglStatus +puglWinStubLeave(PuglView* view, const PuglEventExpose* expose); + +#endif // PUGL_DETAIL_WIN_H diff --git a/src/win_cairo.c b/src/win_cairo.c new file mode 100644 index 0000000..18c4a95 --- /dev/null +++ b/src/win_cairo.c @@ -0,0 +1,178 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win_cairo.c + @brief Cairo graphics backend for Windows. +*/ + +#include "stub.h" +#include "types.h" +#include "win.h" + +#include "pugl/pugl_cairo.h" + +#include +#include + +#include + +typedef struct { + cairo_surface_t* surface; + cairo_t* cr; + HDC drawDc; + HBITMAP drawBitmap; +} PuglWinCairoSurface; + +static PuglStatus +puglWinCairoCreateDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + surface->drawDc = CreateCompatibleDC(impl->hdc); + surface->drawBitmap = CreateCompatibleBitmap( + impl->hdc, (int)view->frame.width, (int)view->frame.height); + + DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoDestroyDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + DeleteDC(surface->drawDc); + DeleteObject(surface->drawBitmap); + + surface->drawDc = NULL; + surface->drawBitmap = NULL; + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoConfigure(PuglView* view) +{ + const PuglStatus st = puglWinStubConfigure(view); + + if (!st) { + view->impl->surface = (PuglWinCairoSurface*)calloc( + 1, sizeof(PuglWinCairoSurface)); + } + + return st; +} + +static void +puglWinCairoClose(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + cairo_surface_destroy(surface->surface); + + surface->surface = NULL; +} + +static PuglStatus +puglWinCairoOpen(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || + cairo_surface_status(surface->surface) || + !(surface->cr = cairo_create(surface->surface)) || + cairo_status(surface->cr)) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + puglWinCairoClose(view); + puglWinCairoDestroyDrawContext(view); + free(surface); + impl->surface = NULL; + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglStatus st = PUGL_SUCCESS; + + if (expose && + !(st = puglWinCairoCreateDrawContext(view)) && + !(st = puglWinCairoOpen(view))) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return st; +} + +static PuglStatus +puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + if (expose) { + cairo_surface_flush(surface->surface); + BitBlt(impl->hdc, + 0, 0, (int)view->frame.width, (int)view->frame.height, + surface->drawDc, 0, 0, SRCCOPY); + + puglWinCairoClose(view); + puglWinCairoDestroyDrawContext(view); + + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +static void* +puglWinCairoGetContext(PuglView* view) +{ + return ((PuglWinCairoSurface*)view->impl->surface)->cr; +} + +const PuglBackend* +puglCairoBackend() +{ + static const PuglBackend backend = {puglWinCairoConfigure, + puglStubCreate, + puglWinCairoDestroy, + puglWinCairoEnter, + puglWinCairoLeave, + puglWinCairoGetContext}; + + return &backend; +} diff --git a/src/win_gl.c b/src/win_gl.c new file mode 100644 index 0000000..f011f5b --- /dev/null +++ b/src/win_gl.c @@ -0,0 +1,324 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win_gl.c + @brief OpenGL graphics backend for Windows. +*/ + +#include "stub.h" +#include "types.h" +#include "win.h" + +#include "pugl/pugl_gl.h" + +#include + +#include + +#include +#include + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 + +typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*); +typedef BOOL (*WglSwapInterval)(int); +typedef BOOL (*WglChoosePixelFormat)( + HDC, const int*, const FLOAT*, UINT, int*, UINT*); + +typedef struct { + WglChoosePixelFormat wglChoosePixelFormat; + WglCreateContextAttribs wglCreateContextAttribs; + WglSwapInterval wglSwapInterval; +} PuglWinGlProcs; + +typedef struct { + PuglWinGlProcs procs; + HGLRC hglrc; +} PuglWinGlSurface; + +// Struct to manage the fake window used during configuration +typedef struct { + HWND hwnd; + HDC hdc; +} PuglFakeWindow; + +static PuglStatus +puglWinError(PuglFakeWindow* fakeWin, const PuglStatus status) +{ + if (fakeWin->hwnd) { + ReleaseDC(fakeWin->hwnd, fakeWin->hdc); + DestroyWindow(fakeWin->hwnd); + } + + return status; +} + +static PuglWinGlProcs puglWinGlGetProcs(void) +{ + const PuglWinGlProcs procs = { + (WglChoosePixelFormat)( + wglGetProcAddress("wglChoosePixelFormatARB")), + (WglCreateContextAttribs)( + wglGetProcAddress("wglCreateContextAttribsARB")), + (WglSwapInterval)( + wglGetProcAddress("wglSwapIntervalEXT")) + }; + + return procs; +} + +static PuglStatus +puglWinGlConfigure(PuglView* view) +{ + PuglInternals* impl = view->impl; + + // Set attributes to default if they are unset + // (There is no GLX_DONT_CARE equivalent on Windows) + if (view->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_DEPTH_BITS] = 0; + } + if (view->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_STENCIL_BITS] = 0; + } + if (view->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { + view->hints[PUGL_SAMPLES] = 1; + } + if (view->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { + view->hints[PUGL_DOUBLE_BUFFER] = 1; + } + if (view->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { + view->hints[PUGL_SWAP_INTERVAL] = 1; + } + + // clang-format off + const int pixelAttrs[] = { + WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, view->hints[PUGL_DOUBLE_BUFFER], + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLES] ? 1 : 0, + WGL_SAMPLES_ARB, view->hints[PUGL_SAMPLES], + WGL_RED_BITS_ARB, view->hints[PUGL_RED_BITS], + WGL_GREEN_BITS_ARB, view->hints[PUGL_GREEN_BITS], + WGL_BLUE_BITS_ARB, view->hints[PUGL_BLUE_BITS], + WGL_ALPHA_BITS_ARB, view->hints[PUGL_ALPHA_BITS], + WGL_DEPTH_BITS_ARB, view->hints[PUGL_DEPTH_BITS], + WGL_STENCIL_BITS_ARB, view->hints[PUGL_STENCIL_BITS], + 0, + }; + // clang-format on + + PuglWinGlSurface* const surface = + (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface)); + impl->surface = surface; + + // Create fake window for getting at GL context + PuglStatus st = PUGL_SUCCESS; + PuglFakeWindow fakeWin = {0, 0}; + static const char* title = "Pugl Configuration"; + if ((st = puglWinCreateWindow(view, title, &fakeWin.hwnd, &fakeWin.hdc))) { + return puglWinError(&fakeWin, st); + } + + // Set pixel format for fake window + const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(view->hints); + const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd); + if (!fakePfId || !SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) { + return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); + } + + // Create fake GL context to get at the functions we need + HGLRC fakeRc = wglCreateContext(fakeWin.hdc); + if (!fakeRc) { + return puglWinError(&fakeWin, PUGL_CREATE_CONTEXT_FAILED); + } + + // Enter fake context and get extension functions + wglMakeCurrent(fakeWin.hdc, fakeRc); + surface->procs = puglWinGlGetProcs(); + + if (surface->procs.wglChoosePixelFormat) { + // Choose pixel format based on attributes + UINT numFormats = 0; + if (!surface->procs.wglChoosePixelFormat( + fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { + return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); + } + + DescribePixelFormat( + impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd); + } else { + // Modern extensions not available, use basic pixel format + impl->pfd = fakePfd; + impl->pfId = fakePfId; + } + + // Dispose of fake window and context + wglMakeCurrent(NULL, NULL); + wglDeleteContext(fakeRc); + ReleaseDC(fakeWin.hwnd, fakeWin.hdc); + DestroyWindow(fakeWin.hwnd); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface; + PuglStatus st = PUGL_SUCCESS; + + const int contextAttribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, + view->hints[PUGL_CONTEXT_VERSION_MAJOR], + + WGL_CONTEXT_MINOR_VERSION_ARB, + view->hints[PUGL_CONTEXT_VERSION_MINOR], + + WGL_CONTEXT_FLAGS_ARB, + (view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), + + WGL_CONTEXT_PROFILE_MASK_ARB, + (view->hints[PUGL_USE_COMPAT_PROFILE] + ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB + : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), + + 0}; + + // Create real window with desired pixel format + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_SET_FORMAT_FAILED; + } + + // Create GL context + if (surface->procs.wglCreateContextAttribs && + !(surface->hglrc = surface->procs.wglCreateContextAttribs( + impl->hdc, 0, contextAttribs))) { + return PUGL_CREATE_CONTEXT_FAILED; + } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + // Enter context and set swap interval + wglMakeCurrent(impl->hdc, surface->hglrc); + const int swapInterval = view->hints[PUGL_SWAP_INTERVAL]; + if (surface->procs.wglSwapInterval && swapInterval != PUGL_DONT_CARE) { + surface->procs.wglSwapInterval(swapInterval); + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlDestroy(PuglView* view) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + if (surface) { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(surface->hglrc); + free(surface); + view->impl->surface = NULL; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + + wglMakeCurrent(view->impl->hdc, surface->hglrc); + + if (expose) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlLeave(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + SwapBuffers(view->impl->hdc); + } + + wglMakeCurrent(NULL, NULL); + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + const PuglGlFunc func = (PuglGlFunc)wglGetProcAddress(name); + + /* Windows has the annoying property that wglGetProcAddress returns NULL + for functions from OpenGL 1.1, so we fall back to pulling them directly + from opengl32.dll */ + + return func + ? func + : (PuglGlFunc)GetProcAddress(GetModuleHandle("opengl32.dll"), name); +} + +const PuglBackend* +puglGlBackend(void) +{ + static const PuglBackend backend = {puglWinGlConfigure, + puglWinGlCreate, + puglWinGlDestroy, + puglWinGlEnter, + puglWinGlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/src/win_stub.c b/src/win_stub.c new file mode 100644 index 0000000..e066e4d --- /dev/null +++ b/src/win_stub.c @@ -0,0 +1,80 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "stub.h" +#include "types.h" +#include "win.h" + +#include "pugl/pugl_stub.h" + +PuglStatus +puglWinStubConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglStatus st = PUGL_SUCCESS; + + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } + + impl->pfd = puglWinGetPixelFormatDescriptor(view->hints); + impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd); + + if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_SET_FORMAT_FAILED; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubEnter(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubLeave(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = {puglWinStubConfigure, + puglStubCreate, + puglStubDestroy, + puglWinStubEnter, + puglWinStubLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/src/win_vulkan.c b/src/win_vulkan.c new file mode 100644 index 0000000..5e3622e --- /dev/null +++ b/src/win_vulkan.c @@ -0,0 +1,131 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win_vulkan.c Vulkan graphics backend for Windows. +*/ + +#define VK_NO_PROTOTYPES 1 + +#include "stub.h" +#include "types.h" +#include "win.h" + +#include "pugl/pugl_stub.h" +#include "pugl/pugl_vulkan.h" + +#include +#include + +#include + +struct PuglVulkanLoaderImpl +{ + HMODULE libvulkan; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; +}; + +PuglVulkanLoader* +puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) +{ + PuglVulkanLoader* loader = + (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); + if (!loader) { + return NULL; + } + + if (!(loader->libvulkan = LoadLibrary("vulkan-1.dll"))) { + free(loader); + return NULL; + } + + loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)GetProcAddress( + loader->libvulkan, "vkGetInstanceProcAddr"); + + loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)GetProcAddress( + loader->libvulkan, "vkGetDeviceProcAddr"); + + return loader; +} + +void +puglFreeVulkanLoader(PuglVulkanLoader* loader) +{ + if (loader) { + FreeLibrary(loader->libvulkan); + free(loader); + } +} + +PFN_vkGetInstanceProcAddr +puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetInstanceProcAddr; +} + +PFN_vkGetDeviceProcAddr +puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetDeviceProcAddr; +} + +const PuglBackend* +puglVulkanBackend() +{ + static const PuglBackend backend = {puglWinStubConfigure, + puglStubCreate, + puglStubDestroy, + puglWinStubEnter, + puglWinStubLeave, + puglStubGetContext}; + + return &backend; +} + +const char* const* +puglGetInstanceExtensions(uint32_t* const count) +{ + static const char* const extensions[] = {"VK_KHR_surface", + "VK_KHR_win32_surface"}; + + *count = 2; + return extensions; +} + +VkResult +puglCreateSurface(const PuglVulkanLoader* const loader, + PuglView* const view, + VkInstance instance, + const VkAllocationCallbacks* const pAllocator, + VkSurfaceKHR* const pSurface) +{ + PuglInternals* const impl = view->impl; + + PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = + (PFN_vkCreateWin32SurfaceKHR)puglGetInstanceProcAddrFunc(loader)( + instance, "vkCreateWin32SurfaceKHR"); + + const VkWin32SurfaceCreateInfoKHR createInfo = { + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + NULL, + 0, + GetModuleHandle(NULL), + impl->hwnd, + }; + + return vkCreateWin32SurfaceKHR(instance, &createInfo, pAllocator, pSurface); +} diff --git a/src/x11.c b/src/x11.c new file mode 100644 index 0000000..18f8521 --- /dev/null +++ b/src/x11.c @@ -0,0 +1,1349 @@ +/* + Copyright 2012-2020 David Robillard + Copyright 2013 Robin Gareus + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.c + @brief X11 implementation. +*/ + +#define _POSIX_C_SOURCE 199309L + +#include "x11.h" + +#include "implementation.h" +#include "types.h" + +#include "pugl/pugl.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_XRANDR +# include +#endif + +#ifdef HAVE_XSYNC +# include +# include +#endif + +#ifdef HAVE_XCURSOR +# include +# include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +enum WmClientStateMessageAction { + WM_STATE_REMOVE, + WM_STATE_ADD, + WM_STATE_TOGGLE +}; + +static const long eventMask = + (ExposureMask | StructureNotifyMask | + VisibilityChangeMask | FocusChangeMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); + +static bool +puglInitXSync(PuglWorldInternals* impl) +{ +#ifdef HAVE_XSYNC + int syncMajor = 0; + int syncMinor = 0; + int errorBase = 0; + XSyncSystemCounter* counters = NULL; + int numCounters = 0; + + if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) && + XSyncInitialize(impl->display, &syncMajor, &syncMinor) && + (counters = XSyncListSystemCounters(impl->display, &numCounters))) { + + for (int n = 0; n < numCounters; ++n) { + if (!strcmp(counters[n].name, "SERVERTIME")) { + impl->serverTimeCounter = counters[n].counter; + impl->syncSupported = true; + break; + } + } + + XSyncFreeSystemCounterList(counters); + } +#else + (void)impl; +#endif + + return false; +} + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags) +{ + if (type == PUGL_PROGRAM && (flags & PUGL_WORLD_THREADS)) { + XInitThreads(); + } + + Display* display = XOpenDisplay(NULL); + if (!display) { + return NULL; + } + + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + + impl->display = display; + + // Intern the various atoms we will need + impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0); + impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0); + impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); + impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); + impl->atoms.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 0); + impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0); + impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); + impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = + XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); + + // Open input method + XSetLocaleModifiers(""); + if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) { + XSetLocaleModifiers("@im="); + impl->xim = XOpenIM(display, NULL, NULL, NULL); + } + + puglInitXSync(impl); + XFlush(display); + + return impl; +} + +void* +puglGetNativeWorld(PuglWorld* world) +{ + return world->impl->display; +} + +PuglInternals* +puglInitViewInternals(void) +{ + PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); + +#ifdef HAVE_XCURSOR + impl->cursorShape = XC_arrow; +#endif + + return impl; +} + +static PuglStatus +puglPollX11Socket(PuglWorld* world, const double timeout) +{ + if (XPending(world->impl->display) > 0) { + return PUGL_SUCCESS; + } + + const int fd = ConnectionNumber(world->impl->display); + const int nfds = fd + 1; + int ret = 0; + fd_set fds; + FD_ZERO(&fds); // NOLINT + FD_SET(fd, &fds); + + if (timeout < 0.0) { + ret = select(nfds, &fds, NULL, NULL, NULL); + } else { + const long sec = (long)timeout; + const long usec = (long)((timeout - (double)sec) * 1e6); + struct timeval tv = {sec, usec}; + ret = select(nfds, &fds, NULL, NULL, &tv); + } + + return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS; +} + +static PuglView* +puglFindView(PuglWorld* world, const Window window) +{ + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i]->impl->win == window) { + return world->views[i]; + } + } + + return NULL; +} + +static PuglStatus +updateSizeHints(const PuglView* view) +{ + if (!view->impl->win) { + return PUGL_SUCCESS; + } + + Display* display = view->world->impl->display; + XSizeHints sizeHints = {0}; + + if (!view->hints[PUGL_RESIZABLE]) { + sizeHints.flags = PBaseSize | PMinSize | PMaxSize; + sizeHints.base_width = (int)view->frame.width; + sizeHints.base_height = (int)view->frame.height; + sizeHints.min_width = (int)view->frame.width; + sizeHints.min_height = (int)view->frame.height; + sizeHints.max_width = (int)view->frame.width; + sizeHints.max_height = (int)view->frame.height; + } else { + if (view->defaultWidth || view->defaultHeight) { + sizeHints.flags = PBaseSize; + sizeHints.base_width = view->defaultWidth; + sizeHints.base_height = view->defaultHeight; + } + if (view->minWidth || view->minHeight) { + sizeHints.flags = PMinSize; + sizeHints.min_width = view->minWidth; + sizeHints.min_height = view->minHeight; + } + if (view->maxWidth || view->maxHeight) { + sizeHints.flags = PMaxSize; + sizeHints.max_width = view->maxWidth; + sizeHints.max_height = view->maxHeight; + } + if (view->minAspectX) { + sizeHints.flags |= PAspect; + sizeHints.min_aspect.x = view->minAspectX; + sizeHints.min_aspect.y = view->minAspectY; + sizeHints.max_aspect.x = view->maxAspectX; + sizeHints.max_aspect.y = view->maxAspectY; + } + } + + XSetNormalHints(display, view->impl->win, &sizeHints); + return PUGL_SUCCESS; +} + +#ifdef HAVE_XCURSOR +static PuglStatus +puglDefineCursorShape(PuglView* view, unsigned shape) +{ + PuglInternals* const impl = view->impl; + PuglWorld* const world = view->world; + Display* const display = world->impl->display; + const Cursor cur = XcursorShapeLoadCursor(display, shape); + + if (cur) { + XDefineCursor(display, impl->win, cur); + XFreeCursor(display, cur); + return PUGL_SUCCESS; + } + + return PUGL_FAILURE; +} +#endif + +PuglStatus +puglRealize(PuglView* view) +{ + PuglInternals* const impl = view->impl; + if (impl->win) { + return PUGL_FAILURE; + } + + PuglWorld* const world = view->world; + PuglX11Atoms* const atoms = &view->world->impl->atoms; + Display* const display = world->impl->display; + + impl->display = display; + impl->screen = DefaultScreen(display); + + if (!view->backend || !view->backend->configure) { + return PUGL_BAD_BACKEND; + } else if (view->frame.width == 0.0 && view->frame.height == 0.0) { + if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) { + return PUGL_BAD_CONFIGURATION; + } + + const int screenWidth = DisplayWidth(display, impl->screen); + const int screenHeight = DisplayHeight(display, impl->screen); + + view->frame.width = view->defaultWidth; + view->frame.height = view->defaultHeight; + view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0; + view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0; + } + + PuglStatus st = view->backend->configure(view); + if (st || !impl->vi) { + view->backend->destroy(view); + return st ? st : PUGL_BACKEND_FAILED; + } + + Window xParent = view->parent ? (Window)view->parent + : RootWindow(display, impl->screen); + + Colormap cmap = XCreateColormap( + display, xParent, impl->vi->visual, AllocNone); + + XSetWindowAttributes attr = {0}; + attr.colormap = cmap; + attr.event_mask = eventMask; + + const Window win = impl->win = XCreateWindow( + display, xParent, + (int)view->frame.x, (int)view->frame.y, + (unsigned)view->frame.width, (unsigned)view->frame.height, + 0, impl->vi->depth, InputOutput, + impl->vi->visual, CWColormap | CWEventMask, &attr); + + if ((st = view->backend->create(view))) { + return st; + } + +#ifdef HAVE_XRANDR + // Set refresh rate hint to the real refresh rate + XRRScreenConfiguration* conf = XRRGetScreenInfo(display, xParent); + short current_rate = XRRConfigCurrentRate(conf); + + view->hints[PUGL_REFRESH_RATE] = current_rate; + XRRFreeScreenConfigInfo(conf); +#endif + + updateSizeHints(view); + + XClassHint classHint = { world->className, world->className }; + XSetClassHint(display, win, &classHint); + + if (view->title) { + puglSetWindowTitle(view, view->title); + } + + if (!view->parent) { + XSetWMProtocols(display, win, &atoms->WM_DELETE_WINDOW, 1); + } + + if (view->transientParent) { + XSetTransientForHint(display, win, (Window)(view->transientParent)); + } + + // Create input context + const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing; + if (!(impl->xic = XCreateIC(world->impl->xim, + XNInputStyle, im_style, + XNClientWindow, win, + XNFocusWindow, win, + NULL))) { + view->world->logFunc(view->world, + PUGL_LOG_LEVEL_WARNING, + "XCreateID failed\n"); + } + +#ifdef HAVE_XCURSOR + puglDefineCursorShape(view, impl->cursorShape); +#endif + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + PuglStatus st = PUGL_SUCCESS; + + if (!view->impl->win) { + if ((st = puglRealize(view))) { + return st; + } + } + + XMapRaised(view->impl->display, view->impl->win); + puglPostRedisplay(view); + + return st; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + XUnmapWindow(view->impl->display, view->impl->win); + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view && view->impl) { + if (view->impl->xic) { + XDestroyIC(view->impl->xic); + } + if (view->backend) { + view->backend->destroy(view); + } + if (view->impl->display) { + XDestroyWindow(view->impl->display, view->impl->win); + } + XFree(view->impl->vi); + free(view->impl); + } +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + if (world->impl->xim) { + XCloseIM(world->impl->xim); + } + XCloseDisplay(world->impl->display); + free(world->impl->timers); + free(world->impl); +} + +static PuglKey +keySymToSpecial(KeySym sym) +{ + switch (sym) { + case XK_F1: return PUGL_KEY_F1; + case XK_F2: return PUGL_KEY_F2; + case XK_F3: return PUGL_KEY_F3; + case XK_F4: return PUGL_KEY_F4; + case XK_F5: return PUGL_KEY_F5; + case XK_F6: return PUGL_KEY_F6; + case XK_F7: return PUGL_KEY_F7; + case XK_F8: return PUGL_KEY_F8; + case XK_F9: return PUGL_KEY_F9; + case XK_F10: return PUGL_KEY_F10; + case XK_F11: return PUGL_KEY_F11; + case XK_F12: return PUGL_KEY_F12; + case XK_Left: return PUGL_KEY_LEFT; + case XK_Up: return PUGL_KEY_UP; + case XK_Right: return PUGL_KEY_RIGHT; + case XK_Down: return PUGL_KEY_DOWN; + case XK_Page_Up: return PUGL_KEY_PAGE_UP; + case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; + case XK_Home: return PUGL_KEY_HOME; + case XK_End: return PUGL_KEY_END; + case XK_Insert: return PUGL_KEY_INSERT; + case XK_Shift_L: return PUGL_KEY_SHIFT_L; + case XK_Shift_R: return PUGL_KEY_SHIFT_R; + case XK_Control_L: return PUGL_KEY_CTRL_L; + case XK_Control_R: return PUGL_KEY_CTRL_R; + case XK_Alt_L: return PUGL_KEY_ALT_L; + case XK_ISO_Level3_Shift: + case XK_Alt_R: return PUGL_KEY_ALT_R; + case XK_Super_L: return PUGL_KEY_SUPER_L; + case XK_Super_R: return PUGL_KEY_SUPER_R; + case XK_Menu: return PUGL_KEY_MENU; + case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK; + case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK; + case XK_Num_Lock: return PUGL_KEY_NUM_LOCK; + case XK_Print: return PUGL_KEY_PRINT_SCREEN; + case XK_Pause: return PUGL_KEY_PAUSE; + default: break; + } + return (PuglKey)0; +} + +static int +lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym) +{ + Status status = 0; + +#ifdef X_HAVE_UTF8_STRING + const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status); +#else + const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status); +#endif + + return status == XBufferOverflow ? 0 : n; +} + +static void +translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) +{ + const unsigned state = xevent->xkey.state; + const bool filter = XFilterEvent(xevent, None); + + event->key.keycode = xevent->xkey.keycode; + xevent->xkey.state = 0; + + // Lookup unshifted key + char ustr[8] = {0}; + KeySym sym = 0; + const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL); + const PuglKey special = keySymToSpecial(sym); + + event->key.key = ((special || ufound <= 0) + ? special + : puglDecodeUTF8((const uint8_t*)ustr)); + + if (xevent->type == KeyPress && !filter && !special) { + // Lookup shifted key for possible text event + xevent->xkey.state = state; + + char sstr[8] = {0}; + const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym); + if (sfound > 0) { + // Dispatch key event now + puglDispatchEvent(view, event); + + // "Return" a text event in its place + event->text.type = PUGL_TEXT; + event->text.character = puglDecodeUTF8((const uint8_t*)sstr); + memcpy(event->text.string, sstr, sizeof(sstr)); + } + } +} + +static uint32_t +translateModifiers(const unsigned xstate) +{ + return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0u) | + ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0u) | + ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0u) | + ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u)); +} + +static PuglEvent +translateEvent(PuglView* view, XEvent xevent) +{ + const PuglX11Atoms* atoms = &view->world->impl->atoms; + + PuglEvent event = {{PUGL_NOTHING, 0}}; + event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0; + + switch (xevent.type) { + case ClientMessage: + if (xevent.xclient.message_type == atoms->WM_PROTOCOLS) { + const Atom protocol = (Atom)xevent.xclient.data.l[0]; + if (protocol == atoms->WM_DELETE_WINDOW) { + event.type = PUGL_CLOSE; + } + } else if (xevent.xclient.message_type == atoms->PUGL_CLIENT_MSG) { + event.type = PUGL_CLIENT; + event.client.data1 = (uintptr_t)xevent.xclient.data.l[0]; + event.client.data2 = (uintptr_t)xevent.xclient.data.l[1]; + } + break; + case VisibilityNotify: + view->visible = xevent.xvisibility.state != VisibilityFullyObscured; + break; + case MapNotify: + event.type = PUGL_MAP; + break; + case UnmapNotify: + event.type = PUGL_UNMAP; + view->visible = false; + break; + case ConfigureNotify: + event.type = PUGL_CONFIGURE; + event.configure.x = xevent.xconfigure.x; + event.configure.y = xevent.xconfigure.y; + event.configure.width = xevent.xconfigure.width; + event.configure.height = xevent.xconfigure.height; + break; + case Expose: + event.type = PUGL_EXPOSE; + event.expose.x = xevent.xexpose.x; + event.expose.y = xevent.xexpose.y; + event.expose.width = xevent.xexpose.width; + event.expose.height = xevent.xexpose.height; + break; + case MotionNotify: + event.type = PUGL_MOTION; + event.motion.time = (double)xevent.xmotion.time / 1e3; + event.motion.x = xevent.xmotion.x; + event.motion.y = xevent.xmotion.y; + event.motion.xRoot = xevent.xmotion.x_root; + event.motion.yRoot = xevent.xmotion.y_root; + event.motion.state = translateModifiers(xevent.xmotion.state); + if (xevent.xmotion.is_hint == NotifyHint) { + event.motion.flags |= PUGL_IS_HINT; + } + break; + case ButtonPress: + if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) { + event.type = PUGL_SCROLL; + event.scroll.time = (double)xevent.xbutton.time / 1e3; + event.scroll.x = xevent.xbutton.x; + event.scroll.y = xevent.xbutton.y; + event.scroll.xRoot = xevent.xbutton.x_root; + event.scroll.yRoot = xevent.xbutton.y_root; + event.scroll.state = translateModifiers(xevent.xbutton.state); + event.scroll.dx = 0.0; + event.scroll.dy = 0.0; + switch (xevent.xbutton.button) { + case 4: + event.scroll.dy = 1.0; + event.scroll.direction = PUGL_SCROLL_UP; + break; + case 5: + event.scroll.dy = -1.0; + event.scroll.direction = PUGL_SCROLL_DOWN; + break; + case 6: + event.scroll.dx = -1.0; + event.scroll.direction = PUGL_SCROLL_LEFT; + break; + case 7: + event.scroll.dx = 1.0; + event.scroll.direction = PUGL_SCROLL_RIGHT; + break; + } + // fallthru + } + // fallthru + case ButtonRelease: + if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) { + event.button.type = ((xevent.type == ButtonPress) + ? PUGL_BUTTON_PRESS + : PUGL_BUTTON_RELEASE); + event.button.time = (double)xevent.xbutton.time / 1e3; + event.button.x = xevent.xbutton.x; + event.button.y = xevent.xbutton.y; + event.button.xRoot = xevent.xbutton.x_root; + event.button.yRoot = xevent.xbutton.y_root; + event.button.state = translateModifiers(xevent.xbutton.state); + event.button.button = xevent.xbutton.button; + } + break; + case KeyPress: + case KeyRelease: + event.type = ((xevent.type == KeyPress) + ? PUGL_KEY_PRESS + : PUGL_KEY_RELEASE); + event.key.time = (double)xevent.xkey.time / 1e3; + event.key.x = xevent.xkey.x; + event.key.y = xevent.xkey.y; + event.key.xRoot = xevent.xkey.x_root; + event.key.yRoot = xevent.xkey.y_root; + event.key.state = translateModifiers(xevent.xkey.state); + translateKey(view, &xevent, &event); + break; + case EnterNotify: + case LeaveNotify: + event.type = ((xevent.type == EnterNotify) + ? PUGL_POINTER_IN + : PUGL_POINTER_OUT); + event.crossing.time = (double)xevent.xcrossing.time / 1e3; + event.crossing.x = xevent.xcrossing.x; + event.crossing.y = xevent.xcrossing.y; + event.crossing.xRoot = xevent.xcrossing.x_root; + event.crossing.yRoot = xevent.xcrossing.y_root; + event.crossing.state = translateModifiers(xevent.xcrossing.state); + event.crossing.mode = PUGL_CROSSING_NORMAL; + if (xevent.xcrossing.mode == NotifyGrab) { + event.crossing.mode = PUGL_CROSSING_GRAB; + } else if (xevent.xcrossing.mode == NotifyUngrab) { + event.crossing.mode = PUGL_CROSSING_UNGRAB; + } + break; + + case FocusIn: + case FocusOut: + event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT; + event.focus.mode = PUGL_CROSSING_NORMAL; + if (xevent.xfocus.mode == NotifyGrab) { + event.focus.mode = PUGL_CROSSING_GRAB; + } else if (xevent.xfocus.mode == NotifyUngrab) { + event.focus.mode = PUGL_CROSSING_UNGRAB; + } + break; + + default: + break; + } + + return event; +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + XSetInputFocus( + view->impl->display, view->impl->win, RevertToNone, CurrentTime); + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + int revertTo = 0; + Window focusedWindow = 0; + XGetInputFocus(view->impl->display, &focusedWindow, &revertTo); + return focusedWindow == view->impl->win; +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + XEvent event = {0}; + + event.type = ClientMessage; + event.xclient.window = impl->win; + event.xclient.format = 32; + event.xclient.message_type = atoms->NET_WM_STATE; + event.xclient.data.l[0] = WM_STATE_ADD; + event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + + const Window root = RootWindow(impl->display, impl->screen); + XSendEvent(impl->display, + root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ +#ifdef HAVE_XSYNC + if (view->world->impl->syncSupported) { + XSyncValue value; + XSyncIntToValue(&value, (int)floor(timeout * 1000.0)); + + PuglWorldInternals* w = view->world->impl; + Display* const display = w->display; + const XSyncCounter counter = w->serverTimeCounter; + const XSyncTestType type = XSyncPositiveTransition; + const XSyncTrigger trigger = {counter, XSyncRelative, value, type}; + XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive}; + const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr); + const PuglTimer timer = {alarm, view, id}; + + if (alarm != None) { + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + // Replace existing timer + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + w->timers[i] = timer; + return PUGL_SUCCESS; + } + } + + // Add new timer + const size_t size = ++w->numTimers * sizeof(timer); + w->timers = (PuglTimer*)realloc(w->timers, size); + w->timers[w->numTimers - 1] = timer; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; + (void)timeout; +#endif + + return PUGL_FAILURE; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ +#ifdef HAVE_XSYNC + PuglWorldInternals* w = view->world->impl; + + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + + if (i == w->numTimers - 1) { + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } else { + memmove(w->timers + i, + w->timers + i + 1, + sizeof(PuglTimer) * (w->numTimers - i - 1)); + + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } + + --w->numTimers; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; +#endif + + return PUGL_FAILURE; +} + +static XEvent +puglEventToX(PuglView* view, const PuglEvent* event) +{ + XEvent xev = {0}; + xev.xany.send_event = True; + + switch (event->type) { + case PUGL_EXPOSE: { + const double x = floor(event->expose.x); + const double y = floor(event->expose.y); + const double w = ceil(event->expose.x + event->expose.width) - x; + const double h = ceil(event->expose.y + event->expose.height) - y; + + xev.xexpose.type = Expose; + xev.xexpose.serial = 0; + xev.xexpose.display = view->impl->display; + xev.xexpose.window = view->impl->win; + xev.xexpose.x = (int)x; + xev.xexpose.y = (int)y; + xev.xexpose.width = (int)w; + xev.xexpose.height = (int)h; + break; + } + + case PUGL_CLIENT: + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.display = view->impl->display; + xev.xclient.window = view->impl->win; + xev.xclient.message_type = view->world->impl->atoms.PUGL_CLIENT_MSG; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (long)event->client.data1; + xev.xclient.data.l[1] = (long)event->client.data2; + break; + + default: + break; + } + + return xev; +} + +PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event) +{ + XEvent xev = puglEventToX(view, event); + + if (xev.type) { + if (XSendEvent(view->impl->display, view->impl->win, False, 0, &xev)) { + return PUGL_SUCCESS; + } else { + return PUGL_UNKNOWN_ERROR; + } + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* view) +{ + XEvent xevent; + XPeekEvent(view->impl->display, &xevent); + return PUGL_SUCCESS; +} +#endif + +static void +mergeExposeEvents(PuglEvent* dst, const PuglEvent* src) +{ + if (!dst->type) { + *dst = *src; + } else { + const double max_x = MAX(dst->expose.x + dst->expose.width, + src->expose.x + src->expose.width); + const double max_y = MAX(dst->expose.y + dst->expose.height, + src->expose.y + src->expose.height); + + dst->expose.x = MIN(dst->expose.x, src->expose.x); + dst->expose.y = MIN(dst->expose.y, src->expose.y); + dst->expose.width = max_x - dst->expose.x; + dst->expose.height = max_y - dst->expose.y; + } +} + +static void +handleSelectionNotify(const PuglWorld* world, PuglView* view) +{ + uint8_t* str = NULL; + Atom type = 0; + int fmt = 0; + unsigned long len = 0; + unsigned long left = 0; + + XGetWindowProperty(world->impl->display, + view->impl->win, + XA_PRIMARY, + 0, + 0x1FFFFFFF, + False, + AnyPropertyType, + &type, + &fmt, + &len, + &left, + &str); + + if (str && fmt == 8 && type == world->impl->atoms.UTF8_STRING && + left == 0) { + puglSetBlob(&view->clipboard, str, len); + } + + XFree(str); +} + +static void +handleSelectionRequest(const PuglWorld* world, + PuglView* view, + const XSelectionRequestEvent* request) +{ + XSelectionEvent note = {SelectionNotify, + request->serial, + False, + world->impl->display, + request->requestor, + request->selection, + request->target, + None, + request->time}; + + const char* type = NULL; + size_t len = 0; + const void* data = puglGetInternalClipboard(view, &type, &len); + if (data && request->selection == world->impl->atoms.CLIPBOARD && + request->target == world->impl->atoms.UTF8_STRING) { + note.property = request->property; + XChangeProperty(world->impl->display, + note.requestor, + note.property, + note.target, + 8, + PropModeReplace, + (const uint8_t*)data, + (int)len); + } else { + note.property = None; + } + + XSendEvent(world->impl->display, note.requestor, True, 0, (XEvent*)¬e); +} + +/// Flush pending configure and expose events for all views +static void +flushExposures(PuglWorld* world) +{ + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* const view = world->views[i]; + + if (view->visible) { + puglDispatchSimpleEvent(view, PUGL_UPDATE); + } + + const PuglEvent configure = view->impl->pendingConfigure; + const PuglEvent expose = view->impl->pendingExpose; + + view->impl->pendingConfigure.type = PUGL_NOTHING; + view->impl->pendingExpose.type = PUGL_NOTHING; + + if (configure.type || expose.type) { + view->backend->enter(view, expose.type ? &expose.expose : NULL); + puglDispatchEventInContext(view, &configure); + puglDispatchEventInContext(view, &expose); + view->backend->leave(view, expose.type ? &expose.expose : NULL); + } + } +} + +static bool +handleTimerEvent(PuglWorld* world, XEvent xevent) +{ +#ifdef HAVE_XSYNC + if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) { + XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent); + + for (size_t i = 0; i < world->impl->numTimers; ++i) { + if (world->impl->timers[i].alarm == notify->alarm) { + PuglEvent event = {{PUGL_TIMER, 0}}; + event.timer.id = world->impl->timers[i].id; + puglDispatchEvent(world->impl->timers[i].view, + (const PuglEvent*)&event); + } + } + + return true; + } +#else + (void)world; + (void)xevent; +#endif + + return false; +} + +static PuglStatus +puglDispatchX11Events(PuglWorld* world) +{ + const PuglX11Atoms* const atoms = &world->impl->atoms; + + // Flush output to the server once at the start + Display* display = world->impl->display; + XFlush(display); + + // Process all queued events (without further flushing) + while (XEventsQueued(display, QueuedAfterReading) > 0) { + XEvent xevent; + XNextEvent(display, &xevent); + + if (handleTimerEvent(world, xevent)) { + continue; + } + + PuglView* view = puglFindView(world, xevent.xany.window); + if (!view) { + continue; + } + + // Handle special events + PuglInternals* const impl = view->impl; + if (xevent.type == KeyRelease && view->hints[PUGL_IGNORE_KEY_REPEAT]) { + XEvent next; + if (XCheckTypedWindowEvent(display, impl->win, KeyPress, &next) && + next.type == KeyPress && + next.xkey.time == xevent.xkey.time && + next.xkey.keycode == xevent.xkey.keycode) { + continue; + } + } else if (xevent.type == FocusIn) { + XSetICFocus(impl->xic); + } else if (xevent.type == FocusOut) { + XUnsetICFocus(impl->xic); + } else if (xevent.type == SelectionClear) { + puglSetBlob(&view->clipboard, NULL, 0); + } else if (xevent.type == SelectionNotify && + xevent.xselection.selection == atoms->CLIPBOARD && + xevent.xselection.target == atoms->UTF8_STRING && + xevent.xselection.property == XA_PRIMARY) { + handleSelectionNotify(world, view); + } else if (xevent.type == SelectionRequest) { + handleSelectionRequest(world, view, &xevent.xselectionrequest); + } + + // Translate X11 event to Pugl event + const PuglEvent event = translateEvent(view, xevent); + + if (event.type == PUGL_EXPOSE) { + // Expand expose event to be dispatched after loop + mergeExposeEvents(&view->impl->pendingExpose, &event); + } else if (event.type == PUGL_CONFIGURE) { + // Expand configure event to be dispatched after loop + view->impl->pendingConfigure = event; + view->frame.x = event.configure.x; + view->frame.y = event.configure.y; + view->frame.width = event.configure.width; + view->frame.height = event.configure.height; + } else if (event.type == PUGL_MAP && view->parent) { + XWindowAttributes attrs; + XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); + + const PuglEventConfigure configure = { + PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height}; + + puglDispatchEvent(view, (const PuglEvent*)&configure); + puglDispatchEvent(view, &event); + } else { + // Dispatch event to application immediately + puglDispatchEvent(view, &event); + } + } + + return PUGL_SUCCESS; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglUpdate(view->world, 0.0); +} +#endif + +PuglStatus +puglUpdate(PuglWorld* world, double timeout) +{ + const double startTime = puglGetTime(world); + PuglStatus st = PUGL_SUCCESS; + + world->impl->dispatchingEvents = true; + + if (timeout < 0.0) { + st = puglPollX11Socket(world, timeout); + st = st ? st : puglDispatchX11Events(world); + } else if (timeout <= 0.001) { + st = puglDispatchX11Events(world); + } else { + const double endTime = startTime + timeout - 0.001; + for (double t = startTime; t < endTime; t = puglGetTime(world)) { + if ((st = puglPollX11Socket(world, endTime - t)) || + (st = puglDispatchX11Events(world))) { + break; + } + } + } + + flushExposures(world); + + world->impl->dispatchingEvents = false; + + return st; +} + +double +puglGetTime(const PuglWorld* world) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) - + world->startTime; +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + const PuglRect rect = { 0, 0, view->frame.width, view->frame.height }; + + return puglPostRedisplayRect(view, rect); +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, PuglRect rect) +{ + const PuglEventExpose event = { + PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height + }; + + if (view->world->impl->dispatchingEvents) { + // Currently dispatching events, add/expand expose for the loop end + mergeExposeEvents(&view->impl->pendingExpose, (const PuglEvent*)&event); + } else if (view->visible) { + // Not dispatching events, send an X expose so we wake up next time + return puglSendEvent(view, (const PuglEvent*)&event); + } + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->win; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + Display* display = view->world->impl->display; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + puglSetString(&view->title, title); + + if (view->impl->win) { + XStoreName(display, view->impl->win, title); + XChangeProperty(display, view->impl->win, atoms->NET_WM_NAME, + atoms->UTF8_STRING, 8, PropModeReplace, + (const uint8_t*)title, (int)strlen(title)); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + if (view->impl->win) { + if (!XMoveResizeWindow(view->world->impl->display, + view->impl->win, + (int)frame.x, + (int)frame.y, + (unsigned)frame.width, + (unsigned)frame.height)) { + return PUGL_UNKNOWN_ERROR; + } + } + + view->frame = frame; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetDefaultSize(PuglView* const view, const int width, const int height) +{ + view->defaultWidth = width; + view->defaultHeight = height; + return updateSizeHints(view); +} + +PuglStatus +puglSetMinSize(PuglView* const view, const int width, const int height) +{ + view->minWidth = width; + view->minHeight = height; + return updateSizeHints(view); +} + +PuglStatus +puglSetMaxSize(PuglView* const view, const int width, const int height) +{ + view->minWidth = width; + view->minHeight = height; + return updateSizeHints(view); +} + +PuglStatus +puglSetAspectRatio(PuglView* const view, + const int minX, + const int minY, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + + return updateSizeHints(view); +} + +PuglStatus +puglSetTransientFor(PuglView* view, PuglNativeView parent) +{ + Display* display = view->world->impl->display; + + view->transientParent = parent; + + if (view->impl->win) { + XSetTransientForHint(display, view->impl->win, + (Window)view->transientParent); + } + + return PUGL_SUCCESS; +} + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD); + if (owner != None && owner != impl->win) { + // Clear internal selection + puglSetBlob(&view->clipboard, NULL, 0); + + // Request selection from the owner + XConvertSelection(impl->display, + atoms->CLIPBOARD, + atoms->UTF8_STRING, + XA_PRIMARY, + impl->win, + CurrentTime); + + // Run event loop until data is received + while (!view->clipboard.data) { + puglUpdate(view->world, -1.0); + } + } + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime); + return PUGL_SUCCESS; +} + +#ifdef HAVE_XCURSOR +static const unsigned cursor_nums[] = { + XC_arrow, // ARROW + XC_xterm, // CARET + XC_crosshair, // CROSSHAIR + XC_hand2, // HAND + XC_pirate, // NO + XC_sb_h_double_arrow, // LEFT_RIGHT + XC_sb_v_double_arrow, // UP_DOWN +}; +#endif + +PuglStatus +puglSetCursor(PuglView* view, PuglCursor cursor) +{ +#ifdef HAVE_XCURSOR + PuglInternals* const impl = view->impl; + const unsigned index = (unsigned)cursor; + const unsigned count = sizeof(cursor_nums) / sizeof(cursor_nums[0]); + if (index >= count) { + return PUGL_BAD_PARAMETER; + } + + const unsigned shape = cursor_nums[index]; + if (!impl->win || impl->cursorShape == shape) { + return PUGL_SUCCESS; + } + + impl->cursorShape = cursor_nums[index]; + + return puglDefineCursorShape(view, impl->cursorShape); +#else + (void)view; + (void)cursor; + return PUGL_FAILURE; +#endif +} diff --git a/src/x11.h b/src/x11.h new file mode 100644 index 0000000..3705cd9 --- /dev/null +++ b/src/x11.h @@ -0,0 +1,82 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.h + @brief Shared definitions for X11 implementation. +*/ + +#ifndef PUGL_DETAIL_X11_H +#define PUGL_DETAIL_X11_H + +#include "types.h" + +#include "pugl/pugl.h" + +#include +#include +#include + +#include +#include +#include + +typedef struct { + Atom CLIPBOARD; + Atom UTF8_STRING; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom PUGL_CLIENT_MSG; + Atom NET_WM_NAME; + Atom NET_WM_STATE; + Atom NET_WM_STATE_DEMANDS_ATTENTION; +} PuglX11Atoms; + +typedef struct { + XID alarm; + PuglView* view; + uintptr_t id; +} PuglTimer; + +struct PuglWorldInternalsImpl { + Display* display; + PuglX11Atoms atoms; + XIM xim; + PuglTimer* timers; + size_t numTimers; + XID serverTimeCounter; + int syncEventBase; + bool syncSupported; + bool dispatchingEvents; +}; + +struct PuglInternalsImpl { + Display* display; + XVisualInfo* vi; + Window win; + XIC xic; + PuglSurface* surface; + PuglEvent pendingConfigure; + PuglEvent pendingExpose; + int screen; +#ifdef HAVE_XCURSOR + unsigned cursorShape; +#endif +}; + +PuglStatus puglX11StubConfigure(PuglView* view); + +#endif // PUGL_DETAIL_X11_H diff --git a/src/x11_cairo.c b/src/x11_cairo.c new file mode 100644 index 0000000..f0b38a9 --- /dev/null +++ b/src/x11_cairo.c @@ -0,0 +1,171 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11_cairo.c + @brief Cairo graphics backend for X11. +*/ + +#include "types.h" +#include "x11.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_cairo.h" + +#include +#include +#include + +#include + +typedef struct { + cairo_surface_t* back; + cairo_surface_t* front; + cairo_t* cr; +} PuglX11CairoSurface; + +static void +puglX11CairoClose(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + cairo_surface_destroy(surface->front); + cairo_surface_destroy(surface->back); + surface->front = surface->back = NULL; +} + +static PuglStatus +puglX11CairoOpen(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + surface->back = cairo_xlib_surface_create(impl->display, + impl->win, + impl->vi->visual, + (int)view->frame.width, + (int)view->frame.height); + + surface->front = cairo_surface_create_similar( + surface->back, + cairo_surface_get_content(surface->back), + (int)view->frame.width, + (int)view->frame.height); + + if (cairo_surface_status(surface->back) || + cairo_surface_status(surface->front)) { + puglX11CairoClose(view); + return PUGL_CREATE_CONTEXT_FAILED; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + + impl->surface = (cairo_surface_t*)calloc(1, sizeof(PuglX11CairoSurface)); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + puglX11CairoClose(view); + free(surface); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + PuglStatus st = PUGL_SUCCESS; + + if (expose && !(st = puglX11CairoOpen(view))) { + surface->cr = cairo_create(surface->front); + + if (cairo_status(surface->cr)) { + st = PUGL_CREATE_CONTEXT_FAILED; + } + } + + return st; +} + +static PuglStatus +puglX11CairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + if (expose) { + // Destroy front context and create a new one for drawing to the back + cairo_destroy(surface->cr); + surface->cr = cairo_create(surface->back); + + // Clip to expose region + cairo_rectangle(surface->cr, + expose->x, + expose->y, + expose->width, + expose->height); + cairo_clip(surface->cr); + + // Paint front onto back + cairo_set_source_surface(surface->cr, surface->front, 0, 0); + cairo_paint(surface->cr); + + // Flush to X and close everything + cairo_destroy(surface->cr); + cairo_surface_flush(surface->back); + puglX11CairoClose(view); + surface->cr = NULL; + } + + return PUGL_SUCCESS; +} + +static void* +puglX11CairoGetContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + return surface->cr; +} + +const PuglBackend* +puglCairoBackend(void) +{ + static const PuglBackend backend = {puglX11StubConfigure, + puglX11CairoCreate, + puglX11CairoDestroy, + puglX11CairoEnter, + puglX11CairoLeave, + puglX11CairoGetContext}; + + return &backend; +} diff --git a/src/x11_gl.c b/src/x11_gl.c new file mode 100644 index 0000000..72c2421 --- /dev/null +++ b/src/x11_gl.c @@ -0,0 +1,240 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11_gl.c + @brief OpenGL graphics backend for X11. +*/ + +#include "stub.h" +#include "types.h" +#include "x11.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_gl.h" + +#include +#include +#include +#include + +#include +#include +#include + +typedef struct { + GLXFBConfig fb_config; + GLXContext ctx; +} PuglX11GlSurface; + +static int +puglX11GlHintValue(const int value) +{ + return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value; +} + +static int +puglX11GlGetAttrib(Display* const display, + GLXFBConfig fb_config, + const int attrib) +{ + int value = 0; + glXGetFBConfigAttrib(display, fb_config, attrib, &value); + return value; +} + +static PuglStatus +puglX11GlConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + const int screen = impl->screen; + Display* const display = impl->display; + + PuglX11GlSurface* const surface = + (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface)); + impl->surface = surface; + + const int attrs[] = { + GLX_X_RENDERABLE, True, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]), + GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]), + GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]), + GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]), + GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]), + GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]), + GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]), + GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), + None + }; + + int n_fbc = 0; + GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc); + if (n_fbc <= 0) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + surface->fb_config = fbc[0]; + impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]); + + view->hints[PUGL_RED_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_RED_SIZE); + view->hints[PUGL_GREEN_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_GREEN_SIZE); + view->hints[PUGL_BLUE_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_BLUE_SIZE); + view->hints[PUGL_ALPHA_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_ALPHA_SIZE); + view->hints[PUGL_DEPTH_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_DEPTH_SIZE); + view->hints[PUGL_STENCIL_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_STENCIL_SIZE); + view->hints[PUGL_SAMPLES] = puglX11GlGetAttrib( + display, fbc[0], GLX_SAMPLES); + view->hints[PUGL_DOUBLE_BUFFER] = puglX11GlGetAttrib( + display, fbc[0], GLX_DOUBLEBUFFER); + + char msg[256]; + + snprintf( + msg, + sizeof(msg), + "Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d DOUBLE=%d SAMPLES=%d\n", + impl->vi->visualid, + puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER), + puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES)); + + view->world->logFunc(view->world, PUGL_LOG_LEVEL_INFO, msg); + + XFree(fbc); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose)) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx); + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11GlLeave(PuglView* view, const PuglEventExpose* expose) +{ + if (expose && view->hints[PUGL_DOUBLE_BUFFER]) { + glXSwapBuffers(view->impl->display, view->impl->win); + } + + glXMakeCurrent(view->impl->display, None, NULL); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11GlCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface; + Display* const display = impl->display; + GLXFBConfig fb_config = surface->fb_config; + + const int ctx_attrs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MAJOR], + GLX_CONTEXT_MINOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MINOR], + GLX_CONTEXT_FLAGS_ARB, (view->hints[PUGL_USE_DEBUG_CONTEXT] + ? GLX_CONTEXT_DEBUG_BIT_ARB + : 0), + GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints[PUGL_USE_COMPAT_PROFILE] + ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB + : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), + 0}; + + PFNGLXCREATECONTEXTATTRIBSARBPROC create_context = + (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress( + (const uint8_t*)"glXCreateContextAttribsARB"); + + PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = + (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress( + (const uint8_t*)"glXSwapIntervalEXT"); + + surface->ctx = create_context(display, fb_config, 0, True, ctx_attrs); + if (!surface->ctx) { + surface->ctx = + glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); + } + + if (!surface->ctx) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + const int swapInterval = view->hints[PUGL_SWAP_INTERVAL]; + if (glXSwapIntervalEXT && swapInterval != PUGL_DONT_CARE) { + puglX11GlEnter(view, NULL); + glXSwapIntervalEXT(display, impl->win, swapInterval); + puglX11GlLeave(view, NULL); + } + + glXGetConfig(impl->display, + impl->vi, + GLX_DOUBLEBUFFER, + &view->hints[PUGL_DOUBLE_BUFFER]); + + glXQueryDrawable(display, + impl->win, + GLX_SWAP_INTERVAL_EXT, + (unsigned int*)&view->hints[PUGL_SWAP_INTERVAL]); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11GlDestroy(PuglView* view) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + if (surface) { + glXDestroyContext(view->impl->display, surface->ctx); + free(surface); + view->impl->surface = NULL; + } + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + return glXGetProcAddress((const uint8_t*)name); +} + +const PuglBackend* +puglGlBackend(void) +{ + static const PuglBackend backend = {puglX11GlConfigure, + puglX11GlCreate, + puglX11GlDestroy, + puglX11GlEnter, + puglX11GlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/src/x11_stub.c b/src/x11_stub.c new file mode 100644 index 0000000..0f2e6d2 --- /dev/null +++ b/src/x11_stub.c @@ -0,0 +1,58 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "pugl/pugl_stub.h" + +#include "stub.h" +#include "types.h" +#include "x11.h" + +#include "pugl/pugl.h" + +#include + +PuglStatus +puglX11StubConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + XVisualInfo pat = {0}; + int n = 0; + + pat.screen = impl->screen; + impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); + + view->hints[PUGL_RED_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_GREEN_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_BLUE_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_ALPHA_BITS] = 0; + + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = { + puglX11StubConfigure, + puglStubCreate, + puglStubDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext, + }; + + return &backend; +} diff --git a/src/x11_vulkan.c b/src/x11_vulkan.c new file mode 100644 index 0000000..1ca2ace --- /dev/null +++ b/src/x11_vulkan.c @@ -0,0 +1,135 @@ +/* + Copyright 2012-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11_vulkan.c Vulkan graphics backend for X11. +*/ + +#define VK_NO_PROTOTYPES 1 + +#include "stub.h" +#include "types.h" +#include "x11.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_vulkan.h" + +#include +#include + +#include + +#include +#include + +struct PuglVulkanLoaderImpl +{ + void* libvulkan; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; +}; + +PuglVulkanLoader* +puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) +{ + PuglVulkanLoader* loader = + (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); + if (!loader) { + return NULL; + } + + if (!(loader->libvulkan = dlopen("libvulkan.so", RTLD_LAZY))) { + free(loader); + return NULL; + } + + loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym( + loader->libvulkan, "vkGetInstanceProcAddr"); + + loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)dlsym( + loader->libvulkan, "vkGetDeviceProcAddr"); + + return loader; +} + +void +puglFreeVulkanLoader(PuglVulkanLoader* loader) +{ + if (loader) { + dlclose(loader->libvulkan); + free(loader); + } +} + +PFN_vkGetInstanceProcAddr +puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetInstanceProcAddr; +} + +PFN_vkGetDeviceProcAddr +puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) +{ + return loader->vkGetDeviceProcAddr; +} + +const PuglBackend* +puglVulkanBackend(void) +{ + static const PuglBackend backend = {puglX11StubConfigure, + puglStubCreate, + puglStubDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext}; + + return &backend; +} + +const char* const* +puglGetInstanceExtensions(uint32_t* const count) +{ + static const char* const extensions[] = {"VK_KHR_surface", + "VK_KHR_xlib_surface"}; + + *count = 2; + return extensions; +} + +VkResult +puglCreateSurface(const PuglVulkanLoader* const loader, + PuglView* const view, + VkInstance instance, + const VkAllocationCallbacks* const allocator, + VkSurfaceKHR* const surface) +{ + PuglInternals* const impl = view->impl; + PuglWorldInternals* world_impl = view->world->impl; + + PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = + (PFN_vkCreateXlibSurfaceKHR)puglGetInstanceProcAddrFunc(loader)( + instance, "vkCreateXlibSurfaceKHR"); + + const VkXlibSurfaceCreateInfoKHR info = { + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + NULL, + 0, + world_impl->display, + impl->win, + }; + + return vkCreateXlibSurfaceKHR(instance, &info, allocator, surface); +} diff --git a/wscript b/wscript index 205072b..93ba6ba 100644 --- a/wscript +++ b/wscript @@ -55,7 +55,6 @@ def configure(conf): if 'COMPILER_CXX' in conf.env: autowaf.set_cxx_lang(conf, 'c++11') - conf.env.ALL_HEADERS = Options.options.all_headers conf.env.TARGET_PLATFORM = Options.options.target or sys.platform platform = conf.env.TARGET_PLATFORM @@ -357,12 +356,6 @@ def build(bld): bld.install_files(includedir, bld.path.ant_glob('include/pugl/*.h')) bld.install_files(includedir, bld.path.ant_glob('include/pugl/*.hpp')) bld.install_files(includedir, bld.path.ant_glob('include/pugl/*.ipp')) - if bld.env.ALL_HEADERS: - detaildir = os.path.join(includedir, 'detail') - bld.install_files(detaildir, - bld.path.ant_glob('include/pugl/detail/*.h')) - bld.install_files(detaildir, - bld.path.ant_glob('include/pugl/detail/*.c')) # Library dependencies of pugl libraries (for building examples) deps = {} @@ -418,86 +411,82 @@ def build(bld): deps=kwargs, requires=['pugl-%s' % PUGL_MAJOR_VERSION]) - lib_source = ['include/pugl/detail/implementation.c'] + lib_source = ['src/implementation.c'] if bld.env.TARGET_PLATFORM == 'win32': platform = 'win' build_platform('win', uselib=['GDI32', 'USER32'], - source=lib_source + ['include/pugl/detail/win.c']) + source=lib_source + ['src/win.c']) build_backend('win', 'stub', uselib=['GDI32', 'USER32'], - source=['include/pugl/detail/win_stub.c']) + source=['src/win_stub.c']) if bld.env.HAVE_GL: build_backend('win', 'gl', uselib=['GDI32', 'USER32', 'GL'], - source=['include/pugl/detail/win_gl.c']) + source=['src/win_gl.c']) if bld.env.HAVE_VULKAN: build_backend('win', 'vulkan', uselib=['GDI32', 'USER32', 'VULKAN'], - source=['include/pugl/detail/win_vulkan.c', - 'include/pugl/detail/win_stub.c']) + source=['src/win_vulkan.c', 'src/win_stub.c']) if bld.env.HAVE_CAIRO: build_backend('win', 'cairo', uselib=['CAIRO', 'GDI32', 'USER32'], - source=['include/pugl/detail/win_cairo.c', - 'include/pugl/detail/win_stub.c']) + source=['src/win_cairo.c', 'src/win_stub.c']) elif bld.env.TARGET_PLATFORM == 'darwin': platform = 'mac' build_platform('mac', framework=['Cocoa', 'Corevideo'], - source=lib_source + ['include/pugl/detail/mac.m']) + source=lib_source + ['src/mac.m']) build_backend('mac', 'stub', framework=['Cocoa', 'Corevideo'], - source=['include/pugl/detail/mac_stub.m']) + source=['src/mac_stub.m']) if bld.env.HAVE_GL: build_backend('mac', 'gl', framework=['Cocoa', 'Corevideo', 'OpenGL'], - source=['include/pugl/detail/mac_gl.m']) + source=['src/mac_gl.m']) if bld.env.HAVE_VULKAN: build_backend('mac', 'vulkan', framework=['Cocoa', 'QuartzCore'], - source=['include/pugl/detail/mac_vulkan.m']) + source=['src/mac_vulkan.m']) if bld.env.HAVE_CAIRO: build_backend('mac', 'cairo', framework=['Cocoa', 'Corevideo'], uselib=['CAIRO'], - source=['include/pugl/detail/mac_cairo.m']) + source=['src/mac_cairo.m']) else: platform = 'x11' build_platform('x11', uselib=['M', 'X11', 'XSYNC', 'XCURSOR', 'XRANDR'], - source=lib_source + ['include/pugl/detail/x11.c']) + source=lib_source + ['src/x11.c']) build_backend('x11', 'stub', uselib=['X11'], - source=['include/pugl/detail/x11_stub.c']) + source=['src/x11_stub.c']) if bld.env.HAVE_GL: glx_lib = 'GLX' if bld.env.LIB_GLX else 'GL' build_backend('x11', 'gl', uselib=[glx_lib, 'X11'], - source=['include/pugl/detail/x11_gl.c']) + source=['src/x11_gl.c']) if bld.env.HAVE_VULKAN: build_backend('x11', 'vulkan', uselib=['DL', 'X11'], - source=['include/pugl/detail/x11_vulkan.c', - 'include/pugl/detail/x11_stub.c']) + source=['src/x11_vulkan.c', 'src/x11_stub.c']) if bld.env.HAVE_CAIRO: build_backend('x11', 'cairo', uselib=['CAIRO', 'X11'], - source=['include/pugl/detail/x11_cairo.c', - 'include/pugl/detail/x11_stub.c']) + source=['src/x11_cairo.c', 'src/x11_stub.c']) def build_example(prog, source, platform, backend, **kwargs): lang = 'cxx' if source[0].endswith('.cpp') else 'c' @@ -695,9 +684,9 @@ def lint(ctx): "-Xiwyu", "--check_also=pugl/*.h", "-Xiwyu", "--check_also=pugl/*.hpp", "-Xiwyu", "--check_also=pugl/*.ipp", - "-Xiwyu", "--check_also=pugl/detail/*.c", - "-Xiwyu", "--check_also=pugl/detail/*.h", - "-Xiwyu", "--check_also=pugl/detail/*.m"] + "-Xiwyu", "--check_also=src/*.c", + "-Xiwyu", "--check_also=src/*.h", + "-Xiwyu", "--check_also=src/*.m"] output = subprocess.check_output(cmd).decode('utf-8') if 'error: ' in output: sys.stdout.write(output) -- cgit v1.2.1