diff options
Diffstat (limited to 'pugl')
-rw-r--r-- | pugl/cairo_gl.h | 105 | ||||
-rw-r--r-- | pugl/gl.h | 32 | ||||
-rw-r--r-- | pugl/glew.h | 32 | ||||
-rw-r--r-- | pugl/glu.h | 32 | ||||
-rw-r--r-- | pugl/pugl.h | 622 | ||||
-rw-r--r-- | pugl/pugl.hpp | 106 | ||||
-rw-r--r-- | pugl/pugl_internal.h | 241 | ||||
-rw-r--r-- | pugl/pugl_osx.m | 727 | ||||
-rw-r--r-- | pugl/pugl_win.cpp | 644 | ||||
-rw-r--r-- | pugl/pugl_x11.c | 722 |
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; +} |