aboutsummaryrefslogtreecommitdiffstats
path: root/include/pugl
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2020-10-21 12:14:26 +0200
committerDavid Robillard <d@drobilla.net>2020-10-21 15:43:29 +0200
commit380edd6da07fb1fd6c89c046190dad3b7fd95386 (patch)
tree889969700d44e36ffbaafa053c293eec2a453bca /include/pugl
parente78e00a46f84ec6e528a7435575e6434813ba67c (diff)
downloadpugl-380edd6da07fb1fd6c89c046190dad3b7fd95386.tar.gz
pugl-380edd6da07fb1fd6c89c046190dad3b7fd95386.tar.bz2
pugl-380edd6da07fb1fd6c89c046190dad3b7fd95386.zip
Move includes to a separate directory
This is more conventional and directories named "include" are specially understood by some tooling like clang-tidy.
Diffstat (limited to 'include/pugl')
-rw-r--r--include/pugl/detail/implementation.c493
-rw-r--r--include/pugl/detail/implementation.h77
-rw-r--r--include/pugl/detail/mac.h55
-rw-r--r--include/pugl/detail/mac.m1459
-rw-r--r--include/pugl/detail/mac_cairo.m164
-rw-r--r--include/pugl/detail/mac_gl.m201
-rw-r--r--include/pugl/detail/mac_stub.m96
-rw-r--r--include/pugl/detail/stub.h75
-rw-r--r--include/pugl/detail/types.h118
-rw-r--r--include/pugl/detail/win.c1152
-rw-r--r--include/pugl/detail/win.h147
-rw-r--r--include/pugl/detail/win_cairo.c178
-rw-r--r--include/pugl/detail/win_gl.c325
-rw-r--r--include/pugl/detail/win_stub.c80
-rw-r--r--include/pugl/detail/x11.c1348
-rw-r--r--include/pugl/detail/x11.h76
-rw-r--r--include/pugl/detail/x11_cairo.c170
-rw-r--r--include/pugl/detail/x11_gl.c238
-rw-r--r--include/pugl/detail/x11_stub.c54
-rw-r--r--include/pugl/gl.h36
-rw-r--r--include/pugl/glu.h36
-rw-r--r--include/pugl/pugl.h1624
-rw-r--r--include/pugl/pugl.hpp726
-rw-r--r--include/pugl/pugl.ipp154
-rw-r--r--include/pugl/pugl_cairo.h50
-rw-r--r--include/pugl/pugl_cairo.hpp50
-rw-r--r--include/pugl/pugl_gl.h61
-rw-r--r--include/pugl/pugl_gl.hpp60
-rw-r--r--include/pugl/pugl_stub.h60
-rw-r--r--include/pugl/pugl_stub.hpp50
30 files changed, 9413 insertions, 0 deletions
diff --git a/include/pugl/detail/implementation.c b/include/pugl/detail/implementation.c
new file mode 100644
index 0000000..6a5f932
--- /dev/null
+++ b/include/pugl/detail/implementation.c
@@ -0,0 +1,493 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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);
+}
+
+PuglStatus
+puglEnterContext(PuglView* view, bool drawing)
+{
+ const PuglEventExpose expose = {
+ PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};
+
+ view->backend->enter(view, drawing ? &expose : NULL);
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglLeaveContext(PuglView* view, bool drawing)
+{
+ const PuglEventExpose expose = {
+ PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};
+
+ view->backend->leave(view, drawing ? &expose : NULL);
+
+ return PUGL_SUCCESS;
+}
+
+#endif
+
+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);
+
+ 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 {
+ 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);
+ view->eventFunc(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
new file mode 100644
index 0000000..ff97fef
--- /dev/null
+++ b/include/pugl/detail/implementation.h
@@ -0,0 +1,77 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <stddef.h>
+#include <stdint.h>
+
+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
new file mode 100644
index 0000000..7b64cfe
--- /dev/null
+++ b/include/pugl/detail/mac.h
@@ -0,0 +1,55 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+ Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
+
+ 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.
+*/
+
+#include "pugl/pugl.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <stdint.h>
+
+@interface PuglWrapperView : NSView<NSTextInputClient>
+
+- (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;
+};
diff --git a/include/pugl/detail/mac.m b/include/pugl/detail/mac.m
new file mode 100644
index 0000000..354546a
--- /dev/null
+++ b/include/pugl/detail/mac.m
@@ -0,0 +1,1459 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+ Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
+
+ 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/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/pugl.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <mach/mach_time.h>
+
+#include <stdlib.h>
+
+#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;
+ NSTimer* timer;
+ 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
+{
+ timer = [NSTimer timerWithTimeInterval:(1 / 60.0)
+ target:self
+ selector:@selector(resizeTick)
+ userInfo:nil
+ repeats:YES];
+ [[NSRunLoop currentRunLoop] addTimer:timer
+ forMode:NSRunLoopCommonModes];
+
+ [super viewWillStartLiveResize];
+}
+
+- (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
+{
+ [super viewDidEndLiveResize];
+ [timer invalidate];
+ timer = NULL;
+}
+
+@end
+
+@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
+
+- (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
new file mode 100644
index 0000000..18209d9
--- /dev/null
+++ b/include/pugl/detail/mac_cairo.m
@@ -0,0 +1,164 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ 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 <cairo-quartz.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include <assert.h>
+
+@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
new file mode 100644
index 0000000..4d4f324
--- /dev/null
+++ b/include/pugl/detail/mac_gl.m
@@ -0,0 +1,201 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ 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
new file mode 100644
index 0000000..8271735
--- /dev/null
+++ b/include/pugl/detail/mac_stub.m
@@ -0,0 +1,96 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ 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 <Cocoa/Cocoa.h>
+
+@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/stub.h b/include/pugl/detail/stub.h
new file mode 100644
index 0000000..acd3181
--- /dev/null
+++ b/include/pugl/detail/stub.h
@@ -0,0 +1,75 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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
new file mode 100644
index 0000000..edd2bd0
--- /dev/null
+++ b/include/pugl/detail/types.h
@@ -0,0 +1,118 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+// Unused parameter macro to suppresses warnings and make it impossible to use
+#if defined(__cplusplus)
+# define PUGL_UNUSED(name)
+#elif defined(__GNUC__)
+# 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
new file mode 100644
index 0000000..078b0a0
--- /dev/null
+++ b/include/pugl/detail/win.c
@@ -0,0 +1,1152 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <windows.h>
+#include <windowsx.h>
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wctype.h>
+
+#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_RESIZE_TIMER_ID 9461
+#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;
+ if ((st = view->backend->configure(view))) {
+ return st;
+ } else if ((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 = special + 1u; // Right hand key
+ } else {
+ event->key = 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 int w = size->right - size->left;
+ const int h = size->bottom - size->top;
+ const float a = (float)w / (float)h;
+
+ switch (wParam) {
+ case WMSZ_TOP:
+ size->top = (a < minA ? (LONG)(size->bottom - w * minA) :
+ a > maxA ? (LONG)(size->bottom - w * maxA) :
+ size->top);
+ break;
+ case WMSZ_TOPRIGHT:
+ case WMSZ_RIGHT:
+ case WMSZ_BOTTOMRIGHT:
+ size->right = (a < minA ? (LONG)(size->left + h * minA) :
+ a > maxA ? (LONG)(size->left + h * maxA) :
+ size->right);
+ break;
+ case WMSZ_BOTTOM:
+ size->bottom = (a < minA ? (LONG)(size->top + w * minA) :
+ a > maxA ? (LONG)(size->top + w * maxA) :
+ size->bottom);
+ break;
+ case WMSZ_BOTTOMLEFT:
+ case WMSZ_LEFT:
+ case WMSZ_TOPLEFT:
+ size->left = (a < minA ? (LONG)(size->right - h * minA) :
+ a > maxA ? (LONG)(size->right - h * maxA) :
+ size->left);
+ break;
+ }
+}
+
+static LRESULT
+handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PuglEvent event;
+ void* dummy_ptr = NULL;
+ RECT rect;
+ MINMAXINFO* mmi;
+ POINT pt;
+
+ memset(&event, 0, sizeof(event));
+
+ event.any.type = PUGL_NOTHING;
+ 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:
+ view->impl->resizing = true;
+ SetTimer(view->impl->hwnd,
+ PUGL_RESIZE_TIMER_ID,
+ 1000 / (UINT)view->hints[PUGL_REFRESH_RATE],
+ NULL);
+ break;
+ case WM_TIMER:
+ if (wParam == PUGL_RESIZE_TIMER_ID) {
+ RedrawWindow(view->impl->hwnd, NULL, NULL,
+ RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
+ } else 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:
+ KillTimer(view->impl->hwnd, PUGL_RESIZE_TIMER_ID);
+ view->impl->resizing = false;
+ puglPostRedisplay(view);
+ 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) / (float)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) / (float)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, (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
new file mode 100644
index 0000000..b0d92e0
--- /dev/null
+++ b/include/pugl/detail/win.h
@@ -0,0 +1,147 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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.
+*/
+
+#include "pugl/detail/implementation.h"
+
+#include <windows.h>
+
+#include <stdbool.h>
+
+typedef PIXELFORMATDESCRIPTOR PuglWinPFD;
+
+struct PuglWorldInternalsImpl {
+ double timerFrequency;
+};
+
+struct PuglInternalsImpl {
+ PuglWinPFD pfd;
+ int pfId;
+ HWND hwnd;
+ HCURSOR cursor;
+ HDC hdc;
+ PuglSurface* surface;
+ bool flashing;
+ bool resizing;
+ 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);
diff --git a/include/pugl/detail/win_cairo.c b/include/pugl/detail/win_cairo.c
new file mode 100644
index 0000000..1b9afb9
--- /dev/null
+++ b/include/pugl/detail/win_cairo.c
@@ -0,0 +1,178 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <cairo-win32.h>
+#include <cairo.h>
+
+#include <stdlib.h>
+
+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;
+
+ cairo_status_t st = CAIRO_STATUS_SUCCESS;
+ if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) ||
+ (st = cairo_surface_status(surface->surface)) ||
+ !(surface->cr = cairo_create(surface->surface)) ||
+ (st = 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
new file mode 100644
index 0000000..096c715
--- /dev/null
+++ b/include/pugl/detail/win_gl.c
@@ -0,0 +1,325 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <windows.h>
+
+#include <GL/gl.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#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) {
+ return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED);
+ } else if (!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
new file mode 100644
index 0000000..ab9e6aa
--- /dev/null
+++ b/include/pugl/detail/win_stub.c
@@ -0,0 +1,80 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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/x11.c b/include/pugl/detail/x11.c
new file mode 100644
index 0000000..fd76606
--- /dev/null
+++ b/include/pugl/detail/x11.c
@@ -0,0 +1,1348 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+ Copyright 2013 Robin Gareus <robin@gareus.org>
+ 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 <X11/X.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#ifdef HAVE_XRANDR
+# include <X11/extensions/Xrandr.h>
+#endif
+
+#ifdef HAVE_XSYNC
+# include <X11/extensions/sync.h>
+# include <X11/extensions/syncconst.h>
+#endif
+
+#ifdef HAVE_XCURSOR
+# include <X11/Xcursor/Xcursor.h>
+# include <X11/cursorfont.h>
+#endif
+
+#include <sys/select.h>
+#include <sys/time.h>
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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*)&note);
+}
+
+/// 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
new file mode 100644
index 0000000..b5ca75c
--- /dev/null
+++ b/include/pugl/detail/x11.h
@@ -0,0 +1,76 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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.
+*/
+
+#include "pugl/detail/types.h"
+#include "pugl/pugl.h"
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+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);
diff --git a/include/pugl/detail/x11_cairo.c b/include/pugl/detail/x11_cairo.c
new file mode 100644
index 0000000..0112c4e
--- /dev/null
+++ b/include/pugl/detail/x11_cairo.c
@@ -0,0 +1,170 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <X11/Xutil.h>
+#include <cairo-xlib.h>
+#include <cairo.h>
+
+#include <stdlib.h>
+
+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
new file mode 100644
index 0000000..b756355
--- /dev/null
+++ b/include/pugl/detail/x11_gl.c
@@ -0,0 +1,238 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 <GL/glx.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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
new file mode 100644
index 0000000..8efd68f
--- /dev/null
+++ b/include/pugl/detail/x11_stub.c
@@ -0,0 +1,54 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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"
+
+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/gl.h b/include/pugl/gl.h
new file mode 100644
index 0000000..9f7a741
--- /dev/null
+++ b/include/pugl/gl.h
@@ -0,0 +1,36 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 gl.h
+ @brief Portable header wrapper for gl.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+// IWYU pragma: begin_exports
+
+#ifdef __APPLE__
+# include "OpenGL/gl.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/gl.h"
+#endif
+
+// IWYU pragma: end_exports
diff --git a/include/pugl/glu.h b/include/pugl/glu.h
new file mode 100644
index 0000000..423a917
--- /dev/null
+++ b/include/pugl/glu.h
@@ -0,0 +1,36 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 glu.h
+ @brief Portable header wrapper for glu.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+// IWYU pragma: begin_exports
+
+#ifdef __APPLE__
+# include "OpenGL/glu.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/glu.h"
+#endif
+
+// IWYU pragma: end_exports
diff --git a/include/pugl/pugl.h b/include/pugl/pugl.h
new file mode 100644
index 0000000..ad24c0d
--- /dev/null
+++ b/include/pugl/pugl.h
@@ -0,0 +1,1624 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl.h
+ @brief Pugl API.
+*/
+
+#ifndef PUGL_PUGL_H
+#define PUGL_PUGL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef PUGL_SHARED
+# ifdef _WIN32
+# define PUGL_LIB_IMPORT __declspec(dllimport)
+# define PUGL_LIB_EXPORT __declspec(dllexport)
+# else
+# define PUGL_LIB_IMPORT __attribute__((visibility("default")))
+# define PUGL_LIB_EXPORT __attribute__((visibility("default")))
+# endif
+# ifdef PUGL_INTERNAL
+# define PUGL_API PUGL_LIB_EXPORT
+# else
+# define PUGL_API PUGL_LIB_IMPORT
+# endif
+#else
+# define PUGL_API
+#endif
+
+#ifndef PUGL_DISABLE_DEPRECATED
+# if defined(__clang__)
+# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep)))
+# elif defined(__GNUC__)
+# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep)))
+# else
+# define PUGL_DEPRECATED_BY(rep)
+# endif
+#endif
+
+#if defined(__GNUC__)
+# define PUGL_CONST_FUNC __attribute__((const))
+#else
+# define PUGL_CONST_FUNC
+#endif
+
+#ifdef __cplusplus
+# define PUGL_BEGIN_DECLS extern "C" {
+# define PUGL_END_DECLS }
+#else
+# define PUGL_BEGIN_DECLS
+# define PUGL_END_DECLS
+#endif
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup pugl Pugl
+ A minimal portable API for embeddable GUIs.
+ @{
+
+ @defgroup pugl_c C API
+ Public C API.
+ @{
+*/
+
+/**
+ A rectangle.
+
+ This is used to describe things like view position and size. Pugl generally
+ uses coordinates where the top left corner is 0,0.
+*/
+typedef struct {
+ double x;
+ double y;
+ double width;
+ double height;
+} PuglRect;
+
+/**
+ @defgroup events Events
+
+ Event definitions.
+
+ All updates to the view happen via events, which are dispatched to the
+ view's event function by Pugl. Most events map directly to one from the
+ underlying window system, but some are constructed by Pugl itself so there
+ is not necessarily a direct correspondence.
+
+ @{
+*/
+
+/**
+ Keyboard modifier flags.
+*/
+typedef enum {
+ PUGL_MOD_SHIFT = 1u << 0u, ///< Shift key
+ PUGL_MOD_CTRL = 1u << 1u, ///< Control key
+ PUGL_MOD_ALT = 1u << 2u, ///< Alt/Option key
+ PUGL_MOD_SUPER = 1u << 3u ///< Mod4/Command/Windows key
+} PuglMod;
+
+/**
+ Bitwise OR of #PuglMod values.
+*/
+typedef uint32_t PuglMods;
+
+/**
+ Keyboard key codepoints.
+
+ All keys are identified by a Unicode code point in PuglEventKey::key. This
+ enumeration defines constants for special keys that do not have a standard
+ code point, and some convenience constants for control characters. Note
+ that all keys are handled in the same way, this enumeration is just for
+ convenience when writing hard-coded key bindings.
+
+ Keys that do not have a standard code point use values in the Private Use
+ Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). Applications
+ must take care to not interpret these values beyond key detection, the
+ mapping used here is arbitrary and specific to Pugl.
+*/
+typedef enum {
+ // ASCII control codes
+ PUGL_KEY_BACKSPACE = 0x08,
+ PUGL_KEY_ESCAPE = 0x1B,
+ PUGL_KEY_DELETE = 0x7F,
+
+ // Unicode Private Use Area
+ PUGL_KEY_F1 = 0xE000,
+ PUGL_KEY_F2,
+ PUGL_KEY_F3,
+ PUGL_KEY_F4,
+ PUGL_KEY_F5,
+ PUGL_KEY_F6,
+ PUGL_KEY_F7,
+ PUGL_KEY_F8,
+ PUGL_KEY_F9,
+ PUGL_KEY_F10,
+ PUGL_KEY_F11,
+ PUGL_KEY_F12,
+ PUGL_KEY_LEFT,
+ PUGL_KEY_UP,
+ PUGL_KEY_RIGHT,
+ PUGL_KEY_DOWN,
+ PUGL_KEY_PAGE_UP,
+ PUGL_KEY_PAGE_DOWN,
+ PUGL_KEY_HOME,
+ PUGL_KEY_END,
+ PUGL_KEY_INSERT,
+ PUGL_KEY_SHIFT,
+ PUGL_KEY_SHIFT_L = PUGL_KEY_SHIFT,
+ PUGL_KEY_SHIFT_R,
+ PUGL_KEY_CTRL,
+ PUGL_KEY_CTRL_L = PUGL_KEY_CTRL,
+ PUGL_KEY_CTRL_R,
+ PUGL_KEY_ALT,
+ PUGL_KEY_ALT_L = PUGL_KEY_ALT,
+ PUGL_KEY_ALT_R,
+ PUGL_KEY_SUPER,
+ PUGL_KEY_SUPER_L = PUGL_KEY_SUPER,
+ PUGL_KEY_SUPER_R,
+ PUGL_KEY_MENU,
+ PUGL_KEY_CAPS_LOCK,
+ PUGL_KEY_SCROLL_LOCK,
+ PUGL_KEY_NUM_LOCK,
+ PUGL_KEY_PRINT_SCREEN,
+ PUGL_KEY_PAUSE
+} PuglKey;
+
+/**
+ The type of a PuglEvent.
+*/
+typedef enum {
+ PUGL_NOTHING, ///< No event
+ PUGL_CREATE, ///< View created, a #PuglEventCreate
+ PUGL_DESTROY, ///< View destroyed, a #PuglEventDestroy
+ PUGL_CONFIGURE, ///< View moved/resized, a #PuglEventConfigure
+ PUGL_MAP, ///< View made visible, a #PuglEventMap
+ PUGL_UNMAP, ///< View made invisible, a #PuglEventUnmap
+ PUGL_UPDATE, ///< View ready to draw, a #PuglEventUpdate
+ PUGL_EXPOSE, ///< View must be drawn, a #PuglEventExpose
+ PUGL_CLOSE, ///< View will be closed, a #PuglEventClose
+ PUGL_FOCUS_IN, ///< Keyboard focus entered view, a #PuglEventFocus
+ PUGL_FOCUS_OUT, ///< Keyboard focus left view, a #PuglEventFocus
+ PUGL_KEY_PRESS, ///< Key pressed, a #PuglEventKey
+ PUGL_KEY_RELEASE, ///< Key released, a #PuglEventKey
+ PUGL_TEXT, ///< Character entered, a #PuglEventText
+ PUGL_POINTER_IN, ///< Pointer entered view, a #PuglEventCrossing
+ PUGL_POINTER_OUT, ///< Pointer left view, a #PuglEventCrossing
+ PUGL_BUTTON_PRESS, ///< Mouse button pressed, a #PuglEventButton
+ PUGL_BUTTON_RELEASE, ///< Mouse button released, a #PuglEventButton
+ PUGL_MOTION, ///< Pointer moved, a #PuglEventMotion
+ PUGL_SCROLL, ///< Scrolled, a #PuglEventScroll
+ PUGL_CLIENT, ///< Custom client message, a #PuglEventClient
+ PUGL_TIMER, ///< Timer triggered, a #PuglEventTimer
+
+#ifndef PUGL_DISABLE_DEPRECATED
+ PUGL_ENTER_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_IN") = PUGL_POINTER_IN,
+ PUGL_LEAVE_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_OUT") = PUGL_POINTER_OUT,
+ PUGL_MOTION_NOTIFY PUGL_DEPRECATED_BY("PUGL_MOTION") = PUGL_MOTION,
+#endif
+
+} PuglEventType;
+
+/**
+ Common flags for all event types.
+*/
+typedef enum {
+ PUGL_IS_SEND_EVENT = 1, ///< Event is synthetic
+ PUGL_IS_HINT = 2 ///< Event is a hint (not direct user input)
+} PuglEventFlag;
+
+/**
+ Bitwise OR of #PuglEventFlag values.
+*/
+typedef uint32_t PuglEventFlags;
+
+/**
+ Reason for a PuglEventCrossing.
+*/
+typedef enum {
+ PUGL_CROSSING_NORMAL, ///< Crossing due to pointer motion
+ PUGL_CROSSING_GRAB, ///< Crossing due to a grab
+ PUGL_CROSSING_UNGRAB ///< Crossing due to a grab release
+} PuglCrossingMode;
+
+/**
+ Scroll direction.
+
+ Describes the direction of a #PuglEventScroll along with whether the scroll
+ is a "smooth" scroll. The discrete directions are for devices like mouse
+ wheels with constrained axes, while a smooth scroll is for those with
+ arbitrary scroll direction freedom, like some touchpads.
+*/
+typedef enum {
+ PUGL_SCROLL_UP, ///< Scroll up
+ PUGL_SCROLL_DOWN, ///< Scroll down
+ PUGL_SCROLL_LEFT, ///< Scroll left
+ PUGL_SCROLL_RIGHT, ///< Scroll right
+ PUGL_SCROLL_SMOOTH ///< Smooth scroll in any direction
+} PuglScrollDirection;
+
+/**
+ Common header for all event structs.
+*/
+typedef struct {
+ PuglEventType type; ///< Event type
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+} PuglEventAny;
+
+/**
+ View create event.
+
+ This event is sent when a view is realized before it is first displayed,
+ with the graphics context entered. This is typically used for setting up
+ the graphics system, for example by loading OpenGL extensions.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventCreate;
+
+/**
+ View destroy event.
+
+ This event is the counterpart to #PuglEventCreate, and it is sent when the
+ view is being destroyed. This is typically used for tearing down the
+ graphics system, or otherwise freeing any resources allocated when the
+ create event was handled.
+
+ This is the last event sent to any view, and immediately after it is
+ processed, the view is destroyed and may no longer be used.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventDestroy;
+
+/**
+ View resize or move event.
+
+ A configure event is sent whenever the view is resized or moved. When a
+ configure event is received, the graphics context is active but not set up
+ for drawing. For example, it is valid to adjust the OpenGL viewport or
+ otherwise configure the context, but not to draw anything.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_CONFIGURE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double x; ///< New parent-relative X coordinate
+ double y; ///< New parent-relative Y coordinate
+ double width; ///< New width
+ double height; ///< New height
+} PuglEventConfigure;
+
+/**
+ View show event.
+
+ This event is sent when a view is mapped to the screen and made visible.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventMap;
+
+/**
+ View hide event.
+
+ This event is sent when a view is unmapped from the screen and made
+ invisible.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventUnmap;
+
+/**
+ View update event.
+
+ This event is sent to every view near the end of a main loop iteration when
+ any pending exposures are about to be redrawn. It is typically used to mark
+ regions to expose with puglPostRedisplay() or puglPostRedisplayRect(). For
+ example, to continuously animate, a view calls puglPostRedisplay() when an
+ update event is received, and it will then shortly receive an expose event.
+*/
+typedef PuglEventAny PuglEventUpdate;
+
+/**
+ Expose event for when a region must be redrawn.
+
+ When an expose event is received, the graphics context is active, and the
+ view must draw the entire specified region. The contents of the region are
+ undefined, there is no preservation of anything drawn previously.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_EXPOSE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double width; ///< Width of exposed region
+ double height; ///< Height of exposed region
+} PuglEventExpose;
+
+/**
+ View close event.
+
+ This event is sent when the view is to be closed, for example when the user
+ clicks the close button.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventClose;
+
+/**
+ Keyboard focus event.
+
+ This event is sent whenever the view gains or loses the keyboard focus. The
+ view with the keyboard focus will receive any key press or release events.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ PuglCrossingMode mode; ///< Reason for focus change
+} PuglEventFocus;
+
+/**
+ Key press or release event.
+
+ This event represents low-level key presses and releases. This can be used
+ for "direct" keyboard handing like key bindings, but must not be interpreted
+ as text input.
+
+ Keys are represented portably as Unicode code points, using the "natural"
+ code point for the key where possible (see #PuglKey for details). The `key`
+ field is the code for the pressed key, without any modifiers applied. For
+ example, a press or release of the 'A' key will have `key` 97 ('a')
+ regardless of whether shift or control are being held.
+
+ Alternatively, the raw `keycode` can be used to work directly with physical
+ keys, but note that this value is not portable and differs between platforms
+ and hardware.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_KEY_PRESS or #PUGL_KEY_RELEASE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ uint32_t keycode; ///< Raw key code
+ uint32_t key; ///< Unshifted Unicode character code, or 0
+} PuglEventKey;
+
+/**
+ Character input event.
+
+ This event represents text input, usually as the result of a key press. The
+ text is given both as a Unicode character code and a UTF-8 string.
+
+ Note that this event is generated by the platform's input system, so there
+ is not necessarily a direct correspondence between text events and physical
+ key presses. For example, with some input methods a sequence of several key
+ presses will generate a single character.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_TEXT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ uint32_t keycode; ///< Raw key code
+ uint32_t character; ///< Unicode character code
+ char string[8]; ///< UTF-8 string
+} PuglEventText;
+
+/**
+ Pointer enter or leave event.
+
+ This event is sent when the pointer enters or leaves the view. This can
+ happen for several reasons (not just the user dragging the pointer over the
+ window edge), as described by the `mode` field.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_POINTER_IN or #PUGL_POINTER_OUT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ PuglCrossingMode mode; ///< Reason for crossing
+} PuglEventCrossing;
+
+/**
+ Button press or release event.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_BUTTON_PRESS or #PUGL_BUTTON_RELEASE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ uint32_t button; ///< Button number starting from 1
+} PuglEventButton;
+
+/**
+ Pointer motion event.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_MOTION
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+} PuglEventMotion;
+
+/**
+ Scroll event.
+
+ The scroll distance is expressed in "lines", an arbitrary unit that
+ corresponds to a single tick of a detented mouse wheel. For example, `dy` =
+ 1.0 scrolls 1 line up. Some systems and devices support finer resolution
+ and/or higher values for fast scrolls, so programs should handle any value
+ gracefully.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_SCROLL
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ PuglScrollDirection direction; ///< Scroll direction
+ double dx; ///< Scroll X distance in lines
+ double dy; ///< Scroll Y distance in lines
+} PuglEventScroll;
+
+/**
+ Custom client message event.
+
+ This can be used to send a custom message to a view, which is delivered via
+ the window system and processed in the event loop as usual. Among other
+ things, this makes it possible to wake up the event loop for any reason.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_CLIENT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ uintptr_t data1; ///< Client-specific data
+ uintptr_t data2; ///< Client-specific data
+} PuglEventClient;
+
+/**
+ Timer event.
+
+ This event is sent at the regular interval specified in the call to
+ puglStartTimer() that activated it.
+
+ The `id` is the application-specific ID given to puglStartTimer() which
+ distinguishes this timer from others. It should always be checked in the
+ event handler, even in applications that register only one timer.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_TIMER
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ uintptr_t id; ///< Timer ID
+} PuglEventTimer;
+
+/**
+ View event.
+
+ This is a union of all event types. The type must be checked to determine
+ which fields are safe to access. A pointer to PuglEvent can either be cast
+ to the appropriate type, or the union members used.
+
+ The graphics system may only be accessed when handling certain events. The
+ graphics context is active for #PUGL_CREATE, #PUGL_DESTROY, #PUGL_CONFIGURE,
+ and #PUGL_EXPOSE, but only enabled for drawing for #PUGL_EXPOSE.
+*/
+typedef union {
+ PuglEventAny any; ///< Valid for all event types
+ PuglEventType type; ///< Event type
+ PuglEventButton button; ///< #PUGL_BUTTON_PRESS, #PUGL_BUTTON_RELEASE
+ PuglEventConfigure configure; ///< #PUGL_CONFIGURE
+ PuglEventExpose expose; ///< #PUGL_EXPOSE
+ PuglEventKey key; ///< #PUGL_KEY_PRESS, #PUGL_KEY_RELEASE
+ PuglEventText text; ///< #PUGL_TEXT
+ PuglEventCrossing crossing; ///< #PUGL_POINTER_IN, #PUGL_POINTER_OUT
+ PuglEventMotion motion; ///< #PUGL_MOTION
+ PuglEventScroll scroll; ///< #PUGL_SCROLL
+ PuglEventFocus focus; ///< #PUGL_FOCUS_IN, #PUGL_FOCUS_OUT
+ PuglEventClient client; ///< #PUGL_CLIENT
+ PuglEventTimer timer; ///< #PUGL_TIMER
+} PuglEvent;
+
+/**
+ @}
+ @defgroup status Status
+
+ Status codes and error handling.
+
+ @{
+*/
+
+/**
+ Return status code.
+*/
+typedef enum {
+ PUGL_SUCCESS, ///< Success
+ PUGL_FAILURE, ///< Non-fatal failure
+ PUGL_UNKNOWN_ERROR, ///< Unknown system error
+ PUGL_BAD_BACKEND, ///< Invalid or missing backend
+ PUGL_BAD_CONFIGURATION, ///< Invalid view configuration
+ PUGL_BAD_PARAMETER, ///< Invalid parameter
+ PUGL_BACKEND_FAILED, ///< Backend initialisation failed
+ PUGL_REGISTRATION_FAILED, ///< Class registration failed
+ PUGL_REALIZE_FAILED, ///< System view realization failed
+ PUGL_SET_FORMAT_FAILED, ///< Failed to set pixel format
+ PUGL_CREATE_CONTEXT_FAILED, ///< Failed to create drawing context
+ PUGL_UNSUPPORTED_TYPE, ///< Unsupported data type
+} PuglStatus;
+
+/**
+ Return a string describing a status code.
+*/
+PUGL_API PUGL_CONST_FUNC
+const char*
+puglStrerror(PuglStatus status);
+
+/**
+ @}
+ @defgroup world World
+
+ The top-level context of a Pugl application or plugin.
+
+ The world contains all library-wide state. There is no static data in Pugl,
+ so it is safe to use multiple worlds in a single process. This is to
+ facilitate plugins or other situations where it is not possible to share a
+ world, but a single world should be shared for all views where possible.
+
+ @{
+*/
+
+/**
+ The "world" of application state.
+
+ The world represents everything that is not associated with a particular
+ view. Several worlds can be created in a single process, but code using
+ different worlds must be isolated so they are never mixed. Views are
+ strongly associated with the world they were created in.
+*/
+typedef struct PuglWorldImpl PuglWorld;
+
+/**
+ Handle for the world's opaque user data.
+*/
+typedef void* PuglWorldHandle;
+
+/**
+ The type of a World.
+*/
+typedef enum {
+ PUGL_PROGRAM, ///< Top-level application
+ PUGL_MODULE ///< Plugin or module within a larger application
+} PuglWorldType;
+
+/**
+ World flags.
+*/
+typedef enum {
+ /**
+ Set up support for threads if necessary.
+
+ - X11: Calls XInitThreads() which is required for some drivers.
+ */
+ PUGL_WORLD_THREADS = 1u << 0u
+} PuglWorldFlag;
+
+/**
+ Bitwise OR of #PuglWorldFlag values.
+*/
+typedef uint32_t PuglWorldFlags;
+
+/**
+ A log message level, compatible with syslog.
+*/
+typedef enum {
+ PUGL_LOG_LEVEL_ERR = 3, ///< Error
+ PUGL_LOG_LEVEL_WARNING = 4, ///< Warning
+ PUGL_LOG_LEVEL_INFO = 6, ///< Informational message
+ PUGL_LOG_LEVEL_DEBUG = 7 ///< Debug message
+} PuglLogLevel;
+
+/**
+ A function called to report log messages.
+
+ @param world The world that produced this log message.
+ @param level Log level.
+ @param msg Message string.
+*/
+typedef void (*PuglLogFunc)(PuglWorld* world,
+ PuglLogLevel level,
+ const char* msg);
+
+/**
+ Create a new world.
+
+ @param type The type, which dictates what this world is responsible for.
+ @param flags Flags to control world features.
+ @return A new world, which must be later freed with puglFreeWorld().
+*/
+PUGL_API PuglWorld*
+puglNewWorld(PuglWorldType type, PuglWorldFlags flags);
+
+/**
+ Free a world allocated with puglNewWorld().
+*/
+PUGL_API void
+puglFreeWorld(PuglWorld* world);
+
+/**
+ Set the user data for the world.
+
+ This is usually a pointer to a struct that contains all the state which must
+ be accessed by several views.
+
+ The handle is opaque to Pugl and is not interpreted in any way.
+*/
+PUGL_API void
+puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle);
+
+/**
+ Get the user data for the world.
+*/
+PUGL_API PuglWorldHandle
+puglGetWorldHandle(PuglWorld* world);
+
+/**
+ Return a pointer to the native handle of the world.
+
+ @return
+ - X11: A pointer to the `Display`.
+ - MacOS: `NULL`.
+ - Windows: The `HMODULE` of the calling process.
+*/
+PUGL_API void*
+puglGetNativeWorld(PuglWorld* world);
+
+/**
+ Set the function to call to log a message.
+
+ This will be called to report any log messages generated internally by Pugl
+ which are enabled according to the log level.
+*/
+PUGL_API PuglStatus
+puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc);
+
+/**
+ Set the level of log messages to emit.
+
+ Any log messages with a level less than or equal to `level` will be emitted.
+*/
+PUGL_API PuglStatus
+puglSetLogLevel(PuglWorld* world, PuglLogLevel level);
+
+/**
+ Set the class name of the application.
+
+ This is a stable identifier for the application, used as the window
+ class/instance name on X11 and Windows. It is not displayed to the user,
+ but can be used in scripts and by window managers, so it should be the same
+ for every instance of the application, but different from other
+ applications.
+*/
+PUGL_API PuglStatus
+puglSetClassName(PuglWorld* world, const char* name);
+
+/**
+ Return the time in seconds.
+
+ This is a monotonically increasing clock with high resolution. The returned
+ time is only useful to compare against other times returned by this
+ function, its absolute value has no meaning.
+*/
+PUGL_API double
+puglGetTime(const PuglWorld* world);
+
+/**
+ Update by processing events from the window system.
+
+ This function is a single iteration of the main loop, and should be called
+ repeatedly to update all views.
+
+ If `timeout` is zero, then this function will not block. Plugins should
+ always use a timeout of zero to avoid blocking the host.
+
+ If a positive `timeout` is given, then events will be processed for that
+ amount of time, starting from when this function was called.
+
+ If a negative `timeout` is given, this function will block indefinitely
+ until an event occurs.
+
+ For continuously animating programs, a timeout that is a reasonable fraction
+ of the ideal frame period should be used, to minimise input latency by
+ ensuring that as many input events are consumed as possible before drawing.
+
+ @return
+ - #PUGL_SUCCESS if events are read
+ - #PUGL_FAILURE if not, or an error.
+*/
+PUGL_API PuglStatus
+puglUpdate(PuglWorld* world, double timeout);
+
+/**
+ @}
+
+ @defgroup view View
+
+ A drawable region that receives events.
+
+ A view can be thought of as a window, but does not necessarily correspond to
+ a top-level window in a desktop environment. For example, a view can be
+ embedded in some other window, or represent an embedded system where there
+ is no concept of multiple windows at all.
+
+ @{
+*/
+
+/**
+ A drawable region that receives events.
+*/
+typedef struct PuglViewImpl PuglView;
+
+/**
+ A graphics backend.
+
+ The backend dictates how graphics are set up for a view, and how drawing is
+ performed. A backend must be set by calling puglSetBackend() before
+ realising a view.
+
+ If you are using a local copy of Pugl, it is possible to implement a custom
+ backend. See the definition of `PuglBackendImpl` in the source code for
+ details.
+*/
+typedef struct PuglBackendImpl PuglBackend;
+
+/**
+ A native view handle.
+
+ X11: This is a `Window`.
+
+ MacOS: This is a pointer to an `NSView*`.
+
+ Windows: This is a `HWND`.
+*/
+typedef uintptr_t PuglNativeView;
+
+/**
+ Handle for a view's opaque user data.
+*/
+typedef void* PuglHandle;
+
+/**
+ A hint for configuring a view.
+*/
+typedef enum {
+ PUGL_USE_COMPAT_PROFILE, ///< Use compatible (not core) OpenGL profile
+ PUGL_USE_DEBUG_CONTEXT, ///< True to use a debug OpenGL context
+ PUGL_CONTEXT_VERSION_MAJOR, ///< OpenGL context major version
+ PUGL_CONTEXT_VERSION_MINOR, ///< OpenGL context minor version
+ PUGL_RED_BITS, ///< Number of bits for red channel
+ PUGL_GREEN_BITS, ///< Number of bits for green channel
+ PUGL_BLUE_BITS, ///< Number of bits for blue channel
+ PUGL_ALPHA_BITS, ///< Number of bits for alpha channel
+ PUGL_DEPTH_BITS, ///< Number of bits for depth buffer
+ PUGL_STENCIL_BITS, ///< Number of bits for stencil buffer
+ PUGL_SAMPLES, ///< Number of samples per pixel (AA)
+ PUGL_DOUBLE_BUFFER, ///< True if double buffering should be used
+ PUGL_SWAP_INTERVAL, ///< Number of frames between buffer swaps
+ PUGL_RESIZABLE, ///< True if view should be resizable
+ PUGL_IGNORE_KEY_REPEAT, ///< True if key repeat events are ignored
+ PUGL_REFRESH_RATE, ///< Refresh rate in Hz
+
+ PUGL_NUM_VIEW_HINTS
+} PuglViewHint;
+
+/**
+ A special view hint value.
+*/
+typedef enum {
+ PUGL_DONT_CARE = -1, ///< Use best available value
+ PUGL_FALSE = 0, ///< Explicitly false
+ PUGL_TRUE = 1 ///< Explicitly true
+} PuglViewHintValue;
+
+/**
+ A function called when an event occurs.
+*/
+typedef PuglStatus (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
+
+/**
+ @name Setup
+ Functions for creating and destroying a view.
+ @{
+*/
+
+/**
+ Create a new view.
+
+ A newly created view does not correspond to a real system view or window.
+ It must first be configured, then the system view can be created with
+ puglRealize().
+*/
+PUGL_API PuglView*
+puglNewView(PuglWorld* world);
+
+/**
+ Free a view created with puglNewView().
+*/
+PUGL_API void
+puglFreeView(PuglView* view);
+
+/**
+ Return the world that `view` is a part of.
+*/
+PUGL_API PuglWorld*
+puglGetWorld(PuglView* view);
+
+/**
+ Set the user data for a view.
+
+ This is usually a pointer to a struct that contains all the state which must
+ be accessed by a view. Everything needed to process events should be stored
+ here, not in static variables.
+
+ The handle is opaque to Pugl and is not interpreted in any way.
+*/
+PUGL_API void
+puglSetHandle(PuglView* view, PuglHandle handle);
+
+/**
+ Get the user data for a view.
+*/
+PUGL_API PuglHandle
+puglGetHandle(PuglView* view);
+
+/**
+ Set the graphics backend to use for a view.
+
+ This must be called once to set the graphics backend before calling
+ puglRealize().
+
+ Pugl includes the following backends:
+
+ - puglGlBackend(), declared in pugl_gl.h
+ - puglCairoBackend(), declared in pugl_cairo.h
+
+ Note that backends are modular and not compiled into the main Pugl library
+ to avoid unnecessary dependencies. To use a particular backend,
+ applications must link against the appropriate backend library, or be sure
+ to compile in the appropriate code if using a local copy of Pugl.
+*/
+PUGL_API PuglStatus
+puglSetBackend(PuglView* view, const PuglBackend* backend);
+
+/**
+ Set the function to call when an event occurs.
+*/
+PUGL_API PuglStatus
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
+
+/**
+ Set a hint to configure view properties.
+
+ This only has an effect when called before puglRealize().
+*/
+PUGL_API PuglStatus
+puglSetViewHint(PuglView* view, PuglViewHint hint, int value);
+
+/**
+ Get the value for a view hint.
+
+ If the view has been realized, this can be used to get the actual value of a
+ hint which was initially set to PUGL_DONT_CARE, or has been adjusted from
+ the suggested value.
+*/
+PUGL_API int
+puglGetViewHint(const PuglView* view, PuglViewHint hint);
+
+/**
+ @}
+ @anchor frame
+ @name Frame
+ Functions for working with the position and size of a view.
+ @{
+*/
+
+/**
+ Get the current position and size of the view.
+
+ The position is in screen coordinates with an upper left origin.
+*/
+PUGL_API PuglRect
+puglGetFrame(const PuglView* view);
+
+/**
+ Set the current position and size of the view.
+
+ The position is in screen coordinates with an upper left origin.
+
+ @return #PUGL_UNKNOWN_ERROR on failure, in which case the view frame is
+ unchanged.
+*/
+PUGL_API PuglStatus
+puglSetFrame(PuglView* view, PuglRect frame);
+
+/**
+ Set the default size of the view.
+
+ This should be called before puglResize() to set the default size of the
+ view, which will be the initial size of the window if this is a top level
+ view.
+
+ @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is
+ not yet realized.
+*/
+PUGL_API PuglStatus
+puglSetDefaultSize(PuglView* view, int width, int height);
+
+/**
+ Set the minimum size of the view.
+
+ If an initial minimum size is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
+
+ @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is
+ not yet realized.
+*/
+PUGL_API PuglStatus
+puglSetMinSize(PuglView* view, int width, int height);
+
+/**
+ Set the maximum size of the view.
+
+ If an initial maximum size is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
+
+ @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is
+ not yet realized.
+*/
+PUGL_API PuglStatus
+puglSetMaxSize(PuglView* view, int width, int height);
+
+/**
+ Set the view aspect ratio range.
+
+ The x and y values here represent a ratio of width to height. To set a
+ fixed aspect ratio, set the minimum and maximum values to the same ratio.
+
+ Note that setting different minimum and maximum constraints does not
+ currenty work on MacOS (the minimum is used), so only setting a fixed aspect
+ ratio works properly across all platforms.
+
+ If an initial aspect ratio is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
+
+ @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is
+ not yet realized.
+*/
+PUGL_API PuglStatus
+puglSetAspectRatio(PuglView* view, int minX, int minY, int maxX, int maxY);
+
+/**
+ @}
+ @name Windows
+ Functions for working with system views and the window hierarchy.
+ @{
+*/
+
+/**
+ Set the title of the window.
+
+ This only makes sense for non-embedded views that will have a corresponding
+ top-level window, and sets the title, typically displayed in the title bar
+ or in window switchers.
+*/
+PUGL_API PuglStatus
+puglSetWindowTitle(PuglView* view, const char* title);
+
+/**
+ Set the parent window for embedding a view in an existing window.
+
+ This must be called before puglRealize(), reparenting is not supported.
+*/
+PUGL_API PuglStatus
+puglSetParentWindow(PuglView* view, PuglNativeView parent);
+
+/**
+ Set the transient parent of the window.
+
+ Set this for transient children like dialogs, to have them properly
+ associated with their parent window. This should be called before
+ puglRealize().
+
+ A view can either have a parent (for embedding) or a transient parent (for
+ top-level windows like dialogs), but not both.
+*/
+PUGL_API PuglStatus
+puglSetTransientFor(PuglView* view, PuglNativeView parent);
+
+/**
+ Realise a view by creating a corresponding system view or window.
+
+ After this call, the (initially invisible) underlying system view exists and
+ can be accessed with puglGetNativeWindow(). There is currently no
+ corresponding unrealize function, the system view will be destroyed along
+ with the view when puglFreeView() is called.
+
+ The view should be fully configured using the above functions before this is
+ called. This function may only be called once per view.
+*/
+PUGL_API PuglStatus
+puglRealize(PuglView* view);
+
+/**
+ Show the view.
+
+ If the view has not yet been realized, the first call to this function will
+ do so automatically.
+
+ If the view is currently hidden, it will be shown and possibly raised to the
+ top depending on the platform.
+*/
+PUGL_API PuglStatus
+puglShowWindow(PuglView* view);
+
+/**
+ Hide the current window.
+*/
+PUGL_API PuglStatus
+puglHideWindow(PuglView* view);
+
+/**
+ Return true iff the view is currently visible.
+*/
+PUGL_API bool
+puglGetVisible(const PuglView* view);
+
+/**
+ Return the native window handle.
+*/
+PUGL_API PuglNativeView
+puglGetNativeWindow(PuglView* view);
+
+/**
+ @}
+ @name Graphics
+ Functions for working with the graphics context and scheduling redisplays.
+ @{
+*/
+
+/**
+ Get the graphics context.
+
+ This is a backend-specific context used for drawing if the backend graphics
+ API requires one. It is only available during an expose.
+
+ @return
+ - Cairo: A pointer to a
+ [`cairo_t`](http://www.cairographics.org/manual/cairo-cairo-t.html).
+ - OpenGL: `NULL`.
+ - Stub: `NULL`.
+*/
+PUGL_API void*
+puglGetContext(PuglView* view);
+
+/**
+ Request a redisplay for the entire view.
+
+ This will cause an expose event to be dispatched later. If called from
+ within the event handler, the expose should arrive at the end of the current
+ event loop iteration, though this is not strictly guaranteed on all
+ platforms. If called elsewhere, an expose will be enqueued to be processed
+ in the next event loop iteration.
+*/
+PUGL_API PuglStatus
+puglPostRedisplay(PuglView* view);
+
+/**
+ Request a redisplay of the given rectangle within the view.
+
+ This has the same semantics as puglPostRedisplay(), but allows giving a
+ precise region for redrawing only a portion of the view.
+*/
+PUGL_API PuglStatus
+puglPostRedisplayRect(PuglView* view, PuglRect rect);
+
+/**
+ @}
+ @anchor interaction
+ @name Interaction
+ Functions for interacting with the user and window system.
+ @{
+*/
+
+/**
+ A mouse cursor type.
+
+ This is a portable subset of mouse cursors that exist on X11, MacOS, and
+ Windows.
+*/
+typedef enum {
+ PUGL_CURSOR_ARROW, ///< Default pointing arrow
+ PUGL_CURSOR_CARET, ///< Caret (I-Beam) for text entry
+ PUGL_CURSOR_CROSSHAIR, ///< Cross-hair
+ PUGL_CURSOR_HAND, ///< Hand with a pointing finger
+ PUGL_CURSOR_NO, ///< Operation not allowed
+ PUGL_CURSOR_LEFT_RIGHT, ///< Left/right arrow for horizontal resize
+ PUGL_CURSOR_UP_DOWN, ///< Up/down arrow for vertical resize
+} PuglCursor;
+
+/**
+ Grab the keyboard input focus.
+*/
+PUGL_API PuglStatus
+puglGrabFocus(PuglView* view);
+
+/**
+ Return whether `view` has the keyboard input focus.
+*/
+PUGL_API bool
+puglHasFocus(const PuglView* view);
+
+/**
+ Set the clipboard contents.
+
+ This sets the system clipboard contents, which can be retrieved with
+ puglGetClipboard() or pasted into other applications.
+
+ @param view The view.
+ @param type The MIME type of the data, "text/plain" is assumed if `NULL`.
+ @param data The data to copy to the clipboard.
+ @param len The length of data in bytes (including terminator if necessary).
+*/
+PUGL_API PuglStatus
+puglSetClipboard(PuglView* view,
+ const char* type,
+ const void* data,
+ size_t len);
+
+/**
+ Get the clipboard contents.
+
+ This gets the system clipboard contents, which may have been set with
+ puglSetClipboard() or copied from another application.
+
+ @param view The view.
+ @param[out] type Set to the MIME type of the data.
+ @param[out] len Set to the length of the data in bytes.
+ @return The clipboard contents, or `NULL`.
+*/
+PUGL_API const void*
+puglGetClipboard(PuglView* view, const char** type, size_t* len);
+
+/**
+ Set the mouse cursor.
+
+ This changes the system cursor that is displayed when the pointer is inside
+ the view. May fail if setting the cursor is not supported on this system,
+ for example if compiled on X11 without Xcursor support.
+
+ @return
+ - #PUGL_BAD_PARAMETER if the given cursor is invalid.
+ - #PUGL_FAILURE if the cursor isknown but loading it from the system fails.
+*/
+PUGL_API PuglStatus
+puglSetCursor(PuglView* view, PuglCursor cursor);
+
+/**
+ Request user attention.
+
+ This hints to the system that the window or application requires attention
+ from the user. The exact effect depends on the platform, but is usually
+ something like a flashing task bar entry or bouncing application icon.
+*/
+PUGL_API PuglStatus
+puglRequestAttention(PuglView* view);
+
+/**
+ Activate a repeating timer event.
+
+ This starts a timer which will send a #PuglEventTimer to `view` every
+ `timeout` seconds. This can be used to perform some action in a view at a
+ regular interval with relatively low frequency. Note that the frequency of
+ timer events may be limited by how often puglUpdate() is called.
+
+ If the given timer already exists, it is replaced.
+
+ @param view The view to begin seding #PUGL_TIMER events to.
+
+ @param id The identifier for this timer. This is an application-specific ID
+ that should be a low number, typically the value of a constant or `enum`
+ that starts from 0. There is a platform-specific limit to the number of
+ supported timers, and overhead associated with each, so applications should
+ create only a few timers and perform several tasks in one if necessary.
+
+ @param timeout The period, in seconds, of this timer. This is not
+ guaranteed to have a resolution better than 10ms (the maximum timer
+ resolution on Windows) and may be rounded up if it is too short. On X11 and
+ MacOS, a resolution of about 1ms can usually be relied on.
+
+ @return
+ - #PUGL_FAILURE if timers are not supported by this system or build.
+ - #PUGL_UNKNOWN_ERROR if setting the timer failed.
+*/
+PUGL_API PuglStatus
+puglStartTimer(PuglView* view, uintptr_t id, double timeout);
+
+/**
+ Stop an active timer.
+
+ @param view The view that the timer is set for.
+ @param id The ID previously passed to puglStartTimer().
+
+ @return
+ - #PUGL_FAILURE if timers are not supported by this system or build.
+ - #PUGL_UNKNOWN_ERROR if stopping the timer failed.
+*/
+PUGL_API PuglStatus
+puglStopTimer(PuglView* view, uintptr_t id);
+
+/**
+ Send an event to a view via the window system.
+
+ If supported, the event will be delivered to the view via the event loop
+ like other events. Note that this function only works for certain event
+ types.
+
+ Currently, only #PUGL_CLIENT events are supported on all platforms.
+
+ X11: A #PUGL_EXPOSE event can be sent, which is similar to calling
+ puglPostRedisplayRect(), but will always send a message to the X server,
+ even when called in an event handler.
+
+ @return
+ - #PUGL_UNSUPPORTED_TYPE if sending events of this type is not supported.
+ - #PUGL_UNKNOWN_ERROR if sending the event failed.
+*/
+PUGL_API PuglStatus
+puglSendEvent(PuglView* view, const PuglEvent* event);
+
+/**
+ @}
+*/
+
+#ifndef PUGL_DISABLE_DEPRECATED
+
+/**
+ @}
+ @name Deprecated API
+ @{
+*/
+
+/**
+ A native window handle.
+
+ X11: This is a `Window`.
+
+ MacOS: This is a pointer to an `NSView*`.
+
+ Windows: This is a `HWND`.
+*/
+typedef uintptr_t PuglNativeWindow;
+
+/**
+ Create a Pugl application and view.
+
+ To create a window, call the various puglInit* functions as necessary, then
+ call puglRealize().
+
+ @deprecated Use puglNewApp() and puglNewView().
+
+ @param pargc Pointer to argument count (currently unused).
+ @param argv Arguments (currently unused).
+ @return A newly created view.
+*/
+static inline PUGL_DEPRECATED_BY("puglNewView") PuglView*
+puglInit(const int* pargc, char** argv)
+{
+ (void)pargc;
+ (void)argv;
+
+ return puglNewView(puglNewWorld(PUGL_MODULE, 0));
+}
+
+/**
+ Destroy an app and view created with `puglInit()`.
+
+ @deprecated Use puglFreeApp() and puglFreeView().
+*/
+static inline PUGL_DEPRECATED_BY("puglFreeView") void
+puglDestroy(PuglView* view)
+{
+ PuglWorld* const world = puglGetWorld(view);
+
+ puglFreeView(view);
+ puglFreeWorld(world);
+}
+
+/**
+ Set the window class name before creating a window.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetClassName") void
+puglInitWindowClass(PuglView* view, const char* name)
+{
+ puglSetClassName(puglGetWorld(view), name);
+}
+
+/**
+ Set the window size before creating a window.
+
+ @deprecated Use puglSetFrame().
+*/
+static inline PUGL_DEPRECATED_BY("puglSetFrame") void
+puglInitWindowSize(PuglView* view, int width, int height)
+{
+ PuglRect frame = puglGetFrame(view);
+
+ frame.width = width;
+ frame.height = height;
+
+ puglSetFrame(view, frame);
+}
+
+/**
+ Set the minimum window size before creating a window.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetMinSize") void
+puglInitWindowMinSize(PuglView* view, int width, int height)
+{
+ puglSetMinSize(view, width, height);
+}
+
+/**
+ Set the window aspect ratio range before creating a window.
+
+ The x and y values here represent a ratio of width to height. To set a
+ fixed aspect ratio, set the minimum and maximum values to the same ratio.
+
+ Note that setting different minimum and maximum constraints does not
+ currenty work on MacOS (the minimum is used), so only setting a fixed aspect
+ ratio works properly across all platforms.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetAspectRatio") void
+puglInitWindowAspectRatio(PuglView* view,
+ int minX,
+ int minY,
+ int maxX,
+ int maxY)
+{
+ puglSetAspectRatio(view, minX, minY, maxX, maxY);
+}
+
+/**
+ Set transient parent before creating a window.
+
+ On X11, parent must be a Window.
+ On OSX, parent must be an NSView*.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetTransientFor") void
+puglInitTransientFor(PuglView* view, uintptr_t parent)
+{
+ puglSetTransientFor(view, (PuglNativeWindow)parent);
+}
+
+/**
+ Enable or disable resizing before creating a window.
+
+ @deprecated Use puglSetViewHint() with #PUGL_RESIZABLE.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetViewHint") void
+puglInitResizable(PuglView* view, bool resizable)
+{
+ puglSetViewHint(view, PUGL_RESIZABLE, resizable);
+}
+
+/**
+ Get the current size of the view.
+
+ @deprecated Use puglGetFrame().
+
+*/
+static inline PUGL_DEPRECATED_BY("puglGetFrame") void
+puglGetSize(PuglView* view, int* width, int* height)
+{
+ const PuglRect frame = puglGetFrame(view);
+
+ *width = (int)frame.width;
+ *height = (int)frame.height;
+}
+
+/**
+ Ignore synthetic repeated key events.
+
+ @deprecated Use puglSetViewHint() with #PUGL_IGNORE_KEY_REPEAT.
+*/
+static inline PUGL_DEPRECATED_BY("puglSetViewHint") void
+puglIgnoreKeyRepeat(PuglView* view, bool ignore)
+{
+ puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, ignore);
+}
+
+/**
+ Set a hint before creating a window.
+
+ @deprecated Use puglSetWindowHint().
+*/
+static inline PUGL_DEPRECATED_BY("puglSetViewHint") void
+puglInitWindowHint(PuglView* view, PuglViewHint hint, int value)
+{
+ puglSetViewHint(view, hint, value);
+}
+
+/**
+ Set the parent window before creating a window (for embedding).
+
+ @deprecated Use puglSetWindowParent().
+*/
+static inline PUGL_DEPRECATED_BY("puglSetParentWindow") void
+puglInitWindowParent(PuglView* view, PuglNativeWindow parent)
+{
+ puglSetParentWindow(view, parent);
+}
+
+/**
+ Set the graphics backend to use.
+
+ @deprecated Use puglSetBackend().
+*/
+static inline PUGL_DEPRECATED_BY("puglSetBackend") int
+puglInitBackend(PuglView* view, const PuglBackend* backend)
+{
+ return (int)puglSetBackend(view, backend);
+}
+
+/**
+ Realise a view by creating a corresponding system view or window.
+
+ The view should be fully configured using the above functions before this is
+ called. This function may only be called once per view.
+
+ @deprecated Use puglRealize(), or just show the view.
+*/
+static inline PUGL_DEPRECATED_BY("puglRealize") PuglStatus
+puglCreateWindow(PuglView* view, const char* title)
+{
+ puglSetWindowTitle(view, title);
+ return puglRealize(view);
+}
+
+/**
+ Block and wait for an event to be ready.
+
+ This can be used in a loop to only process events via puglProcessEvents when
+ necessary. This function will block indefinitely if no events are
+ available, so is not appropriate for use in programs that need to perform
+ regular updates (e.g. animation).
+
+ @deprecated Use puglPollEvents().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglPollEvents") PuglStatus
+puglWaitForEvent(PuglView* view);
+
+/**
+ Process all pending window events.
+
+ This handles input events as well as rendering, so it should be called
+ regularly and rapidly enough to keep the UI responsive. This function does
+ not block if no events are pending.
+
+ @deprecated Use puglDispatchEvents().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglDispatchEvents") PuglStatus
+puglProcessEvents(PuglView* view);
+
+/**
+ Poll for events that are ready to be processed.
+
+ This polls for events that are ready for any view in the world, potentially
+ blocking depending on `timeout`.
+
+ @param world The world to poll for events.
+
+ @param timeout Maximum time to wait, in seconds. If zero, the call returns
+ immediately, if negative, the call blocks indefinitely.
+
+ @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglPollEvents(PuglWorld* world, double timeout);
+
+/**
+ Dispatch any pending events to views.
+
+ This processes all pending events, dispatching them to the appropriate
+ views. View event handlers will be called in the scope of this call. This
+ function does not block, if no events are pending then it will return
+ immediately.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglDispatchEvents(PuglWorld* world);
+
+/**
+ Enter the graphics context.
+
+ Note that, unlike some similar libraries, Pugl automatically enters and
+ leaves the graphics context when required and application should not
+ normally do this. Drawing in Pugl is only allowed during the processing of
+ an expose event.
+
+ However, this can be used to enter the graphics context elsewhere, for
+ example to call any GL functions during setup.
+
+ @param view The view being entered.
+ @param drawing If true, prepare for drawing.
+
+ @deprecated Set up graphics when a #PUGL_CREATE event is received.
+*/
+PUGL_API PUGL_DEPRECATED_BY("PUGL_CREATE") PuglStatus
+puglEnterContext(PuglView* view, bool drawing);
+
+/**
+ Leave the graphics context.
+
+ This must be called after puglEnterContext() with a matching `drawing`
+ parameter.
+
+ @param view The view being left.
+ @param drawing If true, finish drawing, for example by swapping buffers.
+
+ @deprecated Shut down graphics when a #PUGL_DESTROY event is received.
+*/
+PUGL_API PUGL_DEPRECATED_BY("PUGL_DESTROY") PuglStatus
+puglLeaveContext(PuglView* view, bool drawing);
+
+#endif /* PUGL_DISABLE_DEPRECATED */
+
+/**
+ @}
+ @}
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif /* PUGL_PUGL_H */
diff --git a/include/pugl/pugl.hpp b/include/pugl/pugl.hpp
new file mode 100644
index 0000000..9709e51
--- /dev/null
+++ b/include/pugl/pugl.hpp
@@ -0,0 +1,726 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl.hpp
+ @brief Pugl C++ API wrapper.
+*/
+
+#ifndef PUGL_PUGL_HPP
+#define PUGL_PUGL_HPP
+
+#include "pugl/pugl.h"
+
+#include <cassert>
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+
+/**
+ @defgroup pugl_cxx C++ API
+ C++ API wrapper.
+
+ @ingroup pugl
+ @{
+*/
+
+/**
+ Pugl C++ API namespace.
+*/
+namespace pugl {
+
+namespace detail {
+
+/// Free function for a C object
+template<typename T>
+using FreeFunc = void (*)(T*);
+
+/// Simple overhead-free deleter for a C object
+template<typename T, FreeFunc<T> Free>
+struct Deleter {
+ void operator()(T* ptr) { Free(ptr); }
+};
+
+/// Generic C++ wrapper for a C object
+template<class T, FreeFunc<T> Free>
+class Wrapper
+{
+public:
+ Wrapper(const Wrapper&) = delete;
+ Wrapper& operator=(const Wrapper&) = delete;
+
+ Wrapper(Wrapper&&) = default;
+ Wrapper& operator=(Wrapper&&) = default;
+
+ T* cobj() { return _ptr.get(); }
+ const T* cobj() const { return _ptr.get(); }
+
+protected:
+ explicit Wrapper(T* ptr)
+ : _ptr(ptr, Deleter<T, Free>{})
+ {}
+
+private:
+ std::unique_ptr<T, Deleter<T, Free>> _ptr;
+};
+
+} // namespace detail
+
+using Rect = PuglRect; ///< @copydoc PuglRect
+
+/**
+ @defgroup eventsxx Events
+ @ingroup pugl_cxx
+ @copydoc events
+ @{
+*/
+
+/**
+ A strongly-typed analogue of PuglEvent.
+
+ This is bit-for-bit identical to the corresponding PuglEvent, so events are
+ simply cast to this type to avoid any copying overhead.
+
+ @tparam t The `type` field of the corresponding PuglEvent.
+
+ @tparam Base The specific struct type of the corresponding PuglEvent.
+*/
+template<PuglEventType t, class Base>
+struct Event final : Base {
+ using BaseEvent = Base;
+
+ static constexpr const PuglEventType type = t;
+};
+
+using Mod = PuglMod; ///< @copydoc PuglMod
+using Mods = PuglMods; ///< @copydoc PuglMods
+using Key = PuglKey; ///< @copydoc PuglKey
+using EventType = PuglEventType; ///< @copydoc PuglEventType
+using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag
+using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags
+using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode
+
+/// @copydoc PuglEventCreate
+using CreateEvent = Event<PUGL_CREATE, PuglEventCreate>;
+
+/// @copydoc PuglEventDestroy
+using DestroyEvent = Event<PUGL_DESTROY, PuglEventDestroy>;
+
+/// @copydoc PuglEventConfigure
+using ConfigureEvent = Event<PUGL_CONFIGURE, PuglEventConfigure>;
+
+/// @copydoc PuglEventMap
+using MapEvent = Event<PUGL_MAP, PuglEventMap>;
+
+/// @copydoc PuglEventUnmap
+using UnmapEvent = Event<PUGL_UNMAP, PuglEventUnmap>;
+
+/// @copydoc PuglEventUpdate
+using UpdateEvent = Event<PUGL_UPDATE, PuglEventUpdate>;
+
+/// @copydoc PuglEventExpose
+using ExposeEvent = Event<PUGL_EXPOSE, PuglEventExpose>;
+
+/// @copydoc PuglEventClose
+using CloseEvent = Event<PUGL_CLOSE, PuglEventClose>;
+
+/// @copydoc PuglEventFocus
+using FocusInEvent = Event<PUGL_FOCUS_IN, PuglEventFocus>;
+
+/// @copydoc PuglEventFocus
+using FocusOutEvent = Event<PUGL_FOCUS_OUT, PuglEventFocus>;
+
+/// @copydoc PuglEventKey
+using KeyPressEvent = Event<PUGL_KEY_PRESS, PuglEventKey>;
+
+/// @copydoc PuglEventKey
+using KeyReleaseEvent = Event<PUGL_KEY_RELEASE, PuglEventKey>;
+
+/// @copydoc PuglEventText
+using TextEvent = Event<PUGL_TEXT, PuglEventText>;
+
+/// @copydoc PuglEventCrossing
+using PointerInEvent = Event<PUGL_POINTER_IN, PuglEventCrossing>;
+
+/// @copydoc PuglEventCrossing
+using PointerOutEvent = Event<PUGL_POINTER_OUT, PuglEventCrossing>;
+
+/// @copydoc PuglEventButton
+using ButtonPressEvent = Event<PUGL_BUTTON_PRESS, PuglEventButton>;
+
+/// @copydoc PuglEventButton
+using ButtonReleaseEvent = Event<PUGL_BUTTON_RELEASE, PuglEventButton>;
+
+/// @copydoc PuglEventMotion
+using MotionEvent = Event<PUGL_MOTION, PuglEventMotion>;
+
+/// @copydoc PuglEventScroll
+using ScrollEvent = Event<PUGL_SCROLL, PuglEventScroll>;
+
+/// @copydoc PuglEventClient
+using ClientEvent = Event<PUGL_CLIENT, PuglEventClient>;
+
+/// @copydoc PuglEventTimer
+using TimerEvent = Event<PUGL_TIMER, PuglEventTimer>;
+
+/**
+ @}
+ @defgroup statusxx Status
+ @ingroup pugl_cxx
+ @copydoc status
+ @{
+*/
+
+/// @copydoc PuglStatus
+enum class Status {
+ success, ///< @copydoc PUGL_SUCCESS
+ failure, ///< @copydoc PUGL_FAILURE
+ unknownError, ///< @copydoc PUGL_UNKNOWN_ERROR
+ badBackend, ///< @copydoc PUGL_BAD_BACKEND
+ badConfiguration, ///< @copydoc PUGL_BAD_CONFIGURATION
+ badParameter, ///< @copydoc PUGL_BAD_PARAMETER
+ backendFailed, ///< @copydoc PUGL_BACKEND_FAILED
+ registrationFailed, ///< @copydoc PUGL_REGISTRATION_FAILED
+ realizeFailed, ///< @copydoc PUGL_REALIZE_FAILED
+ setFormatFailed, ///< @copydoc PUGL_SET_FORMAT_FAILED
+ createContextFailed, ///< @copydoc PUGL_CREATE_CONTEXT_FAILED
+ unsupportedType, ///< @copydoc PUGL_UNSUPPORTED_TYPE
+};
+
+static_assert(Status(PUGL_UNSUPPORTED_TYPE) == Status::unsupportedType, "");
+
+/// @copydoc puglStrerror
+static inline const char*
+strerror(const pugl::Status status)
+{
+ return puglStrerror(static_cast<PuglStatus>(status));
+}
+
+/**
+ @}
+ @defgroup worldxx World
+ @ingroup pugl_cxx
+ @copydoc world
+ @{
+*/
+
+class World;
+
+/// @copydoc PuglWorldType
+enum class WorldType {
+ program, ///< @copydoc PUGL_PROGRAM
+ module, ///< @copydoc PUGL_MODULE
+};
+
+static_assert(WorldType(PUGL_MODULE) == WorldType::module, "");
+
+/// @copydoc PuglWorldFlag
+enum class WorldFlag {
+ threads = PUGL_WORLD_THREADS, ///< @copydoc PUGL_WORLD_THREADS
+};
+
+static_assert(WorldFlag(PUGL_WORLD_THREADS) == WorldFlag::threads, "");
+
+using WorldFlags = PuglWorldFlags; ///< @copydoc PuglWorldFlags
+
+/// @copydoc PuglLogLevel
+enum class LogLevel {
+ err = PUGL_LOG_LEVEL_ERR, ///< @copydoc PUGL_LOG_LEVEL_ERR
+ warning = PUGL_LOG_LEVEL_WARNING, ///< @copydoc PUGL_LOG_LEVEL_WARNING
+ info = PUGL_LOG_LEVEL_INFO, ///< @copydoc PUGL_LOG_LEVEL_INFO
+ debug = PUGL_LOG_LEVEL_DEBUG, ///< @copydoc PUGL_LOG_LEVEL_DEBUG
+};
+
+static_assert(LogLevel(PUGL_LOG_LEVEL_DEBUG) == LogLevel::debug, "");
+
+/// @copydoc PuglLogFunc
+using LogFunc =
+ std::function<void(World& world, LogLevel level, const char* msg)>;
+
+/**
+ A `std::chrono` compatible clock that uses Pugl time.
+*/
+class Clock
+{
+public:
+ using rep = double; ///< Time representation
+ using duration = std::chrono::duration<double>; ///< Duration in seconds
+ using time_point = std::chrono::time_point<Clock>; ///< A Pugl time point
+
+ static constexpr bool is_steady = true; ///< Steady clock flag, always true
+
+ /// Construct a clock that uses time from puglGetTime()
+ explicit Clock(World& world)
+ : _world{world}
+ {}
+
+ Clock(const Clock&) = delete;
+ Clock& operator=(const Clock&) = delete;
+
+ Clock(Clock&&) = delete;
+ Clock& operator=(Clock&&) = delete;
+
+ /// Return the current time
+ time_point now() const;
+
+private:
+ const pugl::World& _world;
+};
+
+/// @copydoc PuglWorld
+class World : public detail::Wrapper<PuglWorld, puglFreeWorld>
+{
+public:
+ World(const World&) = delete;
+ World& operator=(const World&) = delete;
+
+ World(World&&) = delete;
+ World& operator=(World&&) = delete;
+
+ explicit World(WorldType type, WorldFlags flags)
+ : Wrapper{puglNewWorld(static_cast<PuglWorldType>(type), flags)}
+ , _clock(*this)
+ {
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::World");
+ }
+ }
+
+ explicit World(WorldType type)
+ : World{type, {}}
+ {
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::World");
+ }
+ }
+
+ /// @copydoc puglGetNativeWorld
+ void* nativeWorld() { return puglGetNativeWorld(cobj()); }
+
+ // TODO: setLogFunc
+
+ Status setLogLevel(const LogLevel level)
+ {
+ return static_cast<Status>(
+ puglSetLogLevel(cobj(), static_cast<PuglLogLevel>(level)));
+ }
+
+ /// @copydoc puglSetClassName
+ Status setClassName(const char* const name)
+ {
+ return static_cast<Status>(puglSetClassName(cobj(), name));
+ }
+
+ /// @copydoc puglGetTime
+ double time() const { return puglGetTime(cobj()); }
+
+ /// @copydoc puglUpdate
+ Status update(const double timeout)
+ {
+ return static_cast<Status>(puglUpdate(cobj(), timeout));
+ }
+
+ /// Return a clock that uses Pugl time
+ const Clock& clock() { return _clock; }
+
+private:
+ Clock _clock;
+};
+
+inline Clock::time_point
+Clock::now() const
+{
+ return time_point{duration{_world.time()}};
+}
+
+/**
+ @}
+ @defgroup viewxx View
+ @ingroup pugl_cxx
+ @copydoc view
+ @{
+*/
+
+using Backend = PuglBackend; ///< @copydoc PuglBackend
+using NativeView = PuglNativeView; ///< @copydoc PuglNativeView
+
+/// @copydoc PuglViewHint
+enum class ViewHint {
+ useCompatProfile, ///< @copydoc PUGL_USE_COMPAT_PROFILE
+ useDebugContext, ///< @copydoc PUGL_USE_DEBUG_CONTEXT
+ contextVersionMajor, ///< @copydoc PUGL_CONTEXT_VERSION_MAJOR
+ contextVersionMinor, ///< @copydoc PUGL_CONTEXT_VERSION_MINOR
+ redBits, ///< @copydoc PUGL_RED_BITS
+ greenBits, ///< @copydoc PUGL_GREEN_BITS
+ blueBits, ///< @copydoc PUGL_BLUE_BITS
+ alphaBits, ///< @copydoc PUGL_ALPHA_BITS
+ depthBits, ///< @copydoc PUGL_DEPTH_BITS
+ stencilBits, ///< @copydoc PUGL_STENCIL_BITS
+ samples, ///< @copydoc PUGL_SAMPLES
+ doubleBuffer, ///< @copydoc PUGL_DOUBLE_BUFFER
+ swapInterval, ///< @copydoc PUGL_SWAP_INTERVAL
+ resizable, ///< @copydoc PUGL_RESIZABLE
+ ignoreKeyRepeat, ///< @copydoc PUGL_IGNORE_KEY_REPEAT
+ refreshRate, ///< @copydoc PUGL_REFRESH_RATE
+};
+
+static_assert(ViewHint(PUGL_REFRESH_RATE) == ViewHint::refreshRate, "");
+
+using ViewHintValue = PuglViewHintValue; ///< @copydoc PuglViewHintValue
+
+/// @copydoc PuglCursor
+enum class Cursor {
+ arrow, ///< @copydoc PUGL_CURSOR_ARROW
+ caret, ///< @copydoc PUGL_CURSOR_CARET
+ crosshair, ///< @copydoc PUGL_CURSOR_CROSSHAIR
+ hand, ///< @copydoc PUGL_CURSOR_HAND
+ no, ///< @copydoc PUGL_CURSOR_NO
+ leftRight, ///< @copydoc PUGL_CURSOR_LEFT_RIGHT
+ upDown, ///< @copydoc PUGL_CURSOR_UP_DOWN
+};
+
+static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, "");
+
+/// @copydoc PuglView
+class View : protected detail::Wrapper<PuglView, puglFreeView>
+{
+public:
+ /**
+ @name Setup
+ Methods for creating and destroying a view.
+ @{
+ */
+
+ explicit View(World& world)
+ : Wrapper{puglNewView(world.cobj())}
+ , _world(world)
+ {
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::View");
+ }
+
+ puglSetHandle(cobj(), this);
+ puglSetEventFunc(cobj(), dispatchEvent);
+ }
+
+ virtual ~View() = default;
+
+ View(const View&) = delete;
+ View& operator=(const View&) = delete;
+
+ View(View&&) = delete;
+ View&& operator=(View&&) = delete;
+
+ const pugl::World& world() const { return _world; }
+ pugl::World& world() { return _world; }
+
+ /// @copydoc puglSetViewHint
+ Status setHint(ViewHint hint, int value)
+ {
+ return static_cast<Status>(
+ puglSetViewHint(cobj(), static_cast<PuglViewHint>(hint), value));
+ }
+
+ /// @copydoc puglGetViewHint
+ int getHint(ViewHint hint)
+ {
+ return puglGetViewHint(cobj(), static_cast<PuglViewHint>(hint));
+ }
+
+ /**
+ @}
+ @name Frame
+ Methods for working with the position and size of a view.
+ @{
+ */
+
+ /// @copydoc puglGetFrame
+ Rect frame() const { return puglGetFrame(cobj()); }
+
+ /// @copydoc puglSetFrame
+ Status setFrame(Rect frame)
+ {
+ return static_cast<Status>(puglSetFrame(cobj(), frame));
+ }
+
+ /// @copydoc puglSetDefaultSize
+ Status setDefaultSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetDefaultSize(cobj(), width, height));
+ }
+
+ /// @copydoc puglSetMinSize
+ Status setMinSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetMinSize(cobj(), width, height));
+ }
+
+ /// @copydoc puglSetMaxSize
+ Status setMaxSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetMaxSize(cobj(), width, height));
+ }
+
+ /// @copydoc puglSetAspectRatio
+ Status setAspectRatio(int minX, int minY, int maxX, int maxY)
+ {
+ return static_cast<Status>(
+ puglSetAspectRatio(cobj(), minX, minY, maxX, maxY));
+ }
+
+ /**
+ @}
+ @name Windows
+ Methods for working with top-level windows.
+ @{
+ */
+
+ /// @copydoc puglSetWindowTitle
+ Status setWindowTitle(const char* title)
+ {
+ return static_cast<Status>(puglSetWindowTitle(cobj(), title));
+ }
+
+ /// @copydoc puglSetParentWindow
+ Status setParentWindow(NativeView parent)
+ {
+ return static_cast<Status>(puglSetParentWindow(cobj(), parent));
+ }
+
+ /// @copydoc puglSetTransientFor
+ Status setTransientFor(NativeView parent)
+ {
+ return static_cast<Status>(puglSetTransientFor(cobj(), parent));
+ }
+
+ /// @copydoc puglRealize
+ Status realize() { return static_cast<Status>(puglRealize(cobj())); }
+
+ /// @copydoc puglShowWindow
+ Status showWindow() { return static_cast<Status>(puglShowWindow(cobj())); }
+
+ /// @copydoc puglHideWindow
+ Status hideWindow() { return static_cast<Status>(puglHideWindow(cobj())); }
+
+ /// @copydoc puglGetVisible
+ bool visible() const { return puglGetVisible(cobj()); }
+
+ /// @copydoc puglGetNativeWindow
+ NativeView nativeWindow() { return puglGetNativeWindow(cobj()); }
+
+ /**
+ @}
+ @name Graphics
+ Methods for working with the graphics context and scheduling
+ redisplays.
+ @{
+ */
+
+ /// @copydoc puglGetContext
+ void* context() { return puglGetContext(cobj()); }
+
+ /// @copydoc puglPostRedisplay
+ Status postRedisplay()
+ {
+ return static_cast<Status>(puglPostRedisplay(cobj()));
+ }
+
+ /// @copydoc puglPostRedisplayRect
+ Status postRedisplayRect(const Rect rect)
+ {
+ return static_cast<Status>(puglPostRedisplayRect(cobj(), rect));
+ }
+
+ /**
+ @}
+ @name Interaction
+ Methods for interacting with the user and window system.
+ @{
+ */
+
+ /// @copydoc puglGrabFocus
+ Status grabFocus() { return static_cast<Status>(puglGrabFocus(cobj())); }
+
+ /// @copydoc puglHasFocus
+ bool hasFocus() const { return puglHasFocus(cobj()); }
+
+ /// @copydoc puglSetBackend
+ Status setBackend(const PuglBackend* backend)
+ {
+ return static_cast<Status>(puglSetBackend(cobj(), backend));
+ }
+
+ /// @copydoc puglSetCursor
+ Status setCursor(const Cursor cursor)
+ {
+ return static_cast<Status>(
+ puglSetCursor(cobj(), static_cast<PuglCursor>(cursor)));
+ }
+
+ /// @copydoc puglRequestAttention
+ Status requestAttention()
+ {
+ return static_cast<Status>(puglRequestAttention(cobj()));
+ }
+
+ /**
+ @}
+ @name Event Handlers
+ Methods called when events are dispatched to the view.
+ @{
+ */
+
+ virtual Status onCreate(const CreateEvent&) PUGL_CONST_FUNC;
+ virtual Status onDestroy(const DestroyEvent&) PUGL_CONST_FUNC;
+ virtual Status onConfigure(const ConfigureEvent&) PUGL_CONST_FUNC;
+ virtual Status onMap(const MapEvent&) PUGL_CONST_FUNC;
+ virtual Status onUnmap(const UnmapEvent&) PUGL_CONST_FUNC;
+ virtual Status onUpdate(const UpdateEvent&) PUGL_CONST_FUNC;
+ virtual Status onExpose(const ExposeEvent&) PUGL_CONST_FUNC;
+ virtual Status onClose(const CloseEvent&) PUGL_CONST_FUNC;
+ virtual Status onFocusIn(const FocusInEvent&) PUGL_CONST_FUNC;
+ virtual Status onFocusOut(const FocusOutEvent&) PUGL_CONST_FUNC;
+ virtual Status onKeyPress(const KeyPressEvent&) PUGL_CONST_FUNC;
+ virtual Status onKeyRelease(const KeyReleaseEvent&) PUGL_CONST_FUNC;
+ virtual Status onText(const TextEvent&) PUGL_CONST_FUNC;
+ virtual Status onPointerIn(const PointerInEvent&) PUGL_CONST_FUNC;
+ virtual Status onPointerOut(const PointerOutEvent&) PUGL_CONST_FUNC;
+ virtual Status onButtonPress(const ButtonPressEvent&) PUGL_CONST_FUNC;
+ virtual Status onButtonRelease(const ButtonReleaseEvent&) PUGL_CONST_FUNC;
+ virtual Status onMotion(const MotionEvent&) PUGL_CONST_FUNC;
+ virtual Status onScroll(const ScrollEvent&) PUGL_CONST_FUNC;
+ virtual Status onClient(const ClientEvent&) PUGL_CONST_FUNC;
+ virtual Status onTimer(const TimerEvent&) PUGL_CONST_FUNC;
+
+ /**
+ @}
+ */
+
+ PuglView* cobj() { return Wrapper::cobj(); }
+ const PuglView* cobj() const { return Wrapper::cobj(); }
+
+private:
+ template<class Typed, class Base>
+ static const Typed& typedEventRef(const Base& base)
+ {
+ const auto& event = static_cast<const Typed&>(base);
+ static_assert(sizeof(event) == sizeof(typename Typed::BaseEvent), "");
+ static_assert(std::is_standard_layout<Typed>::value, "");
+ assert(event.type == Typed::type);
+ return event;
+ }
+
+ static PuglStatus
+ dispatchEvent(PuglView* view, const PuglEvent* event) noexcept
+ {
+ try {
+ View* self = static_cast<View*>(puglGetHandle(view));
+
+ return self->dispatch(event);
+ } catch (...) {
+ return PUGL_UNKNOWN_ERROR;
+ }
+ }
+
+ PuglStatus dispatch(const PuglEvent* event)
+ {
+ switch (event->type) {
+ case PUGL_NOTHING:
+ return PUGL_SUCCESS;
+ case PUGL_CREATE:
+ return static_cast<PuglStatus>(
+ onCreate(typedEventRef<CreateEvent>(event->any)));
+ case PUGL_DESTROY:
+ return static_cast<PuglStatus>(
+ onDestroy(typedEventRef<DestroyEvent>(event->any)));
+ case PUGL_CONFIGURE:
+ return static_cast<PuglStatus>(
+ onConfigure(typedEventRef<ConfigureEvent>(event->configure)));
+ case PUGL_MAP:
+ return static_cast<PuglStatus>(
+ onMap(typedEventRef<MapEvent>(event->any)));
+ case PUGL_UNMAP:
+ return static_cast<PuglStatus>(
+ onUnmap(typedEventRef<UnmapEvent>(event->any)));
+ case PUGL_UPDATE:
+ return static_cast<PuglStatus>(
+ onUpdate(typedEventRef<UpdateEvent>(event->any)));
+ case PUGL_EXPOSE:
+ return static_cast<PuglStatus>(
+ onExpose(typedEventRef<ExposeEvent>(event->expose)));
+ case PUGL_CLOSE:
+ return static_cast<PuglStatus>(
+ onClose(typedEventRef<CloseEvent>(event->any)));
+ case PUGL_FOCUS_IN:
+ return static_cast<PuglStatus>(
+ onFocusIn(typedEventRef<FocusInEvent>(event->focus)));
+ case PUGL_FOCUS_OUT:
+ return static_cast<PuglStatus>(
+ onFocusOut(typedEventRef<FocusOutEvent>(event->focus)));
+ case PUGL_KEY_PRESS:
+ return static_cast<PuglStatus>(
+ onKeyPress(typedEventRef<KeyPressEvent>(event->key)));
+ case PUGL_KEY_RELEASE:
+ return static_cast<PuglStatus>(
+ onKeyRelease(typedEventRef<KeyReleaseEvent>(event->key)));
+ case PUGL_TEXT:
+ return static_cast<PuglStatus>(
+ onText(typedEventRef<TextEvent>(event->text)));
+ case PUGL_POINTER_IN:
+ return static_cast<PuglStatus>(
+ onPointerIn(typedEventRef<PointerInEvent>(event->crossing)));
+ case PUGL_POINTER_OUT:
+ return static_cast<PuglStatus>(
+ onPointerOut(typedEventRef<PointerOutEvent>(event->crossing)));
+ case PUGL_BUTTON_PRESS:
+ return static_cast<PuglStatus>(
+ onButtonPress(typedEventRef<ButtonPressEvent>(event->button)));
+ case PUGL_BUTTON_RELEASE:
+ return static_cast<PuglStatus>(onButtonRelease(
+ typedEventRef<ButtonReleaseEvent>(event->button)));
+ case PUGL_MOTION:
+ return static_cast<PuglStatus>(
+ onMotion(typedEventRef<MotionEvent>(event->motion)));
+ case PUGL_SCROLL:
+ return static_cast<PuglStatus>(
+ onScroll(typedEventRef<ScrollEvent>(event->scroll)));
+ case PUGL_CLIENT:
+ return static_cast<PuglStatus>(
+ onClient(typedEventRef<ClientEvent>(event->client)));
+ case PUGL_TIMER:
+ return static_cast<PuglStatus>(
+ onTimer(typedEventRef<TimerEvent>(event->timer)));
+ }
+
+ return PUGL_FAILURE;
+ }
+
+ World& _world;
+};
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+/**
+ @}
+*/
+
+#endif /* PUGL_PUGL_HPP */
diff --git a/include/pugl/pugl.ipp b/include/pugl/pugl.ipp
new file mode 100644
index 0000000..0ed8c4d
--- /dev/null
+++ b/include/pugl/pugl.ipp
@@ -0,0 +1,154 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl.ipp
+ @brief Pugl C++ API wrapper implementation.
+
+ This file must be included exactly once in the application.
+*/
+
+#include "pugl/pugl.hpp"
+
+namespace pugl {
+
+Status
+View::onCreate(const CreateEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onDestroy(const DestroyEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onConfigure(const ConfigureEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onMap(const MapEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onUnmap(const UnmapEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onUpdate(const UpdateEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onExpose(const ExposeEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onClose(const CloseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onFocusIn(const FocusInEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onFocusOut(const FocusOutEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onKeyPress(const KeyPressEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onKeyRelease(const KeyReleaseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onText(const TextEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onPointerIn(const PointerInEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onPointerOut(const PointerOutEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onButtonPress(const ButtonPressEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onButtonRelease(const ButtonReleaseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onMotion(const MotionEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onScroll(const ScrollEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onClient(const ClientEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onTimer(const TimerEvent&)
+{
+ return pugl::Status::success;
+}
+
+} // namespace pugl
diff --git a/include/pugl/pugl_cairo.h b/include/pugl/pugl_cairo.h
new file mode 100644
index 0000000..4b3b621
--- /dev/null
+++ b/include/pugl/pugl_cairo.h
@@ -0,0 +1,50 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_cairo.h
+ @brief Declaration of Cairo backend accessor.
+*/
+
+#ifndef PUGL_PUGL_CAIRO_H
+#define PUGL_PUGL_CAIRO_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup cairo Cairo
+ Cairo graphics support.
+ @ingroup pugl_c
+ @{
+*/
+
+/**
+ Cairo graphics backend accessor.
+
+ Pass the return value to puglInitBackend() to draw to a view with Cairo.
+*/
+PUGL_API PUGL_CONST_FUNC const PuglBackend*
+puglCairoBackend(void);
+
+/**
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif // PUGL_PUGL_CAIRO_H
diff --git a/include/pugl/pugl_cairo.hpp b/include/pugl/pugl_cairo.hpp
new file mode 100644
index 0000000..5b17ab7
--- /dev/null
+++ b/include/pugl/pugl_cairo.hpp
@@ -0,0 +1,50 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_cairo.hpp
+ @brief Declaration of Cairo backend accessor for C++.
+*/
+
+#ifndef PUGL_PUGL_CAIRO_HPP
+#define PUGL_PUGL_CAIRO_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_cairo.h"
+
+namespace pugl {
+
+/**
+ @defgroup cairoxx Cairo
+ Cairo graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc puglCairoBackend
+static inline const PuglBackend*
+cairoBackend()
+{
+ return puglCairoBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_CAIRO_HPP
diff --git a/include/pugl/pugl_gl.h b/include/pugl/pugl_gl.h
new file mode 100644
index 0000000..471c5ac
--- /dev/null
+++ b/include/pugl/pugl_gl.h
@@ -0,0 +1,61 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_gl.h
+ @brief OpenGL-specific API.
+*/
+
+#ifndef PUGL_PUGL_GL_H
+#define PUGL_PUGL_GL_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup gl OpenGL
+ OpenGL graphics support.
+ @ingroup pugl_c
+ @{
+*/
+
+/**
+ OpenGL extension function.
+*/
+typedef void (*PuglGlFunc)(void);
+
+/**
+ Return the address of an OpenGL extension function.
+*/
+PUGL_API PuglGlFunc
+puglGetProcAddress(const char* name);
+
+/**
+ OpenGL graphics backend.
+
+ Pass the return value to puglSetBackend() to draw to a view with OpenGL.
+*/
+PUGL_API PUGL_CONST_FUNC const PuglBackend*
+puglGlBackend(void);
+
+PUGL_END_DECLS
+
+/**
+ @}
+*/
+
+#endif // PUGL_PUGL_GL_H
diff --git a/include/pugl/pugl_gl.hpp b/include/pugl/pugl_gl.hpp
new file mode 100644
index 0000000..4bc5bbd
--- /dev/null
+++ b/include/pugl/pugl_gl.hpp
@@ -0,0 +1,60 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_gl.hpp
+ @brief OpenGL-specific C++ API.
+*/
+
+#ifndef PUGL_PUGL_GL_HPP
+#define PUGL_PUGL_GL_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+namespace pugl {
+
+/**
+ @defgroup glxx OpenGL
+ OpenGL graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc PuglGlFunc
+using GlFunc = PuglGlFunc;
+
+/// @copydoc puglGetProcAddress
+static inline GlFunc
+getProcAddress(const char* name)
+{
+ return puglGetProcAddress(name);
+}
+
+/// @copydoc puglGlBackend
+static inline const PuglBackend*
+glBackend()
+{
+ return puglGlBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_GL_HPP
diff --git a/include/pugl/pugl_stub.h b/include/pugl/pugl_stub.h
new file mode 100644
index 0000000..f50418e
--- /dev/null
+++ b/include/pugl/pugl_stub.h
@@ -0,0 +1,60 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_stub.h
+ @brief Stub backend functions and accessor declaration.
+*/
+
+#ifndef PUGL_PUGL_STUB_H
+#define PUGL_PUGL_STUB_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup stub Stub
+
+ Stub graphics backend.
+
+ The stub backend functions do nothing and always
+ return success. These do not make for a usable backend on their own since
+ the platform implementation would fail to create a window, but are useful
+ for other backends to reuse since not all need non-trivial implementations
+ of every backend function.
+
+ @ingroup pugl_c
+ @{
+*/
+
+/**
+ Stub graphics backend.
+
+ This backend just creates a simple native window without setting up any
+ portable graphics API.
+*/
+PUGL_API PUGL_CONST_FUNC
+const PuglBackend*
+puglStubBackend(void);
+
+/**
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif // PUGL_PUGL_STUB_H
diff --git a/include/pugl/pugl_stub.hpp b/include/pugl/pugl_stub.hpp
new file mode 100644
index 0000000..c5f3901
--- /dev/null
+++ b/include/pugl/pugl_stub.hpp
@@ -0,0 +1,50 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ 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 pugl_stub.hpp
+ @brief Declaration of Stub backend accessor for C++.
+*/
+
+#ifndef PUGL_PUGL_STUB_HPP
+#define PUGL_PUGL_STUB_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+namespace pugl {
+
+/**
+ @defgroup stubxx Stub
+ Stub graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc puglStubBackend
+static inline const PuglBackend*
+stubBackend()
+{
+ return puglStubBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_STUB_HPP