From 37fe29ab9c4a5ea22bc5996b020fa39c854965fa Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 27 Jul 2019 21:24:36 +0200 Subject: Reorganize source to separate private implementation details Taking a page from C++ convention, where "detail" is for things that should not be included in user code. --- pugl/cairo_gl.h | 103 ---- pugl/detail/cairo_gl.h | 103 ++++ pugl/detail/implementation.c | 275 +++++++++++ pugl/detail/implementation.h | 46 ++ pugl/detail/mac.m | 1069 ++++++++++++++++++++++++++++++++++++++++++ pugl/detail/types.h | 107 +++++ pugl/detail/win.c | 677 ++++++++++++++++++++++++++ pugl/detail/win.h | 100 ++++ pugl/detail/win_cairo.c | 200 ++++++++ pugl/detail/win_gl.c | 306 ++++++++++++ pugl/detail/x11.c | 577 +++++++++++++++++++++++ pugl/detail/x11.h | 41 ++ pugl/detail/x11_cairo.c | 139 ++++++ pugl/detail/x11_gl.c | 213 +++++++++ pugl/pugl.h | 2 +- pugl/pugl.hpp | 2 +- pugl/pugl_internal.h | 281 ----------- pugl/pugl_internal_types.h | 106 ----- pugl/pugl_osx.m | 1069 ------------------------------------------ pugl/pugl_win.c | 677 -------------------------- pugl/pugl_win.h | 100 ---- pugl/pugl_win_cairo.c | 200 -------- pugl/pugl_win_gl.c | 306 ------------ pugl/pugl_x11.c | 577 ----------------------- pugl/pugl_x11.h | 42 -- pugl/pugl_x11_cairo.c | 139 ------ pugl/pugl_x11_gl.c | 213 --------- wscript | 29 +- 28 files changed, 3870 insertions(+), 3829 deletions(-) delete mode 100644 pugl/cairo_gl.h create mode 100644 pugl/detail/cairo_gl.h create mode 100644 pugl/detail/implementation.c create mode 100644 pugl/detail/implementation.h create mode 100644 pugl/detail/mac.m create mode 100644 pugl/detail/types.h create mode 100644 pugl/detail/win.c create mode 100644 pugl/detail/win.h create mode 100644 pugl/detail/win_cairo.c create mode 100644 pugl/detail/win_gl.c create mode 100644 pugl/detail/x11.c create mode 100644 pugl/detail/x11.h create mode 100644 pugl/detail/x11_cairo.c create mode 100644 pugl/detail/x11_gl.c delete mode 100644 pugl/pugl_internal.h delete mode 100644 pugl/pugl_internal_types.h delete mode 100644 pugl/pugl_osx.m delete mode 100644 pugl/pugl_win.c delete mode 100644 pugl/pugl_win.h delete mode 100644 pugl/pugl_win_cairo.c delete mode 100644 pugl/pugl_win_gl.c delete mode 100644 pugl/pugl_x11.c delete mode 100644 pugl/pugl_x11.h delete mode 100644 pugl/pugl_x11_cairo.c delete mode 100644 pugl/pugl_x11_gl.c diff --git a/pugl/cairo_gl.h b/pugl/cairo_gl.h deleted file mode 100644 index b6e8566..0000000 --- a/pugl/cairo_gl.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright 2016-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file cairo_gl.h Generic Cairo to OpenGL drawing support. -*/ - -#include "pugl/gl.h" - -#include -#include -#include - -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) { - return NULL; - } - - 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); - - 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) -{ - (void)ctx; - (void)width; - (void)height; - glDisable(GL_DEPTH_TEST); - glEnable(GL_TEXTURE_RECTANGLE_ARB); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); -} - -static void -pugl_cairo_gl_draw(PuglCairoGL* ctx, int width, int height) -{ - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glViewport(0, 0, width, height); - - 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(); -} diff --git a/pugl/detail/cairo_gl.h b/pugl/detail/cairo_gl.h new file mode 100644 index 0000000..b6e8566 --- /dev/null +++ b/pugl/detail/cairo_gl.h @@ -0,0 +1,103 @@ +/* + Copyright 2016-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file cairo_gl.h Generic Cairo to OpenGL drawing support. +*/ + +#include "pugl/gl.h" + +#include +#include +#include + +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) { + return NULL; + } + + 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); + + 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) +{ + (void)ctx; + (void)width; + (void)height; + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); +} + +static void +pugl_cairo_gl_draw(PuglCairoGL* ctx, int width, int height) +{ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glViewport(0, 0, width, height); + + 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(); +} diff --git a/pugl/detail/implementation.c b/pugl/detail/implementation.c new file mode 100644 index 0000000..5cd7ce5 --- /dev/null +++ b/pugl/detail/implementation.c @@ -0,0 +1,275 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file implementation.c Platform-independent implementation. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/pugl.h" + +#include +#include + +static PuglHints +puglDefaultHints(void) +{ + static const PuglHints hints = { + 2, 0, 4, 4, 4, 4, 24, 8, 0, true, true, false + }; + return hints; +} + +PuglView* +puglInit(int* PUGL_UNUSED(pargc), char** PUGL_UNUSED(argv)) +{ + PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); + if (!view) { + return NULL; + } + + PuglInternals* impl = puglInitInternals(); + if (!impl) { + return NULL; + } + + view->hints = puglDefaultHints(); + view->impl = impl; + view->width = 640; + view->height = 480; + view->start_time = puglGetTime(view); + + return view; +} + +void +puglInitWindowHint(PuglView* view, PuglWindowHint hint, int value) +{ + switch (hint) { + case PUGL_USE_COMPAT_PROFILE: + view->hints.use_compat_profile = value; + break; + case PUGL_CONTEXT_VERSION_MAJOR: + view->hints.context_version_major = value; + break; + case PUGL_CONTEXT_VERSION_MINOR: + view->hints.context_version_minor = value; + break; + case PUGL_RED_BITS: + view->hints.red_bits = value; + break; + case PUGL_GREEN_BITS: + view->hints.green_bits = value; + break; + case PUGL_BLUE_BITS: + view->hints.blue_bits = value; + break; + case PUGL_ALPHA_BITS: + view->hints.alpha_bits = value; + break; + case PUGL_DEPTH_BITS: + view->hints.depth_bits = value; + break; + case PUGL_STENCIL_BITS: + view->hints.stencil_bits = value; + break; + case PUGL_SAMPLES: + view->hints.samples = value; + break; + case PUGL_DOUBLE_BUFFER: + view->hints.double_buffer = value; + break; + case PUGL_RESIZABLE: + view->hints.resizable = value; + break; + } +} + +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->hints.resizable = resizable; +} + +void +puglInitTransientFor(PuglView* view, uintptr_t parent) +{ + view->transient_parent = parent; +} + +int +puglInitBackend(PuglView* view, const PuglBackend* backend) +{ + view->backend = backend; + return 0; +} + +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* +puglGetContext(PuglView* view) +{ + return view->backend->getContext(view); +} + +void +puglEnterContext(PuglView* view, bool drawing) +{ + view->backend->enter(view, drawing); +} + +void +puglLeaveContext(PuglView* view, bool drawing) +{ + view->backend->leave(view, drawing); +} + +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. */ +uint32_t +puglDecodeUTF8(const uint8_t* buf) +{ +#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0) + + // http://en.wikipedia.org/wiki/UTF-8 + + if (buf[0] < 0x80) { + return buf[0]; + } else if (buf[0] < 0xC2) { + return 0xFFFD; + } else if (buf[0] < 0xE0) { + FAIL_IF((buf[1] & 0xC0) != 0x80); + return (buf[0] << 6) + buf[1] - 0x3080u; + } 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] - 0xE2080u; + } 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] - 0x3C82080u); + } + return 0xFFFD; +} + +void +puglDispatchEvent(PuglView* view, const PuglEvent* event) +{ + switch (event->type) { + case PUGL_NOTHING: + break; + case PUGL_CONFIGURE: + view->width = (int)event->configure.width; + view->height = (int)event->configure.height; + puglEnterContext(view, false); + view->eventFunc(view, event); + puglLeaveContext(view, false); + break; + case PUGL_EXPOSE: + if (event->expose.count == 0) { + puglEnterContext(view, true); + view->eventFunc(view, event); + puglLeaveContext(view, true); + } + break; + default: + view->eventFunc(view, event); + } +} diff --git a/pugl/detail/implementation.h b/pugl/detail/implementation.h new file mode 100644 index 0000000..50f54f5 --- /dev/null +++ b/pugl/detail/implementation.h @@ -0,0 +1,46 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file implementation.h Shared declarations for implementation. +*/ + +#ifndef PUGL_DETAIL_IMPLEMENTATION_H +#define PUGL_DETAIL_IMPLEMENTATION_H + +#include "pugl/detail/types.h" +#include "pugl/pugl.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Allocate and initialise internals (implemented once per platform) */ +PuglInternals* puglInitInternals(void); + +/** Return the Unicode code point for `buf` or the replacement character. */ +uint32_t puglDecodeUTF8(const uint8_t* buf); + +/** Dispatch `event` to `view`, optimising configure/expose if possible. */ +void puglDispatchEvent(PuglView* view, const PuglEvent* event); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // PUGL_DETAIL_IMPLEMENTATION_H diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m new file mode 100644 index 0000000..83adcd1 --- /dev/null +++ b/pugl/detail/mac.m @@ -0,0 +1,1069 @@ +/* + Copyright 2012-2019 David Robillard + Copyright 2017 Hanspeter Portner + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file mac.m MacOS implementation. +*/ + +#define GL_SILENCE_DEPRECATION 1 + +#include "pugl/detail/implementation.h" +#include "pugl/gl.h" +#include "pugl/pugl_gl_backend.h" + +#ifdef PUGL_HAVE_CAIRO +#include "pugl/detail/cairo_gl.h" +#include "pugl/pugl_cairo_backend.h" +#endif + +#import + +#include + +#include + +#ifndef __MAC_10_10 +#define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core +typedef NSUInteger NSEventModifierFlags; +typedef NSUInteger NSWindowStyleMask; +#endif + +@class PuglOpenGLView; + +struct PuglInternalsImpl { + NSApplication* app; + PuglOpenGLView* glview; + id window; + NSEvent* nextEvent; + uint32_t mods; +#ifdef PUGL_HAVE_CAIRO + cairo_surface_t* surface; + cairo_t* cr; + PuglCairoGL cairo_gl; +#endif +}; + +@interface PuglWindow : NSWindow +{ +@public + PuglView* puglview; +} + +- (void) setPuglview:(PuglView*)view; + +@end + +@implementation PuglWindow + +- (id) initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)aStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + (void)flag; + + NSWindow* result = [super initWithContentRect:contentRect + styleMask:aStyle + backing:bufferingType + defer:NO]; + + [result setAcceptsMouseMovedEvents:YES]; + return (PuglWindow*)result; +} + +- (void)setPuglview:(PuglView*)view +{ + puglview = view; + [self setContentSize:NSMakeSize(view->width, view->height)]; +} + +- (BOOL) canBecomeKeyWindow +{ + return YES; +} + +- (BOOL) canBecomeMainWindow +{ + return YES; +} + +@end + +@interface PuglOpenGLView : NSOpenGLView +{ +@public + PuglView* puglview; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; + NSTimer* timer; + NSTimer* urgentTimer; +} + +@end + +@implementation PuglOpenGLView + +- (id) initWithFrame:(NSRect)frame +{ + const int major = puglview->hints.context_version_major; + const int profile = ((puglview->hints.use_compat_profile || major < 3) + ? NSOpenGLProfileVersionLegacy + : puglview->hints.context_version_major >= 4 + ? NSOpenGLProfileVersion4_1Core + : NSOpenGLProfileVersion3_2Core); + + NSOpenGLPixelFormatAttribute pixelAttribs[16] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAccelerated, + NSOpenGLPFAOpenGLProfile, profile, + NSOpenGLPFAColorSize, 32, + NSOpenGLPFADepthSize, 32, + NSOpenGLPFAMultisample, puglview->hints.samples ? 1 : 0, + NSOpenGLPFASampleBuffers, puglview->hints.samples ? 1 : 0, + NSOpenGLPFASamples, puglview->hints.samples, + 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 +{ + [super reshape]; + [[self openGLContext] update]; + + if (!puglview) { + return; + } + + const NSRect bounds = [self bounds]; + const PuglEventConfigure ev = { + PUGL_CONFIGURE, + 0, + bounds.origin.x, + bounds.origin.y, + bounds.size.width, + bounds.size.height, + }; + + puglview->backend->resize(puglview, ev.width, ev.height); + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) drawRect:(NSRect)rect +{ + const PuglEventExpose ev = { + PUGL_EXPOSE, + 0, + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + 0 + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (BOOL) isFlipped +{ + return YES; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +static uint32_t +getModifiers(const NSEvent* const ev) +{ + const NSEventModifierFlags modifierFlags = [ev modifierFlags]; + + return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) | + ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) | + ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) | + ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0)); +} + +static PuglKey +keySymToSpecial(const NSEvent* const ev) +{ + NSString* chars = [ev charactersIgnoringModifiers]; + if ([chars length] == 1) { + switch ([chars characterAtIndex:0]) { + case NSF1FunctionKey: return PUGL_KEY_F1; + case NSF2FunctionKey: return PUGL_KEY_F2; + case NSF3FunctionKey: return PUGL_KEY_F3; + case NSF4FunctionKey: return PUGL_KEY_F4; + case NSF5FunctionKey: return PUGL_KEY_F5; + case NSF6FunctionKey: return PUGL_KEY_F6; + case NSF7FunctionKey: return PUGL_KEY_F7; + case NSF8FunctionKey: return PUGL_KEY_F8; + case NSF9FunctionKey: return PUGL_KEY_F9; + case NSF10FunctionKey: return PUGL_KEY_F10; + case NSF11FunctionKey: return PUGL_KEY_F11; + case NSF12FunctionKey: return PUGL_KEY_F12; + case NSDeleteCharacter: return PUGL_KEY_BACKSPACE; + case NSDeleteFunctionKey: return PUGL_KEY_DELETE; + case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT; + case NSUpArrowFunctionKey: return PUGL_KEY_UP; + case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT; + case NSDownArrowFunctionKey: return PUGL_KEY_DOWN; + case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP; + case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN; + case NSHomeFunctionKey: return PUGL_KEY_HOME; + case NSEndFunctionKey: return PUGL_KEY_END; + case NSInsertFunctionKey: return PUGL_KEY_INSERT; + case NSMenuFunctionKey: return PUGL_KEY_MENU; + case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK; + case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK; + case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN; + case NSPauseFunctionKey: return PUGL_KEY_PAUSE; + } + // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged] + } + return (PuglKey)0; +} + +- (void) updateTrackingAreas +{ + if (trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const int opts = (NSTrackingMouseEnteredAndExited | + NSTrackingMouseMoved | + NSTrackingActiveAlways); + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (NSPoint) eventLocation:(NSEvent*)event +{ + return [self convertPoint:[event locationInWindow] fromView:nil]; +} + +static void +handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type) +{ + const NSPoint wloc = [view eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventCrossing ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + PUGL_CROSSING_NORMAL + }; + puglDispatchEvent(view->puglview, (const PuglEvent*)&ev); +} + +- (void) mouseEntered:(NSEvent*)event +{ + handleCrossing(self, event, PUGL_ENTER_NOTIFY); +} + +- (void) mouseExited:(NSEvent*)event +{ + handleCrossing(self, event, PUGL_LEAVE_NOTIFY); +} + +- (void) mouseMoved:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventMotion ev = { + PUGL_MOTION_NOTIFY, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + 0, + 1 + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) mouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) rightMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) otherMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) mouseDown:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1 + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) mouseUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1 + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) rightMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void) rightMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void) otherMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void) otherMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void) scrollWheel:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventScroll ev = { + PUGL_SCROLL, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event deltaX], + [event deltaY] + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) keyDown:(NSEvent*)event +{ + if (puglview->ignoreKeyRepeat && [event isARepeat]) { + return; + } + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = ( + spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0 + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + + if (!spec) { + [self interpretKeyEvents:@[event]]; + } +} + +- (void) keyUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = + (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0 + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (BOOL) hasMarkedText +{ + return [markedText length] > 0; +} + +- (NSRange) markedRange +{ + return (([markedText length] > 0) + ? NSMakeRange(0, [markedText length] - 1) + : NSMakeRange(NSNotFound, 0)); +} + +- (NSRange) selectedRange +{ + return NSMakeRange(NSNotFound, 0); +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selected + replacementRange:(NSRange)replacement +{ + (void)selected; + (void)replacement; + [markedText release]; + markedText = ( + [string isKindOfClass:[NSAttributedString class]] + ? [[NSMutableAttributedString alloc] initWithAttributedString:string] + : [[NSMutableAttributedString alloc] initWithString:string]); +} + +- (void) unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*) validAttributesForMarkedText +{ + return @[]; +} + +- (NSAttributedString*) + attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actual +{ + (void)range; + (void)actual; + return nil; +} + +- (NSUInteger) characterIndexForPoint:(NSPoint)point +{ + (void)point; + return 0; +} + +- (NSRect) firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actual +{ + (void)range; + (void)actual; + + const NSRect frame = [(id)puglview bounds]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void) insertText:(id)string + replacementRange:(NSRange)replacement +{ + (void)replacement; + + NSEvent* const event = [NSApp currentEvent]; + NSString* const characters = + ([string isKindOfClass:[NSAttributedString class]] + ? [string string] + : (NSString*)string); + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + for (size_t i = 0; i < [characters length]; ++i) { + const uint32_t code = [characters characterAtIndex:i]; + char utf8[8] = {0}; + NSUInteger len = 0; + + [characters getBytes:utf8 + maxLength:sizeof(utf8) + usedLength:&len + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(i, i + 1) + remainingRange:nil]; + + PuglEventText ev = { PUGL_TEXT, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + code, + { 0, 0, 0, 0, 0, 0, 0, 0 } }; + + memcpy(ev.string, utf8, len); + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } +} + +- (void) flagsChanged:(NSEvent*)event +{ + const uint32_t mods = getModifiers(event); + PuglEventType type = PUGL_NOTHING; + PuglKey special = 0; + + if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) { + type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SHIFT; + } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) { + type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_CTRL; + } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) { + type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_ALT; + } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) { + type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SUPER; + } + + if (special != 0) { + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + PuglEventKey ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + mods, + [event keyCode], + special + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } + + puglview->impl->mods = mods; +} + +- (BOOL) preservesContentInLiveResize +{ + return NO; +} + +- (void) viewWillStartLiveResize +{ + timer = [NSTimer timerWithTimeInterval:(1 / 60.0) + target:self + selector:@selector(resizeTick) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer + forMode:NSRunLoopCommonModes]; + + [super viewWillStartLiveResize]; +} + +- (void) resizeTick +{ + puglPostRedisplay(puglview); +} + +- (void) urgentTick +{ + [NSApp requestUserAttention:NSInformationalRequest]; +} + +- (void) viewDidEndLiveResize +{ + [super viewDidEndLiveResize]; + [timer invalidate]; + timer = NULL; +} + +@end + +@interface PuglWindowDelegate : NSObject +{ + PuglWindow* window; +} + +- (instancetype) initWithPuglWindow:(PuglWindow*)window; + +@end + +@implementation PuglWindowDelegate + +- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow +{ + if ((self = [super init])) { + window = puglWindow; + } + + return self; +} + +- (BOOL) windowShouldClose:(id)sender +{ + (void)sender; + + PuglEvent ev = { 0 }; + ev.type = PUGL_CLOSE; + puglDispatchEvent(window->puglview, &ev); + return YES; +} + +- (void) windowDidBecomeKey:(NSNotification*)notification +{ + (void)notification; + + PuglOpenGLView* glview = window->puglview->impl->glview; + if (window->puglview->impl->glview->urgentTimer) { + [glview->urgentTimer invalidate]; + glview->urgentTimer = NULL; + } + + PuglEvent ev = { 0 }; + ev.type = PUGL_FOCUS_IN; + ev.focus.grab = false; + puglDispatchEvent(window->puglview, &ev); +} + +- (void) windowDidResignKey:(NSNotification*)notification +{ + (void)notification; + + PuglEvent ev = { 0 }; + ev.type = PUGL_FOCUS_OUT; + ev.focus.grab = false; + puglDispatchEvent(window->puglview, &ev); +} + +@end + +PuglInternals* +puglInitInternals(void) +{ + return (PuglInternals*)calloc(1, sizeof(PuglInternals)); +} + +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 alloc]; + impl->glview->trackingArea = nil; + impl->glview->markedText = [[NSMutableAttributedString alloc] init]; + impl->glview->puglview = view; + + [impl->glview initWithFrame:NSMakeRect(0, 0, 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->hints.resizable) { + [impl->glview setAutoresizingMask:NSViewNotSizable]; + } + + if (view->parent) { + NSView* pview = (NSView*)view->parent; + [pview addSubview:impl->glview]; + [impl->glview setHidden:NO]; + [[impl->glview window] makeFirstResponder:impl->glview]; + } 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 | + NSMiniaturizableWindowMask ); + if (view->hints.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; + + ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc] + initWithPuglWindow:window]; + + if (view->min_aspect_x && view->min_aspect_y) { + [window setContentAspectRatio:NSMakeSize(view->min_aspect_x, + view->min_aspect_y)]; + } + + [window setContentView:impl->glview]; + [impl->app activateIgnoringOtherApps:YES]; + [window makeFirstResponder:impl->glview]; + [window makeKeyAndOrderFront:window]; + } + + [impl->glview updateTrackingAreas]; + + 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) +{ + view->backend->destroy(view); + 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) +{ + [view->impl->window makeKeyWindow]; +} + +void +puglRequestAttention(PuglView* view) +{ + if (![view->impl->window isKeyWindow]) { + [NSApp requestUserAttention:NSInformationalRequest]; + view->impl->glview->urgentTimer = + [NSTimer scheduledTimerWithTimeInterval:2.0 + target:view->impl->glview + selector:@selector(urgentTick) + userInfo:nil + repeats:YES]; + } +} + +PuglStatus +puglWaitForEvent(PuglView* view) +{ + /* Note that dequeue:NO is broken (it blocks forever even when events are + pending), so we work around this by dequeueing the event here and + storing it in view->impl->nextEvent for later processing. */ + if (!view->impl->nextEvent) { + view->impl->nextEvent = + [view->impl->window nextEventMatchingMask:NSAnyEventMask]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglProcessEvents(PuglView* view) +{ + if (view->impl->nextEvent) { + // Process event that was dequeued earier by puglWaitForEvent + [view->impl->app sendEvent: view->impl->nextEvent]; + view->impl->nextEvent = NULL; + } + + // Process all pending events + for (NSEvent* ev = NULL; + (ev = [view->impl->window nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue:YES]);) { + [view->impl->app sendEvent: ev]; + } + + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char *name) +{ + CFBundleRef framework = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + + CFStringRef symbol = CFStringCreateWithCString( + kCFAllocatorDefault, name, kCFStringEncodingASCII); + + PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName( + framework, symbol); + + CFRelease(symbol); + + return func; +} + +double +puglGetTime(PuglView* view) +{ + return (mach_absolute_time() / 1e9) - view->start_time; +} + +void +puglPostRedisplay(PuglView* view) +{ + [view->impl->glview setNeedsDisplay: YES]; +} + +PuglNativeWindow +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeWindow)view->impl->glview; +} + +// Backend + +static int +puglMacConfigure(PuglView* PUGL_UNUSED(view)) +{ + return 0; +} + +static int +puglMacCreate(PuglView* PUGL_UNUSED(view)) +{ + return 0; +} + +static int +puglMacGlDestroy(PuglView* PUGL_UNUSED(view)) +{ + return 0; +} + +static int +puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing)) +{ + [[view->impl->glview openGLContext] makeCurrentContext]; + return 0; +} + +static int +puglMacGlLeave(PuglView* view, bool drawing) +{ + if (drawing) { + [[view->impl->glview openGLContext] flushBuffer]; + } + + [NSOpenGLContext clearCurrentContext]; + + return 0; +} + +static int +puglMacGlResize(PuglView* PUGL_UNUSED(view), + int PUGL_UNUSED(width), + int PUGL_UNUSED(height)) +{ + return 0; +} + +static void* +puglMacGlGetContext(PuglView* PUGL_UNUSED(view)) +{ + return NULL; +} + +const PuglBackend* puglGlBackend(void) +{ + static const PuglBackend backend = { + puglMacConfigure, + puglMacCreate, + puglMacGlDestroy, + puglMacGlEnter, + puglMacGlLeave, + puglMacGlResize, + puglMacGlGetContext + }; + + return &backend; +} + +#ifdef PUGL_HAVE_CAIRO + +static int +puglMacCairoDestroy(PuglView* view) +{ + pugl_cairo_gl_free(&view->impl->cairo_gl); + return 0; +} + +static int +puglMacCairoEnter(PuglView* view, bool PUGL_UNUSED(drawing)) +{ + [[view->impl->glview openGLContext] makeCurrentContext]; + + return 0; +} + +static int +puglMacCairoLeave(PuglView* view, bool drawing) +{ + if (drawing) { + pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height); + [[view->impl->glview openGLContext] flushBuffer]; + } + + [NSOpenGLContext clearCurrentContext]; + + return 0; +} + + +static int +puglMacCairoResize(PuglView* view, int width, int height) +{ + PuglInternals* impl = view->impl; + + cairo_surface_destroy(impl->surface); + cairo_destroy(impl->cr); + impl->surface = pugl_cairo_gl_create(&impl->cairo_gl, width, height, 4); + impl->cr = cairo_create(impl->surface); + pugl_cairo_gl_configure(&impl->cairo_gl, width, height); + + return 0; +} + +static void* +puglMacCairoGetContext(PuglView* view) +{ + return view->impl->cr; +} + +const PuglBackend* puglCairoBackend(void) +{ + static const PuglBackend backend = { + puglMacConfigure, + puglMacCreate, + puglMacCairoDestroy, + puglMacCairoEnter, + puglMacCairoLeave, + puglMacCairoResize, + puglMacCairoGetContext + }; + + return &backend; +} + +#endif diff --git a/pugl/detail/types.h b/pugl/detail/types.h new file mode 100644 index 0000000..0cf9a99 --- /dev/null +++ b/pugl/detail/types.h @@ -0,0 +1,107 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file types.h Shared internal type definitions. +*/ + +#ifndef PUGL_DETAIL_TYPES_H +#define PUGL_DETAIL_TYPES_H + +#include "pugl/pugl.h" + +#include +#include + +// Unused parameter macro to suppresses warnings and make it impossible to use +#if defined(__cplusplus) || defined(_MSC_VER) +# define PUGL_UNUSED(name) +#elif defined(__GNUC__) +# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__)) +#else +# define PUGL_UNUSED(name) +#endif + +/** Platform-specific internals. */ +typedef struct PuglInternalsImpl PuglInternals; + +/** View hints. */ +typedef struct { + int context_version_major; + int context_version_minor; + int red_bits; + int green_bits; + int blue_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + int samples; + int double_buffer; + bool use_compat_profile; + bool resizable; +} PuglHints; + +/** Cross-platform view definition. */ +struct PuglViewImpl { + const PuglBackend* backend; + PuglInternals* impl; + PuglHandle handle; + PuglEventFunc eventFunc; + char* windowClass; + PuglNativeWindow parent; + double start_time; + uintptr_t transient_parent; + PuglHints hints; + 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 visible; +}; + +/** Opaque surface used by graphics backend. */ +typedef void PuglSurface; + +/** Graphics backend interface. */ +struct PuglBackendImpl { + /** Get visual information from display and setup view as necessary. */ + int (*configure)(PuglView*); + + /** Create surface and drawing context. */ + int (*create)(PuglView*); + + /** Destroy surface and drawing context. */ + int (*destroy)(PuglView*); + + /** Enter drawing context, for drawing if parameter is true. */ + int (*enter)(PuglView*, bool); + + /** Leave drawing context, after drawing if parameter is true. */ + int (*leave)(PuglView*, bool); + + /** Resize drawing context to the given width and height. */ + int (*resize)(PuglView*, int, int); + + /** Return the puglGetContext() handle for the application, if any. */ + void* (*getContext)(PuglView*); +}; + +#endif // PUGL_DETAIL_TYPES_H diff --git a/pugl/detail/win.c b/pugl/detail/win.c new file mode 100644 index 0000000..953c620 --- /dev/null +++ b/pugl/detail/win.c @@ -0,0 +1,677 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.c Windows implementation. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/detail/win.h" + +#include +#include + +#include +#include +#include +#include + +#ifndef WM_MOUSEWHEEL +# define WM_MOUSEWHEEL 0x020A +#endif +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef WHEEL_DELTA +# define WHEEL_DELTA 120 +#endif +#ifndef GWLP_USERDATA +# define GWLP_USERDATA (-21) +#endif + +#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) +#define PUGL_RESIZE_TIMER_ID 9461 +#define PUGL_URGENT_TIMER_ID 9462 + +typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); + +static const TCHAR* DEFAULT_CLASSNAME = "Pugl"; + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +PuglInternals* +puglInitInternals(void) +{ + PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); + + HMODULE user32 = LoadLibrary("user32.dll"); + if (user32) { + PFN_SetProcessDPIAware SetProcessDPIAware = + (PFN_SetProcessDPIAware)GetProcAddress( + user32, "SetProcessDPIAware"); + if (SetProcessDPIAware) { + SetProcessDPIAware(); + } + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + impl->timerFrequency = (double)frequency.QuadPart; + + return impl; +} + +int +puglCreateWindow(PuglView* view, const char* title) +{ + PuglInternals* impl = view->impl; + + const char* className = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME; + + title = title ? title : "Window"; + + // Get refresh rate for resize draw timer + DEVMODEA devMode = {0}; + EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode); + view->impl->refreshRate = devMode.dmDisplayFrequency; + + // Register window class + 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 = className; + if (!RegisterClassEx(&wc)) { + return 1; + } + + if (!view->backend || !view->backend->configure) { + return 1; + } + + int st = view->backend->configure(view); + if (st || !impl->surface) { + return 2; + } else if ((st = view->backend->create(view))) { + return 3; + } + + SetWindowText(impl->hwnd, title); + SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); + + return 0; +} + +void +puglShowWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_SHOWNORMAL); + SetFocus(impl->hwnd); + view->visible = true; +} + +void +puglHideWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_HIDE); + view->visible = false; +} + +void +puglDestroy(PuglView* view) +{ + if (view) { + view->backend->destroy(view); + ReleaseDC(view->impl->hwnd, view->impl->hdc); + DestroyWindow(view->impl->hwnd); + UnregisterClass(view->windowClass ? view->windowClass : DEFAULT_CLASSNAME, NULL); + free(view->windowClass); + free(view->impl); + free(view); + } +} + +static PuglKey +keySymToSpecial(WPARAM 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_BACK: return PUGL_KEY_BACKSPACE; + case VK_DELETE: return PUGL_KEY_DELETE; + case VK_LEFT: return PUGL_KEY_LEFT; + case VK_UP: return PUGL_KEY_UP; + case VK_RIGHT: return PUGL_KEY_RIGHT; + case VK_DOWN: return PUGL_KEY_DOWN; + case VK_PRIOR: return PUGL_KEY_PAGE_UP; + case VK_NEXT: return PUGL_KEY_PAGE_DOWN; + case VK_HOME: return PUGL_KEY_HOME; + case VK_END: return PUGL_KEY_END; + case VK_INSERT: return PUGL_KEY_INSERT; + case VK_SHIFT: + case VK_LSHIFT: return PUGL_KEY_SHIFT_L; + case VK_RSHIFT: return PUGL_KEY_SHIFT_R; + case VK_CONTROL: + case VK_LCONTROL: return PUGL_KEY_CTRL_L; + case VK_RCONTROL: return PUGL_KEY_CTRL_R; + case VK_MENU: + case VK_LMENU: return PUGL_KEY_ALT_L; + case VK_RMENU: return PUGL_KEY_ALT_R; + case VK_LWIN: return PUGL_KEY_SUPER_L; + case VK_RWIN: return PUGL_KEY_SUPER_R; + case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK; + case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK; + case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK; + case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN; + case VK_PAUSE: return PUGL_KEY_PAUSE; + } + return (PuglKey)0; +} + +static uint32_t +getModifiers(void) +{ + return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) | + ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) | + ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) | + ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) | + ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u)); +} + +static void +initMouseEvent(PuglEvent* event, + PuglView* view, + int button, + bool press, + LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ClientToScreen(view->impl->hwnd, &pt); + + if (press) { + SetCapture(view->impl->hwnd); + } else { + ReleaseCapture(); + } + + event->button.time = GetMessageTime() / 1e3; + event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; + event->button.x = GET_X_LPARAM(lParam); + event->button.y = GET_Y_LPARAM(lParam); + event->button.x_root = pt.x; + event->button.y_root = pt.y; + event->button.state = getModifiers(); + event->button.button = (uint32_t)button; +} + +static void +initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(view->impl->hwnd, &pt); + + event->scroll.time = GetMessageTime() / 1e3; + event->scroll.type = PUGL_SCROLL; + event->scroll.x = pt.x; + event->scroll.y = pt.y; + event->scroll.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; +} + +/** Return the code point for buf, or the replacement character on error. */ +static uint32_t +puglDecodeUTF16(const wchar_t* buf, const int len) +{ + const uint32_t c0 = buf[0]; + const uint32_t c1 = buf[0]; + if (c0 >= 0xD800 && c0 < 0xDC00) { + if (len < 2) { + return 0xFFFD; // Surrogate, but length is only 1 + } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) { + return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000; + } + + return 0xFFFD; // Unpaired surrogates + } + + return c0; +} + +static void +initKeyEvent(PuglEventKey* event, + PuglView* view, + bool press, + WPARAM wParam, + LPARAM lParam) +{ + POINT rpos = { 0, 0 }; + GetCursorPos(&rpos); + + POINT cpos = { rpos.x, rpos.y }; + ScreenToClient(view->impl->hwnd, &rpos); + + const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16); + const unsigned vkey = ((wParam == VK_SHIFT) + ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX) + : (unsigned)wParam); + + const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); + const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); + const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1; + const bool ext = lParam & 0x01000000; + + event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + event->time = GetMessageTime() / 1e3; + event->state = getModifiers(); + event->x_root = rpos.x; + event->y_root = rpos.y; + event->x = cpos.x; + event->y = cpos.y; + event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16); + event->key = 0; + + const PuglKey special = keySymToSpecial(vkey); + if (special) { + if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) { + event->key = special + 1u; // Right hand key + } else { + event->key = special; + } + } else if (!dead) { + // Translate unshifted key + BYTE keyboardState[256] = {0}; + wchar_t buf[5] = {0}; + const int ulen = ToUnicode(vkey, vcode, keyboardState, buf, 4, 1<<2); + event->key = puglDecodeUTF16(buf, ulen); + } +} + +static void +initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam) +{ + const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF }; + + initKeyEvent(&event->key, view, true, wParam, lParam); + event->type = PUGL_TEXT; + event->text.character = puglDecodeUTF16(utf16, 2); + + if (!WideCharToMultiByte( + CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) { + memset(event->text.string, 0, 8); + } +} + +static bool +ignoreKeyEvent(PuglView* view, LPARAM lParam) +{ + return view->ignoreKeyRepeat && (lParam & (1 << 30)); +} + +static RECT +handleConfigure(PuglView* view, PuglEvent* event) +{ + RECT rect; + GetClientRect(view->impl->hwnd, &rect); + view->width = rect.right - rect.left; + view->height = rect.bottom - rect.top; + + event->configure.type = PUGL_CONFIGURE; + event->configure.x = rect.left; + event->configure.y = rect.top; + event->configure.width = view->width; + event->configure.height = view->height; + + view->backend->resize(view, view->width, view->height); + return rect; +} + +static void +handleCrossing(PuglView* view, const PuglEventType type, POINT pos) +{ + POINT root_pos = pos; + ClientToScreen(view->impl->hwnd, &root_pos); + + const PuglEventCrossing ev = { + type, + 0, + GetMessageTime() / 1e3, + (double)pos.x, + (double)pos.y, + (double)root_pos.x, + (double)root_pos.y, + getModifiers(), + PUGL_CROSSING_NORMAL + }; + puglDispatchEvent(view, (const PuglEvent*)&ev); +} + +static void +stopFlashing(PuglView* view) +{ + if (view->impl->flashing) { + KillTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID); + FlashWindow(view->impl->hwnd, FALSE); + } +} + +static void +constrainAspect(const PuglView* const view, + RECT* const size, + const WPARAM wParam) +{ + const float minAspect = view->min_aspect_x / (float)view->min_aspect_y; + const float maxAspect = view->max_aspect_x / (float)view->max_aspect_y; + const int w = size->right - size->left; + const int h = size->bottom - size->top; + const float a = w / (float)h; + + switch (wParam) { + case WMSZ_TOP: + size->top = (a < minAspect ? (LONG)(size->bottom - w * minAspect) : + a > maxAspect ? (LONG)(size->bottom - w * maxAspect) : + size->top); + break; + case WMSZ_TOPRIGHT: + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + size->right = (a < minAspect ? (LONG)(size->left + h * minAspect) : + a > maxAspect ? (LONG)(size->left + h * maxAspect) : + size->right); + break; + case WMSZ_BOTTOM: + size->bottom = (a < minAspect ? (LONG)(size->top + w * minAspect) : + a > maxAspect ? (LONG)(size->top + w * maxAspect) : + size->bottom); + break; + case WMSZ_BOTTOMLEFT: + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + size->left = (a < minAspect ? (LONG)(size->right - h * minAspect) : + a > maxAspect ? (LONG)(size->right - h * maxAspect) : + size->left); + break; + } +} + +static LRESULT +handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglEvent event; + void* dummy_ptr = NULL; + RECT rect; + MINMAXINFO* mmi; + POINT pt; + + memset(&event, 0, sizeof(event)); + + event.any.type = PUGL_NOTHING; + if (InSendMessageEx(dummy_ptr)) { + event.any.flags |= PUGL_IS_SEND_EVENT; + } + + switch (message) { + case WM_SHOWWINDOW: + rect = handleConfigure(view, &event); + puglPostRedisplay(view); + break; + case WM_SIZE: + rect = handleConfigure(view, &event); + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT| + RDW_UPDATENOW); + break; + case WM_SIZING: + if (view->min_aspect_x) { + constrainAspect(view, (RECT*)lParam, wParam); + return TRUE; + } + break; + case WM_ENTERSIZEMOVE: + view->impl->resizing = true; + SetTimer(view->impl->hwnd, + PUGL_RESIZE_TIMER_ID, + 1000 / view->impl->refreshRate, + NULL); + break; + case WM_TIMER: + if (wParam == PUGL_RESIZE_TIMER_ID) { + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); + } else if (wParam == PUGL_URGENT_TIMER_ID) { + FlashWindow(view->impl->hwnd, TRUE); + } + break; + case WM_EXITSIZEMOVE: + KillTimer(view->impl->hwnd, PUGL_RESIZE_TIMER_ID); + view->impl->resizing = false; + puglPostRedisplay(view); + break; + case WM_GETMINMAXINFO: + mmi = (MINMAXINFO*)lParam; + mmi->ptMinTrackSize.x = view->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_ERASEBKGND: + return true; + case WM_MOUSEMOVE: + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + + if (!view->impl->mouseTracked) { + TRACKMOUSEEVENT tme = {0}; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = view->impl->hwnd; + TrackMouseEvent(&tme); + + stopFlashing(view); + handleCrossing(view, PUGL_ENTER_NOTIFY, pt); + view->impl->mouseTracked = true; + } + + ClientToScreen(view->impl->hwnd, &pt); + event.motion.type = PUGL_MOTION_NOTIFY; + event.motion.time = GetMessageTime() / 1e3; + 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_MOUSELEAVE: + GetCursorPos(&pt); + ScreenToClient(view->impl->hwnd, &pt); + handleCrossing(view, PUGL_LEAVE_NOTIFY, pt); + view->impl->mouseTracked = false; + break; + case WM_LBUTTONDOWN: + initMouseEvent(&event, view, 1, true, lParam); + break; + case WM_MBUTTONDOWN: + initMouseEvent(&event, view, 2, true, lParam); + break; + case WM_RBUTTONDOWN: + initMouseEvent(&event, view, 3, true, lParam); + break; + case WM_LBUTTONUP: + initMouseEvent(&event, view, 1, false, lParam); + break; + case WM_MBUTTONUP: + initMouseEvent(&event, view, 2, false, lParam); + break; + case WM_RBUTTONUP: + initMouseEvent(&event, view, 3, false, lParam); + break; + case WM_MOUSEWHEEL: + initScrollEvent(&event, view, lParam); + event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + break; + case WM_MOUSEHWHEEL: + initScrollEvent(&event, view, lParam); + event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + break; + case WM_KEYDOWN: + if (!ignoreKeyEvent(view, lParam)) { + initKeyEvent(&event.key, view, true, wParam, lParam); + } + break; + case WM_KEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_CHAR: + initCharEvent(&event, view, wParam, lParam); + break; + case WM_SETFOCUS: + stopFlashing(view); + event.type = PUGL_FOCUS_IN; + break; + case WM_KILLFOCUS: + event.type = PUGL_FOCUS_OUT; + break; + case WM_SYSKEYDOWN: + initKeyEvent(&event.key, view, true, wParam, lParam); + break; + case WM_SYSKEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_SYSCHAR: + return TRUE; + case 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) +{ + SetFocus(view->impl->hwnd); +} + +void +puglRequestAttention(PuglView* view) +{ + if (!view->impl->mouseTracked || GetFocus() != view->impl->hwnd) { + FlashWindow(view->impl->hwnd, TRUE); + SetTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID, 500, NULL); + view->impl->flashing = true; + } +} + +PuglStatus +puglWaitForEvent(PuglView* PUGL_UNUSED(view)) +{ + WaitMessage(); + return PUGL_SUCCESS; +} + +PuglStatus +puglProcessEvents(PuglView* view) +{ + MSG msg; + while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return PUGL_SUCCESS; +} + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (message) { + case WM_CREATE: + PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); + return 0; + case WM_CLOSE: + PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); + return 0; + case WM_DESTROY: + return 0; + default: + if (view && hwnd == view->impl->hwnd) { + return handleMessage(view, message, wParam, lParam); + } else { + return DefWindowProc(hwnd, message, wParam, lParam); + } + } +} + +double +puglGetTime(PuglView* view) +{ + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + const double now = (double)count.QuadPart / view->impl->timerFrequency; + return now - view->start_time; +} + +void +puglPostRedisplay(PuglView* view) +{ + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); + UpdateWindow(view->impl->hwnd); +} + +PuglNativeWindow +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeWindow)view->impl->hwnd; +} diff --git a/pugl/detail/win.h b/pugl/detail/win.h new file mode 100644 index 0000000..9af5cbb --- /dev/null +++ b/pugl/detail/win.h @@ -0,0 +1,100 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.h Shared definitions for Windows implementation. +*/ + +#include "pugl/detail/implementation.h" + +#include + +#include + +typedef PIXELFORMATDESCRIPTOR PuglWinPFD; + +struct PuglInternalsImpl { + PuglWinPFD pfd; + int pfId; + HWND hwnd; + HDC hdc; + PuglSurface* surface; + DWORD refreshRate; + double timerFrequency; + bool flashing; + bool resizing; + bool mouseTracked; +}; + +static inline PuglWinPFD +puglWinGetPixelFormatDescriptor(const PuglHints* const hints) +{ + const int rgbBits = hints->red_bits + hints->green_bits + hints->blue_bits; + + PuglWinPFD pfd; + ZeroMemory(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL; + pfd.dwFlags |= hints->double_buffer ? PFD_DOUBLEBUFFER : 0; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = (BYTE)rgbBits; + pfd.cRedBits = (BYTE)hints->red_bits; + pfd.cGreenBits = (BYTE)hints->green_bits; + pfd.cBlueBits = (BYTE)hints->blue_bits; + pfd.cAlphaBits = (BYTE)hints->alpha_bits; + pfd.cDepthBits = (BYTE)hints->depth_bits; + pfd.cStencilBits = (BYTE)hints->stencil_bits; + pfd.iLayerType = PFD_MAIN_PLANE; + return pfd; +} + +static inline PuglStatus +puglWinCreateWindow(const PuglView* const view, + const char* const title, + HWND* const hwnd, + HDC* const hdc) +{ + const char* className = view->windowClass ? view->windowClass : "Pugl"; + + const unsigned winFlags = + (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | + (view->parent + ? WS_CHILD + : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | + (view->hints.resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0)))); + + const unsigned winExFlags = + WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW); + + // Calculate total window size to accommodate requested view size + RECT wr = { 0, 0, view->width, view->height }; + AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags); + + // Create window and get drawing context + if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right-wr.left, wr.bottom-wr.top, + (HWND)view->parent, NULL, NULL, NULL))) { + return PUGL_ERR_CREATE_WINDOW; + } else if (!(*hdc = GetDC(*hwnd))) { + DestroyWindow(*hwnd); + *hwnd = NULL; + return PUGL_ERR_CREATE_WINDOW; + } + + return PUGL_SUCCESS; +} diff --git a/pugl/detail/win_cairo.c b/pugl/detail/win_cairo.c new file mode 100644 index 0000000..c322f25 --- /dev/null +++ b/pugl/detail/win_cairo.c @@ -0,0 +1,200 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win_cairo.c Cairo graphics backend for Windows. +*/ + +#include "pugl/detail/types.h" +#include "pugl/detail/win.h" +#include "pugl/pugl_cairo_backend.h" + +#include +#include + +#include + +typedef struct { + cairo_surface_t* surface; + cairo_t* cr; + HDC drawDc; + HBITMAP drawBitmap; +} PuglWinCairoSurface; + +static int +puglWinCairoCreateDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + surface->drawDc = CreateCompatibleDC(impl->hdc); + surface->drawBitmap = CreateCompatibleBitmap( + impl->hdc, view->width, view->height); + + DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); + + cairo_status_t st = CAIRO_STATUS_SUCCESS; + if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || + (st = cairo_surface_status(surface->surface)) || + !(surface->cr = cairo_create(surface->surface)) || + (st = cairo_status(surface->cr))) { + return PUGL_ERR_CREATE_CONTEXT; + } + + cairo_save(surface->cr); + return 0; +} + +static int +puglWinCairoDestroyDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + DeleteDC(surface->drawDc); + DeleteObject(surface->drawBitmap); + cairo_destroy(surface->cr); + cairo_surface_destroy(surface->surface); + + surface->surface = NULL; + surface->cr = NULL; + surface->drawDc = NULL; + surface->drawBitmap = NULL; + + return 0; +} + +static int +puglWinCairoConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglStatus st = PUGL_SUCCESS; + + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } + + impl->pfd = puglWinGetPixelFormatDescriptor(&view->hints); + impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd); + + if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_ERR_SET_FORMAT; + } + + impl->surface = (PuglWinCairoSurface*)calloc( + 1, sizeof(PuglWinCairoSurface)); + + return 0; +} + +static int +puglWinCairoCreate(PuglView* view) +{ + return puglWinCairoCreateDrawContext(view); +} + +static int +puglWinCairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + puglWinCairoDestroyDrawContext(view); + free(surface); + impl->surface = NULL; + + return 0; +} + +static int +puglWinCairoEnter(PuglView* view, bool drawing) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + if (!drawing) { + return 0; + } + + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + cairo_save(surface->cr); + + return 0; +} + +static int +puglWinCairoLeave(PuglView* view, bool drawing) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + if (!drawing) { + return 0; + } + + cairo_restore(surface->cr); + cairo_surface_flush(surface->surface); + BitBlt(impl->hdc, 0, 0, view->width, view->height, + surface->drawDc, 0, 0, SRCCOPY); + + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + SwapBuffers(view->impl->hdc); + + return 0; +} + +static int +puglWinCairoResize(PuglView* view, + int width, + int height) +{ + view->width = width; + view->height = height; + int st = 0; + if ((st = puglWinCairoDestroyDrawContext(view)) || + (st = puglWinCairoCreateDrawContext(view))) { + fprintf(stderr, "ERR\n"); + return st; + } + + return 0; +} + +static void* +puglWinCairoGetContext(PuglView* view) +{ + return ((PuglWinCairoSurface*)view->impl->surface)->cr; +} + +const PuglBackend* +puglCairoBackend() +{ + static const PuglBackend backend = { + puglWinCairoConfigure, + puglWinCairoCreate, + puglWinCairoDestroy, + puglWinCairoEnter, + puglWinCairoLeave, + puglWinCairoResize, + puglWinCairoGetContext + }; + + return &backend; +} diff --git a/pugl/detail/win_gl.c b/pugl/detail/win_gl.c new file mode 100644 index 0000000..17ee68d --- /dev/null +++ b/pugl/detail/win_gl.c @@ -0,0 +1,306 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win_gl.c OpenGL graphics backend for Windows. +*/ + +#include "pugl/detail/types.h" +#include "pugl/detail/win.h" +#include "pugl/pugl_gl_backend.h" + +#include + +#include + +#include +#include + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 + +typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*); +typedef BOOL (*WglSwapInterval)(int); +typedef BOOL (*WglChoosePixelFormat)( + HDC, const int*, const FLOAT*, UINT, int*, UINT*); + +typedef struct { + WglChoosePixelFormat wglChoosePixelFormat; + WglCreateContextAttribs wglCreateContextAttribs; + WglSwapInterval wglSwapInterval; +} PuglWinGlProcs; + +typedef struct { + PuglWinGlProcs procs; + HGLRC hglrc; +} PuglWinGlSurface; + +// Struct to manage the fake window used during configuration +typedef struct { + HWND hwnd; + HDC hdc; +} PuglFakeWindow; + +static int +puglWinError(PuglFakeWindow* fakeWin, const int status) +{ + if (fakeWin->hwnd) { + ReleaseDC(fakeWin->hwnd, fakeWin->hdc); + DestroyWindow(fakeWin->hwnd); + } + + return status; +} + +static PuglWinGlProcs puglWinGlGetProcs(void) +{ + const PuglWinGlProcs procs = { + (WglChoosePixelFormat)( + wglGetProcAddress("wglChoosePixelFormatARB")), + (WglCreateContextAttribs)( + wglGetProcAddress("wglCreateContextAttribsARB")), + (WglSwapInterval)( + wglGetProcAddress("wglSwapIntervalEXT")) + }; + + return procs; +} + +static int +puglWinGlConfigure(PuglView* view) +{ + PuglInternals* impl = view->impl; + + const int pixelAttrs[] = { + WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, view->hints.double_buffer, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_SAMPLE_BUFFERS_ARB, view->hints.samples ? 1 : 0, + WGL_SAMPLES_ARB, view->hints.samples, + WGL_RED_BITS_ARB, view->hints.red_bits, + WGL_GREEN_BITS_ARB, view->hints.green_bits, + WGL_BLUE_BITS_ARB, view->hints.blue_bits, + WGL_ALPHA_BITS_ARB, view->hints.alpha_bits, + WGL_DEPTH_BITS_ARB, view->hints.depth_bits, + WGL_STENCIL_BITS_ARB, view->hints.stencil_bits, + 0, + }; + + PuglWinGlSurface* const surface = + (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface)); + impl->surface = surface; + + // Create fake window for getting at GL context + PuglStatus st = PUGL_SUCCESS; + PuglFakeWindow fakeWin = { 0, 0 }; + if ((st = puglWinCreateWindow(view, "Pugl Configuration", + &fakeWin.hwnd, &fakeWin.hdc))) { + return puglWinError(&fakeWin, st); + } + + // Set pixel format for fake window + const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(&view->hints); + const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd); + if (!fakePfId) { + return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); + } else if (!SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) { + return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); + } + + // Create fake GL context to get at the functions we need + HGLRC fakeRc = wglCreateContext(fakeWin.hdc); + if (!fakeRc) { + return puglWinError(&fakeWin, PUGL_ERR_CREATE_CONTEXT); + } + + // Enter fake context and get extension functions + wglMakeCurrent(fakeWin.hdc, fakeRc); + surface->procs = puglWinGlGetProcs(); + + if (surface->procs.wglChoosePixelFormat) { + // Choose pixel format based on attributes + UINT numFormats = 0; + if (!surface->procs.wglChoosePixelFormat( + fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { + return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); + } + + DescribePixelFormat( + impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd); + } else { + // Modern extensions not available, use basic pixel format + impl->pfd = fakePfd; + impl->pfId = fakePfId; + } + + // Dispose of fake window and context + wglMakeCurrent(NULL, NULL); + wglDeleteContext(fakeRc); + ReleaseDC(fakeWin.hwnd, fakeWin.hdc); + DestroyWindow(fakeWin.hwnd); + + return 0; +} + +static int +puglWinGlCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface; + PuglStatus st = PUGL_SUCCESS; + + const int contextAttribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major, + WGL_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor, + WGL_CONTEXT_PROFILE_MASK_ARB, + (view->hints.use_compat_profile + ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB + : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), + 0 + }; + + // Create real window with desired pixel format + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_ERR_SET_FORMAT; + } + + // Create GL context + if (surface->procs.wglCreateContextAttribs && + !(surface->hglrc = surface->procs.wglCreateContextAttribs( + impl->hdc, 0, contextAttribs))) { + return PUGL_ERR_CREATE_CONTEXT; + } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) { + return PUGL_ERR_CREATE_CONTEXT; + } + + // Enter context and set swap interval + wglMakeCurrent(impl->hdc, surface->hglrc); + if (surface->procs.wglSwapInterval) { + surface->procs.wglSwapInterval(1); + } + + return 0; +} + +static int +puglWinGlDestroy(PuglView* view) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + if (surface) { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(surface->hglrc); + free(surface); + view->impl->surface = NULL; + } + + return 0; +} + +static int +puglWinGlEnter(PuglView* view, bool drawing) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + + wglMakeCurrent(view->impl->hdc, surface->hglrc); + + if (drawing) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return 0; +} + +static int +puglWinGlLeave(PuglView* view, bool drawing) +{ + if (drawing) { + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + SwapBuffers(view->impl->hdc); + } + + wglMakeCurrent(NULL, NULL); + + return 0; +} + +static int +puglWinGlResize(PuglView* PUGL_UNUSED(view), + int PUGL_UNUSED(width), + int PUGL_UNUSED(height)) +{ + return 0; +} + +static void* +puglWinGlGetContext(PuglView* PUGL_UNUSED(view)) +{ + return NULL; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + return (PuglGlFunc)wglGetProcAddress(name); +} + +const PuglBackend* +puglGlBackend() +{ + static const PuglBackend backend = { + puglWinGlConfigure, + puglWinGlCreate, + puglWinGlDestroy, + puglWinGlEnter, + puglWinGlLeave, + puglWinGlResize, + puglWinGlGetContext + }; + + return &backend; +} diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c new file mode 100644 index 0000000..70341ea --- /dev/null +++ b/pugl/detail/x11.c @@ -0,0 +1,577 @@ +/* + Copyright 2012-2019 David Robillard + Copyright 2013 Robin Gareus + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.c X11 implementation. +*/ + +#define _POSIX_C_SOURCE 199309L + +#include "pugl/detail/implementation.h" +#include "pugl/detail/x11.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +enum WmClientStateMessageAction { + WM_STATE_REMOVE, + WM_STATE_ADD, + WM_STATE_TOGGLE +}; + +static const long eventMask = + (ExposureMask | StructureNotifyMask | FocusChangeMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); + +PuglInternals* +puglInitInternals(void) +{ + return (PuglInternals*)calloc(1, sizeof(PuglInternals)); +} + +int +puglCreateWindow(PuglView* view, const char* title) +{ + PuglInternals* const impl = view->impl; + Display* const display = XOpenDisplay(0); + + impl->display = display; + impl->screen = DefaultScreen(display); + + // Intern the various atoms we will need + impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); + impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); + impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); + impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = + XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); + + if (!view->backend || !view->backend->configure) { + return 1; + } else if (view->backend->configure(view) || !impl->vi) { + view->backend->destroy(view); + return 2; + } + + Window xParent = view->parent ? (Window)view->parent + : RootWindow(display, impl->screen); + + Colormap cmap = XCreateColormap( + display, xParent, impl->vi->visual, AllocNone); + + XSetWindowAttributes attr = {0}; + attr.colormap = cmap; + attr.event_mask = eventMask; + + const Window win = impl->win = XCreateWindow( + display, xParent, + 0, 0, view->width, view->height, 0, impl->vi->depth, InputOutput, + impl->vi->visual, CWColormap | CWEventMask, &attr); + + if (view->backend->create(view)) { + return 3; + } + + XSizeHints sizeHints = {0}; + if (!view->hints.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; + } 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(display, win, &sizeHints); + + if (title) { + XStoreName(display, win, title); + } + + if (!view->parent) { + XSetWMProtocols(display, win, &view->impl->atoms.WM_DELETE_WINDOW, 1); + } + + if (view->transient_parent) { + XSetTransientForHint(display, win, (Window)(view->transient_parent)); + } + + XSetLocaleModifiers(""); + if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) { + XSetLocaleModifiers("@im="); + if (!(impl->xim = XOpenIM(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, win, + XNFocusWindow, win, + NULL))) { + fprintf(stderr, "warning: XCreateIC failed\n"); + } + + 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) { + if (view->impl->xic) { + XDestroyIC(view->impl->xic); + } + if (view->impl->xim) { + XCloseIM(view->impl->xim); + } + view->backend->destroy(view); + XDestroyWindow(view->impl->display, view->impl->win); + XCloseDisplay(view->impl->display); + XFree(view->impl->vi); + 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_L; + case XK_Shift_R: return PUGL_KEY_SHIFT_R; + case XK_Control_L: return PUGL_KEY_CTRL_L; + case XK_Control_R: return PUGL_KEY_CTRL_R; + case XK_Alt_L: return PUGL_KEY_ALT_L; + case XK_ISO_Level3_Shift: + case XK_Alt_R: return PUGL_KEY_ALT_R; + case XK_Super_L: return PUGL_KEY_SUPER_L; + case XK_Super_R: return PUGL_KEY_SUPER_R; + case XK_Menu: return PUGL_KEY_MENU; + case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK; + case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK; + case XK_Num_Lock: return PUGL_KEY_NUM_LOCK; + case XK_Print: return PUGL_KEY_PRINT_SCREEN; + case XK_Pause: return PUGL_KEY_PAUSE; + default: break; + } + return (PuglKey)0; +} + +static int +lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym) +{ + Status status = 0; + +#ifdef X_HAVE_UTF8_STRING + const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status); +#else + const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status); +#endif + + return status == XBufferOverflow ? 0 : n; +} + +static void +translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) +{ + const unsigned state = xevent->xkey.state; + const bool filter = XFilterEvent(xevent, None); + + event->key.keycode = xevent->xkey.keycode; + xevent->xkey.state = 0; + + // Lookup unshifted key + char ustr[8] = {0}; + KeySym sym = 0; + const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL); + const PuglKey special = keySymToSpecial(sym); + + event->key.key = ((special || ufound <= 0) + ? special + : puglDecodeUTF8((const uint8_t*)ustr)); + + if (xevent->type == KeyPress && !filter && !special) { + // Lookup shifted key for possible text event + xevent->xkey.state = state; + + char sstr[8] = {0}; + const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym); + if (sfound > 0) { + // Dispatch key event now + puglDispatchEvent(view, event); + + // "Return" a text event in its place + event->text.type = PUGL_TEXT; + event->text.character = puglDecodeUTF8((const uint8_t*)sstr); + memcpy(event->text.string, sstr, sizeof(sstr)); + } + } +} + +static uint32_t +translateModifiers(const unsigned xstate) +{ + return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0) | + ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0) | + ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0) | + ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0)); +} + +static PuglEvent +translateEvent(PuglView* view, XEvent xevent) +{ + PuglEvent event = {0}; + event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0; + + switch (xevent.type) { + case ClientMessage: + if (xevent.xclient.message_type == view->impl->atoms.WM_PROTOCOLS) { + const Atom protocol = (Atom)xevent.xclient.data.l[0]; + if (protocol == view->impl->atoms.WM_DELETE_WINDOW) { + event.type = PUGL_CLOSE; + } + } + break; + case MapNotify: { + XWindowAttributes attrs = {0}; + XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); + event.type = PUGL_CONFIGURE; + event.configure.x = attrs.x; + event.configure.y = attrs.y; + event.configure.width = attrs.width; + event.configure.height = attrs.height; + 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 / 1e3; + 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 / 1e3; + 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.0; break; + case 5: event.scroll.dy = -1.0; break; + case 6: event.scroll.dx = -1.0; break; + case 7: event.scroll.dx = 1.0; 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 / 1e3; + 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 / 1e3; + 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 / 1e3; + 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); +} + +void +puglRequestAttention(PuglView* view) +{ + PuglInternals* const impl = view->impl; + XEvent event = {0}; + event.type = ClientMessage; + event.xclient.window = impl->win; + event.xclient.format = 32; + event.xclient.message_type = impl->atoms.NET_WM_STATE; + event.xclient.data.l[0] = WM_STATE_ADD; + event.xclient.data.l[1] = impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + + const Window root = RootWindow(impl->display, impl->screen); + XSendEvent(impl->display, + root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + (XEvent*)&event); +} + +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. */ + PuglInternals* const impl = view->impl; + PuglEvent expose_event = { 0 }; + PuglEvent config_event = { 0 }; + XEvent xevent; + while (XPending(impl->display) > 0) { + XNextEvent(impl->display, &xevent); + if (xevent.type == KeyRelease) { + // Ignore key repeat if necessary + if (view->ignoreKeyRepeat && + XEventsQueued(impl->display, QueuedAfterReading)) { + XEvent next; + XPeekEvent(impl->display, &next); + if (next.type == KeyPress && + next.xkey.time == xevent.xkey.time && + next.xkey.keycode == xevent.xkey.keycode) { + XNextEvent(impl->display, &xevent); + continue; + } + } + } else if (xevent.type == FocusIn) { + XSetICFocus(impl->xic); + } else if (xevent.type == FocusOut) { + XUnsetICFocus(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 || expose_event.type) { + const bool draw = expose_event.type && expose_event.expose.count == 0; + + puglEnterContext(view, draw); + + if (config_event.type) { + view->width = (int)config_event.configure.width; + view->height = (int)config_event.configure.height; + view->backend->resize(view, view->width, view->height); + view->eventFunc(view, (const PuglEvent*)&config_event); + } + + if (draw) { + view->eventFunc(view, (const PuglEvent*)&expose_event); + } + + puglLeaveContext(view, draw); + } + + return PUGL_SUCCESS; +} + +double +puglGetTime(PuglView* view) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((double)ts.tv_sec + ts.tv_nsec / 1000000000.0) - view->start_time; +} + +void +puglPostRedisplay(PuglView* view) +{ + XExposeEvent ev = {Expose, 0, True, + view->impl->display, view->impl->win, + 0, 0, + view->width, view->height, + 0}; + + XSendEvent(view->impl->display, view->impl->win, False, 0, (XEvent*)&ev); +} + +PuglNativeWindow +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeWindow)view->impl->win; +} diff --git a/pugl/detail/x11.h b/pugl/detail/x11.h new file mode 100644 index 0000000..98f42b0 --- /dev/null +++ b/pugl/detail/x11.h @@ -0,0 +1,41 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.h Shared definitions for X11 implementation. +*/ + +#include "pugl/detail/implementation.h" + +#include +#include + +struct PuglInternalsImpl { + Display* display; + int screen; + XVisualInfo* vi; + Window win; + XIM xim; + XIC xic; + PuglSurface* surface; + + struct { + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom NET_WM_STATE; + Atom NET_WM_STATE_DEMANDS_ATTENTION; + } atoms; +}; diff --git a/pugl/detail/x11_cairo.c b/pugl/detail/x11_cairo.c new file mode 100644 index 0000000..97624a2 --- /dev/null +++ b/pugl/detail/x11_cairo.c @@ -0,0 +1,139 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11_cairo.c Cairo graphics backend for X11. +*/ + +#include "pugl/detail/types.h" +#include "pugl/detail/x11.h" +#include "pugl/pugl_cairo_backend.h" + +#include +#include +#include + +#include +#include + +typedef struct { + cairo_surface_t* surface; + cairo_t* cr; +} PuglX11CairoSurface; + +static int +puglX11CairoConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + + XVisualInfo pat; + int n; + pat.screen = impl->screen; + impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); + + return 0; +} + +static int +puglX11CairoCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = + (PuglX11CairoSurface*)calloc(1, sizeof(PuglX11CairoSurface)); + + impl->surface = surface; + + surface->surface = cairo_xlib_surface_create( + impl->display, impl->win, impl->vi->visual, view->width, view->height); + + if (!surface->surface) { + return 1; + } + + cairo_status_t st = cairo_surface_status(surface->surface); + if (st) { + fprintf(stderr, "error: failed to create cairo surface (%s)\n", + cairo_status_to_string(st)); + } else if (!(surface->cr = cairo_create(surface->surface))) { + fprintf(stderr, "error: failed to create cairo context\n"); + } else if ((st = cairo_status(surface->cr))) { + cairo_surface_destroy(surface->surface); + fprintf(stderr, "error: cairo context is invalid (%s)\n", + cairo_status_to_string(st)); + } + return (int)st; +} + +static int +puglX11CairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + cairo_destroy(surface->cr); + cairo_surface_destroy(surface->surface); + free(surface); + impl->surface = NULL; + return 0; +} + +static int +puglX11CairoEnter(PuglView* PUGL_UNUSED(view), bool PUGL_UNUSED(drawing)) +{ + return 0; +} + +static int +puglX11CairoLeave(PuglView* PUGL_UNUSED(view), bool PUGL_UNUSED(drawing)) +{ + return 0; +} + +static int +puglX11CairoResize(PuglView* view, int width, int height) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + cairo_xlib_surface_set_size(surface->surface, width, height); + + return 0; +} + +static void* +puglX11CairoGetContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + return surface->cr; +} + +const PuglBackend* +puglCairoBackend(void) +{ + static const PuglBackend backend = { + puglX11CairoConfigure, + puglX11CairoCreate, + puglX11CairoDestroy, + puglX11CairoEnter, + puglX11CairoLeave, + puglX11CairoResize, + puglX11CairoGetContext + }; + + return &backend; +} diff --git a/pugl/detail/x11_gl.c b/pugl/detail/x11_gl.c new file mode 100644 index 0000000..7fcf169 --- /dev/null +++ b/pugl/detail/x11_gl.c @@ -0,0 +1,213 @@ +/* + Copyright 2012-2019 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11_gl.c OpenGL graphics backend for X11. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/detail/x11.h" +#include "pugl/pugl_gl_backend.h" + +#include +#include + +#include +#include + +typedef struct { + GLXFBConfig fb_config; + GLXContext ctx; + int double_buffered; +} PuglX11GlSurface; + +static int +puglX11GlHintValue(const int value) +{ + return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value; +} + +static int +puglX11GlGetAttrib(Display* const display, + const GLXFBConfig fb_config, + const int attrib) +{ + int value = 0; + glXGetFBConfigAttrib(display, fb_config, attrib, &value); + return value; +} + +static int +puglX11GlConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + const int screen = impl->screen; + Display* const display = impl->display; + + PuglX11GlSurface* const surface = + (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface)); + impl->surface = surface; + + const int attrs[] = { + GLX_X_RENDERABLE, True, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_SAMPLES, view->hints.samples, + GLX_RED_SIZE, puglX11GlHintValue(view->hints.red_bits), + GLX_GREEN_SIZE, puglX11GlHintValue(view->hints.green_bits), + GLX_BLUE_SIZE, puglX11GlHintValue(view->hints.blue_bits), + GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints.alpha_bits), + GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints.depth_bits), + GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints.stencil_bits), + GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints.double_buffer), + None + }; + + int n_fbc = 0; + GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc); + if (n_fbc <= 0) { + fprintf(stderr, "error: Failed to create GL context\n"); + return 1; + } + + surface->fb_config = fbc[0]; + impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]); + + printf("Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d" + " DOUBLE=%d SAMPLES=%d\n", + impl->vi->visualid, + puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER), + puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES)); + + XFree(fbc); + + return 0; +} + +static int +puglX11GlCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface; + Display* const display = impl->display; + const GLXFBConfig fb_config = surface->fb_config; + + const int ctx_attrs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major, + GLX_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor, + GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints.use_compat_profile + ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB + : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), + 0}; + + typedef GLXContext (*CreateContextAttribs)( + Display*, GLXFBConfig, GLXContext, Bool, const int*); + + CreateContextAttribs create_context = + (CreateContextAttribs)glXGetProcAddress( + (const GLubyte*)"glXCreateContextAttribsARB"); + + impl->surface = surface; + surface->ctx = create_context(display, fb_config, 0, GL_TRUE, ctx_attrs); + if (!surface->ctx) { + surface->ctx = + glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); + } + + glXGetConfig(impl->display, + impl->vi, + GLX_DOUBLEBUFFER, + &surface->double_buffered); + + return 0; +} + +static int +puglX11GlDestroy(PuglView* view) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + if (surface) { + glXDestroyContext(view->impl->display, surface->ctx); + free(surface); + view->impl->surface = NULL; + } + return 0; +} + +static int +puglX11GlEnter(PuglView* view, bool PUGL_UNUSED(drawing)) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx); + return 0; +} + +static int +puglX11GlLeave(PuglView* view, bool drawing) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + + if (drawing && surface->double_buffered) { + glXSwapBuffers(view->impl->display, view->impl->win); + } else if (drawing) { + glFlush(); + } + + glXMakeCurrent(view->impl->display, None, NULL); + + return 0; +} + +static int +puglX11GlResize(PuglView* PUGL_UNUSED(view), + int PUGL_UNUSED(width), + int PUGL_UNUSED(height)) +{ + return 0; +} + +static void* +puglX11GlGetContext(PuglView* PUGL_UNUSED(view)) +{ + return NULL; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + return glXGetProcAddress((const GLubyte*)name); +} + +const PuglBackend* puglGlBackend(void) +{ + static const PuglBackend backend = { + puglX11GlConfigure, + puglX11GlCreate, + puglX11GlDestroy, + puglX11GlEnter, + puglX11GlLeave, + puglX11GlResize, + puglX11GlGetContext + }; + + return &backend; +} diff --git a/pugl/pugl.h b/pugl/pugl.h index fab2b2b..903f1cb 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -15,7 +15,7 @@ */ /** - @file pugl.h Public API. + @file pugl.h Public Pugl API. */ #ifndef PUGL_H_INCLUDED diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp index ec96889..c2602dd 100644 --- a/pugl/pugl.hpp +++ b/pugl/pugl.hpp @@ -15,7 +15,7 @@ */ /** - @file pugl.hpp C++ wrapper for API. + @file pugl.hpp Public Pugl C++ API wrapper. */ #ifndef PUGL_HPP_INCLUDED diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h deleted file mode 100644 index 18d7a7a..0000000 --- a/pugl/pugl_internal.h +++ /dev/null @@ -1,281 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_internal.h Platform-independent implementation. - - Note that 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. -*/ - -#include "pugl/pugl.h" -#include "pugl/pugl_internal_types.h" - -#include -#include - -PuglInternals* puglInitInternals(void); - -static PuglHints -puglDefaultHints(void) -{ - static const PuglHints hints = { - 2, 0, 4, 4, 4, 4, 24, 8, 0, true, true, false - }; - return hints; -} - -PuglView* -puglInit(int* PUGL_UNUSED(pargc), char** PUGL_UNUSED(argv)) -{ - PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); - if (!view) { - return NULL; - } - - PuglInternals* impl = puglInitInternals(); - if (!impl) { - return NULL; - } - - view->hints = puglDefaultHints(); - view->impl = impl; - view->width = 640; - view->height = 480; - view->start_time = puglGetTime(view); - - return view; -} - -void -puglInitWindowHint(PuglView* view, PuglWindowHint hint, int value) -{ - switch (hint) { - case PUGL_USE_COMPAT_PROFILE: - view->hints.use_compat_profile = value; - break; - case PUGL_CONTEXT_VERSION_MAJOR: - view->hints.context_version_major = value; - break; - case PUGL_CONTEXT_VERSION_MINOR: - view->hints.context_version_minor = value; - break; - case PUGL_RED_BITS: - view->hints.red_bits = value; - break; - case PUGL_GREEN_BITS: - view->hints.green_bits = value; - break; - case PUGL_BLUE_BITS: - view->hints.blue_bits = value; - break; - case PUGL_ALPHA_BITS: - view->hints.alpha_bits = value; - break; - case PUGL_DEPTH_BITS: - view->hints.depth_bits = value; - break; - case PUGL_STENCIL_BITS: - view->hints.stencil_bits = value; - break; - case PUGL_SAMPLES: - view->hints.samples = value; - break; - case PUGL_DOUBLE_BUFFER: - view->hints.double_buffer = value; - break; - case PUGL_RESIZABLE: - view->hints.resizable = value; - break; - } -} - -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->hints.resizable = resizable; -} - -void -puglInitTransientFor(PuglView* view, uintptr_t parent) -{ - view->transient_parent = parent; -} - -int -puglInitBackend(PuglView* view, const PuglBackend* backend) -{ - view->backend = backend; - return 0; -} - -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* -puglGetContext(PuglView* view) -{ - return view->backend->getContext(view); -} - -void -puglEnterContext(PuglView* view, bool drawing) -{ - view->backend->enter(view, drawing); -} - -void -puglLeaveContext(PuglView* view, bool drawing) -{ - view->backend->leave(view, drawing); -} - -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 inline uint32_t -puglDecodeUTF8(const uint8_t* buf) -{ -#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0) - - // http://en.wikipedia.org/wiki/UTF-8 - - if (buf[0] < 0x80) { - return buf[0]; - } else if (buf[0] < 0xC2) { - return 0xFFFD; - } else if (buf[0] < 0xE0) { - FAIL_IF((buf[1] & 0xC0) != 0x80); - return (buf[0] << 6) + buf[1] - 0x3080u; - } 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] - 0xE2080u; - } 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] - 0x3C82080u); - } - return 0xFFFD; -} - -static void -puglDispatchEvent(PuglView* view, const PuglEvent* event) -{ - switch (event->type) { - case PUGL_NOTHING: - break; - case PUGL_CONFIGURE: - view->width = (int)event->configure.width; - view->height = (int)event->configure.height; - puglEnterContext(view, false); - view->eventFunc(view, event); - puglLeaveContext(view, false); - break; - case PUGL_EXPOSE: - if (event->expose.count == 0) { - puglEnterContext(view, true); - view->eventFunc(view, event); - puglLeaveContext(view, true); - } - break; - default: - view->eventFunc(view, event); - } -} diff --git a/pugl/pugl_internal_types.h b/pugl/pugl_internal_types.h deleted file mode 100644 index 9a0bedc..0000000 --- a/pugl/pugl_internal_types.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_internal_types.h Private platform-independent type definitions. -*/ - -#ifndef PUGL_INTERNAL_TYPES_H -#define PUGL_INTERNAL_TYPES_H - -#include "pugl/pugl.h" - -#include -#include - -// Unused parameter macro to suppresses warnings and make it impossible to use -#if defined(__cplusplus) || defined(_MSC_VER) -# define PUGL_UNUSED(name) -#elif defined(__GNUC__) -# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__)) -#else -# define PUGL_UNUSED(name) -#endif - -/** Platform-specific internals. */ -typedef struct PuglInternalsImpl PuglInternals; - -typedef struct { - int context_version_major; - int context_version_minor; - int red_bits; - int green_bits; - int blue_bits; - int alpha_bits; - int depth_bits; - int stencil_bits; - int samples; - int double_buffer; - bool use_compat_profile; - bool resizable; -} PuglHints; - -/** Cross-platform view definition. */ -struct PuglViewImpl { - const PuglBackend* backend; - PuglInternals* impl; - PuglHandle handle; - PuglEventFunc eventFunc; - char* windowClass; - PuglNativeWindow parent; - double start_time; - uintptr_t transient_parent; - PuglHints hints; - 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 visible; -}; - -/** Opaque surface used by draw context. */ -typedef void PuglSurface; - -/** Graphics backend interface. */ -struct PuglBackendImpl { - /** Get visual information from display and setup view as necessary. */ - int (*configure)(PuglView*); - - /** Create surface and drawing context. */ - int (*create)(PuglView*); - - /** Destroy surface and drawing context. */ - int (*destroy)(PuglView*); - - /** Enter drawing context, for drawing if parameter is true. */ - int (*enter)(PuglView*, bool); - - /** Leave drawing context, after drawing if parameter is true. */ - int (*leave)(PuglView*, bool); - - /** Resize drawing context to the given width and height. */ - int (*resize)(PuglView*, int, int); - - /** Return the puglGetContext() handle for the application, if any. */ - void* (*getContext)(PuglView*); -}; - -#endif // PUGL_INTERNAL_TYPES_H diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m deleted file mode 100644 index a5ec994..0000000 --- a/pugl/pugl_osx.m +++ /dev/null @@ -1,1069 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - Copyright 2017 Hanspeter Portner - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_osx.m MacOS implementation. -*/ - -#define GL_SILENCE_DEPRECATION 1 - -#include "pugl/gl.h" -#include "pugl/pugl_gl_backend.h" -#include "pugl/pugl_internal.h" - -#ifdef PUGL_HAVE_CAIRO -#include "pugl/pugl_cairo_backend.h" -#include "pugl/cairo_gl.h" -#endif - -#import - -#include - -#include - -#ifndef __MAC_10_10 -#define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core -typedef NSUInteger NSEventModifierFlags; -typedef NSUInteger NSWindowStyleMask; -#endif - -@class PuglOpenGLView; - -struct PuglInternalsImpl { - NSApplication* app; - PuglOpenGLView* glview; - id window; - NSEvent* nextEvent; - uint32_t mods; -#ifdef PUGL_HAVE_CAIRO - cairo_surface_t* surface; - cairo_t* cr; - PuglCairoGL cairo_gl; -#endif -}; - -@interface PuglWindow : NSWindow -{ -@public - PuglView* puglview; -} - -- (void) setPuglview:(PuglView*)view; - -@end - -@implementation PuglWindow - -- (id) initWithContentRect:(NSRect)contentRect - styleMask:(NSWindowStyleMask)aStyle - backing:(NSBackingStoreType)bufferingType - defer:(BOOL)flag -{ - (void)flag; - - NSWindow* result = [super initWithContentRect:contentRect - styleMask:aStyle - backing:bufferingType - defer:NO]; - - [result setAcceptsMouseMovedEvents:YES]; - return (PuglWindow*)result; -} - -- (void)setPuglview:(PuglView*)view -{ - puglview = view; - [self setContentSize:NSMakeSize(view->width, view->height)]; -} - -- (BOOL) canBecomeKeyWindow -{ - return YES; -} - -- (BOOL) canBecomeMainWindow -{ - return YES; -} - -@end - -@interface PuglOpenGLView : NSOpenGLView -{ -@public - PuglView* puglview; - NSTrackingArea* trackingArea; - NSMutableAttributedString* markedText; - NSTimer* timer; - NSTimer* urgentTimer; -} - -@end - -@implementation PuglOpenGLView - -- (id) initWithFrame:(NSRect)frame -{ - const int major = puglview->hints.context_version_major; - const int profile = ((puglview->hints.use_compat_profile || major < 3) - ? NSOpenGLProfileVersionLegacy - : puglview->hints.context_version_major >= 4 - ? NSOpenGLProfileVersion4_1Core - : NSOpenGLProfileVersion3_2Core); - - NSOpenGLPixelFormatAttribute pixelAttribs[16] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAAccelerated, - NSOpenGLPFAOpenGLProfile, profile, - NSOpenGLPFAColorSize, 32, - NSOpenGLPFADepthSize, 32, - NSOpenGLPFAMultisample, puglview->hints.samples ? 1 : 0, - NSOpenGLPFASampleBuffers, puglview->hints.samples ? 1 : 0, - NSOpenGLPFASamples, puglview->hints.samples, - 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 -{ - [super reshape]; - [[self openGLContext] update]; - - if (!puglview) { - return; - } - - const NSRect bounds = [self bounds]; - const PuglEventConfigure ev = { - PUGL_CONFIGURE, - 0, - bounds.origin.x, - bounds.origin.y, - bounds.size.width, - bounds.size.height, - }; - - puglview->backend->resize(puglview, ev.width, ev.height); - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void) drawRect:(NSRect)rect -{ - const PuglEventExpose ev = { - PUGL_EXPOSE, - 0, - rect.origin.x, - rect.origin.y, - rect.size.width, - rect.size.height, - 0 - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (BOOL) isFlipped -{ - return YES; -} - -- (BOOL) acceptsFirstResponder -{ - return YES; -} - -static uint32_t -getModifiers(const NSEvent* const ev) -{ - const NSEventModifierFlags modifierFlags = [ev modifierFlags]; - - return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) | - ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) | - ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) | - ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0)); -} - -static PuglKey -keySymToSpecial(const NSEvent* const ev) -{ - NSString* chars = [ev charactersIgnoringModifiers]; - if ([chars length] == 1) { - switch ([chars characterAtIndex:0]) { - case NSF1FunctionKey: return PUGL_KEY_F1; - case NSF2FunctionKey: return PUGL_KEY_F2; - case NSF3FunctionKey: return PUGL_KEY_F3; - case NSF4FunctionKey: return PUGL_KEY_F4; - case NSF5FunctionKey: return PUGL_KEY_F5; - case NSF6FunctionKey: return PUGL_KEY_F6; - case NSF7FunctionKey: return PUGL_KEY_F7; - case NSF8FunctionKey: return PUGL_KEY_F8; - case NSF9FunctionKey: return PUGL_KEY_F9; - case NSF10FunctionKey: return PUGL_KEY_F10; - case NSF11FunctionKey: return PUGL_KEY_F11; - case NSF12FunctionKey: return PUGL_KEY_F12; - case NSDeleteCharacter: return PUGL_KEY_BACKSPACE; - case NSDeleteFunctionKey: return PUGL_KEY_DELETE; - case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT; - case NSUpArrowFunctionKey: return PUGL_KEY_UP; - case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT; - case NSDownArrowFunctionKey: return PUGL_KEY_DOWN; - case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP; - case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN; - case NSHomeFunctionKey: return PUGL_KEY_HOME; - case NSEndFunctionKey: return PUGL_KEY_END; - case NSInsertFunctionKey: return PUGL_KEY_INSERT; - case NSMenuFunctionKey: return PUGL_KEY_MENU; - case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK; - case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK; - case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN; - case NSPauseFunctionKey: return PUGL_KEY_PAUSE; - } - // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged] - } - return (PuglKey)0; -} - -- (void) updateTrackingAreas -{ - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - const int opts = (NSTrackingMouseEnteredAndExited | - NSTrackingMouseMoved | - NSTrackingActiveAlways); - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:opts - owner:self - userInfo:nil]; - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -- (NSPoint) eventLocation:(NSEvent*)event -{ - return [self convertPoint:[event locationInWindow] fromView:nil]; -} - -static void -handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type) -{ - const NSPoint wloc = [view eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventCrossing ev = { - type, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - PUGL_CROSSING_NORMAL - }; - puglDispatchEvent(view->puglview, (const PuglEvent*)&ev); -} - -- (void) mouseEntered:(NSEvent*)event -{ - handleCrossing(self, event, PUGL_ENTER_NOTIFY); -} - -- (void) mouseExited:(NSEvent*)event -{ - handleCrossing(self, event, PUGL_LEAVE_NOTIFY); -} - -- (void) mouseMoved:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventMotion ev = { - PUGL_MOTION_NOTIFY, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - 0, - 1 - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void) mouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void) rightMouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void) otherMouseDragged:(NSEvent*)event -{ - [self mouseMoved: event]; -} - -- (void) mouseDown:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventButton ev = { - PUGL_BUTTON_PRESS, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - (uint32_t)[event buttonNumber] + 1 - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void) mouseUp:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventButton ev = { - PUGL_BUTTON_RELEASE, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - (uint32_t)[event buttonNumber] + 1 - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void) rightMouseDown:(NSEvent*)event -{ - [self mouseDown: event]; -} - -- (void) rightMouseUp:(NSEvent*)event -{ - [self mouseUp: event]; -} - -- (void) otherMouseDown:(NSEvent*)event -{ - [self mouseDown: event]; -} - -- (void) otherMouseUp:(NSEvent*)event -{ - [self mouseUp: event]; -} - -- (void) scrollWheel:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglEventScroll ev = { - PUGL_SCROLL, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event deltaX], - [event deltaY] - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (void) keyDown:(NSEvent*)event -{ - if (puglview->ignoreKeyRepeat && [event isARepeat]) { - return; - } - - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglKey spec = keySymToSpecial(event); - const NSString* chars = [event charactersIgnoringModifiers]; - const char* str = [[chars lowercaseString] UTF8String]; - const uint32_t code = ( - spec ? spec : puglDecodeUTF8((const uint8_t*)str)); - - const PuglEventKey ev = { - PUGL_KEY_PRESS, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - (code != 0xFFFD) ? code : 0 - }; - - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - - if (!spec) { - [self interpretKeyEvents:@[event]]; - } -} - -- (void) keyUp:(NSEvent*)event -{ - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - const PuglKey spec = keySymToSpecial(event); - const NSString* chars = [event charactersIgnoringModifiers]; - const char* str = [[chars lowercaseString] UTF8String]; - const uint32_t code = - (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); - - const PuglEventKey ev = { - PUGL_KEY_RELEASE, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - (code != 0xFFFD) ? code : 0 - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); -} - -- (BOOL) hasMarkedText -{ - return [markedText length] > 0; -} - -- (NSRange) markedRange -{ - return (([markedText length] > 0) - ? NSMakeRange(0, [markedText length] - 1) - : NSMakeRange(NSNotFound, 0)); -} - -- (NSRange) selectedRange -{ - return NSMakeRange(NSNotFound, 0); -} - -- (void)setMarkedText:(id)string - selectedRange:(NSRange)selected - replacementRange:(NSRange)replacement -{ - (void)selected; - (void)replacement; - [markedText release]; - markedText = ( - [string isKindOfClass:[NSAttributedString class]] - ? [[NSMutableAttributedString alloc] initWithAttributedString:string] - : [[NSMutableAttributedString alloc] initWithString:string]); -} - -- (void) unmarkText -{ - [[markedText mutableString] setString:@""]; -} - -- (NSArray*) validAttributesForMarkedText -{ - return @[]; -} - -- (NSAttributedString*) - attributedSubstringForProposedRange:(NSRange)range - actualRange:(NSRangePointer)actual -{ - (void)range; - (void)actual; - return nil; -} - -- (NSUInteger) characterIndexForPoint:(NSPoint)point -{ - (void)point; - return 0; -} - -- (NSRect) firstRectForCharacterRange:(NSRange)range - actualRange:(NSRangePointer)actual -{ - (void)range; - (void)actual; - - const NSRect frame = [(id)puglview bounds]; - return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); -} - -- (void) insertText:(id)string - replacementRange:(NSRange)replacement -{ - (void)replacement; - - NSEvent* const event = [NSApp currentEvent]; - NSString* const characters = - ([string isKindOfClass:[NSAttributedString class]] - ? [string string] - : (NSString*)string); - - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - for (size_t i = 0; i < [characters length]; ++i) { - const uint32_t code = [characters characterAtIndex:i]; - char utf8[8] = {0}; - NSUInteger len = 0; - - [characters getBytes:utf8 - maxLength:sizeof(utf8) - usedLength:&len - encoding:NSUTF8StringEncoding - options:0 - range:NSMakeRange(i, i + 1) - remainingRange:nil]; - - PuglEventText ev = { PUGL_TEXT, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - getModifiers(event), - [event keyCode], - code, - { 0, 0, 0, 0, 0, 0, 0, 0 } }; - - memcpy(ev.string, utf8, len); - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - } -} - -- (void) flagsChanged:(NSEvent*)event -{ - const uint32_t mods = getModifiers(event); - PuglEventType type = PUGL_NOTHING; - PuglKey special = 0; - - if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) { - type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_SHIFT; - } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) { - type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_CTRL; - } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) { - type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_ALT; - } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) { - type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - special = PUGL_KEY_SUPER; - } - - if (special != 0) { - const NSPoint wloc = [self eventLocation:event]; - const NSPoint rloc = [NSEvent mouseLocation]; - PuglEventKey ev = { - type, - 0, - [event timestamp], - wloc.x, - wloc.y, - rloc.x, - [[NSScreen mainScreen] frame].size.height - rloc.y, - mods, - [event keyCode], - special - }; - puglDispatchEvent(puglview, (const PuglEvent*)&ev); - } - - puglview->impl->mods = mods; -} - -- (BOOL) preservesContentInLiveResize -{ - return NO; -} - -- (void) viewWillStartLiveResize -{ - timer = [NSTimer timerWithTimeInterval:(1 / 60.0) - target:self - selector:@selector(resizeTick) - userInfo:nil - repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:timer - forMode:NSRunLoopCommonModes]; - - [super viewWillStartLiveResize]; -} - -- (void) resizeTick -{ - puglPostRedisplay(puglview); -} - -- (void) urgentTick -{ - [NSApp requestUserAttention:NSInformationalRequest]; -} - -- (void) viewDidEndLiveResize -{ - [super viewDidEndLiveResize]; - [timer invalidate]; - timer = NULL; -} - -@end - -@interface PuglWindowDelegate : NSObject -{ - PuglWindow* window; -} - -- (instancetype) initWithPuglWindow:(PuglWindow*)window; - -@end - -@implementation PuglWindowDelegate - -- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow -{ - if ((self = [super init])) { - window = puglWindow; - } - - return self; -} - -- (BOOL) windowShouldClose:(id)sender -{ - (void)sender; - - PuglEvent ev = { 0 }; - ev.type = PUGL_CLOSE; - puglDispatchEvent(window->puglview, &ev); - return YES; -} - -- (void) windowDidBecomeKey:(NSNotification*)notification -{ - (void)notification; - - PuglOpenGLView* glview = window->puglview->impl->glview; - if (window->puglview->impl->glview->urgentTimer) { - [glview->urgentTimer invalidate]; - glview->urgentTimer = NULL; - } - - PuglEvent ev = { 0 }; - ev.type = PUGL_FOCUS_IN; - ev.focus.grab = false; - puglDispatchEvent(window->puglview, &ev); -} - -- (void) windowDidResignKey:(NSNotification*)notification -{ - (void)notification; - - PuglEvent ev = { 0 }; - ev.type = PUGL_FOCUS_OUT; - ev.focus.grab = false; - puglDispatchEvent(window->puglview, &ev); -} - -@end - -PuglInternals* -puglInitInternals(void) -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -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 alloc]; - impl->glview->trackingArea = nil; - impl->glview->markedText = [[NSMutableAttributedString alloc] init]; - impl->glview->puglview = view; - - [impl->glview initWithFrame:NSMakeRect(0, 0, 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->hints.resizable) { - [impl->glview setAutoresizingMask:NSViewNotSizable]; - } - - if (view->parent) { - NSView* pview = (NSView*)view->parent; - [pview addSubview:impl->glview]; - [impl->glview setHidden:NO]; - [[impl->glview window] makeFirstResponder:impl->glview]; - } 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 | - NSMiniaturizableWindowMask ); - if (view->hints.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; - - ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc] - initWithPuglWindow:window]; - - if (view->min_aspect_x && view->min_aspect_y) { - [window setContentAspectRatio:NSMakeSize(view->min_aspect_x, - view->min_aspect_y)]; - } - - [window setContentView:impl->glview]; - [impl->app activateIgnoringOtherApps:YES]; - [window makeFirstResponder:impl->glview]; - [window makeKeyAndOrderFront:window]; - } - - [impl->glview updateTrackingAreas]; - - 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) -{ - view->backend->destroy(view); - 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) -{ - [view->impl->window makeKeyWindow]; -} - -void -puglRequestAttention(PuglView* view) -{ - if (![view->impl->window isKeyWindow]) { - [NSApp requestUserAttention:NSInformationalRequest]; - view->impl->glview->urgentTimer = - [NSTimer scheduledTimerWithTimeInterval:2.0 - target:view->impl->glview - selector:@selector(urgentTick) - userInfo:nil - repeats:YES]; - } -} - -PuglStatus -puglWaitForEvent(PuglView* view) -{ - /* Note that dequeue:NO is broken (it blocks forever even when events are - pending), so we work around this by dequeueing the event here and - storing it in view->impl->nextEvent for later processing. */ - if (!view->impl->nextEvent) { - view->impl->nextEvent = - [view->impl->window nextEventMatchingMask:NSAnyEventMask]; - } - - return PUGL_SUCCESS; -} - -PuglStatus -puglProcessEvents(PuglView* view) -{ - if (view->impl->nextEvent) { - // Process event that was dequeued earier by puglWaitForEvent - [view->impl->app sendEvent: view->impl->nextEvent]; - view->impl->nextEvent = NULL; - } - - // Process all pending events - for (NSEvent* ev = NULL; - (ev = [view->impl->window nextEventMatchingMask:NSAnyEventMask - untilDate:nil - inMode:NSDefaultRunLoopMode - dequeue:YES]);) { - [view->impl->app sendEvent: ev]; - } - - return PUGL_SUCCESS; -} - -PuglGlFunc -puglGetProcAddress(const char *name) -{ - CFBundleRef framework = - CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - - CFStringRef symbol = CFStringCreateWithCString( - kCFAllocatorDefault, name, kCFStringEncodingASCII); - - PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName( - framework, symbol); - - CFRelease(symbol); - - return func; -} - -double -puglGetTime(PuglView* view) -{ - return (mach_absolute_time() / 1e9) - view->start_time; -} - -void -puglPostRedisplay(PuglView* view) -{ - [view->impl->glview setNeedsDisplay: YES]; -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeWindow)view->impl->glview; -} - -// Backend - -static int -puglMacConfigure(PuglView* PUGL_UNUSED(view)) -{ - return 0; -} - -static int -puglMacCreate(PuglView* PUGL_UNUSED(view)) -{ - return 0; -} - -static int -puglMacGlDestroy(PuglView* PUGL_UNUSED(view)) -{ - return 0; -} - -static int -puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing)) -{ - [[view->impl->glview openGLContext] makeCurrentContext]; - return 0; -} - -static int -puglMacGlLeave(PuglView* view, bool drawing) -{ - if (drawing) { - [[view->impl->glview openGLContext] flushBuffer]; - } - - [NSOpenGLContext clearCurrentContext]; - - return 0; -} - -static int -puglMacGlResize(PuglView* PUGL_UNUSED(view), - int PUGL_UNUSED(width), - int PUGL_UNUSED(height)) -{ - return 0; -} - -static void* -puglMacGlGetContext(PuglView* PUGL_UNUSED(view)) -{ - return NULL; -} - -const PuglBackend* puglGlBackend(void) -{ - static const PuglBackend backend = { - puglMacConfigure, - puglMacCreate, - puglMacGlDestroy, - puglMacGlEnter, - puglMacGlLeave, - puglMacGlResize, - puglMacGlGetContext - }; - - return &backend; -} - -#ifdef PUGL_HAVE_CAIRO - -static int -puglMacCairoDestroy(PuglView* view) -{ - pugl_cairo_gl_free(&view->impl->cairo_gl); - return 0; -} - -static int -puglMacCairoEnter(PuglView* view, bool PUGL_UNUSED(drawing)) -{ - [[view->impl->glview openGLContext] makeCurrentContext]; - - return 0; -} - -static int -puglMacCairoLeave(PuglView* view, bool drawing) -{ - if (drawing) { - pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height); - [[view->impl->glview openGLContext] flushBuffer]; - } - - [NSOpenGLContext clearCurrentContext]; - - return 0; -} - - -static int -puglMacCairoResize(PuglView* view, int width, int height) -{ - PuglInternals* impl = view->impl; - - cairo_surface_destroy(impl->surface); - cairo_destroy(impl->cr); - impl->surface = pugl_cairo_gl_create(&impl->cairo_gl, width, height, 4); - impl->cr = cairo_create(impl->surface); - pugl_cairo_gl_configure(&impl->cairo_gl, width, height); - - return 0; -} - -static void* -puglMacCairoGetContext(PuglView* view) -{ - return view->impl->cr; -} - -const PuglBackend* puglCairoBackend(void) -{ - static const PuglBackend backend = { - puglMacConfigure, - puglMacCreate, - puglMacCairoDestroy, - puglMacCairoEnter, - puglMacCairoLeave, - puglMacCairoResize, - puglMacCairoGetContext - }; - - return &backend; -} - -#endif diff --git a/pugl/pugl_win.c b/pugl/pugl_win.c deleted file mode 100644 index 2dd3fcc..0000000 --- a/pugl/pugl_win.c +++ /dev/null @@ -1,677 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_win.c Windows implementation. -*/ - -#include "pugl/pugl_internal.h" -#include "pugl/pugl_win.h" - -#include -#include - -#include -#include -#include -#include - -#ifndef WM_MOUSEWHEEL -# define WM_MOUSEWHEEL 0x020A -#endif -#ifndef WM_MOUSEHWHEEL -# define WM_MOUSEHWHEEL 0x020E -#endif -#ifndef WHEEL_DELTA -# define WHEEL_DELTA 120 -#endif -#ifndef GWLP_USERDATA -# define GWLP_USERDATA (-21) -#endif - -#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) -#define PUGL_RESIZE_TIMER_ID 9461 -#define PUGL_URGENT_TIMER_ID 9462 - -typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); - -static const TCHAR* DEFAULT_CLASSNAME = "Pugl"; - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - -PuglInternals* -puglInitInternals(void) -{ - PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); - - HMODULE user32 = LoadLibrary("user32.dll"); - if (user32) { - PFN_SetProcessDPIAware SetProcessDPIAware = - (PFN_SetProcessDPIAware)GetProcAddress( - user32, "SetProcessDPIAware"); - if (SetProcessDPIAware) { - SetProcessDPIAware(); - } - } - - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - impl->timerFrequency = (double)frequency.QuadPart; - - return impl; -} - -int -puglCreateWindow(PuglView* view, const char* title) -{ - PuglInternals* impl = view->impl; - - const char* className = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME; - - title = title ? title : "Window"; - - // Get refresh rate for resize draw timer - DEVMODEA devMode = {0}; - EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode); - view->impl->refreshRate = devMode.dmDisplayFrequency; - - // Register window class - 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 = className; - if (!RegisterClassEx(&wc)) { - return 1; - } - - if (!view->backend || !view->backend->configure) { - return 1; - } - - int st = view->backend->configure(view); - if (st || !impl->surface) { - return 2; - } else if ((st = view->backend->create(view))) { - return 3; - } - - SetWindowText(impl->hwnd, title); - SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); - - return 0; -} - -void -puglShowWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - ShowWindow(impl->hwnd, SW_SHOWNORMAL); - SetFocus(impl->hwnd); - view->visible = true; -} - -void -puglHideWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - ShowWindow(impl->hwnd, SW_HIDE); - view->visible = false; -} - -void -puglDestroy(PuglView* view) -{ - if (view) { - view->backend->destroy(view); - ReleaseDC(view->impl->hwnd, view->impl->hdc); - DestroyWindow(view->impl->hwnd); - UnregisterClass(view->windowClass ? view->windowClass : DEFAULT_CLASSNAME, NULL); - free(view->windowClass); - free(view->impl); - free(view); - } -} - -static PuglKey -keySymToSpecial(WPARAM 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_BACK: return PUGL_KEY_BACKSPACE; - case VK_DELETE: return PUGL_KEY_DELETE; - case VK_LEFT: return PUGL_KEY_LEFT; - case VK_UP: return PUGL_KEY_UP; - case VK_RIGHT: return PUGL_KEY_RIGHT; - case VK_DOWN: return PUGL_KEY_DOWN; - case VK_PRIOR: return PUGL_KEY_PAGE_UP; - case VK_NEXT: return PUGL_KEY_PAGE_DOWN; - case VK_HOME: return PUGL_KEY_HOME; - case VK_END: return PUGL_KEY_END; - case VK_INSERT: return PUGL_KEY_INSERT; - case VK_SHIFT: - case VK_LSHIFT: return PUGL_KEY_SHIFT_L; - case VK_RSHIFT: return PUGL_KEY_SHIFT_R; - case VK_CONTROL: - case VK_LCONTROL: return PUGL_KEY_CTRL_L; - case VK_RCONTROL: return PUGL_KEY_CTRL_R; - case VK_MENU: - case VK_LMENU: return PUGL_KEY_ALT_L; - case VK_RMENU: return PUGL_KEY_ALT_R; - case VK_LWIN: return PUGL_KEY_SUPER_L; - case VK_RWIN: return PUGL_KEY_SUPER_R; - case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK; - case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK; - case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK; - case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN; - case VK_PAUSE: return PUGL_KEY_PAUSE; - } - return (PuglKey)0; -} - -static uint32_t -getModifiers(void) -{ - return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) | - ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) | - ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) | - ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) | - ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u)); -} - -static void -initMouseEvent(PuglEvent* event, - PuglView* view, - int button, - bool press, - LPARAM lParam) -{ - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ClientToScreen(view->impl->hwnd, &pt); - - if (press) { - SetCapture(view->impl->hwnd); - } else { - ReleaseCapture(); - } - - event->button.time = GetMessageTime() / 1e3; - event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; - event->button.x = GET_X_LPARAM(lParam); - event->button.y = GET_Y_LPARAM(lParam); - event->button.x_root = pt.x; - event->button.y_root = pt.y; - event->button.state = getModifiers(); - event->button.button = (uint32_t)button; -} - -static void -initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam) -{ - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(view->impl->hwnd, &pt); - - event->scroll.time = GetMessageTime() / 1e3; - event->scroll.type = PUGL_SCROLL; - event->scroll.x = pt.x; - event->scroll.y = pt.y; - event->scroll.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; -} - -/** Return the code point for buf, or the replacement character on error. */ -static uint32_t -puglDecodeUTF16(const wchar_t* buf, const int len) -{ - const uint32_t c0 = buf[0]; - const uint32_t c1 = buf[0]; - if (c0 >= 0xD800 && c0 < 0xDC00) { - if (len < 2) { - return 0xFFFD; // Surrogate, but length is only 1 - } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) { - return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000; - } - - return 0xFFFD; // Unpaired surrogates - } - - return c0; -} - -static void -initKeyEvent(PuglEventKey* event, - PuglView* view, - bool press, - WPARAM wParam, - LPARAM lParam) -{ - POINT rpos = { 0, 0 }; - GetCursorPos(&rpos); - - POINT cpos = { rpos.x, rpos.y }; - ScreenToClient(view->impl->hwnd, &rpos); - - const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16); - const unsigned vkey = ((wParam == VK_SHIFT) - ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX) - : (unsigned)wParam); - - const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); - const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); - const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1; - const bool ext = lParam & 0x01000000; - - event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; - event->time = GetMessageTime() / 1e3; - event->state = getModifiers(); - event->x_root = rpos.x; - event->y_root = rpos.y; - event->x = cpos.x; - event->y = cpos.y; - event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16); - event->key = 0; - - const PuglKey special = keySymToSpecial(vkey); - if (special) { - if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) { - event->key = special + 1u; // Right hand key - } else { - event->key = special; - } - } else if (!dead) { - // Translate unshifted key - BYTE keyboardState[256] = {0}; - wchar_t buf[5] = {0}; - const int ulen = ToUnicode(vkey, vcode, keyboardState, buf, 4, 1<<2); - event->key = puglDecodeUTF16(buf, ulen); - } -} - -static void -initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam) -{ - const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF }; - - initKeyEvent(&event->key, view, true, wParam, lParam); - event->type = PUGL_TEXT; - event->text.character = puglDecodeUTF16(utf16, 2); - - if (!WideCharToMultiByte( - CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) { - memset(event->text.string, 0, 8); - } -} - -static bool -ignoreKeyEvent(PuglView* view, LPARAM lParam) -{ - return view->ignoreKeyRepeat && (lParam & (1 << 30)); -} - -static RECT -handleConfigure(PuglView* view, PuglEvent* event) -{ - RECT rect; - GetClientRect(view->impl->hwnd, &rect); - view->width = rect.right - rect.left; - view->height = rect.bottom - rect.top; - - event->configure.type = PUGL_CONFIGURE; - event->configure.x = rect.left; - event->configure.y = rect.top; - event->configure.width = view->width; - event->configure.height = view->height; - - view->backend->resize(view, view->width, view->height); - return rect; -} - -static void -handleCrossing(PuglView* view, const PuglEventType type, POINT pos) -{ - POINT root_pos = pos; - ClientToScreen(view->impl->hwnd, &root_pos); - - const PuglEventCrossing ev = { - type, - 0, - GetMessageTime() / 1e3, - (double)pos.x, - (double)pos.y, - (double)root_pos.x, - (double)root_pos.y, - getModifiers(), - PUGL_CROSSING_NORMAL - }; - puglDispatchEvent(view, (const PuglEvent*)&ev); -} - -static void -stopFlashing(PuglView* view) -{ - if (view->impl->flashing) { - KillTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID); - FlashWindow(view->impl->hwnd, FALSE); - } -} - -static void -constrainAspect(const PuglView* const view, - RECT* const size, - const WPARAM wParam) -{ - const float minAspect = view->min_aspect_x / (float)view->min_aspect_y; - const float maxAspect = view->max_aspect_x / (float)view->max_aspect_y; - const int w = size->right - size->left; - const int h = size->bottom - size->top; - const float a = w / (float)h; - - switch (wParam) { - case WMSZ_TOP: - size->top = (a < minAspect ? (LONG)(size->bottom - w * minAspect) : - a > maxAspect ? (LONG)(size->bottom - w * maxAspect) : - size->top); - break; - case WMSZ_TOPRIGHT: - case WMSZ_RIGHT: - case WMSZ_BOTTOMRIGHT: - size->right = (a < minAspect ? (LONG)(size->left + h * minAspect) : - a > maxAspect ? (LONG)(size->left + h * maxAspect) : - size->right); - break; - case WMSZ_BOTTOM: - size->bottom = (a < minAspect ? (LONG)(size->top + w * minAspect) : - a > maxAspect ? (LONG)(size->top + w * maxAspect) : - size->bottom); - break; - case WMSZ_BOTTOMLEFT: - case WMSZ_LEFT: - case WMSZ_TOPLEFT: - size->left = (a < minAspect ? (LONG)(size->right - h * minAspect) : - a > maxAspect ? (LONG)(size->right - h * maxAspect) : - size->left); - break; - } -} - -static LRESULT -handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) -{ - PuglEvent event; - void* dummy_ptr = NULL; - RECT rect; - MINMAXINFO* mmi; - POINT pt; - - memset(&event, 0, sizeof(event)); - - event.any.type = PUGL_NOTHING; - if (InSendMessageEx(dummy_ptr)) { - event.any.flags |= PUGL_IS_SEND_EVENT; - } - - switch (message) { - case WM_SHOWWINDOW: - rect = handleConfigure(view, &event); - puglPostRedisplay(view); - break; - case WM_SIZE: - rect = handleConfigure(view, &event); - RedrawWindow(view->impl->hwnd, NULL, NULL, - RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT| - RDW_UPDATENOW); - break; - case WM_SIZING: - if (view->min_aspect_x) { - constrainAspect(view, (RECT*)lParam, wParam); - return TRUE; - } - break; - case WM_ENTERSIZEMOVE: - view->impl->resizing = true; - SetTimer(view->impl->hwnd, - PUGL_RESIZE_TIMER_ID, - 1000 / view->impl->refreshRate, - NULL); - break; - case WM_TIMER: - if (wParam == PUGL_RESIZE_TIMER_ID) { - RedrawWindow(view->impl->hwnd, NULL, NULL, - RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); - } else if (wParam == PUGL_URGENT_TIMER_ID) { - FlashWindow(view->impl->hwnd, TRUE); - } - break; - case WM_EXITSIZEMOVE: - KillTimer(view->impl->hwnd, PUGL_RESIZE_TIMER_ID); - view->impl->resizing = false; - puglPostRedisplay(view); - break; - case WM_GETMINMAXINFO: - mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = view->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_ERASEBKGND: - return true; - case WM_MOUSEMOVE: - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - - if (!view->impl->mouseTracked) { - TRACKMOUSEEVENT tme = {0}; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = view->impl->hwnd; - TrackMouseEvent(&tme); - - stopFlashing(view); - handleCrossing(view, PUGL_ENTER_NOTIFY, pt); - view->impl->mouseTracked = true; - } - - ClientToScreen(view->impl->hwnd, &pt); - event.motion.type = PUGL_MOTION_NOTIFY; - event.motion.time = GetMessageTime() / 1e3; - 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_MOUSELEAVE: - GetCursorPos(&pt); - ScreenToClient(view->impl->hwnd, &pt); - handleCrossing(view, PUGL_LEAVE_NOTIFY, pt); - view->impl->mouseTracked = false; - break; - case WM_LBUTTONDOWN: - initMouseEvent(&event, view, 1, true, lParam); - break; - case WM_MBUTTONDOWN: - initMouseEvent(&event, view, 2, true, lParam); - break; - case WM_RBUTTONDOWN: - initMouseEvent(&event, view, 3, true, lParam); - break; - case WM_LBUTTONUP: - initMouseEvent(&event, view, 1, false, lParam); - break; - case WM_MBUTTONUP: - initMouseEvent(&event, view, 2, false, lParam); - break; - case WM_RBUTTONUP: - initMouseEvent(&event, view, 3, false, lParam); - break; - case WM_MOUSEWHEEL: - initScrollEvent(&event, view, lParam); - event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; - break; - case WM_MOUSEHWHEEL: - initScrollEvent(&event, view, lParam); - event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; - break; - case WM_KEYDOWN: - if (!ignoreKeyEvent(view, lParam)) { - initKeyEvent(&event.key, view, true, wParam, lParam); - } - break; - case WM_KEYUP: - initKeyEvent(&event.key, view, false, wParam, lParam); - break; - case WM_CHAR: - initCharEvent(&event, view, wParam, lParam); - break; - case WM_SETFOCUS: - stopFlashing(view); - event.type = PUGL_FOCUS_IN; - break; - case WM_KILLFOCUS: - event.type = PUGL_FOCUS_OUT; - break; - case WM_SYSKEYDOWN: - initKeyEvent(&event.key, view, true, wParam, lParam); - break; - case WM_SYSKEYUP: - initKeyEvent(&event.key, view, false, wParam, lParam); - break; - case WM_SYSCHAR: - return TRUE; - case 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) -{ - SetFocus(view->impl->hwnd); -} - -void -puglRequestAttention(PuglView* view) -{ - if (!view->impl->mouseTracked || GetFocus() != view->impl->hwnd) { - FlashWindow(view->impl->hwnd, TRUE); - SetTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID, 500, NULL); - view->impl->flashing = true; - } -} - -PuglStatus -puglWaitForEvent(PuglView* PUGL_UNUSED(view)) -{ - WaitMessage(); - return PUGL_SUCCESS; -} - -PuglStatus -puglProcessEvents(PuglView* view) -{ - MSG msg; - while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return PUGL_SUCCESS; -} - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - - switch (message) { - case WM_CREATE: - PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); - return 0; - case WM_CLOSE: - PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); - return 0; - case WM_DESTROY: - return 0; - default: - if (view && hwnd == view->impl->hwnd) { - return handleMessage(view, message, wParam, lParam); - } else { - return DefWindowProc(hwnd, message, wParam, lParam); - } - } -} - -double -puglGetTime(PuglView* view) -{ - LARGE_INTEGER count; - QueryPerformanceCounter(&count); - const double now = (double)count.QuadPart / view->impl->timerFrequency; - return now - view->start_time; -} - -void -puglPostRedisplay(PuglView* view) -{ - RedrawWindow(view->impl->hwnd, NULL, NULL, - RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); - UpdateWindow(view->impl->hwnd); -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeWindow)view->impl->hwnd; -} diff --git a/pugl/pugl_win.h b/pugl/pugl_win.h deleted file mode 100644 index c2dba2a..0000000 --- a/pugl/pugl_win.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_win.h Shared definitions for Windows implementation. -*/ - -#include "pugl/pugl_internal_types.h" - -#include - -#include - -typedef PIXELFORMATDESCRIPTOR PuglWinPFD; - -struct PuglInternalsImpl { - PuglWinPFD pfd; - int pfId; - HWND hwnd; - HDC hdc; - PuglSurface* surface; - DWORD refreshRate; - double timerFrequency; - bool flashing; - bool resizing; - bool mouseTracked; -}; - -static inline PuglWinPFD -puglWinGetPixelFormatDescriptor(const PuglHints* const hints) -{ - const int rgbBits = hints->red_bits + hints->green_bits + hints->blue_bits; - - PuglWinPFD pfd; - ZeroMemory(&pfd, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL; - pfd.dwFlags |= hints->double_buffer ? PFD_DOUBLEBUFFER : 0; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = (BYTE)rgbBits; - pfd.cRedBits = (BYTE)hints->red_bits; - pfd.cGreenBits = (BYTE)hints->green_bits; - pfd.cBlueBits = (BYTE)hints->blue_bits; - pfd.cAlphaBits = (BYTE)hints->alpha_bits; - pfd.cDepthBits = (BYTE)hints->depth_bits; - pfd.cStencilBits = (BYTE)hints->stencil_bits; - pfd.iLayerType = PFD_MAIN_PLANE; - return pfd; -} - -static inline PuglStatus -puglWinCreateWindow(const PuglView* const view, - const char* const title, - HWND* const hwnd, - HDC* const hdc) -{ - const char* className = view->windowClass ? view->windowClass : "Pugl"; - - const unsigned winFlags = - (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | - (view->parent - ? WS_CHILD - : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | - (view->hints.resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0)))); - - const unsigned winExFlags = - WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW); - - // Calculate total window size to accommodate requested view size - RECT wr = { 0, 0, view->width, view->height }; - AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags); - - // Create window and get drawing context - if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags, - CW_USEDEFAULT, CW_USEDEFAULT, - wr.right-wr.left, wr.bottom-wr.top, - (HWND)view->parent, NULL, NULL, NULL))) { - return PUGL_ERR_CREATE_WINDOW; - } else if (!(*hdc = GetDC(*hwnd))) { - DestroyWindow(*hwnd); - *hwnd = NULL; - return PUGL_ERR_CREATE_WINDOW; - } - - return PUGL_SUCCESS; -} diff --git a/pugl/pugl_win_cairo.c b/pugl/pugl_win_cairo.c deleted file mode 100644 index 0d2fb5f..0000000 --- a/pugl/pugl_win_cairo.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_win_cairo.c Cairo graphics backend for Windows. -*/ - -#include "pugl/pugl_cairo_backend.h" -#include "pugl/pugl_internal_types.h" -#include "pugl/pugl_win.h" - -#include -#include - -#include - -typedef struct { - cairo_surface_t* surface; - cairo_t* cr; - HDC drawDc; - HBITMAP drawBitmap; -} PuglWinCairoSurface; - -static int -puglWinCairoCreateDrawContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - surface->drawDc = CreateCompatibleDC(impl->hdc); - surface->drawBitmap = CreateCompatibleBitmap( - impl->hdc, view->width, view->height); - - DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); - - cairo_status_t st = CAIRO_STATUS_SUCCESS; - if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || - (st = cairo_surface_status(surface->surface)) || - !(surface->cr = cairo_create(surface->surface)) || - (st = cairo_status(surface->cr))) { - return PUGL_ERR_CREATE_CONTEXT; - } - - cairo_save(surface->cr); - return 0; -} - -static int -puglWinCairoDestroyDrawContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - DeleteDC(surface->drawDc); - DeleteObject(surface->drawBitmap); - cairo_destroy(surface->cr); - cairo_surface_destroy(surface->surface); - - surface->surface = NULL; - surface->cr = NULL; - surface->drawDc = NULL; - surface->drawBitmap = NULL; - - return 0; -} - -static int -puglWinCairoConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglStatus st = PUGL_SUCCESS; - - if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { - return st; - } - - impl->pfd = puglWinGetPixelFormatDescriptor(&view->hints); - impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd); - - if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { - ReleaseDC(impl->hwnd, impl->hdc); - DestroyWindow(impl->hwnd); - impl->hwnd = NULL; - impl->hdc = NULL; - return PUGL_ERR_SET_FORMAT; - } - - impl->surface = (PuglWinCairoSurface*)calloc( - 1, sizeof(PuglWinCairoSurface)); - - return 0; -} - -static int -puglWinCairoCreate(PuglView* view) -{ - return puglWinCairoCreateDrawContext(view); -} - -static int -puglWinCairoDestroy(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - - puglWinCairoDestroyDrawContext(view); - free(surface); - impl->surface = NULL; - - return 0; -} - -static int -puglWinCairoEnter(PuglView* view, bool drawing) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - if (!drawing) { - return 0; - } - - PAINTSTRUCT ps; - BeginPaint(view->impl->hwnd, &ps); - cairo_save(surface->cr); - - return 0; -} - -static int -puglWinCairoLeave(PuglView* view, bool drawing) -{ - PuglInternals* const impl = view->impl; - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; - if (!drawing) { - return 0; - } - - cairo_restore(surface->cr); - cairo_surface_flush(surface->surface); - BitBlt(impl->hdc, 0, 0, view->width, view->height, - surface->drawDc, 0, 0, SRCCOPY); - - PAINTSTRUCT ps; - EndPaint(view->impl->hwnd, &ps); - SwapBuffers(view->impl->hdc); - - return 0; -} - -static int -puglWinCairoResize(PuglView* view, - int width, - int height) -{ - view->width = width; - view->height = height; - int st = 0; - if ((st = puglWinCairoDestroyDrawContext(view)) || - (st = puglWinCairoCreateDrawContext(view))) { - fprintf(stderr, "ERR\n"); - return st; - } - - return 0; -} - -static void* -puglWinCairoGetContext(PuglView* view) -{ - return ((PuglWinCairoSurface*)view->impl->surface)->cr; -} - -const PuglBackend* -puglCairoBackend() -{ - static const PuglBackend backend = { - puglWinCairoConfigure, - puglWinCairoCreate, - puglWinCairoDestroy, - puglWinCairoEnter, - puglWinCairoLeave, - puglWinCairoResize, - puglWinCairoGetContext - }; - - return &backend; -} diff --git a/pugl/pugl_win_gl.c b/pugl/pugl_win_gl.c deleted file mode 100644 index 17528d5..0000000 --- a/pugl/pugl_win_gl.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_win_gl.c OpenGL graphics backend for Windows. -*/ - -#include "pugl/pugl_gl_backend.h" -#include "pugl/pugl_internal_types.h" -#include "pugl/pugl_win.h" - -#include - -#include - -#include -#include - -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_COLOR_BITS_ARB 0x2014 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_FULL_ACCELERATION_ARB 0x2027 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_SAMPLE_BUFFERS_ARB 0x2041 -#define WGL_SAMPLES_ARB 0x2042 - -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 - -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 - -typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*); -typedef BOOL (*WglSwapInterval)(int); -typedef BOOL (*WglChoosePixelFormat)( - HDC, const int*, const FLOAT*, UINT, int*, UINT*); - -typedef struct { - WglChoosePixelFormat wglChoosePixelFormat; - WglCreateContextAttribs wglCreateContextAttribs; - WglSwapInterval wglSwapInterval; -} PuglWinGlProcs; - -typedef struct { - PuglWinGlProcs procs; - HGLRC hglrc; -} PuglWinGlSurface; - -// Struct to manage the fake window used during configuration -typedef struct { - HWND hwnd; - HDC hdc; -} PuglFakeWindow; - -static int -puglWinError(PuglFakeWindow* fakeWin, const int status) -{ - if (fakeWin->hwnd) { - ReleaseDC(fakeWin->hwnd, fakeWin->hdc); - DestroyWindow(fakeWin->hwnd); - } - - return status; -} - -static PuglWinGlProcs puglWinGlGetProcs(void) -{ - const PuglWinGlProcs procs = { - (WglChoosePixelFormat)( - wglGetProcAddress("wglChoosePixelFormatARB")), - (WglCreateContextAttribs)( - wglGetProcAddress("wglCreateContextAttribsARB")), - (WglSwapInterval)( - wglGetProcAddress("wglSwapIntervalEXT")) - }; - - return procs; -} - -static int -puglWinGlConfigure(PuglView* view) -{ - PuglInternals* impl = view->impl; - - const int pixelAttrs[] = { - WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, - WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - WGL_SUPPORT_OPENGL_ARB, GL_TRUE, - WGL_DOUBLE_BUFFER_ARB, view->hints.double_buffer, - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_SAMPLE_BUFFERS_ARB, view->hints.samples ? 1 : 0, - WGL_SAMPLES_ARB, view->hints.samples, - WGL_RED_BITS_ARB, view->hints.red_bits, - WGL_GREEN_BITS_ARB, view->hints.green_bits, - WGL_BLUE_BITS_ARB, view->hints.blue_bits, - WGL_ALPHA_BITS_ARB, view->hints.alpha_bits, - WGL_DEPTH_BITS_ARB, view->hints.depth_bits, - WGL_STENCIL_BITS_ARB, view->hints.stencil_bits, - 0, - }; - - PuglWinGlSurface* const surface = - (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface)); - impl->surface = surface; - - // Create fake window for getting at GL context - PuglStatus st = PUGL_SUCCESS; - PuglFakeWindow fakeWin = { 0, 0 }; - if ((st = puglWinCreateWindow(view, "Pugl Configuration", - &fakeWin.hwnd, &fakeWin.hdc))) { - return puglWinError(&fakeWin, st); - } - - // Set pixel format for fake window - const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(&view->hints); - const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd); - if (!fakePfId) { - return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); - } else if (!SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) { - return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); - } - - // Create fake GL context to get at the functions we need - HGLRC fakeRc = wglCreateContext(fakeWin.hdc); - if (!fakeRc) { - return puglWinError(&fakeWin, PUGL_ERR_CREATE_CONTEXT); - } - - // Enter fake context and get extension functions - wglMakeCurrent(fakeWin.hdc, fakeRc); - surface->procs = puglWinGlGetProcs(); - - if (surface->procs.wglChoosePixelFormat) { - // Choose pixel format based on attributes - UINT numFormats = 0; - if (!surface->procs.wglChoosePixelFormat( - fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { - return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT); - } - - DescribePixelFormat( - impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd); - } else { - // Modern extensions not available, use basic pixel format - impl->pfd = fakePfd; - impl->pfId = fakePfId; - } - - // Dispose of fake window and context - wglMakeCurrent(NULL, NULL); - wglDeleteContext(fakeRc); - ReleaseDC(fakeWin.hwnd, fakeWin.hdc); - DestroyWindow(fakeWin.hwnd); - - return 0; -} - -static int -puglWinGlCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface; - PuglStatus st = PUGL_SUCCESS; - - const int contextAttribs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major, - WGL_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor, - WGL_CONTEXT_PROFILE_MASK_ARB, - (view->hints.use_compat_profile - ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB - : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), - 0 - }; - - // Create real window with desired pixel format - if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { - return st; - } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { - ReleaseDC(impl->hwnd, impl->hdc); - DestroyWindow(impl->hwnd); - impl->hwnd = NULL; - impl->hdc = NULL; - return PUGL_ERR_SET_FORMAT; - } - - // Create GL context - if (surface->procs.wglCreateContextAttribs && - !(surface->hglrc = surface->procs.wglCreateContextAttribs( - impl->hdc, 0, contextAttribs))) { - return PUGL_ERR_CREATE_CONTEXT; - } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) { - return PUGL_ERR_CREATE_CONTEXT; - } - - // Enter context and set swap interval - wglMakeCurrent(impl->hdc, surface->hglrc); - if (surface->procs.wglSwapInterval) { - surface->procs.wglSwapInterval(1); - } - - return 0; -} - -static int -puglWinGlDestroy(PuglView* view) -{ - PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; - if (surface) { - wglMakeCurrent(NULL, NULL); - wglDeleteContext(surface->hglrc); - free(surface); - view->impl->surface = NULL; - } - - return 0; -} - -static int -puglWinGlEnter(PuglView* view, bool drawing) -{ - PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; - - wglMakeCurrent(view->impl->hdc, surface->hglrc); - - if (drawing) { - PAINTSTRUCT ps; - BeginPaint(view->impl->hwnd, &ps); - } - - return 0; -} - -static int -puglWinGlLeave(PuglView* view, bool drawing) -{ - if (drawing) { - PAINTSTRUCT ps; - EndPaint(view->impl->hwnd, &ps); - SwapBuffers(view->impl->hdc); - } - - wglMakeCurrent(NULL, NULL); - - return 0; -} - -static int -puglWinGlResize(PuglView* PUGL_UNUSED(view), - int PUGL_UNUSED(width), - int PUGL_UNUSED(height)) -{ - return 0; -} - -static void* -puglWinGlGetContext(PuglView* PUGL_UNUSED(view)) -{ - return NULL; -} - -PuglGlFunc -puglGetProcAddress(const char* name) -{ - return (PuglGlFunc)wglGetProcAddress(name); -} - -const PuglBackend* -puglGlBackend() -{ - static const PuglBackend backend = { - puglWinGlConfigure, - puglWinGlCreate, - puglWinGlDestroy, - puglWinGlEnter, - puglWinGlLeave, - puglWinGlResize, - puglWinGlGetContext - }; - - return &backend; -} diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c deleted file mode 100644 index 5fcbd9f..0000000 --- a/pugl/pugl_x11.c +++ /dev/null @@ -1,577 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - Copyright 2013 Robin Gareus - Copyright 2011-2012 Ben Loftis, Harrison Consoles - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_x11.c X11 implementation. -*/ - -#define _POSIX_C_SOURCE 199309L - -#include "pugl/pugl_internal.h" -#include "pugl/pugl_x11.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifndef MIN -# define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#ifndef MAX -# define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - -enum WmClientStateMessageAction { - WM_STATE_REMOVE, - WM_STATE_ADD, - WM_STATE_TOGGLE -}; - -static const long eventMask = - (ExposureMask | StructureNotifyMask | FocusChangeMask | - EnterWindowMask | LeaveWindowMask | PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); - -PuglInternals* -puglInitInternals(void) -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -int -puglCreateWindow(PuglView* view, const char* title) -{ - PuglInternals* const impl = view->impl; - Display* const display = XOpenDisplay(0); - - impl->display = display; - impl->screen = DefaultScreen(display); - - // Intern the various atoms we will need - impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); - impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); - impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); - impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = - XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); - - if (!view->backend || !view->backend->configure) { - return 1; - } else if (view->backend->configure(view) || !impl->vi) { - view->backend->destroy(view); - return 2; - } - - Window xParent = view->parent ? (Window)view->parent - : RootWindow(display, impl->screen); - - Colormap cmap = XCreateColormap( - display, xParent, impl->vi->visual, AllocNone); - - XSetWindowAttributes attr = {0}; - attr.colormap = cmap; - attr.event_mask = eventMask; - - const Window win = impl->win = XCreateWindow( - display, xParent, - 0, 0, view->width, view->height, 0, impl->vi->depth, InputOutput, - impl->vi->visual, CWColormap | CWEventMask, &attr); - - if (view->backend->create(view)) { - return 3; - } - - XSizeHints sizeHints = {0}; - if (!view->hints.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; - } 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(display, win, &sizeHints); - - if (title) { - XStoreName(display, win, title); - } - - if (!view->parent) { - XSetWMProtocols(display, win, &view->impl->atoms.WM_DELETE_WINDOW, 1); - } - - if (view->transient_parent) { - XSetTransientForHint(display, win, (Window)(view->transient_parent)); - } - - XSetLocaleModifiers(""); - if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) { - XSetLocaleModifiers("@im="); - if (!(impl->xim = XOpenIM(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, win, - XNFocusWindow, win, - NULL))) { - fprintf(stderr, "warning: XCreateIC failed\n"); - } - - 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) { - if (view->impl->xic) { - XDestroyIC(view->impl->xic); - } - if (view->impl->xim) { - XCloseIM(view->impl->xim); - } - view->backend->destroy(view); - XDestroyWindow(view->impl->display, view->impl->win); - XCloseDisplay(view->impl->display); - XFree(view->impl->vi); - 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_L; - case XK_Shift_R: return PUGL_KEY_SHIFT_R; - case XK_Control_L: return PUGL_KEY_CTRL_L; - case XK_Control_R: return PUGL_KEY_CTRL_R; - case XK_Alt_L: return PUGL_KEY_ALT_L; - case XK_ISO_Level3_Shift: - case XK_Alt_R: return PUGL_KEY_ALT_R; - case XK_Super_L: return PUGL_KEY_SUPER_L; - case XK_Super_R: return PUGL_KEY_SUPER_R; - case XK_Menu: return PUGL_KEY_MENU; - case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK; - case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK; - case XK_Num_Lock: return PUGL_KEY_NUM_LOCK; - case XK_Print: return PUGL_KEY_PRINT_SCREEN; - case XK_Pause: return PUGL_KEY_PAUSE; - default: break; - } - return (PuglKey)0; -} - -static int -lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym) -{ - Status status = 0; - -#ifdef X_HAVE_UTF8_STRING - const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status); -#else - const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status); -#endif - - return status == XBufferOverflow ? 0 : n; -} - -static void -translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) -{ - const unsigned state = xevent->xkey.state; - const bool filter = XFilterEvent(xevent, None); - - event->key.keycode = xevent->xkey.keycode; - xevent->xkey.state = 0; - - // Lookup unshifted key - char ustr[8] = {0}; - KeySym sym = 0; - const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL); - const PuglKey special = keySymToSpecial(sym); - - event->key.key = ((special || ufound <= 0) - ? special - : puglDecodeUTF8((const uint8_t*)ustr)); - - if (xevent->type == KeyPress && !filter && !special) { - // Lookup shifted key for possible text event - xevent->xkey.state = state; - - char sstr[8] = {0}; - const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym); - if (sfound > 0) { - // Dispatch key event now - puglDispatchEvent(view, event); - - // "Return" a text event in its place - event->text.type = PUGL_TEXT; - event->text.character = puglDecodeUTF8((const uint8_t*)sstr); - memcpy(event->text.string, sstr, sizeof(sstr)); - } - } -} - -static uint32_t -translateModifiers(const unsigned xstate) -{ - return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0) | - ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0) | - ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0) | - ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0)); -} - -static PuglEvent -translateEvent(PuglView* view, XEvent xevent) -{ - PuglEvent event = {0}; - event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0; - - switch (xevent.type) { - case ClientMessage: - if (xevent.xclient.message_type == view->impl->atoms.WM_PROTOCOLS) { - const Atom protocol = (Atom)xevent.xclient.data.l[0]; - if (protocol == view->impl->atoms.WM_DELETE_WINDOW) { - event.type = PUGL_CLOSE; - } - } - break; - case MapNotify: { - XWindowAttributes attrs = {0}; - XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); - event.type = PUGL_CONFIGURE; - event.configure.x = attrs.x; - event.configure.y = attrs.y; - event.configure.width = attrs.width; - event.configure.height = attrs.height; - 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 / 1e3; - 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 / 1e3; - 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.0; break; - case 5: event.scroll.dy = -1.0; break; - case 6: event.scroll.dx = -1.0; break; - case 7: event.scroll.dx = 1.0; 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 / 1e3; - 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 / 1e3; - 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 / 1e3; - 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); -} - -void -puglRequestAttention(PuglView* view) -{ - PuglInternals* const impl = view->impl; - XEvent event = {0}; - event.type = ClientMessage; - event.xclient.window = impl->win; - event.xclient.format = 32; - event.xclient.message_type = impl->atoms.NET_WM_STATE; - event.xclient.data.l[0] = WM_STATE_ADD; - event.xclient.data.l[1] = impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION; - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 1; - event.xclient.data.l[4] = 0; - - const Window root = RootWindow(impl->display, impl->screen); - XSendEvent(impl->display, - root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - (XEvent*)&event); -} - -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. */ - PuglInternals* const impl = view->impl; - PuglEvent expose_event = { 0 }; - PuglEvent config_event = { 0 }; - XEvent xevent; - while (XPending(impl->display) > 0) { - XNextEvent(impl->display, &xevent); - if (xevent.type == KeyRelease) { - // Ignore key repeat if necessary - if (view->ignoreKeyRepeat && - XEventsQueued(impl->display, QueuedAfterReading)) { - XEvent next; - XPeekEvent(impl->display, &next); - if (next.type == KeyPress && - next.xkey.time == xevent.xkey.time && - next.xkey.keycode == xevent.xkey.keycode) { - XNextEvent(impl->display, &xevent); - continue; - } - } - } else if (xevent.type == FocusIn) { - XSetICFocus(impl->xic); - } else if (xevent.type == FocusOut) { - XUnsetICFocus(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 || expose_event.type) { - const bool draw = expose_event.type && expose_event.expose.count == 0; - - puglEnterContext(view, draw); - - if (config_event.type) { - view->width = (int)config_event.configure.width; - view->height = (int)config_event.configure.height; - view->backend->resize(view, view->width, view->height); - view->eventFunc(view, (const PuglEvent*)&config_event); - } - - if (draw) { - view->eventFunc(view, (const PuglEvent*)&expose_event); - } - - puglLeaveContext(view, draw); - } - - return PUGL_SUCCESS; -} - -double -puglGetTime(PuglView* view) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ((double)ts.tv_sec + ts.tv_nsec / 1000000000.0) - view->start_time; -} - -void -puglPostRedisplay(PuglView* view) -{ - XExposeEvent ev = {Expose, 0, True, - view->impl->display, view->impl->win, - 0, 0, - view->width, view->height, - 0}; - - XSendEvent(view->impl->display, view->impl->win, False, 0, (XEvent*)&ev); -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeWindow)view->impl->win; -} diff --git a/pugl/pugl_x11.h b/pugl/pugl_x11.h deleted file mode 100644 index e104a15..0000000 --- a/pugl/pugl_x11.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_x11.h Shared definitions for X11 implementation. -*/ - -#include "pugl/pugl.h" -#include "pugl/pugl_internal_types.h" - -#include -#include - -struct PuglInternalsImpl { - Display* display; - int screen; - XVisualInfo* vi; - Window win; - XIM xim; - XIC xic; - PuglSurface* surface; - - struct { - Atom WM_PROTOCOLS; - Atom WM_DELETE_WINDOW; - Atom NET_WM_STATE; - Atom NET_WM_STATE_DEMANDS_ATTENTION; - } atoms; -}; diff --git a/pugl/pugl_x11_cairo.c b/pugl/pugl_x11_cairo.c deleted file mode 100644 index bcec626..0000000 --- a/pugl/pugl_x11_cairo.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_x11_cairo.c Cairo graphics backend for X11. -*/ - -#include "pugl/pugl_cairo_backend.h" -#include "pugl/pugl_internal_types.h" -#include "pugl/pugl_x11.h" - -#include -#include -#include - -#include -#include - -typedef struct { - cairo_surface_t* surface; - cairo_t* cr; -} PuglX11CairoSurface; - -static int -puglX11CairoConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - - XVisualInfo pat; - int n; - pat.screen = impl->screen; - impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); - - return 0; -} - -static int -puglX11CairoCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = - (PuglX11CairoSurface*)calloc(1, sizeof(PuglX11CairoSurface)); - - impl->surface = surface; - - surface->surface = cairo_xlib_surface_create( - impl->display, impl->win, impl->vi->visual, view->width, view->height); - - if (!surface->surface) { - return 1; - } - - cairo_status_t st = cairo_surface_status(surface->surface); - if (st) { - fprintf(stderr, "error: failed to create cairo surface (%s)\n", - cairo_status_to_string(st)); - } else if (!(surface->cr = cairo_create(surface->surface))) { - fprintf(stderr, "error: failed to create cairo context\n"); - } else if ((st = cairo_status(surface->cr))) { - cairo_surface_destroy(surface->surface); - fprintf(stderr, "error: cairo context is invalid (%s)\n", - cairo_status_to_string(st)); - } - return (int)st; -} - -static int -puglX11CairoDestroy(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - cairo_destroy(surface->cr); - cairo_surface_destroy(surface->surface); - free(surface); - impl->surface = NULL; - return 0; -} - -static int -puglX11CairoEnter(PuglView* PUGL_UNUSED(view), bool PUGL_UNUSED(drawing)) -{ - return 0; -} - -static int -puglX11CairoLeave(PuglView* PUGL_UNUSED(view), bool PUGL_UNUSED(drawing)) -{ - return 0; -} - -static int -puglX11CairoResize(PuglView* view, int width, int height) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - cairo_xlib_surface_set_size(surface->surface, width, height); - - return 0; -} - -static void* -puglX11CairoGetContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; - - return surface->cr; -} - -const PuglBackend* -puglCairoBackend(void) -{ - static const PuglBackend backend = { - puglX11CairoConfigure, - puglX11CairoCreate, - puglX11CairoDestroy, - puglX11CairoEnter, - puglX11CairoLeave, - puglX11CairoResize, - puglX11CairoGetContext - }; - - return &backend; -} diff --git a/pugl/pugl_x11_gl.c b/pugl/pugl_x11_gl.c deleted file mode 100644 index 8ccc92d..0000000 --- a/pugl/pugl_x11_gl.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - Copyright 2012-2019 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_x11_gl.c OpenGL graphics backend for X11. -*/ - -#include "pugl/pugl_gl_backend.h" -#include "pugl/pugl_internal_types.h" -#include "pugl/pugl_x11.h" - -#include -#include - -#include -#include - -typedef struct { - GLXFBConfig fb_config; - GLXContext ctx; - int double_buffered; -} PuglX11GlSurface; - -static int -puglX11GlHintValue(const int value) -{ - return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value; -} - -static int -puglX11GlGetAttrib(Display* const display, - const GLXFBConfig fb_config, - const int attrib) -{ - int value = 0; - glXGetFBConfigAttrib(display, fb_config, attrib, &value); - return value; -} - -static int -puglX11GlConfigure(PuglView* view) -{ - PuglInternals* const impl = view->impl; - const int screen = impl->screen; - Display* const display = impl->display; - - PuglX11GlSurface* const surface = - (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface)); - impl->surface = surface; - - const int attrs[] = { - GLX_X_RENDERABLE, True, - GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_SAMPLES, view->hints.samples, - GLX_RED_SIZE, puglX11GlHintValue(view->hints.red_bits), - GLX_GREEN_SIZE, puglX11GlHintValue(view->hints.green_bits), - GLX_BLUE_SIZE, puglX11GlHintValue(view->hints.blue_bits), - GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints.alpha_bits), - GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints.depth_bits), - GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints.stencil_bits), - GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints.double_buffer), - None - }; - - int n_fbc = 0; - GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc); - if (n_fbc <= 0) { - fprintf(stderr, "error: Failed to create GL context\n"); - return 1; - } - - surface->fb_config = fbc[0]; - impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]); - - printf("Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d" - " DOUBLE=%d SAMPLES=%d\n", - impl->vi->visualid, - puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE), - puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER), - puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES)); - - XFree(fbc); - - return 0; -} - -static int -puglX11GlCreate(PuglView* view) -{ - PuglInternals* const impl = view->impl; - PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface; - Display* const display = impl->display; - const GLXFBConfig fb_config = surface->fb_config; - - const int ctx_attrs[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major, - GLX_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor, - GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints.use_compat_profile - ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB - : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), - 0}; - - typedef GLXContext (*CreateContextAttribs)( - Display*, GLXFBConfig, GLXContext, Bool, const int*); - - CreateContextAttribs create_context = - (CreateContextAttribs)glXGetProcAddress( - (const GLubyte*)"glXCreateContextAttribsARB"); - - impl->surface = surface; - surface->ctx = create_context(display, fb_config, 0, GL_TRUE, ctx_attrs); - if (!surface->ctx) { - surface->ctx = - glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); - } - - glXGetConfig(impl->display, - impl->vi, - GLX_DOUBLEBUFFER, - &surface->double_buffered); - - return 0; -} - -static int -puglX11GlDestroy(PuglView* view) -{ - PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; - if (surface) { - glXDestroyContext(view->impl->display, surface->ctx); - free(surface); - view->impl->surface = NULL; - } - return 0; -} - -static int -puglX11GlEnter(PuglView* view, bool PUGL_UNUSED(drawing)) -{ - PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; - glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx); - return 0; -} - -static int -puglX11GlLeave(PuglView* view, bool drawing) -{ - PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; - - if (drawing && surface->double_buffered) { - glXSwapBuffers(view->impl->display, view->impl->win); - } else if (drawing) { - glFlush(); - } - - glXMakeCurrent(view->impl->display, None, NULL); - - return 0; -} - -static int -puglX11GlResize(PuglView* PUGL_UNUSED(view), - int PUGL_UNUSED(width), - int PUGL_UNUSED(height)) -{ - return 0; -} - -static void* -puglX11GlGetContext(PuglView* PUGL_UNUSED(view)) -{ - return NULL; -} - -PuglGlFunc -puglGetProcAddress(const char* name) -{ - return glXGetProcAddress((const GLubyte*)name); -} - -const PuglBackend* puglGlBackend(void) -{ - static const PuglBackend backend = { - puglX11GlConfigure, - puglX11GlCreate, - puglX11GlDestroy, - puglX11GlEnter, - puglX11GlLeave, - puglX11GlResize, - puglX11GlGetContext - }; - - return &backend; -} diff --git a/wscript b/wscript index bbaeff6..e239f36 100644 --- a/wscript +++ b/wscript @@ -93,29 +93,30 @@ def build(bld): autowaf.build_pc(bld, 'PUGL', PUGL_VERSION, PUGL_MAJOR_VERSION, [], {'PUGL_MAJOR_VERSION': PUGL_MAJOR_VERSION}) - libflags = ['-fvisibility=hidden'] - framework = [] - libs = [] + libflags = ['-fvisibility=hidden'] + framework = [] + libs = [] + lib_source = ['pugl/detail/implementation.c'] if bld.env.TARGET_PLATFORM == 'win32': - lib_source = ['pugl/pugl_win.c'] - libs = ['gdi32', 'user32'] + lib_source += ['pugl/detail/win.c'] + libs = ['gdi32', 'user32'] if bld.is_defined('HAVE_GL'): - lib_source += ['pugl/pugl_win_gl.c'] + lib_source += ['pugl/detail/win_gl.c'] libs += ['opengl32'] if bld.is_defined('HAVE_CAIRO'): - lib_source += ['pugl/pugl_win_cairo.c'] + lib_source += ['pugl/detail/win_cairo.c'] libs += ['cairo'] elif bld.env.TARGET_PLATFORM == 'darwin': - lib_source = ['pugl/pugl_osx.m'] - framework = ['Cocoa', 'OpenGL'] + lib_source += ['pugl/detail/mac.m'] + framework = ['Cocoa', 'OpenGL'] else: - lib_source = ['pugl/pugl_x11.c'] - libs = ['X11'] + lib_source += ['pugl/detail/x11.c'] + libs = ['X11'] if bld.is_defined('HAVE_GL'): - lib_source += ['pugl/pugl_x11_gl.c'] + lib_source += ['pugl/detail/x11_gl.c'] libs += ['GL'] if bld.is_defined('HAVE_CAIRO'): - lib_source += ['pugl/pugl_x11_cairo.c'] + lib_source += ['pugl/detail/x11_cairo.c'] if bld.env['MSVC_COMPILER']: libflags = [] else: @@ -212,7 +213,7 @@ def lint(ctx): "-misc-unused-parameters," + "-hicpp-signed-bitwise," + # FIXME? "-readability-else-after-return\" " + - "../pugl/*.c ../*.c") + "../pugl/detail/*.c ../test/*.c") subprocess.call(cmd, cwd='build', shell=True) # Alias .m files to be compiled like .c files, gcc will do the right thing. -- cgit v1.2.1