diff options
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | pugl/pugl.h | 6 | ||||
-rw-r--r-- | pugl/pugl_internal.h | 20 | ||||
-rw-r--r-- | pugl/pugl_osx.m | 249 | ||||
-rw-r--r-- | pugl/pugl_win.cpp | 95 | ||||
-rw-r--r-- | pugl/pugl_x11.c | 17 | ||||
-rw-r--r-- | wscript | 2 |
7 files changed, 252 insertions, 139 deletions
@@ -1,5 +1,5 @@ Author: David Robillard <d@drobilla.net> -Original GLX inspiration: +Original GLX inspiration, portability fixes: Ben Loftis diff --git a/pugl/pugl.h b/pugl/pugl.h index 2ee3016..9be936b 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -246,6 +246,12 @@ PUGL_API PuglHandle puglGetHandle(PuglView* view); /** + Return the timestamp (if any) of the currently-processing event. +*/ +PUGL_API uint32_t +puglGetEventTimestamp(PuglView* view); + +/** Get the currently active modifiers (PuglMod flags). This should only be called from an event handler. diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h index 61d287a..37db9e5 100644 --- a/pugl/pugl_internal.h +++ b/pugl/pugl_internal.h @@ -39,11 +39,13 @@ struct PuglViewImpl { PuglInternals* impl; - int width; - int height; - int mods; - bool ignoreKeyRepeat; - bool redisplay; + int width; + int height; + int mods; + bool mouse_in_view; + bool ignoreKeyRepeat; + bool redisplay; + uint32_t event_timestamp_ms; }; void @@ -58,13 +60,19 @@ puglGetHandle(PuglView* view) return view->handle; } +uint32_t +puglGetEventTimestamp(PuglView* view) +{ + return view->event_timestamp_ms; +} + int puglGetModifiers(PuglView* view) { return view->mods; } -static inline void +void puglDefaultReshape(PuglView* view, int width, int height) { glMatrixMode(GL_PROJECTION); diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m index b3b6bae..5621edc 100644 --- a/pugl/pugl_osx.m +++ b/pugl/pugl_osx.m @@ -24,12 +24,64 @@ #include "pugl_internal.h" +@interface PuglWindow : NSWindow +{ +@public + PuglView* puglview; +} + +- (id) initWithContentRect:(NSRect)contentRect + styleMask:(unsigned int)aStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag; +- (void) setPuglview:(PuglView*)view; +- (BOOL) windowShouldClose:(id)sender; +- (void) becomeKeyWindow:(id)sender; +- (BOOL) canBecomeKeyWindow:(id)sender; +@end + +@implementation PuglWindow + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(unsigned int)aStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + NSWindow* result = [super initWithContentRect:contentRect + styleMask:(NSClosableWindowMask | + NSTitledWindowMask | + NSResizableWindowMask) + backing:NSBackingStoreBuffered defer:NO]; + + [result setAcceptsMouseMovedEvents:YES]; + [result setLevel: CGShieldingWindowLevel() + 1]; + + return result; +} + +- (void)setPuglview:(PuglView*)view +{ + puglview = view; + [self setContentSize:NSMakeSize(view->width, view->height) ]; +} + +- (BOOL)windowShouldClose:(id)sender +{ + if (puglview->closeFunc) + puglview->closeFunc(puglview); + return YES; +} + +@end + @interface PuglOpenGLView : NSOpenGLView { int colorBits; int depthBits; @public - PuglView* view; + PuglView* puglview; + + NSTrackingArea* trackingArea; } - (id) initWithFrame:(NSRect)frame @@ -38,6 +90,7 @@ - (void) reshape; - (void) drawRect:(NSRect)rect; - (void) mouseMoved:(NSEvent*)event; +- (void) mouseDragged:(NSEvent*)event; - (void) mouseDown:(NSEvent*)event; - (void) mouseUp:(NSEvent*)event; - (void) rightMouseDown:(NSEvent*)event; @@ -92,33 +145,37 @@ int width = bounds.size.width; int height = bounds.size.height; - if (view->reshapeFunc) { - view->reshapeFunc(view, width, height); - } else { - puglDefaultReshape(view, width, height); - } + if (puglview) { + /* NOTE: Apparently reshape gets called when the GC gets around to + deleting the view (?), so we must have reset puglview to NULL when + this comes around. + */ + if (puglview->reshapeFunc) { + puglview->reshapeFunc(puglview, width, height); + } else { + puglDefaultReshape(puglview, width, height); + } - view->width = width; - view->height = height; + puglview->width = width; + puglview->height = height; + } } - (void) drawRect:(NSRect)rect { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); - - if (self->view->displayFunc) { - self->view->displayFunc(self->view); - } - + puglDisplay(puglview); glFlush(); glSwapAPPLE(); } -static int -getModifiers(unsigned modifierFlags) +static unsigned +getModifiers(PuglView* view, NSevent* ev) { - int mods = 0; + const unsigned modifierFlags = [ev modifierFlags] + + view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); + + unsigned mods = 0; mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; @@ -126,99 +183,135 @@ getModifiers(unsigned modifierFlags) return mods; } +-(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]; +} + +- (void)mouseEntered:(NSEvent*)theEvent +{ + [self updateTrackingAreas]; +} + +- (void)mouseExited:(NSEvent*)theEvent +{ +} + - (void) mouseMoved:(NSEvent*)event { - if (view->motionFunc) { + if (puglview->motionFunc) { NSPoint loc = [event locationInWindow]; - view->mods = getModifiers([event modifierFlags]); - view->motionFunc(view, loc.x, loc.y); + puglview->mods = getModifiers(puglview, event); + puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); + } +} + +- (void) mouseDragged:(NSEvent*)event +{ + if (puglview->motionFunc) { + NSPoint loc = [event locationInWindow]; + puglview->mods = getModifiers(puglview, event); + puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); } } - (void) mouseDown:(NSEvent*)event { - if (view->mouseFunc) { + if (puglview->mouseFunc) { NSPoint loc = [event locationInWindow]; - view->mods = getModifiers([event modifierFlags]); - view->mouseFunc(view, 1, true, loc.x, loc.y); + puglview->mods = getModifiers(puglview, event); + puglview->mouseFunc(puglview, 1, true, loc.x, puglview->height - loc.y); } } - (void) mouseUp:(NSEvent*)event { - if (view->mouseFunc) { + if (puglview->mouseFunc) { NSPoint loc = [event locationInWindow]; - view->mods = getModifiers([event modifierFlags]); - view->mouseFunc(view, 1, false, loc.x, loc.y); + puglview->mods = getModifiers(puglview, event); + puglview->mouseFunc(puglview, 1, false, loc.x, puglview->height - loc.y); } + [self updateTrackingAreas]; } - (void) rightMouseDown:(NSEvent*)event { - if (view->mouseFunc) { + if (puglview->mouseFunc) { NSPoint loc = [event locationInWindow]; - view->mods = getModifiers([event modifierFlags]); - view->mouseFunc(view, 3, true, loc.x, loc.y); + puglview->mods = getModifiers(puglview, event); + puglview->mouseFunc(puglview, 3, true, loc.x, puglview->height - loc.y); } } - (void) rightMouseUp:(NSEvent*)event { - if (view->mouseFunc) { + if (puglview->mouseFunc) { NSPoint loc = [event locationInWindow]; - view->mods = getModifiers([event modifierFlags]); - view->mouseFunc(view, 3, false, loc.x, loc.y); + puglview->mods = getModifiers(puglview, event); + puglview->mouseFunc(puglview, 3, false, loc.x, puglview->height - loc.y); } } - (void) scrollWheel:(NSEvent*)event { - if (view->scrollFunc) { - view->mods = getModifiers([event modifierFlags]); - view->scrollFunc(view, [event deltaX], [event deltaY]); + if (puglview->scrollFunc) { + puglview->mods = getModifiers(puglview, event); + puglview->scrollFunc(puglview, [event deltaX], [event deltaY]); } + [self updateTrackingAreas]; } - (void) keyDown:(NSEvent*)event { - if (view->keyboardFunc && !(view->ignoreKeyRepeat && [event isARepeat])) { + if (puglview->keyboardFunc && !(puglview->ignoreKeyRepeat && [event isARepeat])) { NSString* chars = [event characters]; - view->mods = getModifiers([event modifierFlags]); - view->keyboardFunc(view, true, [chars characterAtIndex:0]); + puglview->mods = getModifiers(puglview, event); + puglview->keyboardFunc(puglview, true, [chars characterAtIndex:0]); } } - (void) keyUp:(NSEvent*)event { - if (view->keyboardFunc) { + if (puglview->keyboardFunc) { NSString* chars = [event characters]; - view->mods = getModifiers([event modifierFlags]); - view->keyboardFunc(view, false, [chars characterAtIndex:0]); + puglview->mods = getModifiers(puglview, event); + puglview->keyboardFunc(puglview, false, [chars characterAtIndex:0]); } } - (void) flagsChanged:(NSEvent*)event { - if (view->specialFunc) { - int mods = getModifiers([event modifierFlags]); - if ((mods & PUGL_MOD_SHIFT) != (view->mods & PUGL_MOD_SHIFT)) { - view->specialFunc(view, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT); - } else if ((mods & PUGL_MOD_CTRL) != (view->mods & PUGL_MOD_CTRL)) { - view->specialFunc(view, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL); - } else if ((mods & PUGL_MOD_ALT) != (view->mods & PUGL_MOD_ALT)) { - view->specialFunc(view, mods & PUGL_MOD_ALT, PUGL_KEY_ALT); - } else if ((mods & PUGL_MOD_SUPER) != (view->mods & PUGL_MOD_SUPER)) { - view->specialFunc(view, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER); + if (puglview->specialFunc) { + const unsigned mods = getModifiers(puglview, [event modifierFlags]); + if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) { + puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT); + } else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) { + puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL); + } else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) { + puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT); + } else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) { + puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER); } - view->mods = mods; + puglview->mods = mods; } } @end struct PuglInternalsImpl { - PuglOpenGLView* view; - NSModalSession session; + PuglOpenGLView* glview; id window; }; @@ -241,33 +334,26 @@ puglCreate(PuglNativeWindow parent, [NSAutoreleasePool new]; [NSApplication sharedApplication]; - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; NSString* titleString = [[NSString alloc] initWithBytes:title length:strlen(title) encoding:NSUTF8StringEncoding]; - id window = [[[NSWindow alloc] - initWithContentRect:NSMakeRect(0, 0, 512, 512) - styleMask:NSTitledWindowMask - backing:NSBackingStoreBuffered - defer:NO] - autorelease]; + id window = [[PuglWindow new]retain]; - [window cascadeTopLeftFromPoint:NSMakePoint(20, 20)]; + [window setPuglview:view]; [window setTitle:titleString]; - [window setAcceptsMouseMovedEvents:YES]; - impl->view = [PuglOpenGLView new]; + impl->glview = [PuglOpenGLView new]; impl->window = window; - impl->view->view = view; + impl->glview->puglview = view; - [window setContentView:impl->view]; + [window setContentView:impl->glview]; [NSApp activateIgnoringOtherApps:YES]; - [window makeFirstResponder:impl->view]; + [window makeFirstResponder:impl->glview]; - impl->session = [NSApp beginModalSessionForWindow:view->impl->window]; + [window makeKeyAndOrderFront:window]; return view; } @@ -275,8 +361,10 @@ puglCreate(PuglNativeWindow parent, void puglDestroy(PuglView* view) { - [NSApp endModalSession:view->impl->session]; - [view->impl->view release]; + view->impl->glview->puglview = NULL; + [view->impl->window close]; + [view->impl->glview release]; + [view->impl->window release]; free(view->impl); free(view); } @@ -284,30 +372,15 @@ puglDestroy(PuglView* view) void puglDisplay(PuglView* view) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); - if (view->displayFunc) { view->displayFunc(view); } - - glFlush(); - view->redisplay = false; } PuglStatus puglProcessEvents(PuglView* view) { - NSInteger response = [NSApp runModalSession:view->impl->session]; - if (response != NSRunContinuesResponse) { - if (view->closeFunc) { - view->closeFunc(view); - } - } - - if (view->redisplay) { - puglDisplay(view); - } + [view->impl->glview setNeedsDisplay: YES]; return PUGL_SUCCESS; } @@ -321,5 +394,5 @@ puglPostRedisplay(PuglView* view) PuglNativeWindow puglGetNativeWindow(PuglView* view) { - return (PuglNativeWindow)view->impl->view; + return (PuglNativeWindow)view->impl->glview; } diff --git a/pugl/pugl_win.cpp b/pugl/pugl_win.cpp index 64d15a6..14e8070 100644 --- a/pugl/pugl_win.cpp +++ b/pugl/pugl_win.cpp @@ -22,6 +22,9 @@ #include <windowsx.h> #include <GL/gl.h> +#include <stdio.h> +#include <stdlib.h> + #include "pugl_internal.h" #ifndef WM_MOUSEWHEEL @@ -30,11 +33,17 @@ #ifndef WM_MOUSEHWHEEL # define WM_MOUSEHWHEEL 0x020E #endif +#ifndef WHEEL_DELTA +# define WHEEL_DELTA 120 +#endif + +const int LOCAL_CLOSE_MSG = WM_USER + 50; struct PuglInternalsImpl { - HWND hwnd; - HDC hdc; - HGLRC hglrc; + HWND hwnd; + HDC hdc; + HGLRC hglrc; + WNDCLASS wc; }; LRESULT CALLBACK @@ -57,24 +66,36 @@ puglCreate(PuglNativeWindow parent, view->width = width; view->height = height; - WNDCLASS wc; - wc.style = CS_OWNDC; - wc.lpfnWndProc = wndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = 0; - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc.lpszMenuName = NULL; - wc.lpszClassName = "Pugl"; - RegisterClass(&wc); - - impl->hwnd = CreateWindow( - "Pugl", title, - WS_VISIBLE | (parent ? WS_CHILD : (WS_POPUPWINDOW | WS_CAPTION)), - 0, 0, width, height, + // FIXME: This is nasty, and pugl should not have static anything. + // Should class be a parameter? Does this make sense on other platforms? + static int wc_count = 0; + char classNameBuf[256]; + snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d\n", title, wc_count++); + + impl->wc.style = CS_OWNDC; + impl->wc.lpfnWndProc = wndProc; + impl->wc.cbClsExtra = 0; + impl->wc.cbWndExtra = 0; + impl->wc.hInstance = 0; + impl->wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + impl->wc.hCursor = LoadCursor(NULL, IDC_ARROW); + impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + impl->wc.lpszMenuName = NULL; + impl->wc.lpszClassName = classNameBuf; + RegisterClass(&impl->wc); + + // Adjust the overall window size to accomodate our requested client size + RECT wr = { 0, 0, width, height }; + AdjustWindowRectEx( + &wr, WS_SIZEBOX | WS_POPUPWINDOW | WS_CAPTION, FALSE, WS_EX_TOPMOST); + + impl->hwnd = CreateWindowEx( + WS_EX_TOPMOST, + classNameBuf, title, + WS_VISIBLE | (parent ? WS_CHILD : (WS_SIZEBOX | WS_POPUPWINDOW | WS_CAPTION)), + 0, 0, wr.right-wr.left, wr.bottom-wr.top, (HWND)parent, NULL, NULL, NULL); + if (!impl->hwnd) { free(impl); free(view); @@ -114,11 +135,12 @@ puglDestroy(PuglView* view) wglDeleteContext(view->impl->hglrc); ReleaseDC(view->impl->hwnd, view->impl->hdc); DestroyWindow(view->impl->hwnd); + UnregisterClass(view->impl->wc.lpszClassName, NULL); free(view->impl); free(view); } -void +static void puglReshape(PuglView* view, int width, int height) { wglMakeCurrent(view->impl->hdc, view->impl->hglrc); @@ -186,6 +208,13 @@ keySymToSpecial(int sym) static void processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam) { + view->event_timestamp_ms = GetMessageTime(); + if (press) { + SetCapture(view->impl->hwnd); + } else { + ReleaseCapture(); + } + if (view->mouseFunc) { view->mouseFunc(view, button, press, GET_X_LPARAM(lParam), @@ -216,7 +245,11 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) case WM_CREATE: case WM_SHOWWINDOW: case WM_SIZE: - puglReshape(view, view->width, view->height); + RECT rect; + GetClientRect(view->impl->hwnd, &rect); + puglReshape(view, rect.right, rect.bottom); + view->width = rect.right; + view->height = rect.bottom; break; case WM_PAINT: BeginPaint(view->impl->hwnd, &ps); @@ -225,8 +258,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_MOUSEMOVE: if (view->motionFunc) { - view->motionFunc( - view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); } break; case WM_LBUTTONDOWN: @@ -260,6 +292,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_KEYDOWN: + view->event_timestamp_ms = (GetMessageTime()); if (view->ignoreKeyRepeat && (lParam & (1 << 30))) { break; } // else nobreak @@ -273,6 +306,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_QUIT: + case LOCAL_CLOSE_MSG: if (view->closeFunc) { view->closeFunc(view); } @@ -288,13 +322,8 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) PuglStatus puglProcessEvents(PuglView* view) { - MSG msg; - while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { - handleMessage(view, msg.message, msg.wParam, msg.lParam); - } - if (view->redisplay) { - puglDisplay(view); + InvalidateRect(view->impl->hwnd, NULL, FALSE); } return PUGL_SUCCESS; @@ -309,14 +338,10 @@ wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); return 0; case WM_CLOSE: - PostQuitMessage(0); + PostMessage(hwnd, LOCAL_CLOSE_MSG, wParam, lParam); return 0; case WM_DESTROY: return 0; - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - PostMessage(hwnd, message, wParam, lParam); - return 0; default: if (view) { return handleMessage(view, message, wParam, lParam); diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c index 16b0cd7..fd61632 100644 --- a/pugl/pugl_x11.c +++ b/pugl/pugl_x11.c @@ -147,7 +147,7 @@ puglCreate(PuglNativeWindow parent, if (glXIsDirect(impl->display, impl->ctx)) { printf("DRI enabled\n"); } else { - printf("no DRI available\n"); + printf("No DRI available\n"); } XFree(vi); @@ -241,8 +241,10 @@ keySymToSpecial(KeySym sym) } static void -setModifiers(PuglView* view, int xstate) +setModifiers(PuglView* view, unsigned xstate, unsigned xtime) { + view->event_timestamp_ms = xtime; + view->mods = 0; view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; @@ -273,16 +275,15 @@ puglProcessEvents(PuglView* view) break; } puglDisplay(view); - view->redisplay = false; break; case MotionNotify: - setModifiers(view, event.xmotion.state); + setModifiers(view, event.xmotion.state, event.xmotion.time); if (view->motionFunc) { view->motionFunc(view, event.xmotion.x, event.xmotion.y); } break; case ButtonPress: - setModifiers(view, event.xbutton.state); + setModifiers(view, event.xbutton.state, event.xbutton.time); if (event.xbutton.button >= 4 && event.xbutton.button <= 7) { if (view->scrollFunc) { float dx = 0, dy = 0; @@ -298,7 +299,7 @@ puglProcessEvents(PuglView* view) } // nobreak case ButtonRelease: - setModifiers(view, event.xbutton.state); + setModifiers(view, event.xbutton.state, event.xbutton.time); if (view->mouseFunc && (event.xbutton.button < 4 || event.xbutton.button > 7)) { view->mouseFunc(view, @@ -307,7 +308,7 @@ puglProcessEvents(PuglView* view) } break; case KeyPress: { - setModifiers(view, event.xkey.state); + setModifiers(view, event.xkey.state, event.xkey.time); KeySym sym; char str[5]; int n = XLookupString(&event.xkey, str, 4, &sym, NULL); @@ -323,7 +324,7 @@ puglProcessEvents(PuglView* view) } } break; case KeyRelease: { - setModifiers(view, event.xkey.state); + setModifiers(view, event.xkey.state, event.xkey.time); bool repeated = false; if (view->ignoreKeyRepeat && XEventsQueued(view->impl->display, QueuedAfterReading)) { @@ -7,7 +7,7 @@ from waflib.extras import autowaf as autowaf import waflib.Logs as Logs, waflib.Options as Options # Version of this package (even if built as a child) -PUGL_VERSION = '0.0.0' +PUGL_VERSION = '0.1.0' PUGL_MAJOR_VERSION = '0' # Library version (UNIX style major, minor, micro) |