diff options
author | David Robillard <d@drobilla.net> | 2019-07-27 21:24:36 +0200 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2019-07-29 01:59:19 +0200 |
commit | 37fe29ab9c4a5ea22bc5996b020fa39c854965fa (patch) | |
tree | 5ac433830c4113d07c51b662ca6d4707d4be12e8 /pugl/detail | |
parent | 41dea932f866c10eb9c303298545d6b7151cfcd0 (diff) | |
download | pugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.tar.gz pugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.tar.bz2 pugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.zip |
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.
Diffstat (limited to 'pugl/detail')
-rw-r--r-- | pugl/detail/cairo_gl.h | 103 | ||||
-rw-r--r-- | pugl/detail/implementation.c | 275 | ||||
-rw-r--r-- | pugl/detail/implementation.h | 46 | ||||
-rw-r--r-- | pugl/detail/mac.m | 1069 | ||||
-rw-r--r-- | pugl/detail/types.h | 107 | ||||
-rw-r--r-- | pugl/detail/win.c | 677 | ||||
-rw-r--r-- | pugl/detail/win.h | 100 | ||||
-rw-r--r-- | pugl/detail/win_cairo.c | 200 | ||||
-rw-r--r-- | pugl/detail/win_gl.c | 306 | ||||
-rw-r--r-- | pugl/detail/x11.c | 577 | ||||
-rw-r--r-- | pugl/detail/x11.h | 41 | ||||
-rw-r--r-- | pugl/detail/x11_cairo.c | 139 | ||||
-rw-r--r-- | pugl/detail/x11_gl.c | 213 |
13 files changed, 3853 insertions, 0 deletions
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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file cairo_gl.h Generic Cairo to OpenGL drawing support. +*/ + +#include "pugl/gl.h" + +#include <cairo.h> +#include <stdint.h> +#include <stdlib.h> + +typedef struct { + unsigned texture_id; + uint8_t* buffer; +} PuglCairoGL; + +static cairo_surface_t* +pugl_cairo_gl_create(PuglCairoGL* ctx, int width, int height, int bpp) +{ + free(ctx->buffer); + ctx->buffer = (uint8_t*)calloc(bpp * width * height, sizeof(uint8_t)); + if (!ctx->buffer) { + 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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file implementation.c Platform-independent implementation. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/pugl.h" + +#include <stdlib.h> +#include <string.h> + +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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <stdint.h> + +#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 <http://drobilla.net> + Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <Cocoa/Cocoa.h> + +#include <mach/mach_time.h> + +#include <stdlib.h> + +#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<NSTextInputClient> +{ +@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<NSWindowDelegate> +{ + 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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file types.h Shared internal type definitions. +*/ + +#ifndef PUGL_DETAIL_TYPES_H +#define PUGL_DETAIL_TYPES_H + +#include "pugl/pugl.h" + +#include <stdbool.h> +#include <stdint.h> + +// 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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.c Windows implementation. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/detail/win.h" + +#include <windows.h> +#include <windowsx.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wctype.h> + +#ifndef WM_MOUSEWHEEL +# define WM_MOUSEWHEEL 0x020A +#endif +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef WHEEL_DELTA +# define WHEEL_DELTA 120 +#endif +#ifndef GWLP_USERDATA +# define GWLP_USERDATA (-21) +#endif + +#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) +#define PUGL_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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file win.h Shared definitions for Windows implementation. +*/ + +#include "pugl/detail/implementation.h" + +#include <windows.h> + +#include <stdbool.h> + +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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <cairo-win32.h> +#include <cairo.h> + +#include <stdlib.h> + +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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <windows.h> + +#include <GL/gl.h> + +#include <stdbool.h> +#include <stdlib.h> + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_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 <http://drobilla.net> + Copyright 2013 Robin Gareus <robin@gareus.org> + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.c X11 implementation. +*/ + +#define _POSIX_C_SOURCE 199309L + +#include "pugl/detail/implementation.h" +#include "pugl/detail/x11.h" + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +enum WmClientStateMessageAction { + WM_STATE_REMOVE, + WM_STATE_ADD, + WM_STATE_TOGGLE +}; + +static const long eventMask = + (ExposureMask | StructureNotifyMask | 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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file x11.h Shared definitions for X11 implementation. +*/ + +#include "pugl/detail/implementation.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <X11/Xutil.h> +#include <cairo-xlib.h> +#include <cairo.h> + +#include <stdio.h> +#include <stdlib.h> + +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 <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file 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 <GL/gl.h> +#include <GL/glx.h> + +#include <stdlib.h> +#include <stdio.h> + +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; +} |