aboutsummaryrefslogtreecommitdiffstats
path: root/pugl
diff options
context:
space:
mode:
Diffstat (limited to 'pugl')
-rw-r--r--pugl/cairo_gl.h105
-rw-r--r--pugl/gl.h32
-rw-r--r--pugl/glew.h32
-rw-r--r--pugl/glu.h32
-rw-r--r--pugl/pugl.h622
-rw-r--r--pugl/pugl.hpp106
-rw-r--r--pugl/pugl_internal.h241
-rw-r--r--pugl/pugl_osx.m727
-rw-r--r--pugl/pugl_win.cpp644
-rw-r--r--pugl/pugl_x11.c722
10 files changed, 3263 insertions, 0 deletions
diff --git a/pugl/cairo_gl.h b/pugl/cairo_gl.h
new file mode 100644
index 0000000..5c0f1f9
--- /dev/null
+++ b/pugl/cairo_gl.h
@@ -0,0 +1,105 @@
+/*
+ Copyright 2016 David Robillard <http://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.
+*/
+
+#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
+
+#include <cairo/cairo.h>
+#include <stdint.h>
+
+#include "pugl/gl.h"
+
+typedef struct {
+ unsigned texture_id;
+ uint8_t* buffer;
+} PuglCairoGL;
+
+static cairo_surface_t*
+pugl_cairo_gl_create(PuglCairoGL* ctx, int width, int height, int bpp)
+{
+ free(ctx->buffer);
+ ctx->buffer = (uint8_t*)calloc(bpp * width * height, sizeof(uint8_t));
+ if (!ctx->buffer) {
+ fprintf(stderr, "failed to allocate surface buffer\n");
+ return NULL;
+ }
+
+ return cairo_image_surface_create_for_data(
+ ctx->buffer, CAIRO_FORMAT_ARGB32, width, height, bpp * width);
+}
+
+static void
+pugl_cairo_gl_free(PuglCairoGL* ctx)
+{
+ free(ctx->buffer);
+ ctx->buffer = NULL;
+}
+
+static void
+pugl_cairo_gl_configure(PuglCairoGL* ctx, int width, int height)
+{
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_TEXTURE_RECTANGLE_ARB);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glDeleteTextures(1, &ctx->texture_id);
+ glGenTextures(1, &ctx->texture_id);
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, ctx->texture_id);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+}
+
+static void
+pugl_cairo_gl_draw(PuglCairoGL* ctx, int width, int height)
+{
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glPushMatrix();
+ glEnable(GL_TEXTURE_RECTANGLE_ARB);
+ glEnable(GL_TEXTURE_2D);
+
+ glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
+ width, height, 0,
+ GL_BGRA, GL_UNSIGNED_BYTE, ctx->buffer);
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, (GLfloat)height);
+ glVertex2f(-1.0f, -1.0f);
+
+ glTexCoord2f((GLfloat)width, (GLfloat)height);
+ glVertex2f(1.0f, -1.0f);
+
+ glTexCoord2f((GLfloat)width, 0.0f);
+ glVertex2f(1.0f, 1.0f);
+
+ glTexCoord2f(0.0f, 0.0f);
+ glVertex2f(-1.0f, 1.0f);
+ glEnd();
+
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_TEXTURE_RECTANGLE_ARB);
+ glPopMatrix();
+}
+
+#endif
diff --git a/pugl/gl.h b/pugl/gl.h
new file mode 100644
index 0000000..9a6aeef
--- /dev/null
+++ b/pugl/gl.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2012-2014 David Robillard <http://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 Portable header wrapper for gl.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/gl.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/gl.h"
+#endif
+
diff --git a/pugl/glew.h b/pugl/glew.h
new file mode 100644
index 0000000..5374406
--- /dev/null
+++ b/pugl/glew.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2016 David Robillard <http://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 Portable header wrapper for glew.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/glew.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/glew.h"
+#endif
+
diff --git a/pugl/glu.h b/pugl/glu.h
new file mode 100644
index 0000000..0d3e8e1
--- /dev/null
+++ b/pugl/glu.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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 Portable header wrapper for glu.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/glu.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/glu.h"
+#endif
+
diff --git a/pugl/pugl.h b/pugl/pugl.h
new file mode 100644
index 0000000..1b22260
--- /dev/null
+++ b/pugl/pugl.h
@@ -0,0 +1,622 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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 API for Pugl, a minimal portable API for OpenGL.
+*/
+
+#ifndef PUGL_H_INCLUDED
+#define PUGL_H_INCLUDED
+
+#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
+
+#ifdef __cplusplus
+extern "C" {
+#else
+# include <stdbool.h>
+#endif
+
+/**
+ @defgroup pugl Pugl
+ A minimal portable API for OpenGL.
+ @{
+*/
+
+/**
+ A Pugl view.
+*/
+typedef struct PuglViewImpl PuglView;
+
+/**
+ A native window handle.
+
+ On X11, this is a Window.
+ On OSX, this is an NSView*.
+ On Windows, this is a HWND.
+*/
+typedef intptr_t PuglNativeWindow;
+
+/**
+ Handle for opaque user data.
+*/
+typedef void* PuglHandle;
+
+/**
+ Return status code.
+*/
+typedef enum {
+ PUGL_SUCCESS = 0
+} PuglStatus;
+
+/**
+ Drawing context type.
+*/
+typedef enum {
+ PUGL_GL = 0x1,
+ PUGL_CAIRO = 0x2,
+ PUGL_CAIRO_GL = 0x3
+} PuglContextType;
+
+/**
+ Convenience symbols for ASCII control characters.
+*/
+typedef enum {
+ PUGL_CHAR_BACKSPACE = 0x08,
+ PUGL_CHAR_ESCAPE = 0x1B,
+ PUGL_CHAR_DELETE = 0x7F
+} PuglChar;
+
+/**
+ Keyboard modifier flags.
+*/
+typedef enum {
+ PUGL_MOD_SHIFT = 1, /**< Shift key */
+ PUGL_MOD_CTRL = 1 << 1, /**< Control key */
+ PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */
+ PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */
+} PuglMod;
+
+/**
+ Special (non-Unicode) keyboard keys.
+
+ The numerical values of these symbols occupy a reserved range of Unicode
+ points, so it is possible to express either a PuglKey value or a Unicode
+ character in the same variable. This is sometimes useful for interfacing
+ with APIs that do not make this distinction.
+*/
+typedef enum {
+ 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_CTRL,
+ PUGL_KEY_ALT,
+ PUGL_KEY_SUPER
+} PuglKey;
+
+/**
+ The type of a PuglEvent.
+*/
+typedef enum {
+ PUGL_NOTHING, /**< No event */
+ PUGL_BUTTON_PRESS, /**< Mouse button press */
+ PUGL_BUTTON_RELEASE, /**< Mouse button release */
+ PUGL_CONFIGURE, /**< View moved and/or resized */
+ PUGL_EXPOSE, /**< View exposed, redraw required */
+ PUGL_CLOSE, /**< Close view */
+ PUGL_KEY_PRESS, /**< Key press */
+ PUGL_KEY_RELEASE, /**< Key release */
+ PUGL_ENTER_NOTIFY, /**< Pointer entered view */
+ PUGL_LEAVE_NOTIFY, /**< Pointer left view */
+ PUGL_MOTION_NOTIFY, /**< Pointer motion */
+ PUGL_SCROLL, /**< Scroll */
+ PUGL_FOCUS_IN, /**< Keyboard focus entered view */
+ PUGL_FOCUS_OUT /**< Keyboard focus left view */
+} PuglEventType;
+
+typedef enum {
+ PUGL_IS_SEND_EVENT = 1
+} PuglEventFlag;
+
+/**
+ 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;
+
+/**
+ Common header for all event structs.
+*/
+typedef struct {
+ PuglEventType type; /**< Event type. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+} PuglEventAny;
+
+/**
+ Button press or release event.
+
+ For event types PUGL_BUTTON_PRESS and PUGL_BUTTON_RELEASE.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ unsigned button; /**< 1-relative button number. */
+} PuglEventButton;
+
+/**
+ Configure event for when window size or position has changed.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CONFIGURE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t 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;
+
+/**
+ Expose event for when a region must be redrawn.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_EXPOSE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t 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. */
+ int count; /**< Number of expose events to follow. */
+} PuglEventExpose;
+
+/**
+ Window close event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CLOSE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+} PuglEventClose;
+
+/**
+ Key press/release event.
+
+ Keys that correspond to a Unicode character have `character` and `utf8` set.
+ Other keys will have `character` 0, but `special` may be set if this is a
+ known special key.
+
+ A key press may be part of a multi-key sequence to generate a wide
+ character. If `filter` is set, this event is part of a multi-key sequence
+ and should be ignored if the application is reading textual input.
+ Following the series of filtered press events, a press event with
+ `character` and `utf8` (but `keycode` 0) will be sent. This event will have
+ no corresponding release event.
+
+ Generally, an application should either work with raw keyboard press/release
+ events based on `keycode` (ignoring events with `keycode` 0), or
+ read textual input based on `character` or `utf8` (ignoring releases and
+ events with `filter` 1). Note that blindly appending `utf8` will yield
+ incorrect text, since press events are sent for both individually composed
+ keys and the resulting synthetic multi-byte press.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ unsigned keycode; /**< Raw key code. */
+ uint32_t character; /**< Unicode character code, or 0. */
+ PuglKey special; /**< Special key, or 0. */
+ uint8_t utf8[8]; /**< UTF-8 string. */
+ bool filter; /**< True if part of a multi-key sequence. */
+} PuglEventKey;
+
+/**
+ Pointer crossing event (enter and leave).
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_ENTER_NOTIFY or PUGL_LEAVE_NOTIFY. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ PuglCrossingMode mode; /**< Reason for crossing. */
+} PuglEventCrossing;
+
+/**
+ Pointer motion event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_MOTION_NOTIFY. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ bool is_hint; /**< True iff this event is a motion hint. */
+ bool focus; /**< True iff this is the focused window. */
+} 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. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ double dx; /**< Scroll X distance in lines. */
+ double dy; /**< Scroll Y distance in lines. */
+} PuglEventScroll;
+
+/**
+ Keyboard focus event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_FOCUS_IN or PUGL_FOCUS_OUT. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ bool grab; /**< True iff this is a grab/ungrab event. */
+} PuglEventFocus;
+
+/**
+ Interface event.
+
+ This is a union of all event structs. 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.
+*/
+typedef union {
+ PuglEventType type; /**< Event type. */
+ PuglEventAny any; /**< Valid for all event types. */
+ PuglEventButton button; /**< PUGL_BUTTON_PRESS, PUGL_BUTTON_RELEASE. */
+ PuglEventConfigure configure; /**< PUGL_CONFIGURE. */
+ PuglEventExpose expose; /**< PUGL_EXPOSE. */
+ PuglEventClose close; /**< PUGL_CLOSE. */
+ PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */
+ PuglEventCrossing crossing; /**< PUGL_ENTER_NOTIFY, PUGL_LEAVE_NOTIFY. */
+ PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */
+ PuglEventScroll scroll; /**< PUGL_SCROLL. */
+ PuglEventFocus focus; /**< PUGL_FOCUS_IN, PUGL_FOCUS_OUT. */
+} PuglEvent;
+
+/**
+ @name Initialization
+ Configuration functions which must be called before creating a window.
+ @{
+*/
+
+/**
+ Create a Pugl view.
+
+ To create a window, call the various puglInit* functions as necessary, then
+ call puglCreateWindow().
+
+ @param pargc Pointer to argument count (currently unused).
+ @param argv Arguments (currently unused).
+ @return A newly created view.
+*/
+PUGL_API PuglView*
+puglInit(int* pargc, char** argv);
+
+/**
+ Set the window class name before creating a window.
+*/
+PUGL_API void
+puglInitWindowClass(PuglView* view, const char* name);
+
+/**
+ Set the parent window before creating a window (for embedding).
+*/
+PUGL_API void
+puglInitWindowParent(PuglView* view, PuglNativeWindow parent);
+
+/**
+ Set the window size before creating a window.
+*/
+PUGL_API void
+puglInitWindowSize(PuglView* view, int width, int height);
+
+/**
+ Set the minimum window size before creating a window.
+*/
+PUGL_API void
+puglInitWindowMinSize(PuglView* view, int width, int 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.
+*/
+PUGL_API void
+puglInitWindowAspectRatio(PuglView* view,
+ int min_x,
+ int min_y,
+ int max_x,
+ int max_y);
+
+/**
+ Enable or disable resizing before creating a window.
+*/
+PUGL_API void
+puglInitResizable(PuglView* view, bool resizable);
+
+/**
+ Set transient parent before creating a window.
+
+ On X11, parent must be a Window.
+ On OSX, parent must be an NSView*.
+*/
+PUGL_API void
+puglInitTransientFor(PuglView* view, uintptr_t parent);
+
+/**
+ Set the context type before creating a window.
+*/
+PUGL_API void
+puglInitContextType(PuglView* view, PuglContextType type);
+
+/**
+ @}
+*/
+
+/**
+ @name Windows
+ Functions for creating and managing a visible window for a view.
+ @{
+*/
+
+/**
+ Create a window with the settings given by the various puglInit functions.
+
+ @return 1 (pugl does not currently support multiple windows).
+*/
+PUGL_API int
+puglCreateWindow(PuglView* view, const char* title);
+
+/**
+ Show the current window.
+*/
+PUGL_API void
+puglShowWindow(PuglView* view);
+
+/**
+ Hide the current window.
+*/
+PUGL_API void
+puglHideWindow(PuglView* view);
+
+/**
+ Return the native window handle.
+*/
+PUGL_API PuglNativeWindow
+puglGetNativeWindow(PuglView* view);
+
+/**
+ @}
+*/
+
+/**
+ Set the handle to be passed to all callbacks.
+
+ This is generally a pointer to a struct which contains all necessary state.
+ Everything needed in callbacks should be here, not in static variables.
+*/
+PUGL_API void
+puglSetHandle(PuglView* view, PuglHandle handle);
+
+/**
+ Get the handle to be passed to all callbacks.
+*/
+PUGL_API PuglHandle
+puglGetHandle(PuglView* view);
+
+/**
+ Return true iff the view is currently visible.
+*/
+PUGL_API bool
+puglGetVisible(PuglView* view);
+
+/**
+ Get the current size of the view.
+*/
+PUGL_API void
+puglGetSize(PuglView* view, int* width, int* height);
+
+/**
+ @name Context
+ Functions for accessing the drawing context.
+ @{
+*/
+
+/**
+ Get the drawing context.
+
+ For PUGL_GL contexts, this is unused and returns NULL.
+ For PUGL_CAIRO contexts, this returns a pointer to a cairo_t.
+*/
+PUGL_API void*
+puglGetContext(PuglView* view);
+
+
+/**
+ Enter the drawing context.
+
+ This must be called before any code that accesses the drawing context,
+ including any GL functions. This is only necessary for code that does so
+ outside the usual draw callback or handling of an expose event.
+*/
+PUGL_API void
+puglEnterContext(PuglView* view);
+
+/**
+ Leave the drawing context.
+
+ This must be called after puglEnterContext and applies the results of the
+ drawing code (for example, by swapping buffers).
+*/
+PUGL_API void
+puglLeaveContext(PuglView* view, bool flush);
+
+/**
+ @}
+*/
+
+/**
+ @name Event Handling
+ @{
+*/
+
+/**
+ A function called when an event occurs.
+*/
+typedef void (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
+
+/**
+ Set the function to call when an event occurs.
+*/
+PUGL_API void
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
+
+/**
+ Ignore synthetic repeated key events.
+*/
+PUGL_API void
+puglIgnoreKeyRepeat(PuglView* view, bool ignore);
+
+/**
+ Grab the input focus.
+*/
+PUGL_API void
+puglGrabFocus(PuglView* 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).
+*/
+PUGL_API 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.
+*/
+PUGL_API PuglStatus
+puglProcessEvents(PuglView* view);
+
+/**
+ @}
+*/
+
+/**
+ Request a redisplay on the next call to puglProcessEvents().
+*/
+PUGL_API void
+puglPostRedisplay(PuglView* view);
+
+/**
+ Destroy a GL window.
+*/
+PUGL_API void
+puglDestroy(PuglView* view);
+
+/**
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PUGL_H_INCLUDED */
diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp
new file mode 100644
index 0000000..8232887
--- /dev/null
+++ b/pugl/pugl.hpp
@@ -0,0 +1,106 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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 C++ API for Pugl, a minimal portable API for OpenGL.
+*/
+
+#ifndef PUGL_HPP_INCLUDED
+#define PUGL_HPP_INCLUDED
+
+#include "pugl/pugl.h"
+
+/**
+ @defgroup puglmm Puglmm
+ C++ API wrapper for Pugl.
+ @{
+*/
+
+namespace pugl {
+
+class View {
+public:
+ View(int* pargc, char** argv)
+ : _view(puglInit(pargc, argv))
+ {
+ puglSetHandle(_view, this);
+ puglSetEventFunc(_view, _onEvent);
+ }
+
+ virtual ~View() { puglDestroy(_view); }
+
+ virtual void initWindowParent(PuglNativeWindow parent) {
+ puglInitWindowParent(_view, parent);
+ }
+
+ virtual void initWindowSize(int width, int height) {
+ puglInitWindowSize(_view, width, height);
+ }
+
+ virtual void initWindowMinSize(int width, int height) {
+ puglInitWindowMinSize(_view, width, height);
+ }
+
+ virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) {
+ puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y);
+ }
+
+ virtual void initResizable(bool resizable) {
+ puglInitResizable(_view, resizable);
+ }
+
+ virtual void initTransientFor(uintptr_t parent) {
+ puglInitTransientFor(_view, parent);
+ }
+
+ virtual void initContextType(PuglContextType type) {
+ puglInitContextType(_view, type);
+ }
+
+ virtual void createWindow(const char* title) {
+ puglCreateWindow(_view, title);
+ }
+
+ virtual void showWindow() { puglShowWindow(_view); }
+ virtual void hideWindow() { puglHideWindow(_view); }
+ virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); }
+
+ virtual void onEvent(const PuglEvent* event) = 0;
+
+ virtual void* getContext() { return puglGetContext(_view); }
+ virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
+ virtual void grabFocus() { puglGrabFocus(_view); }
+ virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); }
+ virtual PuglStatus processEvents() { return puglProcessEvents(_view); }
+ virtual void postRedisplay() { puglPostRedisplay(_view); }
+
+ PuglView* cobj() { return _view; }
+
+private:
+ static void _onEvent(PuglView* view, const PuglEvent* event) {
+ ((View*)puglGetHandle(view))->onEvent(event);
+ }
+
+ PuglView* _view;
+};
+
+} // namespace pugl
+
+/**
+ @}
+*/
+
+#endif /* PUGL_HPP_INCLUDED */
diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h
new file mode 100644
index 0000000..4a3fc0c
--- /dev/null
+++ b/pugl/pugl_internal.h
@@ -0,0 +1,241 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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_internal.h Private platform-independent definitions.
+
+ Note this file contains function definitions, so it must be compiled into
+ the final binary exactly once. Each platform specific implementation file
+ including it once should achieve this.
+
+ If you are copying the pugl code into your source tree, the following
+ symbols can be defined to tweak pugl behaviour:
+
+ PUGL_HAVE_CAIRO: Include Cairo support code.
+ PUGL_HAVE_GL: Include OpenGL support code.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "pugl/pugl.h"
+
+typedef struct PuglInternalsImpl PuglInternals;
+
+struct PuglViewImpl {
+ PuglHandle handle;
+ PuglEventFunc eventFunc;
+
+ PuglInternals* impl;
+
+ char* windowClass;
+ PuglNativeWindow parent;
+ PuglContextType ctx_type;
+ uintptr_t transient_parent;
+
+ int width;
+ int height;
+ int min_width;
+ int min_height;
+ int min_aspect_x;
+ int min_aspect_y;
+ int max_aspect_x;
+ int max_aspect_y;
+ bool ignoreKeyRepeat;
+ bool redisplay;
+ bool resizable;
+ bool visible;
+};
+
+PuglInternals* puglInitInternals(void);
+
+PuglView*
+puglInit(int* pargc, char** argv)
+{
+ PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
+ if (!view) {
+ return NULL;
+ }
+
+ PuglInternals* impl = puglInitInternals();
+ if (!impl) {
+ return NULL;
+ }
+
+ view->ctx_type = PUGL_GL;
+ view->impl = impl;
+ view->width = 640;
+ view->height = 480;
+
+ return view;
+}
+
+void
+puglInitWindowSize(PuglView* view, int width, int height)
+{
+ view->width = width;
+ view->height = height;
+}
+
+void
+puglInitWindowMinSize(PuglView* view, int width, int height)
+{
+ view->min_width = width;
+ view->min_height = height;
+}
+
+void
+puglInitWindowAspectRatio(PuglView* view,
+ int min_x,
+ int min_y,
+ int max_x,
+ int max_y)
+{
+ view->min_aspect_x = min_x;
+ view->min_aspect_y = min_y;
+ view->max_aspect_x = max_x;
+ view->max_aspect_y = max_y;
+}
+
+void
+puglInitWindowClass(PuglView* view, const char* name)
+{
+ const size_t len = strlen(name);
+
+ free(view->windowClass);
+ view->windowClass = (char*)calloc(1, len + 1);
+ memcpy(view->windowClass, name, len);
+}
+
+void
+puglInitWindowParent(PuglView* view, PuglNativeWindow parent)
+{
+ view->parent = parent;
+}
+
+void
+puglInitResizable(PuglView* view, bool resizable)
+{
+ view->resizable = resizable;
+}
+
+void
+puglInitTransientFor(PuglView* view, uintptr_t parent)
+{
+ view->transient_parent = parent;
+}
+
+void
+puglInitContextType(PuglView* view, PuglContextType type)
+{
+ view->ctx_type = type;
+}
+
+void
+puglSetHandle(PuglView* view, PuglHandle handle)
+{
+ view->handle = handle;
+}
+
+PuglHandle
+puglGetHandle(PuglView* view)
+{
+ return view->handle;
+}
+
+bool
+puglGetVisible(PuglView* view)
+{
+ return view->visible;
+}
+
+void
+puglGetSize(PuglView* view, int* width, int* height)
+{
+ *width = view->width;
+ *height = view->height;
+}
+
+void
+puglIgnoreKeyRepeat(PuglView* view, bool ignore)
+{
+ view->ignoreKeyRepeat = ignore;
+}
+
+void
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
+{
+ view->eventFunc = eventFunc;
+}
+
+/** Return the code point for buf, or the replacement character on error. */
+static uint32_t
+puglDecodeUTF8(const uint8_t* buf)
+{
+#define FAIL_IF(cond) { if (cond) return 0xFFFD; }
+
+ // 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] & 0xC0) != 0x80);
+ return (buf[0] << 6) + buf[1] - 0x3080;
+ } else if (buf[0] < 0xF0) {
+ FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
+ FAIL_IF((buf[2] & 0xC0) != 0x80);
+ return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080;
+ } else if (buf[0] < 0xF5) {
+ FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
+ FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
+ FAIL_IF((buf[2] & 0xC0) != 0x80);
+ FAIL_IF((buf[3] & 0xC0) != 0x80);
+ return ((buf[0] << 18) +
+ (buf[1] << 12) +
+ (buf[2] << 6) +
+ buf[3] - 0x3C82080);
+ }
+ return 0xFFFD;
+}
+
+static void
+puglDispatchEvent(PuglView* view, const PuglEvent* event)
+{
+ switch (event->type) {
+ case PUGL_NOTHING:
+ break;
+ case PUGL_CONFIGURE:
+ view->width = event->configure.width;
+ view->height = event->configure.height;
+ puglEnterContext(view);
+ view->eventFunc(view, event);
+ puglLeaveContext(view, false);
+ break;
+ case PUGL_EXPOSE:
+ if (event->expose.count == 0) {
+ puglEnterContext(view);
+ view->eventFunc(view, event);
+ puglLeaveContext(view, true);
+ }
+ break;
+ default:
+ view->eventFunc(view, event);
+ }
+}
diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m
new file mode 100644
index 0000000..71d98a1
--- /dev/null
+++ b/pugl/pugl_osx.m
@@ -0,0 +1,727 @@
+/*
+ Copyright 2012-2017 David Robillard <http://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 pugl_osx.m OSX/Cocoa Pugl Implementation.
+*/
+
+#include <stdlib.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include "pugl/cairo_gl.h"
+#include "pugl/gl.h"
+#include "pugl/pugl_internal.h"
+
+@class PuglOpenGLView;
+
+struct PuglInternalsImpl {
+ NSApplication* app;
+ PuglOpenGLView* glview;
+ id window;
+ NSEvent* nextEvent;
+ unsigned mods;
+#ifdef PUGL_HAVE_CAIRO
+ cairo_surface_t* surface;
+ cairo_t* cr;
+ PuglCairoGL cairo_gl;
+#endif
+};
+
+@interface PuglWindow : NSWindow
+{
+@public
+ PuglView* puglview;
+}
+
+- (id) initWithContentRect:(NSRect)contentRect
+ styleMask:(unsigned int)aStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)flag;
+- (void) setPuglview:(PuglView*)view;
+- (BOOL) windowShouldClose:(id)sender;
+- (BOOL) canBecomeKeyWindow:(id)sender;
+@end
+
+@implementation PuglWindow
+
+- (id)initWithContentRect:(NSRect)contentRect
+ styleMask:(unsigned int)aStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)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:NSMakeSize(view->width, view->height)];
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ const PuglEventClose ev = {
+ PUGL_CLOSE,
+ puglview,
+ 0
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+
+ return YES;
+}
+
+- (BOOL) canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeMainWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeKeyWindow:(id)sender
+{
+ return NO;
+}
+
+@end
+
+@interface PuglOpenGLView : NSOpenGLView
+{
+@public
+ PuglView* puglview;
+
+ NSTrackingArea* trackingArea;
+}
+
+- (id) initWithFrame:(NSRect)frame;
+- (void) reshape;
+- (void) drawRect:(NSRect)rect;
+- (NSPoint) eventLocation:(NSEvent*)event;
+- (void) mouseEntered:(NSEvent*)event;
+- (void) mouseExited:(NSEvent*)event;
+- (void) mouseMoved:(NSEvent*)event;
+- (void) mouseDragged:(NSEvent*)event;
+- (void) rightMouseDragged:(NSEvent*)event;
+- (void) mouseDown:(NSEvent*)event;
+- (void) mouseUp:(NSEvent*)event;
+- (void) rightMouseDown:(NSEvent*)event;
+- (void) rightMouseUp:(NSEvent*)event;
+- (void) otherMouseDragged:(NSEvent*)event;
+- (void) otherMouseDown:(NSEvent*)event;
+- (void) otherMouseUp:(NSEvent*)event;
+- (void) scrollWheel:(NSEvent*)event;
+- (void) keyDown:(NSEvent*)event;
+- (void) keyUp:(NSEvent*)event;
+- (void) flagsChanged:(NSEvent*)event;
+
+@end
+
+@implementation PuglOpenGLView
+
+- (id) initWithFrame:(NSRect)frame
+{
+ NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAColorSize, 32,
+ NSOpenGLPFADepthSize, 32,
+ 0
+ };
+
+ NSOpenGLPixelFormat* pixelFormat = [
+ [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
+
+ if (pixelFormat) {
+ self = [super initWithFrame:frame pixelFormat:pixelFormat];
+ [pixelFormat release];
+ } else {
+ self = [super initWithFrame:frame];
+ }
+
+ if (self) {
+ [[self openGLContext] makeCurrentContext];
+ [self reshape];
+ }
+ return self;
+}
+
+- (void) reshape
+{
+ [[self openGLContext] update];
+
+ if (!puglview) {
+ return;
+ }
+
+ const NSRect bounds = [self bounds];
+ const PuglEventConfigure ev = {
+ PUGL_CONFIGURE,
+ puglview,
+ 0,
+ bounds.origin.x,
+ bounds.origin.y,
+ bounds.size.width,
+ bounds.size.height,
+ };
+
+#ifdef PUGL_HAVE_CAIRO
+ PuglInternals* impl = puglview->impl;
+ if (puglview->ctx_type & PUGL_CAIRO) {
+ cairo_surface_destroy(impl->surface);
+ cairo_destroy(impl->cr);
+ impl->surface = pugl_cairo_gl_create(
+ &impl->cairo_gl, ev.width, ev.height, 4);
+ impl->cr = cairo_create(impl->surface);
+ pugl_cairo_gl_configure(&impl->cairo_gl, ev.width, ev.height);
+ }
+#endif
+
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) drawRect:(NSRect)rect
+{
+ const PuglEventExpose ev = {
+ PUGL_EXPOSE,
+ puglview,
+ 0,
+ rect.origin.x,
+ rect.origin.y,
+ rect.size.width,
+ rect.size.height,
+ 0
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+
+#ifdef PUGL_HAVE_CAIRO
+ if (puglview->ctx_type & PUGL_CAIRO) {
+ pugl_cairo_gl_draw(
+ &puglview->impl->cairo_gl, puglview->width, puglview->height);
+ }
+#endif
+}
+
+- (BOOL) acceptsFirstResponder
+{
+ return YES;
+}
+
+static unsigned
+getModifiers(PuglView* view, NSEvent* ev)
+{
+ const unsigned modifierFlags = [ev modifierFlags];
+
+ unsigned mods = 0;
+ mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0;
+ mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0;
+ mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0;
+ mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0;
+ return mods;
+}
+
+static PuglKey
+keySymToSpecial(PuglView* view, NSEvent* 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 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;
+ }
+ // 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];
+}
+
+- (NSPoint) eventLocation:(NSEvent*)event
+{
+ return [self convertPoint:[event locationInWindow] fromView:nil];
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent
+{
+ [self updateTrackingAreas];
+}
+
+- (void)mouseExited:(NSEvent*)theEvent
+{
+}
+
+- (void) mouseMoved:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventMotion ev = {
+ PUGL_MOTION_NOTIFY,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ 0,
+ 1
+ };
+ puglDispatchEvent(puglview, (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,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ (unsigned)[event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) mouseUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventButton ev = {
+ PUGL_BUTTON_RELEASE,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ (unsigned)[event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+ [self updateTrackingAreas];
+}
+
+- (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
+{
+ [self updateTrackingAreas];
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventScroll ev = {
+ PUGL_SCROLL,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event deltaX],
+ [event deltaY]
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+ [self updateTrackingAreas];
+}
+
+- (void) keyDown:(NSEvent*)event
+{
+ if (puglview->ignoreKeyRepeat && [event isARepeat]) {
+ return;
+ }
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+ const uint32_t code = puglDecodeUTF8((const uint8_t*)str);
+ PuglEventKey ev = {
+ PUGL_KEY_PRESS,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event keyCode],
+ (code != 0xFFFD) ? code : 0,
+ keySymToSpecial(puglview, event),
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ false
+ };
+ strncpy((char*)ev.utf8, str, 8);
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) keyUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+ const PuglEventKey ev = {
+ PUGL_KEY_RELEASE,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event keyCode],
+ puglDecodeUTF8((const uint8_t*)str),
+ keySymToSpecial(puglview, event),
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ false,
+ };
+ strncpy((char*)ev.utf8, str, 8);
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) flagsChanged:(NSEvent*)event
+{
+ const unsigned mods = getModifiers(puglview, event);
+ PuglEventType type = PUGL_NOTHING;
+ PuglKey special = 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,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ mods,
+ [event keyCode],
+ 0,
+ special,
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ false
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+ }
+
+ puglview->impl->mods = mods;
+}
+
+@end
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+ [[view->impl->glview openGLContext] makeCurrentContext];
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
+ }
+#endif
+
+ if (flush) {
+ [[view->impl->glview openGLContext] flushBuffer];
+ }
+}
+
+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: constant];
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* impl = view->impl;
+
+ [NSAutoreleasePool new];
+ impl->app = [NSApplication sharedApplication];
+
+ impl->glview = [PuglOpenGLView new];
+ impl->glview->puglview = view;
+
+ [impl->glview setFrameSize:NSMakeSize(view->width, view->height)];
+ [impl->glview addConstraint:
+ puglConstraint(impl->glview, NSLayoutAttributeWidth, view->min_width)];
+ [impl->glview addConstraint:
+ puglConstraint(impl->glview, NSLayoutAttributeHeight, view->min_height)];
+ if (!view->resizable) {
+ [impl->glview setAutoresizingMask:NSViewNotSizable];
+ }
+
+ if (view->transient_parent) {
+ NSView* pview = (NSView*)view->transient_parent;
+ [pview addSubview:impl->glview];
+ [impl->glview setHidden:NO];
+ } else {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:title
+ length:strlen(title)
+ encoding:NSUTF8StringEncoding];
+ NSRect frame = NSMakeRect(0, 0, view->min_width, view->min_height);
+ unsigned style = NSClosableWindowMask | NSTitledWindowMask;
+ if (view->resizable) {
+ style |= NSResizableWindowMask;
+ }
+
+ id window = [[[PuglWindow alloc]
+ initWithContentRect:frame
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:NO
+ ] retain];
+ [window setPuglview:view];
+ [window setTitle:titleString];
+ if (view->min_width || view->min_height) {
+ [window setContentMinSize:NSMakeSize(view->min_width,
+ view->min_height)];
+ }
+ impl->window = window;
+
+ [window setContentView:impl->glview];
+ [impl->app activateIgnoringOtherApps:YES];
+ [window makeFirstResponder:impl->glview];
+ [window makeKeyAndOrderFront:window];
+ }
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:YES];
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:NO];
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ pugl_cairo_gl_free(&view->impl->cairo_gl);
+#endif
+ view->impl->glview->puglview = NULL;
+ [view->impl->glview removeFromSuperview];
+ if (view->impl->window) {
+ [view->impl->window close];
+ }
+ [view->impl->glview release];
+ if (view->impl->window) {
+ [view->impl->window release];
+ }
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ // TODO
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ /* OSX supposedly has queue: and untilDate: selectors that can be used for
+ a blocking non-queueing event check, but if used here cause an
+ unsupported selector error at runtime. I have no idea why, so just get
+ the event and keep it around until the call to puglProcessEvents. */
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent = [view->impl->window
+ nextEventMatchingMask: NSAnyEventMask];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ while (true) {
+ // Get the next event, or use the cached one from puglWaitForEvent
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent = [view->impl->window
+ nextEventMatchingMask: NSAnyEventMask];
+ }
+
+ if (!view->impl->nextEvent) {
+ break; // No events to process, done
+ }
+
+ // Dispatch event
+ [view->impl->app sendEvent: view->impl->nextEvent];
+ view->impl->nextEvent = NULL;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ //view->redisplay = true; // unused
+ [view->impl->glview setNeedsDisplay: YES];
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->glview;
+}
+
+void*
+puglGetContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ return view->impl->cr;
+ }
+#endif
+ return NULL;
+}
diff --git a/pugl/pugl_win.cpp b/pugl/pugl_win.cpp
new file mode 100644
index 0000000..83d4474
--- /dev/null
+++ b/pugl/pugl_win.cpp
@@ -0,0 +1,644 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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_win.cpp Windows/WGL Pugl Implementation.
+*/
+
+#include <windows.h>
+#include <windowsx.h>
+#include <GL/gl.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wctype.h>
+
+#include "pugl/pugl_internal.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
+#ifdef _WIN64
+# ifndef GWLP_USERDATA
+# define GWLP_USERDATA (-21)
+# endif
+#else
+# ifndef GWL_USERDATA
+# define GWL_USERDATA (-21)
+# endif
+#endif
+
+#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
+
+struct PuglInternalsImpl {
+ HWND hwnd;
+ HDC hdc;
+ HGLRC hglrc;
+ WNDCLASS wc;
+};
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+PuglView*
+puglInit()
+{
+ PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
+ PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+ if (!view || !impl) {
+ return NULL;
+ }
+
+ view->impl = impl;
+ view->width = 640;
+ view->height = 480;
+
+ return view;
+}
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_GL) {
+ wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
+ }
+#endif
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_GL && flush) {
+ glFlush();
+ SwapBuffers(view->impl->hdc);
+ }
+#endif
+
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ static const TCHAR* DEFAULT_CLASSNAME = "Pugl";
+
+ PuglInternals* impl = view->impl;
+
+ if (!title) {
+ title = "Window";
+ }
+
+ WNDCLASSEX wc;
+ memset(&wc, 0, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.style = CS_OWNDC;
+ wc.lpfnWndProc = wndProc;
+ wc.hInstance = GetModuleHandle(NULL);
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // TODO: user-specified icon
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
+ wc.lpszClassName = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME;
+ if (!RegisterClassEx(&wc)) {
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return NULL;
+ }
+
+ int winFlags = WS_POPUPWINDOW | WS_CAPTION;
+ if (view->resizable) {
+ winFlags |= WS_SIZEBOX;
+ if (view->min_width || view->min_height) {
+ // Adjust the minimum window size to accomodate requested view size
+ RECT mr = { 0, 0, view->min_width, view->min_height };
+ AdjustWindowRectEx(&mr, winFlags, FALSE, WS_EX_TOPMOST);
+ view->min_width = mr.right - mr.left;
+ view->min_height = mr.bottom - mr.top;
+ }
+ }
+
+ // Adjust the window size to accomodate requested view size
+ RECT wr = { 0, 0, view->width, view->height };
+ AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
+
+ impl->hwnd = CreateWindowEx(
+ WS_EX_TOPMOST,
+ wc.lpszClassName, title,
+ (view->parent ? WS_CHILD : winFlags),
+ CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top,
+ (HWND)view->parent, NULL, NULL, NULL);
+
+ if (!impl->hwnd) {
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return 1;
+ }
+
+#ifdef _WIN64
+ SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
+#else
+ SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG)view);
+#endif
+
+ impl->hdc = GetDC(impl->hwnd);
+
+ PIXELFORMATDESCRIPTOR pfd;
+ ZeroMemory(&pfd, sizeof(pfd));
+ pfd.nSize = sizeof(pfd);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = 24;
+ pfd.cDepthBits = 16;
+ pfd.iLayerType = PFD_MAIN_PLANE;
+
+ int format = ChoosePixelFormat(impl->hdc, &pfd);
+ SetPixelFormat(impl->hdc, format, &pfd);
+
+ impl->hglrc = wglCreateContext(impl->hdc);
+ if (!impl->hglrc) {
+ ReleaseDC(impl->hwnd, impl->hdc);
+ DestroyWindow(impl->hwnd);
+ UnregisterClass(impl->wc.lpszClassName, NULL);
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return NULL;
+ }
+ wglMakeCurrent(impl->hdc, impl->hglrc);
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_SHOWNORMAL);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_HIDE);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(view->impl->hglrc);
+ ReleaseDC(view->impl->hwnd, view->impl->hdc);
+ DestroyWindow(view->impl->hwnd);
+ UnregisterClass(view->impl->wc.lpszClassName, NULL);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+static PuglKey
+keySymToSpecial(int sym)
+{
+ 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_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: return PUGL_KEY_SHIFT;
+ case VK_CONTROL: return PUGL_KEY_CTRL;
+ case VK_MENU: return PUGL_KEY_ALT;
+ case VK_LWIN: return PUGL_KEY_SUPER;
+ case VK_RWIN: return PUGL_KEY_SUPER;
+ }
+ return (PuglKey)0;
+}
+
+static unsigned int
+getModifiers()
+{
+ unsigned int mods = 0;
+ mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0;
+ mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0;
+ mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0;
+ mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0;
+ mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0;
+ return mods;
+}
+
+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();
+ 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.x_root = pt.x;
+ event->button.y_root = pt.y;
+ event->button.state = getModifiers();
+ event->button.button = button;
+}
+
+static void
+initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam, WPARAM wParam)
+{
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ ScreenToClient(view->impl->hwnd, &pt);
+
+ event->scroll.time = GetMessageTime();
+ event->scroll.type = PUGL_SCROLL;
+ event->scroll.x = pt.x;
+ event->scroll.y = pt.y;
+ event->scroll.x_root = GET_X_LPARAM(lParam);
+ event->scroll.y_root = GET_Y_LPARAM(lParam);
+ event->scroll.state = getModifiers();
+ event->scroll.dx = 0;
+ event->scroll.dy = 0;
+}
+
+static unsigned int
+utf16_to_code_point(const wchar_t* input, size_t input_size)
+{
+ unsigned int code_unit = *input;
+ // Equiv. range check between 0xD800 to 0xDBFF inclusive
+ if ((code_unit & 0xFC00) == 0xD800) {
+ if (input_size < 2) {
+ // "Error: is surrogate but input_size too small"
+ return 0xFFFD; // replacement character
+ }
+
+ unsigned int code_unit_2 = *++input;
+ // Equiv. range check between 0xDC00 to 0xDFFF inclusive
+ if ((code_unit_2 & 0xFC00) == 0xDC00) {
+ return (code_unit << 10) + code_unit_2 - 0x35FDC00;
+ }
+
+ // TODO: push_back(code_unit_2);
+ // "Error: Unpaired surrogates."
+ return 0xFFFD; // replacement character
+ }
+ return code_unit;
+}
+
+static void
+initKeyEvent(PuglEvent* event, PuglView* view, bool press, LPARAM lParam)
+{
+ POINT rpos = { 0, 0 };
+ GetCursorPos(&rpos);
+
+ POINT cpos = { rpos.x, rpos.y };
+ ScreenToClient(view->impl->hwnd, &rpos);
+
+ event->key.type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ event->key.time = GetMessageTime();
+ event->key.state = getModifiers();
+ event->key.x_root = rpos.x;
+ event->key.y_root = rpos.y;
+ event->key.x = cpos.x;
+ event->key.y = cpos.y;
+ event->key.keycode = (lParam & 0xFF0000) >> 16;
+ event->key.character = 0;
+ event->key.special = static_cast<PuglKey>(0);
+ event->key.filter = 0;
+}
+
+static void
+wcharBufToEvent(wchar_t* buf, int n, PuglEvent* event)
+{
+ if (n > 0) {
+ char* charp = reinterpret_cast<char*>(event->key.utf8);
+ if (!WideCharToMultiByte(CP_UTF8, 0, buf, n,
+ charp, 8, NULL, NULL)) {
+ /* error: could not convert to utf-8,
+ GetLastError has details */
+ memset(event->key.utf8, 0, 8);
+ // replacement character
+ event->key.utf8[0] = 0xEF;
+ event->key.utf8[1] = 0xBF;
+ event->key.utf8[2] = 0xBD;
+ }
+
+ event->key.character = utf16_to_code_point(buf, n);
+ } else {
+ // replacement character
+ event->key.utf8[0] = 0xEF;
+ event->key.utf8[1] = 0xBF;
+ event->key.utf8[2] = 0xBD;
+ event->key.character = 0xFFFD;
+ }
+}
+
+static void
+translateMessageParamsToEvent(LPARAM lParam, WPARAM wParam, PuglEvent* event)
+{
+ /* TODO: This is a kludge. Would be nice to use ToUnicode here, but this
+ breaks composed keys because it messes with the keyboard state. Not
+ sure how to correctly handle this on Windows. */
+
+ // This is how I really want to do this, but it breaks composed keys (é,
+ // è, ü, ö, and so on) because ToUnicode messes with the keyboard state.
+
+ //wchar_t buf[5];
+ //BYTE keyboard_state[256];
+ //int wcharCount = 0;
+ //GetKeyboardState(keyboard_state);
+ //wcharCount = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC),
+ // keyboard_state, buf, 4, 0);
+ //wcharBufToEvent(buf, wcharCount, event);
+
+ // So, since Google refuses to give me a better solution, and if no one
+ // else has a better solution, I will make a hack...
+ wchar_t buf[5] = { 0, 0, 0, 0, 0 };
+ UINT c = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
+ buf[0] = c & 0xffff;
+ // TODO: This does not take caps lock into account
+ // TODO: Dead keys should affect key releases as well
+ if (!(event->key.state && PUGL_MOD_SHIFT))
+ buf[0] = towlower(buf[0]);
+ wcharBufToEvent(buf, 1, event);
+ event->key.filter = ((c >> 31) & 0x1);
+}
+
+static void
+translateCharEventToEvent(WPARAM wParam, PuglEvent* event)
+{
+ wchar_t buf[2];
+ int wcharCount;
+ if (wParam & 0xFFFF0000) {
+ wcharCount = 2;
+ buf[0] = (wParam & 0xFFFF);
+ buf[1] = ((wParam >> 16) & 0xFFFF);
+ } else {
+ wcharCount = 1;
+ buf[0] = (wParam & 0xFFFF);
+ }
+ wcharBufToEvent(buf, wcharCount, event);
+}
+
+static bool
+ignoreKeyEvent(PuglView* view, LPARAM lParam)
+{
+ return view->ignoreKeyRepeat && (lParam & (1 << 30));
+}
+
+static LRESULT
+handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PuglEvent event;
+ void* dummy_ptr = NULL;
+ RECT rect;
+ MINMAXINFO* mmi;
+ POINT pt;
+ bool dispatchThisEvent = true;
+
+ memset(&event, 0, sizeof(event));
+
+ event.any.type = PUGL_NOTHING;
+ event.any.view = view;
+ if (InSendMessageEx(dummy_ptr)) {
+ event.any.flags |= PUGL_IS_SEND_EVENT;
+ }
+
+ switch (message) {
+ case WM_CREATE:
+ case WM_SHOWWINDOW:
+ case WM_SIZE:
+ GetWindowRect(view->impl->hwnd, &rect);
+ event.configure.type = PUGL_CONFIGURE;
+ event.configure.x = rect.left;
+ event.configure.y = rect.top;
+ view->width = rect.right - rect.left;
+ view->height = rect.bottom - rect.top;
+ event.configure.width = view->width;
+ event.configure.height = view->height;
+ break;
+ case WM_GETMINMAXINFO:
+ mmi = (MINMAXINFO*)lParam;
+ mmi->ptMinTrackSize.x = view->min_width;
+ mmi->ptMinTrackSize.y = view->min_height;
+ 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;
+ event.expose.count = 0;
+ break;
+ case WM_MOUSEMOVE:
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(view->impl->hwnd, &pt);
+
+ event.motion.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = GetMessageTime();
+ event.motion.x = GET_X_LPARAM(lParam);
+ event.motion.y = GET_Y_LPARAM(lParam);
+ event.motion.x_root = pt.x;
+ event.motion.y_root = pt.y;
+ event.motion.state = getModifiers();
+ event.motion.is_hint = 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, wParam);
+ event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_MOUSEHWHEEL:
+ initScrollEvent(&event, view, lParam, wParam);
+ event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_KEYDOWN:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ if (!(event.key.special = keySymToSpecial(wParam))) {
+ event.key.type = PUGL_NOTHING;
+ }
+ }
+ break;
+ case WM_CHAR:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ translateCharEventToEvent(wParam, &event);
+ }
+ break;
+ case WM_DEADCHAR:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ translateCharEventToEvent(wParam, &event);
+ event.key.filter = 1;
+ }
+ break;
+ case WM_KEYUP:
+ initKeyEvent(&event, view, false, lParam);
+ if (!(event.key.special = keySymToSpecial(wParam))) {
+ translateMessageParamsToEvent(lParam, wParam, &event);
+ }
+ break;
+ case WM_QUIT:
+ case PUGL_LOCAL_CLOSE_MSG:
+ event.close.type = PUGL_CLOSE;
+ break;
+ default:
+ return DefWindowProc(
+ view->impl->hwnd, message, wParam, lParam);
+ }
+
+ puglDispatchEvent(view, &event);
+
+ return 0;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ // TODO
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ WaitMessage();
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ MSG msg;
+ while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ handleMessage(view, msg.message, msg.wParam, msg.lParam);
+ }
+
+ if (view->redisplay) {
+ InvalidateRect(view->impl->hwnd, NULL, FALSE);
+ view->redisplay = false;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+#ifdef _WIN64
+ PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+#else
+ PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA);
+#endif
+
+ 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);
+ }
+ }
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ view->redisplay = true;
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->hwnd;
+}
diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c
new file mode 100644
index 0000000..ef08ac8
--- /dev/null
+++ b/pugl/pugl_x11.c
@@ -0,0 +1,722 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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 pugl_x11.c X11 Pugl Implementation.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#ifdef PUGL_HAVE_GL
+#include <GL/gl.h>
+#include <GL/glx.h>
+#endif
+
+#ifdef PUGL_HAVE_CAIRO
+#include <cairo/cairo-xlib.h>
+#include <cairo/cairo.h>
+#endif
+
+#include "pugl/cairo_gl.h"
+#include "pugl/pugl_internal.h"
+
+#ifndef MIN
+# define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+# define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifdef PUGL_HAVE_GL
+
+/** Attributes for double-buffered RGBA. */
+static int attrListDbl[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER , True,
+ GLX_RED_SIZE , 4,
+ GLX_GREEN_SIZE , 4,
+ GLX_BLUE_SIZE , 4,
+ GLX_DEPTH_SIZE , 16,
+ /* GLX_SAMPLE_BUFFERS , 1, */
+ /* GLX_SAMPLES , 4, */
+ None
+};
+
+/** Attributes for single-buffered RGBA. */
+static int attrListSgl[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER , False,
+ GLX_RED_SIZE , 4,
+ GLX_GREEN_SIZE , 4,
+ GLX_BLUE_SIZE , 4,
+ GLX_DEPTH_SIZE , 16,
+ /* GLX_SAMPLE_BUFFERS , 1, */
+ /* GLX_SAMPLES , 4, */
+ None
+};
+
+/** Null-terminated list of attributes in order of preference. */
+static int* attrLists[] = { attrListDbl, attrListSgl, NULL };
+
+#endif // PUGL_HAVE_GL
+
+struct PuglInternalsImpl {
+ Display* display;
+ int screen;
+ Window win;
+ XIM xim;
+ XIC xic;
+#ifdef PUGL_HAVE_CAIRO
+ cairo_surface_t* surface;
+ cairo_t* cr;
+#endif
+#ifdef PUGL_HAVE_GL
+ GLXContext ctx;
+ int doubleBuffered;
+#endif
+#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
+ PuglCairoGL cairo_gl;
+#endif
+};
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+static XVisualInfo*
+getVisual(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ XVisualInfo* vi = NULL;
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ for (int* attr = *attrLists; !vi && *attr; ++attr) {
+ vi = glXChooseVisual(impl->display, impl->screen, attr);
+ }
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ XVisualInfo pat;
+ int n;
+ pat.screen = impl->screen;
+ vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
+ }
+#endif
+
+ return vi;
+}
+
+#ifdef PUGL_HAVE_CAIRO
+static int
+createCairoContext(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+
+ if (impl->cr) {
+ cairo_destroy(impl->cr);
+ }
+
+ impl->cr = cairo_create(impl->surface);
+ return cairo_status(impl->cr);
+}
+#endif
+
+static bool
+createContext(PuglView* view, XVisualInfo* vi)
+{
+ PuglInternals* const impl = view->impl;
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
+ glXGetConfig(impl->display, vi, GLX_DOUBLEBUFFER, &impl->doubleBuffered);
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ impl->surface = cairo_xlib_surface_create(
+ impl->display, impl->win, vi->visual, view->width, view->height);
+ }
+#endif
+#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ impl->surface = pugl_cairo_gl_create(
+ &impl->cairo_gl, view->width, view->height, 4);
+ }
+#endif
+
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ if (cairo_surface_status(impl->surface) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "error: failed to create cairo surface\n");
+ return false;
+ }
+
+ if (createCairoContext(view) != CAIRO_STATUS_SUCCESS) {
+ cairo_surface_destroy(impl->surface);
+ fprintf(stderr, "error: failed to create cairo context\n");
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+static void
+destroyContext(PuglView* view)
+{
+#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ pugl_cairo_gl_free(&view->impl->cairo_gl);
+ }
+#endif
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ glXDestroyContext(view->impl->display, view->impl->ctx);
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ cairo_destroy(view->impl->cr);
+ cairo_surface_destroy(view->impl->surface);
+ }
+#endif
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
+ }
+#endif
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_GL
+ if (flush && view->ctx_type & PUGL_GL) {
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
+ }
+#endif
+
+ glFlush();
+ if (view->impl->doubleBuffered) {
+ glXSwapBuffers(view->impl->display, view->impl->win);
+ }
+ }
+
+ glXMakeCurrent(view->impl->display, None, NULL);
+#endif
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* const impl = view->impl;
+
+ impl->display = XOpenDisplay(0);
+ impl->screen = DefaultScreen(impl->display);
+
+ XVisualInfo* const vi = getVisual(view);
+ if (!vi) {
+ return 1;
+ }
+
+ Window xParent = view->parent
+ ? (Window)view->parent
+ : RootWindow(impl->display, impl->screen);
+
+ Colormap cmap = XCreateColormap(
+ impl->display, xParent, vi->visual, AllocNone);
+
+ XSetWindowAttributes attr;
+ memset(&attr, 0, sizeof(XSetWindowAttributes));
+ attr.colormap = cmap;
+ attr.event_mask = (ExposureMask | StructureNotifyMask |
+ EnterWindowMask | LeaveWindowMask |
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | FocusChangeMask);
+
+ impl->win = XCreateWindow(
+ impl->display, xParent,
+ 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
+ CWColormap | CWEventMask, &attr);
+
+ if (!createContext(view, vi)) {
+ return 2;
+ }
+
+ XSizeHints sizeHints;
+ memset(&sizeHints, 0, sizeof(sizeHints));
+ if (!view->resizable) {
+ sizeHints.flags = PMinSize|PMaxSize;
+ sizeHints.min_width = view->width;
+ sizeHints.min_height = view->height;
+ sizeHints.max_width = view->width;
+ sizeHints.max_height = view->height;
+ XSetNormalHints(impl->display, impl->win, &sizeHints);
+ } else {
+ if (view->min_width || view->min_height) {
+ sizeHints.flags = PMinSize;
+ sizeHints.min_width = view->min_width;
+ sizeHints.min_height = view->min_height;
+ }
+ if (view->min_aspect_x) {
+ sizeHints.flags |= PAspect;
+ sizeHints.min_aspect.x = view->min_aspect_x;
+ sizeHints.min_aspect.y = view->min_aspect_y;
+ sizeHints.max_aspect.x = view->max_aspect_x;
+ sizeHints.max_aspect.y = view->max_aspect_y;
+ }
+
+ XSetNormalHints(impl->display, impl->win, &sizeHints);
+ }
+
+ if (title) {
+ XStoreName(impl->display, impl->win, title);
+ }
+
+ if (!view->parent) {
+ Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
+ XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
+ }
+
+ if (view->transient_parent) {
+ XSetTransientForHint(impl->display, impl->win,
+ (Window)(view->transient_parent));
+ }
+
+ XSetLocaleModifiers("");
+ if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
+ XSetLocaleModifiers("@im=");
+ if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
+ fprintf(stderr, "warning: XOpenIM failed\n");
+ }
+ }
+
+ const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
+ if (!(impl->xic = XCreateIC(impl->xim,
+ XNInputStyle, im_style,
+ XNClientWindow, impl->win,
+ XNFocusWindow, impl->win,
+ NULL))) {
+ fprintf(stderr, "warning: XCreateIC failed\n");
+ }
+
+ XFree(vi);
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ XMapRaised(view->impl->display, view->impl->win);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ XUnmapWindow(view->impl->display, view->impl->win);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ if (view) {
+ destroyContext(view);
+ XDestroyWindow(view->impl->display, view->impl->win);
+ XCloseDisplay(view->impl->display);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+ }
+}
+
+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;
+ case XK_Shift_R: return PUGL_KEY_SHIFT;
+ case XK_Control_L: return PUGL_KEY_CTRL;
+ case XK_Control_R: return PUGL_KEY_CTRL;
+ case XK_Alt_L: return PUGL_KEY_ALT;
+ case XK_Alt_R: return PUGL_KEY_ALT;
+ case XK_Super_L: return PUGL_KEY_SUPER;
+ case XK_Super_R: return PUGL_KEY_SUPER;
+ }
+ return (PuglKey)0;
+}
+
+static void
+translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
+{
+ KeySym sym = 0;
+ char* str = (char*)event->key.utf8;
+ memset(str, 0, 8);
+ event->key.filter = XFilterEvent(xevent, None);
+ if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) {
+ if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) {
+ event->key.character = str[0];
+ }
+ } else {
+ /* TODO: Not sure about this. On my system, some characters work with
+ Xutf8LookupString but not with XmbLookupString, and some are the
+ opposite. */
+ Status status = 0;
+#ifdef X_HAVE_UTF8_STRING
+ const int n = Xutf8LookupString(
+ view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
+#else
+ const int n = XmbLookupString(
+ view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
+#endif
+ if (n > 0) {
+ event->key.character = puglDecodeUTF8((const uint8_t*)str);
+ }
+ }
+ event->key.special = keySymToSpecial(sym);
+ event->key.keycode = xevent->xkey.keycode;
+}
+
+static unsigned
+translateModifiers(unsigned xstate)
+{
+ unsigned state = 0;
+ state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
+ state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
+ state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
+ state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
+ return state;
+}
+
+static PuglEvent
+translateEvent(PuglView* view, XEvent xevent)
+{
+ PuglEvent event;
+ memset(&event, 0, sizeof(event));
+
+ event.any.view = view;
+ if (xevent.xany.send_event) {
+ event.any.flags |= PUGL_IS_SEND_EVENT;
+ }
+
+ switch (xevent.type) {
+ case ClientMessage: {
+ char* type = XGetAtomName(view->impl->display,
+ xevent.xclient.message_type);
+ if (!strcmp(type, "WM_PROTOCOLS")) {
+ event.type = PUGL_CLOSE;
+ }
+ 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;
+ event.expose.count = xevent.xexpose.count;
+ break;
+ case MotionNotify:
+ event.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = xevent.xmotion.time;
+ event.motion.x = xevent.xmotion.x;
+ event.motion.y = xevent.xmotion.y;
+ event.motion.x_root = xevent.xmotion.x_root;
+ event.motion.y_root = xevent.xmotion.y_root;
+ event.motion.state = translateModifiers(xevent.xmotion.state);
+ event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
+ break;
+ case ButtonPress:
+ if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
+ event.type = PUGL_SCROLL;
+ event.scroll.time = xevent.xbutton.time;
+ event.scroll.x = xevent.xbutton.x;
+ event.scroll.y = xevent.xbutton.y;
+ event.scroll.x_root = xevent.xbutton.x_root;
+ event.scroll.y_root = 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.0f; break;
+ case 5: event.scroll.dy = -1.0f; break;
+ case 6: event.scroll.dx = -1.0f; break;
+ case 7: event.scroll.dx = 1.0f; 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 = xevent.xbutton.time;
+ event.button.x = xevent.xbutton.x;
+ event.button.y = xevent.xbutton.y;
+ event.button.x_root = xevent.xbutton.x_root;
+ event.button.y_root = 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 = xevent.xkey.time;
+ event.key.x = xevent.xkey.x;
+ event.key.y = xevent.xkey.y;
+ event.key.x_root = xevent.xkey.x_root;
+ event.key.y_root = 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_ENTER_NOTIFY
+ : PUGL_LEAVE_NOTIFY);
+ event.crossing.time = xevent.xcrossing.time;
+ event.crossing.x = xevent.xcrossing.x;
+ event.crossing.y = xevent.xcrossing.y;
+ event.crossing.x_root = xevent.xcrossing.x_root;
+ event.crossing.y_root = 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.grab = (xevent.xfocus.mode != NotifyNormal);
+ break;
+
+ default:
+ break;
+ }
+
+ return event;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ XSetInputFocus(
+ view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ XEvent xevent;
+ XPeekEvent(view->impl->display, &xevent);
+ return PUGL_SUCCESS;
+}
+
+static void
+merge_expose_events(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;
+ dst->expose.count = MIN(dst->expose.count, src->expose.count);
+ }
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ /* Maintain a single expose/configure event to execute after all pending
+ events. This avoids redundant drawing/configuration which prevents a
+ series of window resizes in the same loop from being laggy. */
+ PuglEvent expose_event = { 0 };
+ PuglEvent config_event = { 0 };
+ XEvent xevent;
+ while (XPending(view->impl->display) > 0) {
+ XNextEvent(view->impl->display, &xevent);
+ if (xevent.type == KeyRelease) {
+ // Ignore key repeat if necessary
+ if (view->ignoreKeyRepeat &&
+ XEventsQueued(view->impl->display, QueuedAfterReading)) {
+ XEvent next;
+ XPeekEvent(view->impl->display, &next);
+ if (next.type == KeyPress &&
+ next.xkey.time == xevent.xkey.time &&
+ next.xkey.keycode == xevent.xkey.keycode) {
+ XNextEvent(view->impl->display, &xevent);
+ continue;
+ }
+ }
+ } else if (xevent.type == FocusIn) {
+ XSetICFocus(view->impl->xic);
+ } else if (xevent.type == FocusOut) {
+ XUnsetICFocus(view->impl->xic);
+ }
+
+ // 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
+ merge_expose_events(&expose_event, &event);
+ } else if (event.type == PUGL_CONFIGURE) {
+ // Expand configure event to be dispatched after loop
+ config_event = event;
+ } else {
+ // Dispatch event to application immediately
+ puglDispatchEvent(view, &event);
+ }
+ }
+
+ if (config_event.type) {
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ // Resize surfaces/contexts before dispatching
+ view->redisplay = true;
+ cairo_xlib_surface_set_size(view->impl->surface,
+ config_event.configure.width,
+ config_event.configure.height);
+ }
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ view->redisplay = true;
+ cairo_surface_destroy(view->impl->surface);
+ view->impl->surface = pugl_cairo_gl_create(
+ &view->impl->cairo_gl,
+ config_event.configure.width,
+ config_event.configure.height,
+ 4);
+ pugl_cairo_gl_configure(&view->impl->cairo_gl,
+ config_event.configure.width,
+ config_event.configure.height);
+ createCairoContext(view);
+ }
+#endif
+#endif
+ puglDispatchEvent(view, (const PuglEvent*)&config_event);
+ }
+
+ if (view->redisplay) {
+ expose_event.expose.type = PUGL_EXPOSE;
+ expose_event.expose.view = view;
+ expose_event.expose.x = 0;
+ expose_event.expose.y = 0;
+ expose_event.expose.width = view->width;
+ expose_event.expose.height = view->height;
+ view->redisplay = false;
+ }
+
+ if (expose_event.type) {
+ puglDispatchEvent(view, (const PuglEvent*)&expose_event);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ view->redisplay = true;
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return view->impl->win;
+}
+
+void*
+puglGetContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ return view->impl->cr;
+ }
+#endif
+ return NULL;
+}