diff --git a/source/platform/macos/SDLMain.mm b/source/platform/macos/SDLMain.mm deleted file mode 100644 index 280b1a869..000000000 --- a/source/platform/macos/SDLMain.mm +++ /dev/null @@ -1,373 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs - - Modified for EDuke32 -*/ - -#import -#include - -#if TARGET_OS_MAC - -#include "compat.h" - -#import - -#include "sdl_inc.h" - -#import - -#include /* for MAXPATHLEN */ - -/* For some reason, Apple removed setAppleMenu from the headers in 10.4, - but the method still is there and works. To avoid warnings, we declare - it ourselves here. */ -@interface NSApplication(SDL_Missing_Methods) -- (void)setAppleMenu:(NSMenu *)menu; -@end - - -/* Use this flag to determine whether we use CPS (docking) or not */ -#define SDL_USE_CPS -#ifdef SDL_USE_CPS - -#ifdef __cplusplus -extern "C" { -#endif - -/* Portions of CPS.h */ -typedef struct CPSProcessSerNum -{ - UInt32 lo; - UInt32 hi; -} CPSProcessSerNum; - -extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); -extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); -extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); - -#ifdef __cplusplus -} -#endif - -#endif /* SDL_USE_CPS */ - -static int gArgc; -static char **gArgv; -static BOOL gFinderLaunch; -static BOOL gCalledAppMainline = FALSE; - -static id nsapp; - -static NSString *getApplicationName(void) -{ - const NSDictionary *dict; - NSString *appName = 0; - - /* Determine the application name */ - dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); - if (dict) - appName = [dict objectForKey: @"CFBundleName"]; - - if (![appName length]) - appName = [[NSProcessInfo processInfo] processName]; - - return appName; -} - -@interface NSApplication (SDLApplication) -@end - -@implementation NSApplication (SDLApplication) -/* Invoked from the Quit menu item */ -- (void)terminateCall:(id)sender -{ - /* Post a SDL_QUIT event */ - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); - - UNREFERENCED_PARAMETER(sender); -} -@end - -@interface SDLMain : NSObject -@end - -/* The main class of the application, the application's delegate */ -@implementation SDLMain - -/* Set the working directory to the .app's parent directory */ -- (void) setupWorkingDirectory:(BOOL)shouldChdir -{ - if (shouldChdir) - { - char parentdir[MAXPATHLEN]; - CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); - if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { - chdir(parentdir); /* chdir to the binary app's parent */ - } - CFRelease(url); - CFRelease(url2); - } -} - -static void setApplicationMenu(void) -{ - /* warning: this code is very odd */ - NSMenu *appleMenu; - NSMenuItem *menuItem; - NSString *title; - NSString *appName; - - appName = getApplicationName(); - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - - /* Add menu items */ - title = [@"About " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Hide " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; - - [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Quit " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(terminateCall:) keyEquivalent:@"q"]; - - - /* Put menu into the menubar */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:appleMenu]; - [[nsapp mainMenu] addItem:menuItem]; - - /* Tell the application object that this is now the application menu */ - [nsapp setAppleMenu:appleMenu]; - - /* Finally give up our references to the objects */ - [appleMenu release]; - [menuItem release]; -} - -/* Create a window menu */ -static void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - /* "Minimize" item */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - [menuItem release]; - - /* Put menu into the menubar */ - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[nsapp mainMenu] addItem:windowMenuItem]; - - /* Tell the application object that this is now the window menu */ - [nsapp setWindowsMenu:windowMenu]; - - /* Finally give up our references to the objects */ - [windowMenu release]; - [windowMenuItem release]; -} - -/* Replacement for NSApplicationMain */ -static void CustomApplicationMain (int argc, char **argv) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - SDLMain *sdlMain; - - /* Ensure the application object is initialised */ - nsapp = [NSApplication sharedApplication]; - -#ifdef SDL_USE_CPS - { - CPSProcessSerNum PSN; - /* Tell the dock about us */ - if (!CPSGetCurrentProcess(&PSN)) - if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) - if (!CPSSetFrontProcess(&PSN)) - [NSApplication sharedApplication]; - } -#endif /* SDL_USE_CPS */ - - /* Set up the menubar */ - [nsapp setMainMenu:[[NSMenu alloc] init]]; - setApplicationMenu(); - setupWindowMenu(); - - /* Create SDLMain and make it the app delegate */ - sdlMain = [[SDLMain alloc] init]; - [nsapp setDelegate:sdlMain]; - - /* Start the main event loop */ - [nsapp run]; - - [sdlMain release]; - [pool release]; - - UNREFERENCED_PARAMETER(argc); - UNREFERENCED_PARAMETER(argv); -} - - -/* - * Catch document open requests...this lets us notice files when the app - * was launched by double-clicking a document, or when a document was - * dragged/dropped on the app's icon. You need to have a - * CFBundleDocumentsType section in your Info.plist to get this message, - * apparently. - * - * Files are added to gArgv, so to the app, they'll look like command line - * arguments. Previously, apps launched from the finder had nothing but - * an argv[0]. - * - * This message may be received multiple times to open several docs on launch. - * - * This message is ignored once the app's mainline has been called. - */ -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename -{ - const char *temparg; - size_t arglen; - char *arg; - char **newargv; - - if (!gFinderLaunch) /* MacOS is passing command line args. */ - return FALSE; - - if (gCalledAppMainline) /* app has started, ignore this document. */ - return FALSE; - - temparg = [filename UTF8String]; - arglen = SDL_strlen(temparg) + 1; - arg = (char *) SDL_malloc(arglen); - if (arg == NULL) - return FALSE; - - newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); - if (newargv == NULL) - { - SDL_free(arg); - return FALSE; - } - gArgv = newargv; - - SDL_strlcpy(arg, temparg, arglen); - gArgv[gArgc++] = arg; - gArgv[gArgc] = NULL; - - UNREFERENCED_PARAMETER(theApplication); - - return TRUE; -} - - -/* Called when the internal event loop has just started running */ -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - int status; - - /* Set the working directory to the .app's parent directory */ - [self setupWorkingDirectory:gFinderLaunch]; - - /* Hand off to main application code */ - gCalledAppMainline = TRUE; - status = SDL_main (gArgc, gArgv); - - UNREFERENCED_PARAMETER(note); - - /* We're done, thank you for playing */ - exit(status); -} -@end - - -@implementation NSString (ReplaceSubString) - -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString -{ - unsigned int bufferSize; - unsigned int selfLen = [self length]; - unsigned int aStringLen = [aString length]; - unichar *buffer; - NSRange localRange; - NSString *result; - - bufferSize = selfLen + aStringLen - aRange.length; - buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); - - /* Get first part into buffer */ - localRange.location = 0; - localRange.length = aRange.location; - [self getCharacters:buffer range:localRange]; - - /* Get middle part into buffer */ - localRange.location = 0; - localRange.length = aStringLen; - [aString getCharacters:(buffer+aRange.location) range:localRange]; - - /* Get last part into buffer */ - localRange.location = aRange.location + aRange.length; - localRange.length = selfLen - localRange.location; - [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; - - /* Build output string */ - result = [NSString stringWithCharacters:buffer length:bufferSize]; - - NSDeallocateMemoryPages(buffer, bufferSize); - - return result; -} - -@end - - - -#ifdef main -# undef main -#endif - - -/* Main entry point to executable - should *not* be SDL_main! */ -int main (int argc, char **argv) -{ - /* Copy the arguments into a global variable */ - /* This is passed if we are launched by double-clicking */ - if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { - gArgv = (char **) SDL_malloc(sizeof (char *) * 2); - gArgv[0] = argv[0]; - gArgv[1] = NULL; - gArgc = 1; - gFinderLaunch = YES; - } else { - int i; - gArgc = argc; - gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); - for (i = 0; i <= argc; i++) - gArgv[i] = argv[i]; - gFinderLaunch = NO; - } - - CustomApplicationMain (argc, argv); - - return 0; -} - -#endif diff --git a/source/platform/macos/osxbits.h b/source/platform/macos/osxbits.h deleted file mode 100644 index 639e48869..000000000 --- a/source/platform/macos/osxbits.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef osxbits_h_ -#define osxbits_h_ -#include - -void osx_preopen(void); -void osx_postopen(void); - -int osx_msgbox(const char *name, const char *msg); -int osx_ynbox(const char *name, const char *msg); - -char *osx_gethomedir(void); -char *osx_getsupportdir(int32_t local); -char *osx_getappdir(void); -char *osx_getapplicationsdir(int32_t local); - -#endif diff --git a/source/platform/macos/osxbits.mm b/source/platform/macos/osxbits.mm deleted file mode 100644 index 16f04ec01..000000000 --- a/source/platform/macos/osxbits.mm +++ /dev/null @@ -1,179 +0,0 @@ -#include "compat.h" -#include "osxbits.h" -#import -#import - -#ifndef MAC_OS_X_VERSION_10_5 -# define NSImageScaleNone NSScaleNone -#endif - -#ifndef MAC_OS_X_VERSION_10_12 -# define NSEventModifierFlagOption NSAlternateKeyMask -# define NSEventModifierFlagCommand NSCommandKeyMask -# define NSEventMaskAny NSAnyEventMask -# define NSWindowStyleMaskTitled NSTitledWindowMask -# define NSWindowStyleMaskClosable NSClosableWindowMask -# define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask -# define NSWindowStyleMaskResizable NSResizableWindowMask -# define NSAlertStyleInformational NSInformationalAlertStyle -# define NSControlSizeSmall NSSmallControlSize -#endif - -#ifndef MAC_OS_VERSION_10_3 -# define MAC_OS_VERSION_10_3 1030 -#endif - -id nsapp; - -void osx_preopen(void) -{ - // fix for "ld: absolute address to symbol _NSApp in a different linkage unit not supported" - // (OS X 10.6) when building for PPC - nsapp = [NSApplication sharedApplication]; -} - -void osx_postopen(void) -{ - [nsapp finishLaunching]; -} - -int osx_msgbox(const char *name, const char *msg) -{ - NSString *mmsg = [[NSString alloc] initWithUTF8String:msg]; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 - NSAlert *alert = [[NSAlert alloc] init]; - [alert addButtonWithTitle: @"OK"]; - [alert setInformativeText: mmsg]; - [alert setAlertStyle: NSAlertStyleInformational]; - - [alert runModal]; - - [alert release]; - -#else - NSRunAlertPanel(nil, mmsg, @"OK", nil, nil); -#endif - - [mmsg release]; - - UNREFERENCED_PARAMETER(name); - - return 0; -} - -int osx_ynbox(const char *name, const char *msg) -{ - NSString *mmsg = [[NSString alloc] initWithUTF8String:msg]; - int r; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 - NSAlert *alert = [[NSAlert alloc] init]; - - [alert addButtonWithTitle:@"Yes"]; - [alert addButtonWithTitle:@"No"]; - [alert setInformativeText: mmsg]; - [alert setAlertStyle: NSAlertStyleInformational]; - - r = ([alert runModal] == NSAlertFirstButtonReturn); - - [alert release]; -#else - r = (NSRunAlertPanel(nil, mmsg, @"Yes", @"No", nil) == NSAlertDefaultReturn); -#endif - - [mmsg release]; - - UNREFERENCED_PARAMETER(name); - - return r; -} - -char *osx_gethomedir(void) -{ - NSString *path = NSHomeDirectory(); - const char *Cpath = [path UTF8String]; - char *returnpath = NULL; - - if (Cpath) - returnpath = Bstrdup(Cpath); - - [path release]; - - return returnpath; -} - -char *osx_getsupportdir(int32_t local) -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, local ? NSUserDomainMask : NSLocalDomainMask, YES); - char *returnpath = NULL; - - if ([paths count] > 0) - { - const char *Cpath = [[paths objectAtIndex:0] UTF8String]; - - if (Cpath) - returnpath = Bstrdup(Cpath); - } - - [paths release]; - - return returnpath; -} - -char *osx_getappdir(void) -{ - CFBundleRef mainBundle; - CFURLRef resUrl, fullUrl; - CFStringRef str; - const char *s; - char *dir = NULL; - - mainBundle = CFBundleGetMainBundle(); - if (!mainBundle) { - return NULL; - } - - resUrl = CFBundleCopyResourcesDirectoryURL(mainBundle); - CFRelease(mainBundle); - if (!resUrl) { - return NULL; - } - fullUrl = CFURLCopyAbsoluteURL(resUrl); - if (fullUrl) { - CFRelease(resUrl); - resUrl = fullUrl; - } - - str = CFURLCopyFileSystemPath(resUrl, kCFURLPOSIXPathStyle); - CFRelease(resUrl); - if (!str) { - return NULL; - } - - s = CFStringGetCStringPtr(str, CFStringGetSystemEncoding()); - if (s) { - dir = strdup(s); - } - CFRelease(str); - - return dir; -} - -char *osx_getapplicationsdir(int32_t local) -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, local ? NSUserDomainMask : NSLocalDomainMask, YES); - char *returnpath = NULL; - - if ([paths count] > 0) - { - const char *Cpath = [[paths objectAtIndex:0] UTF8String]; - - if (Cpath) - returnpath = Bstrdup(Cpath); - } - - [paths release]; - - return returnpath; -} diff --git a/source/platform/macos/osxmain.h b/source/platform/macos/osxmain.h deleted file mode 100644 index 4683df57a..000000000 --- a/source/platform/macos/osxmain.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#import - -@interface SDLMain : NSObject -@end diff --git a/source/platform/posix/cocoa/gl_sysfb.h b/source/platform/posix/cocoa/gl_sysfb.h new file mode 100644 index 000000000..144c3cb44 --- /dev/null +++ b/source/platform/posix/cocoa/gl_sysfb.h @@ -0,0 +1,102 @@ +/* + ** gl_sysfb.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2018 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#ifndef COCOA_GL_SYSFB_H_INCLUDED +#define COCOA_GL_SYSFB_H_INCLUDED + +#include "v_video.h" + +#ifdef __OBJC__ +@class NSCursor; +@class CocoaWindow; +#else +typedef struct objc_object NSCursor; +typedef struct objc_object CocoaWindow; +#endif + +class SystemBaseFrameBuffer : public DFrameBuffer +{ +public: + // This must have the same parameters as the Windows version, even if they are not used! + SystemBaseFrameBuffer(void *hMonitor, bool fullscreen); + ~SystemBaseFrameBuffer(); + + bool IsFullscreen() override; + + int GetClientWidth() override; + int GetClientHeight() override; + void ToggleFullscreen(bool yes) override; + void SetWindowSize(int width, int height) override; + + virtual void SetMode(bool fullscreen, bool hiDPI); + + static void UseHiDPI(bool hiDPI); + static void SetCursor(NSCursor* cursor); + static void SetWindowVisible(bool visible); + static void SetWindowTitle(const char* title); + + void SetWindow(CocoaWindow* window) { m_window = window; } + +protected: + SystemBaseFrameBuffer() {} + + void SetFullscreenMode(); + void SetWindowedMode(); + + bool m_fullscreen; + bool m_hiDPI; + + CocoaWindow* m_window; + + int GetTitleBarHeight() const; + +}; + +class SystemGLFrameBuffer : public SystemBaseFrameBuffer +{ + typedef SystemBaseFrameBuffer Super; + +public: + SystemGLFrameBuffer(void *hMonitor, bool fullscreen); + + void SetVSync(bool vsync) override; + + void SetMode(bool fullscreen, bool hiDPI) override; + +protected: + void SwapBuffers(); + + SystemGLFrameBuffer() {} +}; + +#endif // COCOA_GL_SYSFB_H_INCLUDED diff --git a/source/platform/posix/cocoa/i_common.h b/source/platform/posix/cocoa/i_common.h new file mode 100644 index 000000000..f60d82ced --- /dev/null +++ b/source/platform/posix/cocoa/i_common.h @@ -0,0 +1,61 @@ +/* + ** i_common.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#ifndef COCOA_I_COMMON_INCLUDED +#define COCOA_I_COMMON_INCLUDED + +#import + + +// Version of AppKit framework we are interested in +// The following values are needed to build with earlier SDKs + +#define AppKit10_7 1138 +#define AppKit10_8 1187 +#define AppKit10_9 1265 + + +@interface NSWindow(ExitAppOnClose) +- (void)exitAppOnClose; +@end + + +void I_ProcessEvent(NSEvent* event); + +void I_ProcessJoysticks(); + +NSSize I_GetContentViewSize(const NSWindow* window); +void I_SetMainWindowVisible(bool visible); +void I_SetNativeMouse(bool wantNative); + +#endif // COCOA_I_COMMON_INCLUDED diff --git a/source/platform/posix/cocoa/i_input.mm b/source/platform/posix/cocoa/i_input.mm new file mode 100644 index 000000000..261f5e98b --- /dev/null +++ b/source/platform/posix/cocoa/i_input.mm @@ -0,0 +1,804 @@ +/* + ** i_input.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "i_common.h" + +#import + +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "d_event.h" +#include "d_gui.h" +#include "dikeys.h" +#include "doomdef.h" +#include "doomstat.h" +#include "v_video.h" +#include "events.h" +#include "g_game.h" +#include "g_levellocals.h" + + +EXTERN_CVAR(Int, m_use_mouse) + +CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_filter, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CVAR(Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) +{ + if (self < 0) + { + self = 0; + } + else if (self > 2) + { + self = 2; + } +} + + +extern int paused, chatmodeon; +extern constate_e ConsoleState; + +bool GUICapture; + + +namespace +{ + +// TODO: remove this magic! +size_t s_skipMouseMoves; + + +// --------------------------------------------------------------------------- + + +void CheckGUICapture() +{ + bool wantCapture = (MENU_Off == menuactive) + ? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon) + : (MENU_On == menuactive || MENU_OnNoPause == menuactive); + + // [ZZ] check active event handlers that want the UI processing + if (!wantCapture && primaryLevel->localEventManager->CheckUiProcessors()) + { + wantCapture = true; + } + + if (wantCapture != GUICapture) + { + GUICapture = wantCapture; + + ResetButtonStates(); + } +} + +void SetCursorPosition(const NSPoint position) +{ + NSWindow* window = [NSApp keyWindow]; + if (nil == window) + { + return; + } + + const NSRect displayRect = [[window screen] frame]; + const CGPoint eventPoint = CGPointMake(position.x, displayRect.size.height - position.y); + + CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); + + if (NULL != eventSource) + { + CGEventRef mouseMoveEvent = CGEventCreateMouseEvent(eventSource, + kCGEventMouseMoved, eventPoint, kCGMouseButtonLeft); + + if (NULL != mouseMoveEvent) + { + CGEventPost(kCGHIDEventTap, mouseMoveEvent); + CFRelease(mouseMoveEvent); + } + + CFRelease(eventSource); + } + + // TODO: remove this magic! + s_skipMouseMoves = 2; +} + +void CenterCursor() +{ + NSWindow* window = [NSApp keyWindow]; + if (nil == window) + { + return; + } + + const NSRect displayRect = [[window screen] frame]; + const NSRect windowRect = [window frame]; + const NSPoint centerPoint = { NSMidX(windowRect), NSMidY(windowRect) }; + + SetCursorPosition(centerPoint); +} + +bool IsInGame() +{ + switch (mouse_capturemode) + { + default: + case 0: + return gamestate == GS_LEVEL; + + case 1: + return gamestate == GS_LEVEL + || gamestate == GS_INTERMISSION + || gamestate == GS_FINALE; + + case 2: + return true; + } +} + +void CheckNativeMouse() +{ + const bool windowed = (NULL == screen) || !screen->IsFullscreen(); + bool wantNative; + + if (windowed) + { + if (![NSApp isActive] || !use_mouse) + { + wantNative = true; + } + else if (MENU_WaitKey == menuactive) + { + wantNative = false; + } + else + { + wantNative = (!m_use_mouse || MENU_WaitKey != menuactive) + && (!IsInGame() || GUICapture || paused || demoplayback); + } + } + else + { + // ungrab mouse when in the menu with mouse control on. + wantNative = m_use_mouse + && (MENU_On == menuactive || MENU_OnNoPause == menuactive); + } + + if (!wantNative && primaryLevel->localEventManager->CheckRequireMouse()) + wantNative = true; + + I_SetNativeMouse(wantNative); +} + +} // unnamed namespace + + +void I_GetEvent() +{ + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; +} + +void I_StartTic() +{ + CheckGUICapture(); + CheckNativeMouse(); + + I_ProcessJoysticks(); + I_GetEvent(); +} + +void I_StartFrame() +{ +} + + +void I_SetMouseCapture() +{ +} + +void I_ReleaseMouseCapture() +{ +} + +void I_SetNativeMouse(bool wantNative) +{ + static bool nativeMouse = true; + static NSPoint mouseLocation; + + if (wantNative != nativeMouse) + { + nativeMouse = wantNative; + + if (!wantNative) + { + mouseLocation = [NSEvent mouseLocation]; + CenterCursor(); + } + + CGAssociateMouseAndMouseCursorPosition(wantNative); + + if (wantNative) + { + SetCursorPosition(mouseLocation); + + [NSCursor unhide]; + } + else + { + [NSCursor hide]; + } + } +} + + +// --------------------------------------------------------------------------- + + +namespace +{ + +const size_t KEY_COUNT = 128; + + +// See Carbon -> HIToolbox -> Events.h for kVK_ constants + +const uint8_t KEYCODE_TO_DIK[KEY_COUNT] = +{ + DIK_A, DIK_S, DIK_D, DIK_F, DIK_H, DIK_G, DIK_Z, DIK_X, // 0x00 - 0x07 + DIK_C, DIK_V, 0, DIK_B, DIK_Q, DIK_W, DIK_E, DIK_R, // 0x08 - 0x0F + DIK_Y, DIK_T, DIK_1, DIK_2, DIK_3, DIK_4, DIK_6, DIK_5, // 0x10 - 0x17 + DIK_EQUALS, DIK_9, DIK_7, DIK_MINUS, DIK_8, DIK_0, DIK_RBRACKET, DIK_O, // 0x18 - 0x1F + DIK_U, DIK_LBRACKET, DIK_I, DIK_P, DIK_RETURN, DIK_L, DIK_J, DIK_APOSTROPHE, // 0x20 - 0x27 + DIK_K, DIK_SEMICOLON, DIK_BACKSLASH, DIK_COMMA, DIK_SLASH, DIK_N, DIK_M, DIK_PERIOD, // 0x28 - 0x2F + DIK_TAB, DIK_SPACE, DIK_GRAVE, DIK_BACK, 0, DIK_ESCAPE, 0, DIK_LWIN, // 0x30 - 0x37 + DIK_LSHIFT, DIK_CAPITAL, DIK_LMENU, DIK_LCONTROL, DIK_RSHIFT, DIK_RMENU, DIK_RCONTROL, 0, // 0x38 - 0x3F + 0, DIK_DECIMAL, 0, DIK_MULTIPLY, 0, DIK_ADD, 0, 0, // 0x40 - 0x47 + DIK_VOLUMEUP, DIK_VOLUMEDOWN, DIK_MUTE, DIK_SLASH, DIK_NUMPADENTER, 0, DIK_SUBTRACT, 0, // 0x48 - 0x4F + 0, DIK_NUMPAD_EQUALS, DIK_NUMPAD0, DIK_NUMPAD1, DIK_NUMPAD2, DIK_NUMPAD3, DIK_NUMPAD4, DIK_NUMPAD5, // 0x50 - 0x57 + DIK_NUMPAD6, DIK_NUMPAD7, 0, DIK_NUMPAD8, DIK_NUMPAD9, 0, 0, 0, // 0x58 - 0x5F + DIK_F5, DIK_F6, DIK_F7, DIK_F3, DIK_F8, DIK_F9, 0, DIK_F11, // 0x60 - 0x67 + 0, DIK_F13, 0, DIK_F14, 0, DIK_F10, 0, DIK_F12, // 0x68 - 0x6F + 0, DIK_F15, 0, DIK_HOME, 0, DIK_DELETE, DIK_F4, DIK_END, // 0x70 - 0x77 + DIK_F2, 0, DIK_F1, DIK_LEFT, DIK_RIGHT, DIK_DOWN, DIK_UP, 0, // 0x78 - 0x7F +}; + +const uint8_t KEYCODE_TO_ASCII[KEY_COUNT] = +{ + 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', // 0x00 - 0x07 + 'c', 'v', 0, 'b', 'q', 'w', 'e', 'r', // 0x08 - 0x0F + 'y', 't', '1', '2', '3', '4', '6', '5', // 0x10 - 0x17 + '=', '9', '7', '-', '8', '0', ']', 'o', // 0x18 - 0x1F + 'u', '[', 'i', 'p', 13, 'l', 'j', '\'', // 0x20 - 0x27 + 'k', ';', '\\', ',', '/', 'n', 'm', '.', // 0x28 - 0x2F + 9, ' ', '`', 12, 0, 27, 0, 0, // 0x30 - 0x37 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x47 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x48 - 0x4F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78 - 0x7F +}; + + +uint8_t ModifierToDIK(const uint32_t modifier) +{ + switch (modifier) + { + case NSAlphaShiftKeyMask: return DIK_CAPITAL; + case NSShiftKeyMask: return DIK_LSHIFT; + case NSControlKeyMask: return DIK_LCONTROL; + case NSAlternateKeyMask: return DIK_LMENU; + case NSCommandKeyMask: return DIK_LWIN; + } + + return 0; +} + +int16_t ModifierFlagsToGUIKeyModifiers(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return ((modifiers & NSShiftKeyMask ) ? GKM_SHIFT : 0) + | ((modifiers & NSControlKeyMask ) ? GKM_CTRL : 0) + | ((modifiers & NSAlternateKeyMask) ? GKM_ALT : 0) + | ((modifiers & NSCommandKeyMask ) ? GKM_META : 0); +} + +bool ShouldGenerateGUICharEvent(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return !(modifiers & NSControlKeyMask) + && !(modifiers & NSAlternateKeyMask) + && !(modifiers & NSCommandKeyMask) + && !(modifiers & NSFunctionKeyMask); +} + + +NSStringEncoding GetEncodingForUnicodeCharacter(const unichar character) +{ + if (character >= L'\u0100' && character <= L'\u024F') + { + return NSWindowsCP1250StringEncoding; // Central and Eastern Europe + } + else if (character >= L'\u0370' && character <= L'\u03FF') + { + return NSWindowsCP1253StringEncoding; // Greek + } + else if (character >= L'\u0400' && character <= L'\u04FF') + { + return NSWindowsCP1251StringEncoding; // Cyrillic + } + + // TODO: add handling for other characters + // TODO: Turkish should use NSWindowsCP1254StringEncoding + + return NSWindowsCP1252StringEncoding; +} + +unsigned char GetCharacterFromNSEvent(NSEvent* theEvent, unichar *realchar) +{ + const NSString* unicodeCharacters = [theEvent characters]; + + if (0 == [unicodeCharacters length]) + { + return '\0'; + } + + const unichar unicodeCharacter = [unicodeCharacters characterAtIndex:0]; + const NSStringEncoding encoding = GetEncodingForUnicodeCharacter(unicodeCharacter); + + unsigned char character = '\0'; + + if (NSWindowsCP1252StringEncoding == encoding) + { + // TODO: make sure that the following is always correct + character = unicodeCharacter & 0xFF; + } + else + { + const NSData* const characters = + [[theEvent characters] dataUsingEncoding:encoding]; + + character = [characters length] > 0 + ? *static_cast([characters bytes]) + : '\0'; + } + + *realchar = unicodeCharacter; + return character; +} + +void ProcessKeyboardEventInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + unichar realchar; + event.type = EV_GUI_Event; + event.subtype = NSKeyDown == [theEvent type] ? EV_GUI_KeyDown : EV_GUI_KeyUp; + event.data2 = GetCharacterFromNSEvent(theEvent, &realchar); + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + + if (EV_GUI_KeyDown == event.subtype && [theEvent isARepeat]) + { + event.subtype = EV_GUI_KeyRepeat; + } + + const unsigned short keyCode = [theEvent keyCode]; + + switch (keyCode) + { + case kVK_Return: event.data1 = GK_RETURN; break; + case kVK_PageUp: event.data1 = GK_PGUP; break; + case kVK_PageDown: event.data1 = GK_PGDN; break; + case kVK_End: event.data1 = GK_END; break; + case kVK_Home: event.data1 = GK_HOME; break; + case kVK_LeftArrow: event.data1 = GK_LEFT; break; + case kVK_RightArrow: event.data1 = GK_RIGHT; break; + case kVK_UpArrow: event.data1 = GK_UP; break; + case kVK_DownArrow: event.data1 = GK_DOWN; break; + case kVK_Delete: event.data1 = GK_BACKSPACE; break; + case kVK_ForwardDelete: event.data1 = GK_DEL; break; + case kVK_Escape: event.data1 = GK_ESCAPE; break; + case kVK_F1: event.data1 = GK_F1; break; + case kVK_F2: event.data1 = GK_F2; break; + case kVK_F3: event.data1 = GK_F3; break; + case kVK_F4: event.data1 = GK_F4; break; + case kVK_F5: event.data1 = GK_F5; break; + case kVK_F6: event.data1 = GK_F6; break; + case kVK_F7: event.data1 = GK_F7; break; + case kVK_F8: event.data1 = GK_F8; break; + case kVK_F9: event.data1 = GK_F9; break; + case kVK_F10: event.data1 = GK_F10; break; + case kVK_F11: event.data1 = GK_F11; break; + case kVK_F12: event.data1 = GK_F12; break; + default: + event.data1 = KEYCODE_TO_ASCII[keyCode]; + break; + } + + if (event.data1 < 128) + { + event.data1 = toupper(event.data1); + + D_PostEvent(&event); + } + + if (!iscntrl(event.data2) + && EV_GUI_KeyUp != event.subtype + && ShouldGenerateGUICharEvent(theEvent)) + { + event.subtype = EV_GUI_Char; + event.data1 = realchar; + event.data2 = event.data3 & GKM_ALT; + + D_PostEvent(&event); + } +} + + +void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent) +{ + const NSWindow* window = [inEvent window]; + const NSView* view = [window contentView]; + + const NSPoint screenPos = [NSEvent mouseLocation]; + const NSRect screenRect = NSMakeRect(screenPos.x, screenPos.y, 0, 0); + const NSRect windowRect = [window convertRectFromScreen:screenRect]; + + NSPoint viewPos; + NSSize viewSize; + CGFloat scale; + + if (view.layer == nil) + { + viewPos = [view convertPointToBacking:windowRect.origin]; + viewSize = [view convertSizeToBacking:view.frame.size]; + scale = 1.0; + } + else + { + viewPos = windowRect.origin; + viewSize = view.frame.size; + scale = view.layer.contentsScale; + } + + const CGFloat posX = viewPos.x * scale; + const CGFloat posY = (viewSize.height - viewPos.y) * scale; + + outEvent->data1 = static_cast(posX); + outEvent->data2 = static_cast(posY); + + screen->ScaleCoordsFromWindow(outEvent->data1, outEvent->data2); +} + +void ProcessMouseMoveInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + event.type = EV_GUI_Event; + event.subtype = EV_GUI_MouseMove; + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); +} + +void ProcessMouseMoveInGame(NSEvent* theEvent) +{ + int x([theEvent deltaX]); + int y(-[theEvent deltaY]); + + if (0 == x && 0 == y) + { + return; + } + + if (!m_noprescale) + { + x *= 3; + y *= 2; + } + + event_t event = {}; + + static int lastX = 0, lastY = 0; + + if (m_filter) + { + event.x = (x + lastX) / 2; + event.y = (y + lastY) / 2; + } + else + { + event.x = x; + event.y = y; + } + + lastX = x; + lastY = y; + + if (0 != event.x || 0 != event.y) + { + event.type = EV_Mouse; + + D_PostEvent(&event); + } +} + + +void ProcessKeyboardEvent(NSEvent* theEvent) +{ + const unsigned short keyCode = [theEvent keyCode]; + if (keyCode >= KEY_COUNT) + { + assert(!"Unknown keycode"); + return; + } + + const bool isARepeat = [theEvent isARepeat]; + + if (k_allowfullscreentoggle + && (kVK_ANSI_F == keyCode) + && (NSCommandKeyMask & [theEvent modifierFlags]) + && (NSKeyDown == [theEvent type]) + && !isARepeat) + { + ToggleFullscreen = !ToggleFullscreen; + return; + } + + if (GUICapture) + { + ProcessKeyboardEventInMenu(theEvent); + } + else if (!isARepeat) + { + event_t event = {}; + + event.type = NSKeyDown == [theEvent type] ? EV_KeyDown : EV_KeyUp; + event.data1 = KEYCODE_TO_DIK[ keyCode ]; + + if (0 != event.data1) + { + event.data2 = KEYCODE_TO_ASCII[ keyCode ]; + + D_PostEvent(&event); + } + } +} + +void ProcessKeyboardFlagsEvent(NSEvent* theEvent) +{ + if (GUICapture) + { + // Ignore events from modifier keys in menu/console/chat + return; + } + + static const uint32_t FLAGS_MASK = + NSDeviceIndependentModifierFlagsMask & ~NSNumericPadKeyMask; + + const uint32_t modifiers = [theEvent modifierFlags] & FLAGS_MASK; + static uint32_t oldModifiers = 0; + const uint32_t deltaModifiers = modifiers ^ oldModifiers; + + if (0 == deltaModifiers) + { + return; + } + + event_t event = {}; + event.type = modifiers > oldModifiers ? EV_KeyDown : EV_KeyUp; + event.data1 = ModifierToDIK(deltaModifiers); + + oldModifiers = modifiers; + + if (DIK_CAPITAL == event.data1) + { + // Caps Lock is a modifier key which generates one event per state change + // but not per actual key press or release. So treat any event as key down + event.type = EV_KeyDown; + } + + D_PostEvent(&event); +} + + +void ProcessMouseMoveEvent(NSEvent* theEvent) +{ + if (!use_mouse) + { + return; + } + + if (s_skipMouseMoves > 0) + { + --s_skipMouseMoves; + return; + } + + if (GUICapture) + { + ProcessMouseMoveInMenu(theEvent); + } + else + { + ProcessMouseMoveInGame(theEvent); + } +} + +void ProcessMouseButtonEvent(NSEvent* theEvent) +{ + if (!use_mouse) + { + return; + } + + event_t event = {}; + + const NSEventType cocoaEventType = [theEvent type]; + + if (GUICapture) + { + event.type = EV_GUI_Event; + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + + switch (cocoaEventType) + { + case NSLeftMouseDown: event.subtype = EV_GUI_LButtonDown; break; + case NSRightMouseDown: event.subtype = EV_GUI_RButtonDown; break; + case NSOtherMouseDown: event.subtype = EV_GUI_MButtonDown; break; + case NSLeftMouseUp: event.subtype = EV_GUI_LButtonUp; break; + case NSRightMouseUp: event.subtype = EV_GUI_RButtonUp; break; + case NSOtherMouseUp: event.subtype = EV_GUI_MButtonUp; break; + default: break; + } + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); + } + else + { + switch (cocoaEventType) + { + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + event.type = EV_KeyDown; + break; + + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + event.type = EV_KeyUp; + break; + + default: + break; + } + + event.data1 = MIN(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8)); + + D_PostEvent(&event); + } +} + +void ProcessMouseWheelEvent(NSEvent* theEvent) +{ + if (!use_mouse) + { + return; + } + + const int16_t modifiers = ModifierFlagsToGUIKeyModifiers(theEvent); + const CGFloat delta = (modifiers & GKM_SHIFT) + ? [theEvent deltaX] + : [theEvent deltaY]; + const bool isZeroDelta = fabs(delta) < 1.0E-5; + + if (isZeroDelta && GUICapture) + { + return; + } + + event_t event = {}; + + if (GUICapture) + { + event.type = EV_GUI_Event; + event.subtype = delta > 0.0f ? EV_GUI_WheelUp : EV_GUI_WheelDown; + event.data3 = modifiers; + } + else + { + event.type = isZeroDelta ? EV_KeyUp : EV_KeyDown; + event.data1 = delta > 0.0f ? KEY_MWHEELUP : KEY_MWHEELDOWN; + } + + D_PostEvent(&event); +} + +} // unnamed namespace + + +void I_ProcessEvent(NSEvent* event) +{ + const NSEventType eventType = [event type]; + + switch (eventType) + { + case NSMouseMoved: + ProcessMouseMoveEvent(event); + break; + + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSOtherMouseDown: + case NSOtherMouseUp: + ProcessMouseButtonEvent(event); + break; + + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSOtherMouseDragged: + ProcessMouseButtonEvent(event); + ProcessMouseMoveEvent(event); + break; + + case NSScrollWheel: + ProcessMouseWheelEvent(event); + break; + + case NSKeyDown: + case NSKeyUp: + ProcessKeyboardEvent(event); + break; + + case NSFlagsChanged: + ProcessKeyboardFlagsEvent(event); + break; + + default: + break; + } +} diff --git a/source/platform/posix/cocoa/i_joystick.cpp b/source/platform/posix/cocoa/i_joystick.cpp new file mode 100644 index 000000000..bebf43b42 --- /dev/null +++ b/source/platform/posix/cocoa/i_joystick.cpp @@ -0,0 +1,1244 @@ +/* + ** i_joystick.cpp + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include +#include +#include + +#include "d_event.h" +#include "doomdef.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_joy.h" +#include "templates.h" +#include "v_text.h" + + +EXTERN_CVAR(Bool, joy_axespolling) + + +namespace +{ + +FString ToFString(const CFStringRef string) +{ + if (NULL == string) + { + return FString(); + } + + const CFIndex stringLength = CFStringGetLength(string); + + if (0 == stringLength) + { + return FString(); + } + + const size_t bufferSize = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 1; + + char buffer[bufferSize]; + memset(buffer, 0, bufferSize); + + CFStringGetCString(string, buffer, bufferSize, kCFStringEncodingUTF8); + + return FString(buffer); +} + + +// --------------------------------------------------------------------------- + + +class IOKitJoystick : public IJoystickConfig +{ +public: + explicit IOKitJoystick(io_object_t device); + virtual ~IOKitJoystick(); + + virtual FString GetName(); + virtual float GetSensitivity(); + virtual void SetSensitivity(float scale); + + virtual int GetNumAxes(); + virtual float GetAxisDeadZone(int axis); + virtual EJoyAxis GetAxisMap(int axis); + virtual const char* GetAxisName(int axis); + virtual float GetAxisScale(int axis); + + virtual void SetAxisDeadZone(int axis, float deadZone); + virtual void SetAxisMap(int axis, EJoyAxis gameAxis); + virtual void SetAxisScale(int axis, float scale); + + virtual bool IsSensitivityDefault(); + virtual bool IsAxisDeadZoneDefault(int axis); + virtual bool IsAxisMapDefault(int axis); + virtual bool IsAxisScaleDefault(int axis); + + virtual void SetDefaultConfig(); + virtual FString GetIdentifier(); + + void AddAxes(float axes[NUM_JOYAXIS]) const; + + void Update(); + + void UseAxesPolling(bool axesPolling); + + io_object_t* GetNotificationPtr(); + +private: + IOHIDDeviceInterface** m_interface; + IOHIDQueueInterface** m_queue; + + FString m_name; + FString m_identifier; + + float m_sensitivity; + + struct AnalogAxis + { + IOHIDElementCookie cookie; + + char name[64]; + + float value; + + int32_t minValue; + int32_t maxValue; + + float deadZone; + float defaultDeadZone; + float sensitivity; + float defaultSensitivity; + + EJoyAxis gameAxis; + EJoyAxis defaultGameAxis; + + AnalogAxis() + { + memset(this, 0, sizeof *this); + } + }; + + TArray m_axes; + + struct DigitalButton + { + IOHIDElementCookie cookie; + int32_t value; + + explicit DigitalButton(const IOHIDElementCookie cookie) + : cookie(cookie) + , value(0) + { } + }; + + TArray m_buttons; + TArray m_POVs; + + bool m_useAxesPolling; + + io_object_t m_notification; + + + static const float DEFAULT_DEADZONE; + static const float DEFAULT_SENSITIVITY; + + void ProcessAxes(); + bool ProcessAxis (const IOHIDEventStruct& event); + bool ProcessButton(const IOHIDEventStruct& event); + bool ProcessPOV (const IOHIDEventStruct& event); + + void GatherDeviceInfo(io_object_t device, CFDictionaryRef properties); + + static void GatherElementsHandler(const void* value, void* parameter); + void GatherCollectionElements(CFDictionaryRef properties); + + void AddAxis(CFDictionaryRef element); + void AddButton(CFDictionaryRef element); + void AddPOV(CFDictionaryRef element); + + void AddToQueue(IOHIDElementCookie cookie); + void RemoveFromQueue(IOHIDElementCookie cookie); +}; + + +const float IOKitJoystick::DEFAULT_DEADZONE = 0.25f; +const float IOKitJoystick::DEFAULT_SENSITIVITY = 1.0f; + + +IOHIDDeviceInterface** CreateDeviceInterface(const io_object_t device) +{ + IOCFPlugInInterface** plugInInterface = NULL; + SInt32 score = 0; + + const kern_return_t pluginResult = IOCreatePlugInInterfaceForService(device, + kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); + + IOHIDDeviceInterface** interface = NULL; + + if (KERN_SUCCESS == pluginResult) + { + // Call a method of the intermediate plug-in to create the device interface + + const HRESULT queryResult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), reinterpret_cast(&interface)); + + IODestroyPlugInInterface(plugInInterface); // [?] or maybe (*plugInInterface)->Release(plugInInterface); + + if (S_OK == queryResult) + { + const IOReturn openResult = (*interface)->open(interface, 0); + + if (kIOReturnSuccess != openResult) + { + (*interface)->Release(interface); + + Printf(TEXTCOLOR_RED "IOHIDDeviceInterface::open() failed with code 0x%08X\n", openResult); + return NULL; + } + } + else + { + Printf(TEXTCOLOR_RED "IOCFPlugInInterface::QueryInterface() failed with code 0x%08X\n", + static_cast(queryResult)); + return NULL; + } + } + else + { + Printf(TEXTCOLOR_RED "IOCreatePlugInInterfaceForService() failed with code %i\n", pluginResult); + return NULL; + } + + return interface; +} + +IOHIDQueueInterface** CreateDeviceQueue(IOHIDDeviceInterface** const interface) +{ + if (NULL == interface) + { + return NULL; + } + + IOHIDQueueInterface** queue = (*interface)->allocQueue(interface); + + if (NULL == queue) + { + Printf(TEXTCOLOR_RED "IOHIDDeviceInterface::allocQueue() failed\n"); + return NULL; + } + + static const uint32_t QUEUE_FLAGS = 0; + static const uint32_t QUEUE_DEPTH = 0; + + const IOReturn queueResult = (*queue)->create(queue, QUEUE_FLAGS, QUEUE_DEPTH); + + if (kIOReturnSuccess != queueResult) + { + (*queue)->Release(queue); + + Printf(TEXTCOLOR_RED "IOHIDQueueInterface::create() failed with code 0x%08X\n", queueResult); + return NULL; + } + + return queue; +} + + +IOKitJoystick::IOKitJoystick(const io_object_t device) +: m_interface(CreateDeviceInterface(device)) +, m_queue(CreateDeviceQueue(m_interface)) +, m_sensitivity(DEFAULT_SENSITIVITY) +, m_useAxesPolling(true) +, m_notification(0) +{ + if (NULL == m_interface || NULL == m_queue) + { + return; + } + + CFMutableDictionaryRef properties = NULL; + const kern_return_t propertiesResult = + IORegistryEntryCreateCFProperties(device, &properties, kCFAllocatorDefault, kNilOptions); + + if (KERN_SUCCESS != propertiesResult || NULL == properties) + { + Printf(TEXTCOLOR_RED "IORegistryEntryCreateCFProperties() failed with code %i\n", propertiesResult); + return; + } + + GatherDeviceInfo(device, properties); + GatherCollectionElements(properties); + + CFRelease(properties); + + UseAxesPolling(joy_axespolling); + + (*m_queue)->start(m_queue); + + SetDefaultConfig(); +} + +IOKitJoystick::~IOKitJoystick() +{ + M_SaveJoystickConfig(this); + + if (0 != m_notification) + { + IOObjectRelease(m_notification); + } + + if (NULL != m_queue) + { + (*m_queue)->stop(m_queue); + (*m_queue)->dispose(m_queue); + (*m_queue)->Release(m_queue); + } + + if (NULL != m_interface) + { + (*m_interface)->close(m_interface); + (*m_interface)->Release(m_interface); + } +} + + +FString IOKitJoystick::GetName() +{ + return m_name; +} + + +float IOKitJoystick::GetSensitivity() +{ + return m_sensitivity; +} + +void IOKitJoystick::SetSensitivity(float scale) +{ + m_sensitivity = scale; +} + + +int IOKitJoystick::GetNumAxes() +{ + return static_cast(m_axes.Size()); +} + +#define IS_AXIS_VALID (static_cast(axis) < m_axes.Size()) + +float IOKitJoystick::GetAxisDeadZone(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].deadZone : 0.0f; +} + +EJoyAxis IOKitJoystick::GetAxisMap(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].gameAxis : JOYAXIS_None; +} + +const char* IOKitJoystick::GetAxisName(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].name : "Invalid"; +} + +float IOKitJoystick::GetAxisScale(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].sensitivity : 0.0f; +} + +void IOKitJoystick::SetAxisDeadZone(int axis, float deadZone) +{ + if (IS_AXIS_VALID) + { + m_axes[axis].deadZone = clamp(deadZone, 0.0f, 1.0f); + } +} + +void IOKitJoystick::SetAxisMap(int axis, EJoyAxis gameAxis) +{ + if (IS_AXIS_VALID) + { + m_axes[axis].gameAxis = (gameAxis> JOYAXIS_None && gameAxis = 3) + { + m_axes[0].gameAxis = JOYAXIS_Side; + m_axes[1].gameAxis = JOYAXIS_Forward; + m_axes[2].gameAxis = JOYAXIS_Yaw; + + // Four axes? First two are movement, last two are looking around. + + if (axisCount >= 4) + { + m_axes[3].gameAxis = JOYAXIS_Pitch; +// ??? m_axes[3].sensitivity = 0.75f; + + // Five axes? Use the fifth one for moving up and down. + + if (axisCount >= 5) + { + m_axes[4].gameAxis = JOYAXIS_Up; + } + } + } + + // If there is only one axis, then we make no assumptions about how + // the user might want to use it. + + // Preserve defaults for config saving. + + for (size_t i = 0; i < axisCount; ++i) + { + m_axes[i].defaultDeadZone = m_axes[i].deadZone; + m_axes[i].defaultSensitivity = m_axes[i].sensitivity; + m_axes[i].defaultGameAxis = m_axes[i].gameAxis; + } +} + + +FString IOKitJoystick::GetIdentifier() +{ + return m_identifier; +} + + +void IOKitJoystick::AddAxes(float axes[NUM_JOYAXIS]) const +{ + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + const EJoyAxis axis = m_axes[i].gameAxis; + + if (JOYAXIS_None == axis) + { + continue; + } + + axes[axis] -= m_axes[i].value; + } +} + + +void IOKitJoystick::UseAxesPolling(const bool axesPolling) +{ + m_useAxesPolling = axesPolling; + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + AnalogAxis& axis = m_axes[i]; + + if (m_useAxesPolling) + { + RemoveFromQueue(axis.cookie); + } + else + { + AddToQueue(axis.cookie); + } + } +} + + +void IOKitJoystick::Update() +{ + if (NULL == m_queue) + { + return; + } + + IOHIDEventStruct event = { }; + AbsoluteTime zeroTime = { }; + + const IOReturn eventResult = (*m_queue)->getNextEvent(m_queue, &event, zeroTime, 0); + + if (kIOReturnSuccess == eventResult) + { + if (use_joystick) + { + ProcessAxis(event) || ProcessButton(event) || ProcessPOV(event); + } + } + else if (kIOReturnUnderrun != eventResult) + { + Printf(TEXTCOLOR_RED "IOHIDQueueInterface::getNextEvent() failed with code 0x%08X\n", eventResult); + } + + ProcessAxes(); +} + + +void IOKitJoystick::ProcessAxes() +{ + if (NULL == m_interface || !m_useAxesPolling) + { + return; + } + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + AnalogAxis& axis = m_axes[i]; + + static const double scaledMin = -1; + static const double scaledMax = 1; + + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) + { + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + } + else + { + axis.value = 0.0f; + } + } +} + + +bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) +{ + if (m_useAxesPolling) + { + return false; + } + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + if (event.elementCookie != m_axes[i].cookie) + { + continue; + } + + AnalogAxis& axis = m_axes[i]; + + static const double scaledMin = -1; + static const double scaledMax = 1; + + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessButton(const IOHIDEventStruct& event) +{ + for (size_t i = 0, count = m_buttons.Size(); i < count; ++i) + { + if (event.elementCookie != m_buttons[i].cookie) + { + continue; + } + + int32_t& current = m_buttons[i].value; + const int32_t previous = current; + current = event.value; + + Joy_GenerateButtonEvents(previous, current, 1, static_cast(KEY_FIRSTJOYBUTTON + i)); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessPOV(const IOHIDEventStruct& event) +{ + for (size_t i = 0, count = m_POVs.Size(); i ( + CFDictionaryGetValue(properties, CFSTR(kIOHIDManufacturerKey))); + CFStringRef productRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDProductKey))); + CFNumberRef vendorIDRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDVendorIDKey))); + CFNumberRef productIDRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDProductIDKey))); + + CFMutableDictionaryRef usbProperties = NULL; + + if ( NULL == vendorRef || NULL == productRef + || NULL == vendorIDRef || NULL == productIDRef) + { + // OS X is not mirroring all USB properties to HID page, so need to look at USB device page also + // Step up two levels and get dictionary of USB properties + + io_registry_entry_t parent1; + kern_return_t ioResult = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent1); + + if (KERN_SUCCESS == ioResult) + { + io_registry_entry_t parent2; + ioResult = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent2); + + if (KERN_SUCCESS == ioResult) + { + ioResult = IORegistryEntryCreateCFProperties(parent2, &usbProperties, kCFAllocatorDefault, kNilOptions); + + if (KERN_SUCCESS != ioResult) + { + Printf(TEXTCOLOR_RED "IORegistryEntryCreateCFProperties() failed with code %i\n", ioResult); + } + + IOObjectRelease(parent2); + } + else + { + Printf(TEXTCOLOR_RED "IORegistryEntryGetParentEntry(2) failed with code %i\n", ioResult); + } + + IOObjectRelease(parent1); + } + else + { + Printf(TEXTCOLOR_RED "IORegistryEntryGetParentEntry(1) failed with code %i\n", ioResult); + } + } + + if (NULL != usbProperties) + { + if (NULL == vendorRef) + { + vendorRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("USB Vendor Name"))); + } + + if (NULL == productRef) + { + productRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("USB Product Name"))); + } + + if (NULL == vendorIDRef) + { + vendorIDRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("idVendor"))); + } + + if (NULL == productIDRef) + { + productIDRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("idProduct"))); + } + } + + m_name += ToFString(vendorRef); + m_name += " "; + m_name += ToFString(productRef); + + int vendorID = 0, productID = 0; + + if (NULL != vendorIDRef) + { + CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID); + } + + if (NULL != productIDRef) + { + CFNumberGetValue(productIDRef, kCFNumberIntType, &productID); + } + + m_identifier.AppendFormat("VID_%04x_PID_%04x", vendorID, productID); + + if (NULL != usbProperties) + { + CFRelease(usbProperties); + } +} + + +long GetElementValue(const CFDictionaryRef element, const CFStringRef key) +{ + const CFNumberRef number = + static_cast(CFDictionaryGetValue(element, key)); + long result = 0; + + if (NULL != number && CFGetTypeID(number) == CFNumberGetTypeID()) + { + CFNumberGetValue(number, kCFNumberLongType, &result); + } + + return result; +} + +void IOKitJoystick::GatherElementsHandler(const void* value, void* parameter) +{ + assert(NULL != value); + assert(NULL != parameter); + + const CFDictionaryRef element = static_cast(value); + IOKitJoystick* thisPtr = static_cast(parameter); + + if (CFGetTypeID(element) != CFDictionaryGetTypeID()) + { + Printf(TEXTCOLOR_RED "IOKitJoystick: Encountered wrong element type\n"); + return; + } + + const long type = GetElementValue(element, CFSTR(kIOHIDElementTypeKey)); + + if (kIOHIDElementTypeCollection == type) + { + thisPtr->GatherCollectionElements(element); + } + else if (0 != type) + { + const long usagePage = GetElementValue(element, CFSTR(kIOHIDElementUsagePageKey)); + + if (kHIDPage_GenericDesktop == usagePage) + { + const long usage = GetElementValue(element, CFSTR(kIOHIDElementUsageKey)); + + if ( kHIDUsage_GD_Slider == usage + || kHIDUsage_GD_X == usage || kHIDUsage_GD_Y == usage || kHIDUsage_GD_Z == usage + || kHIDUsage_GD_Rx == usage || kHIDUsage_GD_Ry == usage || kHIDUsage_GD_Rz == usage) + { + thisPtr->AddAxis(element); + } + else if (kHIDUsage_GD_Hatswitch == usage && thisPtr->m_POVs.Size() < 4) + { + thisPtr->AddPOV(element); + } + } + else if (kHIDPage_Button == usagePage) + { + thisPtr->AddButton(element); + } + } +} + +void IOKitJoystick::GatherCollectionElements(const CFDictionaryRef properties) +{ + const CFArrayRef topElement = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDElementKey))); + + if (NULL == topElement || CFGetTypeID(topElement) != CFArrayGetTypeID()) + { + Printf(TEXTCOLOR_RED "GatherCollectionElements: invalid properties dictionary\n"); + return; + } + + const CFRange range = { 0, CFArrayGetCount(topElement) }; + + CFArrayApplyFunction(topElement, range, GatherElementsHandler, this); +} + + +IOHIDElementCookie GetElementCookie(const CFDictionaryRef element) +{ + // Use C-style cast to avoid 32/64-bit IOHIDElementCookie type issue + return (IOHIDElementCookie)GetElementValue(element, CFSTR(kIOHIDElementCookieKey)); +} + +void IOKitJoystick::AddAxis(const CFDictionaryRef element) +{ + AnalogAxis axis; + + axis.cookie = GetElementCookie(element); + axis.minValue = GetElementValue(element, CFSTR(kIOHIDElementMinKey)); + axis.maxValue = GetElementValue(element, CFSTR(kIOHIDElementMaxKey)); + + const CFStringRef nameRef = static_cast( + CFDictionaryGetValue(element, CFSTR(kIOHIDElementNameKey))); + + if (NULL != nameRef && CFStringGetTypeID() == CFGetTypeID(nameRef)) + { + CFStringGetCString(nameRef, axis.name, sizeof(axis.name) - 1, kCFStringEncodingUTF8); + } + else + { + snprintf(axis.name, sizeof(axis.name), "Axis %i", m_axes.Size() + 1); + } + + m_axes.Push(axis); +} + +void IOKitJoystick::AddButton(CFDictionaryRef element) +{ + const DigitalButton button(GetElementCookie(element)); + + m_buttons.Push(button); + + AddToQueue(button.cookie); +} + +void IOKitJoystick::AddPOV(CFDictionaryRef element) +{ + const DigitalButton pov(GetElementCookie(element)); + + m_POVs.Push(pov); + + AddToQueue(pov.cookie); +} + + +void IOKitJoystick::AddToQueue(const IOHIDElementCookie cookie) +{ + if (NULL == m_queue) + { + return; + } + + if (!(*m_queue)->hasElement(m_queue, cookie)) + { + (*m_queue)->addElement(m_queue, cookie, 0); + } +} + +void IOKitJoystick::RemoveFromQueue(const IOHIDElementCookie cookie) +{ + if (NULL == m_queue) + { + return; + } + + if ((*m_queue)->hasElement(m_queue, cookie)) + { + (*m_queue)->removeElement(m_queue, cookie); + } +} + + +io_object_t* IOKitJoystick::GetNotificationPtr() +{ + return &m_notification; +} + + +// --------------------------------------------------------------------------- + + +class IOKitJoystickManager +{ +public: + IOKitJoystickManager(); + ~IOKitJoystickManager(); + + void GetJoysticks(TArray& joysticks) const; + + void AddAxes(float axes[NUM_JOYAXIS]) const; + + // Updates axes/buttons states + void Update(); + + void UseAxesPolling(bool axesPolling); + +private: + typedef TDeletingArray JoystickList; + JoystickList m_joysticks; + + static const size_t NOTIFICATION_PORT_COUNT = 2; + + IONotificationPortRef m_notificationPorts[NOTIFICATION_PORT_COUNT]; + io_iterator_t m_notifications [NOTIFICATION_PORT_COUNT]; + + // Rebuilds device list + void Rescan(int usagePage, int usage, size_t notificationPortIndex); + void AddDevices(IONotificationPortRef notificationPort, const io_iterator_t iterator); + + static void OnDeviceAttached(void* refcon, io_iterator_t iterator); + static void OnDeviceRemoved(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); +}; + + +IOKitJoystickManager* s_joystickManager; + + +IOKitJoystickManager::IOKitJoystickManager() +{ + memset(m_notifications, 0, sizeof m_notifications); + + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + m_notificationPorts[i] = IONotificationPortCreate(kIOMasterPortDefault); + + if (NULL == m_notificationPorts[i]) + { + Printf(TEXTCOLOR_RED "IONotificationPortCreate(%zu) failed\n", i); + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(m_notificationPorts[i]), kCFRunLoopDefaultMode); + } + + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, 0); + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, 1); +} + +IOKitJoystickManager::~IOKitJoystickManager() +{ + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + IONotificationPortRef& port = m_notificationPorts[i]; + + if (NULL != port) + { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(port), kCFRunLoopDefaultMode); + + IONotificationPortDestroy(port); + port = NULL; + } + + io_iterator_t& notification = m_notifications[i]; + + if (0 != notification) + { + IOObjectRelease(notification); + notification = 0; + } + } +} + + +void IOKitJoystickManager::GetJoysticks(TArray& joysticks) const +{ + const size_t joystickCount = m_joysticks.Size(); + + joysticks.Resize(joystickCount); + + for (size_t i = 0; i < joystickCount; ++i) + { + M_LoadJoystickConfig(m_joysticks[i]); + + joysticks[i] = m_joysticks[i]; + } +} + +void IOKitJoystickManager::AddAxes(float axes[NUM_JOYAXIS]) const +{ + for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) + { + m_joysticks[i]->AddAxes(axes); + } +} + + +void IOKitJoystickManager::Update() +{ + for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) + { + m_joysticks[i]->Update(); + } +} + + +void IOKitJoystickManager::UseAxesPolling(const bool axesPolling) +{ + for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) + { + m_joysticks[i]->UseAxesPolling(axesPolling); + } +} + + +void PostDeviceChangeEvent() +{ + const event_t event = { EV_DeviceChange }; + D_PostEvent(&event); +} + + +void IOKitJoystickManager::Rescan(const int usagePage, const int usage, const size_t notificationPortIndex) +{ + CFMutableDictionaryRef deviceMatching = IOServiceMatching(kIOHIDDeviceKey); + + if (NULL == deviceMatching) + { + Printf(TEXTCOLOR_RED "IOServiceMatching() returned NULL\n"); + return; + } + + const CFNumberRef usagePageRef = + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage); + CFDictionarySetValue(deviceMatching, CFSTR(kIOHIDPrimaryUsagePageKey), usagePageRef); + + const CFNumberRef usageRef = + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + CFDictionarySetValue(deviceMatching, CFSTR(kIOHIDPrimaryUsageKey), usageRef); + + assert(notificationPortIndex < NOTIFICATION_PORT_COUNT); + io_iterator_t* iteratorPtr = &m_notifications[notificationPortIndex]; + + const IONotificationPortRef notificationPort = m_notificationPorts[notificationPortIndex]; + assert(NULL != notificationPort); + + const kern_return_t notificationResult = IOServiceAddMatchingNotification(notificationPort, + kIOFirstMatchNotification, deviceMatching, OnDeviceAttached, notificationPort, iteratorPtr); + + // IOServiceAddMatchingNotification() consumes one reference of matching dictionary + // Thus CFRelease(deviceMatching) is not needed + + CFRelease(usageRef); + CFRelease(usagePageRef); + + if (KERN_SUCCESS != notificationResult) + { + Printf(TEXTCOLOR_RED "IOServiceAddMatchingNotification() failed with code %i\n", notificationResult); + } + + AddDevices(notificationPort, *iteratorPtr); +} + +void IOKitJoystickManager::AddDevices(const IONotificationPortRef notificationPort, const io_iterator_t iterator) +{ + while (io_object_t device = IOIteratorNext(iterator)) + { + IOKitJoystick* joystick = new IOKitJoystick(device); + m_joysticks.Push(joystick); + + const kern_return_t notificationResult = IOServiceAddInterestNotification(notificationPort, + device, kIOGeneralInterest, OnDeviceRemoved, joystick, joystick->GetNotificationPtr()); + if (KERN_SUCCESS != notificationResult) + { + Printf(TEXTCOLOR_RED "IOServiceAddInterestNotification() failed with code %i\n", notificationResult); + } + + IOObjectRelease(device); + + PostDeviceChangeEvent(); + } +} + + +void IOKitJoystickManager::OnDeviceAttached(void* const refcon, const io_iterator_t iterator) +{ + assert(NULL != refcon); + const IONotificationPortRef notificationPort = static_cast(refcon); + + assert(NULL != s_joystickManager); + s_joystickManager->AddDevices(notificationPort, iterator); +} + +void IOKitJoystickManager::OnDeviceRemoved(void* const refcon, io_service_t, const natural_t messageType, void*) +{ + if (messageType != kIOMessageServiceIsTerminated) + { + return; + } + + assert(NULL != refcon); + IOKitJoystick* const joystick = static_cast(refcon); + + assert(NULL != s_joystickManager); + JoystickList& joysticks = s_joystickManager->m_joysticks; + + for (unsigned int i = 0, count = joysticks.Size(); i < count; ++i) + { + if (joystick == joysticks[i]) + { + joysticks.Delete(i); + break; + } + } + + delete joystick; + + PostDeviceChangeEvent(); +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +void I_ShutdownInput() +{ + delete s_joystickManager; + s_joystickManager = NULL; +} + +void I_GetJoysticks(TArray& sticks) +{ + // Instances of IOKitJoystick depend on GameConfig object. + // M_SaveDefaultsFinal() must be called after destruction of IOKitJoystickManager. + // To ensure this, its initialization is moved here. + // As M_LoadDefaults() was already called at this moment, + // the order of atterm's functions will be correct + + if (NULL == s_joystickManager && !Args->CheckParm("-nojoy")) + { + s_joystickManager = new IOKitJoystickManager; + } + + if (NULL != s_joystickManager) + { + s_joystickManager->GetJoysticks(sticks); + } +} + +void I_GetAxes(float axes[NUM_JOYAXIS]) +{ + for (size_t i = 0; i < NUM_JOYAXIS; ++i) + { + axes[i] = 0.0f; + } + + if (use_joystick && NULL != s_joystickManager) + { + s_joystickManager->AddAxes(axes); + } +} + +IJoystickConfig* I_UpdateDeviceList() +{ + // Does nothing, device list is always kept up-to-date + + return NULL; +} + + +// --------------------------------------------------------------------------- + + +void I_ProcessJoysticks() +{ + if (NULL != s_joystickManager) + { + s_joystickManager->Update(); + } +} + + +// --------------------------------------------------------------------------- + + +CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (NULL != s_joystickManager) + { + s_joystickManager->UseAxesPolling(self); + } +} diff --git a/source/platform/posix/cocoa/i_main.mm b/source/platform/posix/cocoa/i_main.mm new file mode 100644 index 000000000..d0108d2a7 --- /dev/null +++ b/source/platform/posix/cocoa/i_main.mm @@ -0,0 +1,519 @@ +/* + ** i_main.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2018 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "i_common.h" +#include "s_sound.h" + +#include + +#include "c_console.h" +#include "c_cvars.h" +#include "cmdlib.h" +#include "d_main.h" +#include "i_system.h" +#include "m_argv.h" +#include "st_console.h" +#include "version.h" +#include "doomerrors.h" +#include "s_music.h" + + +#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) + + +// --------------------------------------------------------------------------- + + +CVAR (Bool, i_soundinbackground, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +EXTERN_CVAR(Int, vid_defwidth ) +EXTERN_CVAR(Int, vid_defheight) +EXTERN_CVAR(Bool, vid_vsync ) + + +// --------------------------------------------------------------------------- + + +void Mac_I_FatalError(const char* const message) +{ + I_SetMainWindowVisible(false); + S_StopMusic(true); + + FConsoleWindow::GetInstance().ShowFatalError(message); +} + + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 + +// Available since 10.9 with no public declaration/definition until 10.10 + +struct NSOperatingSystemVersion +{ + NSInteger majorVersion; + NSInteger minorVersion; + NSInteger patchVersion; +}; + +@interface NSProcessInfo(OperatingSystemVersion) +- (NSOperatingSystemVersion)operatingSystemVersion; +@end + +#endif // before 10.10 + +void I_DetectOS() +{ + NSOperatingSystemVersion version = {}; + NSProcessInfo* const processInfo = [NSProcessInfo processInfo]; + + if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) + { + version = [processInfo operatingSystemVersion]; + } + + const char* name = "Unknown version"; + + if (10 == version.majorVersion) switch (version.minorVersion) + { + case 7: name = "Mac OS X Lion"; break; + case 8: name = "OS X Mountain Lion"; break; + case 9: name = "OS X Mavericks"; break; + case 10: name = "OS X Yosemite"; break; + case 11: name = "OS X El Capitan"; break; + case 12: name = "macOS Sierra"; break; + case 13: name = "macOS High Sierra"; break; + case 14: name = "macOS Mojave"; break; + case 15: name = "macOS Catalina"; break; + } + + char release[16] = "unknown"; + size_t size = sizeof release - 1; + sysctlbyname("kern.osversion", release, &size, nullptr, 0); + + char model[64] = "Unknown Mac model"; + size = sizeof model - 1; + sysctlbyname("hw.model", model, &size, nullptr, 0); + + const char* const architecture = +#ifdef __i386__ + "32-bit Intel"; +#elif defined __x86_64__ + "64-bit Intel"; +#else + "Unknown"; +#endif + + Printf("%s running %s %d.%d.%d (%s) %s\n", model, name, + int(version.majorVersion), int(version.minorVersion), int(version.patchVersion), + release, architecture); +} + + +FArgs* Args; // command line arguments + + +namespace +{ + +TArray s_argv; + +int DoMain(int argc, char** argv) +{ + printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n", + GetVersionString(), GetGitTime(), __DATE__); + + seteuid(getuid()); + + // Set LC_NUMERIC environment variable in case some library decides to + // clear the setlocale call at least this will be correct. + // Note that the LANG environment variable is overridden by LC_* + setenv("LC_NUMERIC", "C", 1); + setlocale(LC_ALL, "C"); + + // Set reasonable default values for video settings + + const NSSize screenSize = [[NSScreen mainScreen] frame].size; + vid_defwidth = static_cast(screenSize.width); + vid_defheight = static_cast(screenSize.height); + vid_vsync = true; + + Args = new FArgs(argc, argv); + + NSString* exePath = [[NSBundle mainBundle] executablePath]; + progdir = [[exePath stringByDeletingLastPathComponent] UTF8String]; + progdir += "/"; + + auto ret = D_DoomMain(); + FConsoleWindow::DeleteInstance(); + return ret; +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +@interface ApplicationController : NSResponder +{ +} + +- (void)keyDown:(NSEvent*)theEvent; +- (void)keyUp:(NSEvent*)theEvent; + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification; +- (void)applicationWillResignActive:(NSNotification*)aNotification; + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename; + +- (void)processEvents:(NSTimer*)timer; + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; + +- (void)sendExitEvent:(id)sender; + +@end + + +ApplicationController* appCtrl; + + +@implementation ApplicationController + +- (void)keyDown:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + +- (void)keyUp:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + + +extern bool AppActive; + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(1); + + AppActive = true; +} + +- (void)applicationWillResignActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(i_soundinbackground); + + AppActive = false; +} + + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification +{ + // When starting from command line with real executable path, e.g. ZDoom.app/Contents/MacOS/ZDoom + // application remains deactivated for an unknown reason. + // The following call resolves this issue + [NSApp activateIgnoringOtherApps:YES]; + + // Setup timer for custom event loop + + NSTimer* timer = [NSTimer timerWithTimeInterval:0 + target:self + selector:@selector(processEvents:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer + forMode:NSDefaultRunLoopMode]; + + FConsoleWindow::CreateInstance(); + + const size_t argc = s_argv.Size(); + TArray argv(argc + 1, true); + + for (size_t i = 0; i < argc; ++i) + { + argv[i] = s_argv[i].LockBuffer(); + } + + argv[argc] = nullptr; + + exit(DoMain(argc, &argv[0])); +} + + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename +{ + ZD_UNUSED(theApplication); + + // Some parameters from command line are passed to this function + // These parameters need to be skipped to avoid duplication + // Note: SDL has different approach to fix this issue, see the same method in SDLMain.m + + const char* const charFileName = [filename UTF8String]; + + for (size_t i = 0, count = s_argv.Size(); i < count; ++i) + { + if (0 == strcmp(s_argv[i], charFileName)) + { + return FALSE; + } + } + + bool iwad = false; + + if (const char* const extPos = strrchr(charFileName, '.')) + { + iwad = 0 == stricmp(extPos, ".iwad") + || 0 == stricmp(extPos, ".ipk3") + || 0 == stricmp(extPos, ".ipk7"); + } + + s_argv.Push(iwad ? "-iwad" : "-file"); + s_argv.Push(charFileName); + + return TRUE; +} + + +- (void)processEvents:(NSTimer*)timer +{ + ZD_UNUSED(timer); + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + while (true) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate dateWithTimeIntervalSinceNow:0] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (nil == event) + { + break; + } + + I_ProcessEvent(event); + + [NSApp sendEvent:event]; + } + + [NSApp updateWindows]; + + [pool release]; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + [self sendExitEvent:sender]; + return NSTerminateLater; +} + +- (void)sendExitEvent:(id)sender +{ + throw CExitEvent(0); +} + +@end + + +// --------------------------------------------------------------------------- + + +namespace +{ + +NSMenuItem* CreateApplicationMenu() +{ + NSMenu* menu = [NSMenu new]; + + [menu addItemWithTitle:[@"About " stringByAppendingString:@GAMENAME] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Hide " stringByAppendingString:@GAMENAME] + action:@selector(hide:) + keyEquivalent:@"h"]; + [[menu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] + setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; + [menu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Quit " stringByAppendingString:@GAMENAME] + action:@selector(sendExitEvent:) + keyEquivalent:@"q"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + if ([NSApp respondsToSelector:@selector(setAppleMenu:)]) + { + [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; + } + + return menuItem; +} + +NSMenuItem* CreateEditMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Edit"]; + + [menu addItemWithTitle:@"Undo" + action:@selector(undo:) + keyEquivalent:@"z"]; + [menu addItemWithTitle:@"Redo" + action:@selector(redo:) + keyEquivalent:@"Z"]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Cut" + action:@selector(cut:) + keyEquivalent:@"x"]; + [menu addItemWithTitle:@"Copy" + action:@selector(copy:) + keyEquivalent:@"c"]; + [menu addItemWithTitle:@"Paste" + action:@selector(paste:) + keyEquivalent:@"v"]; + [menu addItemWithTitle:@"Delete" + action:@selector(delete:) + keyEquivalent:@""]; + [menu addItemWithTitle:@"Select All" + action:@selector(selectAll:) + keyEquivalent:@"a"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +NSMenuItem* CreateWindowMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:menu]; + + [menu addItemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [menu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Bring All to Front" + action:@selector(arrangeInFront:) + keyEquivalent:@""]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +void CreateMenu() +{ + NSMenu* menuBar = [NSMenu new]; + [menuBar addItem:CreateApplicationMenu()]; + [menuBar addItem:CreateEditMenu()]; + [menuBar addItem:CreateWindowMenu()]; + + [NSApp setMainMenu:menuBar]; +} + +void ReleaseApplicationController() +{ + if (NULL != appCtrl) + { + [NSApp setDelegate:nil]; + [NSApp deactivate]; + + [appCtrl release]; + appCtrl = NULL; + } +} + +} // unnamed namespace + + +int main(int argc, char** argv) +{ + for (int i = 0; i < argc; ++i) + { + const char* const argument = argv[i]; + +#if _DEBUG + if (0 == strcmp(argument, "-wait_for_debugger")) + { + NSAlert* alert = [[NSAlert alloc] init]; + [alert setMessageText:@GAMENAME]; + [alert setInformativeText:@"Waiting for debugger..."]; + [alert addButtonWithTitle:@"Continue"]; + [alert runModal]; + } +#endif // _DEBUG + + s_argv.Push(argument); + } + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + + // The following code isn't mandatory, + // but it enables to run the application without a bundle + if ([NSApp respondsToSelector:@selector(setActivationPolicy:)]) + { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + } + + CreateMenu(); + + atexit(ReleaseApplicationController); + + appCtrl = [ApplicationController new]; + [NSApp setDelegate:appCtrl]; + [NSApp run]; + + [pool release]; + + return EXIT_SUCCESS; +} diff --git a/source/platform/posix/cocoa/i_system.mm b/source/platform/posix/cocoa/i_system.mm new file mode 100644 index 000000000..e28443490 --- /dev/null +++ b/source/platform/posix/cocoa/i_system.mm @@ -0,0 +1,304 @@ +/* + ** i_system.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2018 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "i_common.h" + +#include +#include + +#include "d_protocol.h" +#include "doomdef.h" +#include "doomerrors.h" +#include "doomstat.h" +#include "g_game.h" +#include "gameconfigfile.h" +#include "i_sound.h" +#include "i_system.h" +#include "st_console.h" +#include "v_text.h" +#include "x86.h" +#include "cmdlib.h" + + +void I_Tactile(int /*on*/, int /*off*/, int /*total*/) +{ +} + + +ticcmd_t* I_BaseTiccmd() +{ + static ticcmd_t emptycmd; + return &emptycmd; +} + + + +double PerfToSec, PerfToMillisec; + +static void CalculateCPUSpeed() +{ + long long frequency; + size_t size = sizeof frequency; + + if (0 == sysctlbyname("machdep.tsc.frequency", &frequency, &size, nullptr, 0) && 0 != frequency) + { + PerfToSec = 1.0 / frequency; + PerfToMillisec = 1000.0 / frequency; + + if (!batchrun) + { + Printf("CPU speed: %.0f MHz\n", 0.001 / PerfToMillisec); + } + } +} + +void I_Init(void) +{ + CheckCPUID(&CPU); + CalculateCPUSpeed(); + DumpCPUInfo(&CPU); +} + +void I_SetIWADInfo() +{ +} + + +void I_DebugPrint(const char *cp) +{ + NSLog(@"%s", cp); +} + + +void I_PrintStr(const char* const message) +{ + FConsoleWindow::GetInstance().AddText(message); + + // Strip out any color escape sequences before writing to output + char* const copy = new char[strlen(message) + 1]; + const char* srcp = message; + char* dstp = copy; + + while ('\0' != *srcp) + { + if (TEXTCOLOR_ESCAPE == *srcp) + { + if ('\0' != srcp[1]) + { + srcp += 2; + } + else + { + break; + } + } + else if (0x1d == *srcp || 0x1f == *srcp) // Opening and closing bar character + { + *dstp++ = '-'; + ++srcp; + } + else if (0x1e == *srcp) // Middle bar character + { + *dstp++ = '='; + ++srcp; + } + else + { + *dstp++ = *srcp++; + } + } + + *dstp = '\0'; + + fputs(copy, stdout); + delete[] copy; + fflush(stdout); +} + + +void Mac_I_FatalError(const char* const message); + +void I_ShowFatalError(const char *message) +{ + Mac_I_FatalError(message); +} + + +int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad) +{ + if (!showwin) + { + return defaultiwad; + } + + I_SetMainWindowVisible(false); + + extern int I_PickIWad_Cocoa(WadStuff*, int, bool, int); + const int result = I_PickIWad_Cocoa(wads, numwads, showwin, defaultiwad); + + I_SetMainWindowVisible(true); + + return result; +} + + +bool I_WriteIniFailed() +{ + printf("The config file %s could not be saved:\n%s\n", GameConfig->GetPathName(), strerror(errno)); + return false; // return true to retry +} + + +static const char *pattern; + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1080 +static int matchfile(struct dirent *ent) +#else +static int matchfile(const struct dirent *ent) +#endif +{ + return fnmatch(pattern, ent->d_name, FNM_NOESCAPE) == 0; +} + +void* I_FindFirst(const char* const filespec, findstate_t* const fileinfo) +{ + FString dir; + + const char* const slash = strrchr(filespec, '/'); + + if (slash) + { + pattern = slash+1; + dir = FString(filespec, slash - filespec + 1); + } + else + { + pattern = filespec; + dir = "."; + } + + fileinfo->current = 0; + fileinfo->count = scandir(dir.GetChars(), &fileinfo->namelist, matchfile, alphasort); + + if (fileinfo->count > 0) + { + return fileinfo; + } + + return (void*)-1; +} + +int I_FindNext(void* const handle, findstate_t* const fileinfo) +{ + findstate_t* const state = static_cast(handle); + + if (state->current < fileinfo->count) + { + return ++state->current < fileinfo->count ? 0 : -1; + } + + return -1; +} + +int I_FindClose(void* const handle) +{ + findstate_t* const state = static_cast(handle); + + if (handle != (void*)-1 && state->count > 0) + { + for (int i = 0; i < state->count; ++i) + { + free(state->namelist[i]); + } + + free(state->namelist); + state->namelist = NULL; + state->count = 0; + } + + return 0; +} + +int I_FindAttr(findstate_t* const fileinfo) +{ + dirent* const ent = fileinfo->namelist[fileinfo->current]; + bool isdir; + + if (DirEntryExists(ent->d_name, &isdir)) + { + return isdir ? FA_DIREC : 0; + } + + return 0; +} + + +void I_PutInClipboard(const char* const string) +{ + NSPasteboard* const pasteBoard = [NSPasteboard generalPasteboard]; + NSString* const stringType = NSStringPboardType; + NSArray* const types = [NSArray arrayWithObjects:stringType, nil]; + NSString* const content = [NSString stringWithUTF8String:string]; + + [pasteBoard declareTypes:types + owner:nil]; + [pasteBoard setString:content + forType:stringType]; +} + +FString I_GetFromClipboard(bool returnNothing) +{ + if (returnNothing) + { + return FString(); + } + + NSPasteboard* const pasteBoard = [NSPasteboard generalPasteboard]; + NSString* const value = [pasteBoard stringForType:NSStringPboardType]; + + return FString([value UTF8String]); +} + + +unsigned int I_MakeRNGSeed() +{ + return static_cast(arc4random()); +} + + +TArray I_GetGogPaths() +{ + // GOG's Doom games are Windows only at the moment + return TArray(); +} + diff --git a/source/platform/posix/cocoa/i_video.mm b/source/platform/posix/cocoa/i_video.mm new file mode 100644 index 000000000..b06fb0bb8 --- /dev/null +++ b/source/platform/posix/cocoa/i_video.mm @@ -0,0 +1,1072 @@ +/* + ** i_video.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2018 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "gl_load/gl_load.h" + +#ifdef HAVE_VULKAN +#define VK_USE_PLATFORM_MACOS_MVK +#define VK_USE_PLATFORM_METAL_EXT +#include "volk/volk.h" +#endif + +#include "i_common.h" + +#include "v_video.h" +#include "bitmap.h" +#include "c_dispatch.h" +#include "doomstat.h" +#include "hardware.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_png.h" +#include "swrenderer/r_swrenderer.h" +#include "st_console.h" +#include "v_text.h" +#include "version.h" +#include "doomerrors.h" + +#include "gl/system/gl_framebuffer.h" +#include "vulkan/system/vk_framebuffer.h" +#include "rendering/polyrenderer/backend/poly_framebuffer.h" + + +@implementation NSWindow(ExitAppOnClose) + +- (void)exitAppOnClose +{ + NSButton* closeButton = [self standardWindowButton:NSWindowCloseButton]; + [closeButton setAction:@selector(sendExitEvent:)]; + [closeButton setTarget:[NSApp delegate]]; +} + +@end + +@interface NSWindow(EnterFullscreenOnZoom) +- (void)enterFullscreenOnZoom; +@end + +@implementation NSWindow(EnterFullscreenOnZoom) + +- (void)enterFullscreen:(id)sender +{ + ToggleFullscreen = true; +} + +- (void)enterFullscreenOnZoom +{ + NSButton* zoomButton = [self standardWindowButton:NSWindowZoomButton]; + [zoomButton setEnabled:YES]; + [zoomButton setAction:@selector(enterFullscreen:)]; + [zoomButton setTarget:self]; +} + +@end + +EXTERN_CVAR(Bool, vid_hidpi) +EXTERN_CVAR(Int, vid_defwidth) +EXTERN_CVAR(Int, vid_defheight) +EXTERN_CVAR(Int, vid_preferbackend) +EXTERN_CVAR(Bool, vk_debug) + +CVAR(Bool, mvk_debug, false, 0) + +CUSTOM_CVAR(Bool, vid_autoswitch, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + Printf("You must restart " GAMENAME " to apply graphics switching mode\n"); +} + + +// --------------------------------------------------------------------------- + + +namespace +{ + const NSInteger LEVEL_FULLSCREEN = NSMainMenuWindowLevel + 1; + const NSInteger LEVEL_WINDOWED = NSNormalWindowLevel; + + const NSUInteger STYLE_MASK_FULLSCREEN = NSBorderlessWindowMask; + const NSUInteger STYLE_MASK_WINDOWED = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; +} + + +// --------------------------------------------------------------------------- + + +@interface CocoaWindow : NSWindow +{ + NSString* m_title; +} + +- (BOOL)canBecomeKeyWindow; +- (void)setTitle:(NSString*)title; +- (void)updateTitle; + +@end + + +@implementation CocoaWindow + +- (BOOL)canBecomeKeyWindow +{ + return true; +} + +- (void)setTitle:(NSString*)title +{ + m_title = title; + + [self updateTitle]; +} + +- (void)updateTitle +{ + if (nil == m_title) + { + m_title = [NSString stringWithFormat:@"%s %s", GAMESIG, GetVersionString()]; + } + + [super setTitle:m_title]; +} + +- (void)frameDidChange:(NSNotification*)notification +{ + const NSRect frame = [self frame]; + win_x = frame.origin.x; + win_y = frame.origin.y; + win_w = frame.size.width; + win_h = frame.size.height; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface OpenGLCocoaView : NSOpenGLView +{ + NSCursor* m_cursor; +} + +- (void)setCursor:(NSCursor*)cursor; + +@end + + +@implementation OpenGLCocoaView + +- (void)drawRect:(NSRect)dirtyRect +{ + [NSColor.blackColor setFill]; + NSRectFill(dirtyRect); +} + +- (void)resetCursorRects +{ + [super resetCursorRects]; + + NSCursor* const cursor = nil == m_cursor + ? [NSCursor arrowCursor] + : m_cursor; + + [self addCursorRect:[self bounds] + cursor:cursor]; +} + +- (void)setCursor:(NSCursor*)cursor +{ + m_cursor = cursor; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface VulkanCocoaView : NSView +{ + NSCursor* m_cursor; +} + +- (void)setCursor:(NSCursor*)cursor; + +@end + + +@implementation VulkanCocoaView + +- (void)resetCursorRects +{ + [super resetCursorRects]; + + NSCursor* const cursor = nil == m_cursor + ? [NSCursor arrowCursor] + : m_cursor; + + [self addCursorRect:[self bounds] + cursor:cursor]; +} + +- (void)setCursor:(NSCursor*)cursor +{ + m_cursor = cursor; +} + ++(Class) layerClass +{ + return NSClassFromString(@"CAMetalLayer"); +} + +-(CALayer*) makeBackingLayer +{ + return [self.class.layerClass layer]; +} + +-(BOOL) isOpaque +{ + return YES; +} + +@end + + +// --------------------------------------------------------------------------- + + +extern id appCtrl; + + +namespace +{ + +CocoaWindow* CreateWindow(const NSUInteger styleMask) +{ + CocoaWindow* const window = [CocoaWindow alloc]; + [window initWithContentRect:NSMakeRect(0, 0, vid_defwidth, vid_defheight) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + [window setOpaque:YES]; + [window makeFirstResponder:appCtrl]; + [window setAcceptsMouseMovedEvents:YES]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:window + selector:@selector(frameDidChange:) + name:NSWindowDidEndLiveResizeNotification + object:nil]; + [nc addObserver:window + selector:@selector(frameDidChange:) + name:NSWindowDidMoveNotification + object:nil]; + + return window; +} + +enum class OpenGLProfile +{ + Core, + Legacy +}; + +NSOpenGLPixelFormat* CreatePixelFormat(const OpenGLProfile profile) +{ + NSOpenGLPixelFormatAttribute attributes[16]; + size_t i = 0; + + attributes[i++] = NSOpenGLPFADoubleBuffer; + attributes[i++] = NSOpenGLPFAColorSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(32); + attributes[i++] = NSOpenGLPFADepthSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(24); + attributes[i++] = NSOpenGLPFAStencilSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(8); + + if (profile == OpenGLProfile::Core) + { + attributes[i++] = NSOpenGLPFAOpenGLProfile; + attributes[i++] = NSOpenGLProfileVersion3_2Core; + } + + if (!vid_autoswitch) + { + attributes[i++] = NSOpenGLPFAAllowOfflineRenderers; + } + + attributes[i] = NSOpenGLPixelFormatAttribute(0); + + assert(i < sizeof attributes / sizeof attributes[0]); + + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; +} + +void SetupOpenGLView(CocoaWindow* const window, const OpenGLProfile profile) +{ + NSOpenGLPixelFormat* pixelFormat = CreatePixelFormat(profile); + + if (nil == pixelFormat) + { + I_FatalError("Cannot create OpenGL pixel format, graphics hardware is not supported"); + } + + // Create OpenGL context and view + + const NSRect contentRect = [window contentRectForFrameRect:[window frame]]; + OpenGLCocoaView* glView = [[OpenGLCocoaView alloc] initWithFrame:contentRect + pixelFormat:pixelFormat]; + [[glView openGLContext] makeCurrentContext]; + + [window setContentView:glView]; +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +class CocoaVideo : public IVideo +{ +public: + CocoaVideo() + { + ms_isVulkanEnabled = vid_preferbackend == 1 && NSAppKitVersionNumber >= 1404; // NSAppKitVersionNumber10_11 + } + + ~CocoaVideo() + { +#ifdef HAVE_VULKAN + delete m_vulkanDevice; +#endif + ms_window = nil; + } + + virtual DFrameBuffer* CreateFrameBuffer() override + { + assert(ms_window == nil); + ms_window = CreateWindow(STYLE_MASK_WINDOWED); + + const NSRect contentRect = [ms_window contentRectForFrameRect:[ms_window frame]]; + SystemBaseFrameBuffer *fb = nullptr; + +#ifdef HAVE_VULKAN + if (ms_isVulkanEnabled) + { + NSView* vulkanView = [[VulkanCocoaView alloc] initWithFrame:contentRect]; + vulkanView.wantsLayer = YES; + vulkanView.layer.backgroundColor = NSColor.blackColor.CGColor; + + [ms_window setContentView:vulkanView]; + + // See vk_mvk_moltenvk.h for comprehensive explanation of configuration options set below + // https://github.com/KhronosGroup/MoltenVK/blob/master/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h + + if (vk_debug) + { + // Output errors and informational messages + setenv("MVK_CONFIG_LOG_LEVEL", "2", 0); + + if (mvk_debug) + { + // Extensive MoltenVK logging, too spammy even for vk_debug CVAR + setenv("MVK_DEBUG", "1", 0); + } + } + else + { + // Limit MoltenVK logging to errors only + setenv("MVK_CONFIG_LOG_LEVEL", "1", 0); + } + + if (!vid_autoswitch) + { + // CVAR from pre-Vulkan era has a priority over vk_device selection + setenv("MVK_CONFIG_FORCE_LOW_POWER_GPU", "1", 0); + } + + // The following settings improve performance like suggested at + // https://github.com/KhronosGroup/MoltenVK/issues/581#issuecomment-487293665 + setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "0", 0); + setenv("MVK_CONFIG_PRESENT_WITH_COMMAND_BUFFER", "0", 0); + + try + { + m_vulkanDevice = new VulkanDevice(); + fb = new VulkanFrameBuffer(nullptr, fullscreen, m_vulkanDevice); + } + catch (std::exception const&) + { + ms_isVulkanEnabled = false; + + SetupOpenGLView(ms_window, OpenGLProfile::Core); + } + } + else +#endif + if (vid_preferbackend == 2) + { + SetupOpenGLView(ms_window, OpenGLProfile::Legacy); + + fb = new PolyFrameBuffer(nullptr, fullscreen); + } + else + { + SetupOpenGLView(ms_window, OpenGLProfile::Core); + } + + if (fb == nullptr) + { + fb = new OpenGLRenderer::OpenGLFrameBuffer(0, fullscreen); + } + + fb->SetWindow(ms_window); + fb->SetMode(fullscreen, vid_hidpi); + fb->SetSize(fb->GetClientWidth(), fb->GetClientHeight()); + + // This lame hack is a temporary workaround for strange performance issues + // with fullscreen window and Core Animation's Metal layer + // It is somehow related to initial window level and flags + // Toggling fullscreen -> window -> fullscreen mysteriously solves the problem + if (ms_isVulkanEnabled && fullscreen) + { + fb->SetMode(false, vid_hidpi); + fb->SetMode(true, vid_hidpi); + } + + return fb; + } + + static CocoaWindow* GetWindow() + { + return ms_window; + } + +private: + VulkanDevice *m_vulkanDevice = nullptr; + + static CocoaWindow* ms_window; + + static bool ms_isVulkanEnabled; +}; + + +CocoaWindow* CocoaVideo::ms_window; + +bool CocoaVideo::ms_isVulkanEnabled; + + +// --------------------------------------------------------------------------- + + +static SystemBaseFrameBuffer* frameBuffer; + + +SystemBaseFrameBuffer::SystemBaseFrameBuffer(void*, const bool fullscreen) +: DFrameBuffer(vid_defwidth, vid_defheight) +, m_fullscreen(false) +, m_hiDPI(false) +, m_window(nullptr) +{ + assert(frameBuffer == nullptr); + frameBuffer = this; + + FConsoleWindow::GetInstance().Show(false); +} + +SystemBaseFrameBuffer::~SystemBaseFrameBuffer() +{ + assert(frameBuffer == this); + frameBuffer = nullptr; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:m_window + name:NSWindowDidMoveNotification + object:nil]; + [nc removeObserver:m_window + name:NSWindowDidEndLiveResizeNotification + object:nil]; +} + +bool SystemBaseFrameBuffer::IsFullscreen() +{ + return m_fullscreen; +} + +void SystemBaseFrameBuffer::ToggleFullscreen(bool yes) +{ + SetMode(yes, m_hiDPI); +} + +void SystemBaseFrameBuffer::SetWindowSize(int width, int height) +{ + if (width < VID_MIN_WIDTH || height < VID_MIN_HEIGHT) + { + return; + } + + if (fullscreen) + { + // Enter windowed mode in order to calculate title bar height + fullscreen = false; + SetMode(false, m_hiDPI); + } + + win_w = width; + win_h = height + GetTitleBarHeight(); + + SetMode(false, m_hiDPI); + + [m_window center]; +} + +int SystemBaseFrameBuffer::GetTitleBarHeight() const +{ + const NSRect windowFrame = [m_window frame]; + const NSRect contentFrame = [m_window contentRectForFrameRect:windowFrame]; + const int titleBarHeight = windowFrame.size.height - contentFrame.size.height; + + return titleBarHeight; +} + + +int SystemBaseFrameBuffer::GetClientWidth() +{ + const int clientWidth = I_GetContentViewSize(m_window).width; + return clientWidth > 0 ? clientWidth : GetWidth(); +} + +int SystemBaseFrameBuffer::GetClientHeight() +{ + const int clientHeight = I_GetContentViewSize(m_window).height; + return clientHeight > 0 ? clientHeight : GetHeight(); +} + + +void SystemBaseFrameBuffer::SetFullscreenMode() +{ + if (!m_fullscreen) + { + [m_window setLevel:LEVEL_FULLSCREEN]; + [m_window setStyleMask:STYLE_MASK_FULLSCREEN]; + + [m_window setHidesOnDeactivate:YES]; + } + + const NSRect screenFrame = [[m_window screen] frame]; + [m_window setFrame:screenFrame display:YES]; +} + +void SystemBaseFrameBuffer::SetWindowedMode() +{ + if (m_fullscreen) + { + [m_window setLevel:LEVEL_WINDOWED]; + [m_window setStyleMask:STYLE_MASK_WINDOWED]; + + [m_window setHidesOnDeactivate:NO]; + } + + const int minimumFrameWidth = VID_MIN_WIDTH; + const int minimumFrameHeight = VID_MIN_HEIGHT + GetTitleBarHeight(); + const NSSize minimumFrameSize = NSMakeSize(minimumFrameWidth, minimumFrameHeight); + [m_window setMinSize:minimumFrameSize]; + + const bool isFrameValid = win_x != -1 && win_y != -1 + && win_w >= minimumFrameWidth && win_h >= minimumFrameHeight; + + if (!isFrameValid) + { + const NSRect screenSize = [[NSScreen mainScreen] frame]; + win_x = screenSize.origin.x + screenSize.size.width / 10; + win_y = screenSize.origin.y + screenSize.size.height / 10; + win_w = screenSize.size.width * 8 / 10; + win_h = screenSize.size.height * 8 / 10 + GetTitleBarHeight(); + } + + const NSRect frameSize = NSMakeRect(win_x, win_y, win_w, win_h); + [m_window setFrame:frameSize display:YES]; + [m_window enterFullscreenOnZoom]; + [m_window exitAppOnClose]; +} + +void SystemBaseFrameBuffer::SetMode(const bool fullscreen, const bool hiDPI) +{ + if ([m_window.contentView isKindOfClass:[OpenGLCocoaView class]]) + { + NSOpenGLView* const glView = [m_window contentView]; + [glView setWantsBestResolutionOpenGLSurface:hiDPI]; + } + else + { + assert(m_window.screen != nil); + assert(m_window.contentView.layer != nil); + [m_window.contentView layer].contentsScale = hiDPI ? m_window.screen.backingScaleFactor : 1.0; + } + + if (fullscreen) + { + SetFullscreenMode(); + } + else + { + SetWindowedMode(); + } + + [m_window updateTitle]; + + if (![m_window isKeyWindow]) + { + [m_window makeKeyAndOrderFront:nil]; + } + + m_fullscreen = fullscreen; + m_hiDPI = hiDPI; +} + + +void SystemBaseFrameBuffer::UseHiDPI(const bool hiDPI) +{ + if (frameBuffer != nullptr) + { + frameBuffer->SetMode(frameBuffer->m_fullscreen, hiDPI); + } +} + +void SystemBaseFrameBuffer::SetCursor(NSCursor* cursor) +{ + if (frameBuffer != nullptr) + { + NSWindow* const window = frameBuffer->m_window; + id view = [window contentView]; + + [view setCursor:cursor]; + [window invalidateCursorRectsForView:view]; + } +} + +void SystemBaseFrameBuffer::SetWindowVisible(bool visible) +{ + if (frameBuffer != nullptr) + { + if (visible) + { + [frameBuffer->m_window orderFront:nil]; + } + else + { + [frameBuffer->m_window orderOut:nil]; + } + + I_SetNativeMouse(!visible); + } +} + +void SystemBaseFrameBuffer::SetWindowTitle(const char* title) +{ + if (frameBuffer != nullptr) + { + NSString* const nsTitle = nullptr == title ? nil : + [NSString stringWithCString:title encoding:NSISOLatin1StringEncoding]; + [frameBuffer->m_window setTitle:nsTitle]; + } +} + + +// --------------------------------------------------------------------------- + + +SystemGLFrameBuffer::SystemGLFrameBuffer(void *hMonitor, bool fullscreen) +: SystemBaseFrameBuffer(hMonitor, fullscreen) +{ +} + + +void SystemGLFrameBuffer::SetVSync(bool vsync) +{ + const GLint value = vsync ? 1 : 0; + + [[NSOpenGLContext currentContext] setValues:&value + forParameter:NSOpenGLCPSwapInterval]; +} + + +void SystemGLFrameBuffer::SetMode(const bool fullscreen, const bool hiDPI) +{ + NSOpenGLView* const glView = [m_window contentView]; + [glView setWantsBestResolutionOpenGLSurface:hiDPI]; + + if (fullscreen) + { + SetFullscreenMode(); + } + else + { + SetWindowedMode(); + } + + [m_window updateTitle]; + + if (![m_window isKeyWindow]) + { + [m_window makeKeyAndOrderFront:nil]; + } + + m_fullscreen = fullscreen; + m_hiDPI = hiDPI; +} + + +void SystemGLFrameBuffer::SwapBuffers() +{ + [[NSOpenGLContext currentContext] flushBuffer]; +} + + +// --------------------------------------------------------------------------- + + +IVideo* Video; + + +// --------------------------------------------------------------------------- + + +void I_ShutdownGraphics() +{ + if (NULL != screen) + { + delete screen; + screen = NULL; + } + + delete Video; + Video = NULL; +} + +void I_InitGraphics() +{ + Video = new CocoaVideo; +} + + +// --------------------------------------------------------------------------- + +CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + SystemBaseFrameBuffer::UseHiDPI(self); +} + + +// --------------------------------------------------------------------------- + + +bool I_SetCursor(FTexture *cursorpic) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSCursor* cursor = nil; + + if (NULL != cursorpic && cursorpic->isValid()) + { + // Create bitmap image representation + + auto sbuffer = cursorpic->CreateTexBuffer(0); + + const NSInteger imageWidth = sbuffer.mWidth; + const NSInteger imageHeight = sbuffer.mHeight; + const NSInteger imagePitch = sbuffer.mWidth * 4; + + NSBitmapImageRep* bitmapImageRep = [NSBitmapImageRep alloc]; + [bitmapImageRep initWithBitmapDataPlanes:NULL + pixelsWide:imageWidth + pixelsHigh:imageHeight + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:imagePitch + bitsPerPixel:0]; + + // Load bitmap data to representation + + uint8_t* buffer = [bitmapImageRep bitmapData]; + memcpy(buffer, sbuffer.mBuffer, imagePitch * imageHeight); + + // Swap red and blue components in each pixel + + for (size_t i = 0; i < size_t(imageWidth * imageHeight); ++i) + { + const size_t offset = i * 4; + std::swap(buffer[offset ], buffer[offset + 2]); + } + + // Create image from representation and set it as cursor + + NSData* imageData = [bitmapImageRep representationUsingType:NSPNGFileType + properties:[NSDictionary dictionary]]; + NSImage* cursorImage = [[NSImage alloc] initWithData:imageData]; + + cursor = [[NSCursor alloc] initWithImage:cursorImage + hotSpot:NSMakePoint(0.0f, 0.0f)]; + } + + SystemBaseFrameBuffer::SetCursor(cursor); + + [pool release]; + + return true; +} + + +NSSize I_GetContentViewSize(const NSWindow* const window) +{ + const NSView* const view = [window contentView]; + const NSSize frameSize = [view frame].size; + + return (vid_hidpi) + ? [view convertSizeToBacking:frameSize] + : frameSize; +} + +void I_SetMainWindowVisible(bool visible) +{ + SystemBaseFrameBuffer::SetWindowVisible(visible); +} + +// each platform has its own specific version of this function. +void I_SetWindowTitle(const char* title) +{ + SystemBaseFrameBuffer::SetWindowTitle(title); +} + + +#ifdef HAVE_VULKAN +void I_GetVulkanDrawableSize(int *width, int *height) +{ + NSWindow* const window = CocoaVideo::GetWindow(); + assert(window != nil); + + const NSSize size = I_GetContentViewSize(window); + + if (width != nullptr) + { + *width = int(size.width); + } + + if (height != nullptr) + { + *height = int(size.height); + } +} + +bool I_GetVulkanPlatformExtensions(unsigned int *count, const char **names) +{ + static std::vector extensions; + + if (extensions.empty()) + { + uint32_t extensionPropertyCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionPropertyCount, nullptr); + + std::vector extensionProperties(extensionPropertyCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionPropertyCount, extensionProperties.data()); + + static const char* const EXTENSION_NAMES[] = + { + VK_KHR_SURFACE_EXTENSION_NAME, // KHR_surface, required + VK_EXT_METAL_SURFACE_EXTENSION_NAME, // EXT_metal_surface, optional, preferred + VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // MVK_macos_surface, optional, deprecated + }; + + for (const VkExtensionProperties ¤tProperties : extensionProperties) + { + for (const char *const extensionName : EXTENSION_NAMES) + { + if (strcmp(currentProperties.extensionName, extensionName) == 0) + { + extensions.push_back(extensionName); + } + } + } + } + + static const unsigned int extensionCount = static_cast(extensions.size()); + assert(extensionCount >= 2); // KHR_surface + at least one of the platform surface extentions + + if (count == nullptr && names == nullptr) + { + return false; + } + else if (names == nullptr) + { + *count = extensionCount; + return true; + } + else + { + const bool result = *count >= extensionCount; + *count = std::min(*count, extensionCount); + + for (unsigned int i = 0; i < *count; ++i) + { + names[i] = extensions[i]; + } + + return result; + } +} + +bool I_CreateVulkanSurface(VkInstance instance, VkSurfaceKHR *surface) +{ + NSView *const view = CocoaVideo::GetWindow().contentView; + CALayer *const layer = view.layer; + + // Set magnification filter for swapchain image when it's copied to a physical display surface + // This is needed for gfx-portability because MoltenVK uses preferred nearest sampling by default + const char *const magFilterEnv = getenv("MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST"); + const bool useNearestFilter = magFilterEnv == nullptr || strtol(magFilterEnv, nullptr, 0) != 0; + layer.magnificationFilter = useNearestFilter ? kCAFilterNearest : kCAFilterLinear; + + if (vkCreateMetalSurfaceEXT) + { + // Preferred surface creation path + VkMetalSurfaceCreateInfoEXT surfaceCreateInfo; + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surfaceCreateInfo.pNext = nullptr; + surfaceCreateInfo.flags = 0; + surfaceCreateInfo.pLayer = static_cast(layer); + + const VkResult result = vkCreateMetalSurfaceEXT(instance, &surfaceCreateInfo, nullptr, surface); + return result == VK_SUCCESS; + } + + // Deprecated surface creation path + VkMacOSSurfaceCreateInfoMVK windowCreateInfo; + windowCreateInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + windowCreateInfo.pNext = nullptr; + windowCreateInfo.flags = 0; + windowCreateInfo.pView = view; + + const VkResult result = vkCreateMacOSSurfaceMVK(instance, &windowCreateInfo, nullptr, surface); + return result == VK_SUCCESS; +} +#endif + + +namespace +{ + TArray polyPixelBuffer; + GLuint polyTexture; + + int polyWidth = -1; + int polyHeight = -1; + int polyVSync = -1; +} + +void I_PolyPresentInit() +{ + ogl_LoadFunctions(); + + glGenTextures(1, &polyTexture); + assert(polyTexture != 0); + + glEnable(GL_TEXTURE_RECTANGLE_ARB); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, polyTexture); + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +uint8_t *I_PolyPresentLock(int w, int h, bool vsync, int &pitch) +{ + static const int PIXEL_BYTES = 4; + + if (polyPixelBuffer.Size() == 0 || w != polyWidth || h != polyHeight) + { + polyPixelBuffer.Resize(w * h * PIXEL_BYTES); + + polyWidth = w; + polyHeight = h; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, w, h, 0.0, -1.0, 1.0); + + glViewport(0, 0, w, h); + } + + if (vsync != polyVSync) + { + const GLint value = vsync ? 1 : 0; + + [[NSOpenGLContext currentContext] setValues:&value + forParameter:NSOpenGLCPSwapInterval]; + } + + pitch = w * PIXEL_BYTES; + + return &polyPixelBuffer[0]; +} + +void I_PolyPresentUnlock(int x, int y, int w, int h) +{ + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, &polyPixelBuffer[0]); + + glBegin(GL_QUADS); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(0.0f, 0.0f); + glTexCoord2f(w, 0.0f); + glVertex2f(w, 0.0f); + glTexCoord2f(w, h); + glVertex2f(w, h); + glTexCoord2f(0.0f, h); + glVertex2f(0.0f, h); + glEnd(); + + glFlush(); + + [[NSOpenGLContext currentContext] flushBuffer]; +} + +void I_PolyPresentDeinit() +{ + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &polyTexture); +} diff --git a/source/platform/posix/cocoa/st_console.h b/source/platform/posix/cocoa/st_console.h new file mode 100644 index 000000000..b2af7bade --- /dev/null +++ b/source/platform/posix/cocoa/st_console.h @@ -0,0 +1,96 @@ +/* + ** st_console.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#ifndef COCOA_ST_CONSOLE_INCLUDED +#define COCOA_ST_CONSOLE_INCLUDED + +@class NSButton; +@class NSProgressIndicator; +@class NSScrollView; +@class NSTextField; +@class NSTextView; +@class NSView; +@class NSWindow; + +struct PalEntry; + + +class FConsoleWindow +{ +public: + static FConsoleWindow& GetInstance(); + + static void CreateInstance(); + static void DeleteInstance(); + + void Show(bool visible); + void ShowFatalError(const char* message); + + void AddText(const char* message); + + void SetTitleText(); + void SetProgressBar(bool visible); + + // FStartupScreen functionality + void Progress(int current, int maximum); + void NetInit(const char* message, int playerCount); + void NetProgress(int count); + void NetDone(); + +private: + NSWindow* m_window; + NSTextView* m_textView; + NSScrollView* m_scrollView; + NSProgressIndicator* m_progressBar; + + NSView* m_netView; + NSTextField* m_netMessageText; + NSTextField* m_netCountText; + NSProgressIndicator* m_netProgressBar; + NSButton* m_netAbortButton; + + unsigned int m_characterCount; + + int m_netCurPos; + int m_netMaxPos; + + FConsoleWindow(); + + void ExpandTextView(float height); + + void AddText(const PalEntry& color, const char* message); + + void ScrollTextToBottom(); +}; + +#endif // COCOA_ST_CONSOLE_INCLUDED diff --git a/source/platform/posix/cocoa/st_console.mm b/source/platform/posix/cocoa/st_console.mm new file mode 100644 index 000000000..93d0a1320 --- /dev/null +++ b/source/platform/posix/cocoa/st_console.mm @@ -0,0 +1,532 @@ +/* + ** st_console.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "i_common.h" + +#include "d_main.h" +#include "st_console.h" +#include "v_text.h" +#include "version.h" +#include "i_time.h" + + +static NSColor* RGB(const uint8_t red, const uint8_t green, const uint8_t blue) +{ + return [NSColor colorWithCalibratedRed:red / 255.0f + green:green / 255.0f + blue:blue / 255.0f + alpha:1.0f]; +} + +static NSColor* RGB(const PalEntry& color) +{ + return RGB(color.r, color.g, color.b); +} + +static NSColor* RGB(const uint32_t color) +{ + return RGB(PalEntry(color)); +} + + +static const CGFloat PROGRESS_BAR_HEIGHT = 18.0f; +static const CGFloat NET_VIEW_HEIGHT = 88.0f; + + +FConsoleWindow::FConsoleWindow() +: m_window([NSWindow alloc]) +, m_textView([NSTextView alloc]) +, m_scrollView([NSScrollView alloc]) +, m_progressBar(nil) +, m_netView(nil) +, m_netMessageText(nil) +, m_netCountText(nil) +, m_netProgressBar(nil) +, m_netAbortButton(nil) +, m_characterCount(0) +, m_netCurPos(0) +, m_netMaxPos(0) +{ + const CGFloat initialWidth = 512.0f; + const CGFloat initialHeight = 384.0f; + const NSRect initialRect = NSMakeRect(0.0f, 0.0f, initialWidth, initialHeight); + + [m_textView initWithFrame:initialRect]; + [m_textView setEditable:NO]; + [m_textView setBackgroundColor:RGB(70, 70, 70)]; + [m_textView setMinSize:NSMakeSize(0.0f, initialHeight)]; + [m_textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + [m_textView setVerticallyResizable:YES]; + [m_textView setHorizontallyResizable:NO]; + [m_textView setAutoresizingMask:NSViewWidthSizable]; + + NSTextContainer* const textContainer = [m_textView textContainer]; + [textContainer setContainerSize:NSMakeSize(initialWidth, FLT_MAX)]; + [textContainer setWidthTracksTextView:YES]; + + [m_scrollView initWithFrame:initialRect]; + [m_scrollView setBorderType:NSNoBorder]; + [m_scrollView setHasVerticalScroller:YES]; + [m_scrollView setHasHorizontalScroller:NO]; + [m_scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [m_scrollView setDocumentView:m_textView]; + + NSString* const title = [NSString stringWithFormat:@"%s %s - Console", GAMESIG, GetVersionString()]; + + [m_window initWithContentRect:initialRect + styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + [m_window setMinSize:[m_window frame].size]; + [m_window setShowsResizeIndicator:NO]; + [m_window setTitle:title]; + [m_window center]; + [m_window exitAppOnClose]; + + // Do not allow fullscreen mode for this window + [m_window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + + [[m_window contentView] addSubview:m_scrollView]; + + [m_window makeKeyAndOrderFront:nil]; +} + + +static FConsoleWindow* s_instance; + + +void FConsoleWindow::CreateInstance() +{ + assert(NULL == s_instance); + s_instance = new FConsoleWindow; +} + +void FConsoleWindow::DeleteInstance() +{ + assert(NULL != s_instance); + delete s_instance; + s_instance = NULL; +} + +FConsoleWindow& FConsoleWindow::GetInstance() +{ + assert(NULL != s_instance); + return *s_instance; +} + + +void FConsoleWindow::Show(const bool visible) +{ + if (visible) + { + [m_window orderFront:nil]; + } + else + { + [m_window orderOut:nil]; + } +} + +void FConsoleWindow::ShowFatalError(const char* const message) +{ + SetProgressBar(false); + NetDone(); + + const CGFloat textViewWidth = [m_scrollView frame].size.width; + + ExpandTextView(-32.0f); + + NSButton* quitButton = [[NSButton alloc] initWithFrame:NSMakeRect(textViewWidth - 76.0f, 0.0f, 72.0f, 30.0f)]; + [quitButton setAutoresizingMask:NSViewMinXMargin]; + [quitButton setBezelStyle:NSRoundedBezelStyle]; + [quitButton setTitle:@"Quit"]; + [quitButton setKeyEquivalent:@"\r"]; + [quitButton setTarget:NSApp]; + [quitButton setAction:@selector(stopModal)]; + + NSView* quitPanel = [[NSView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, textViewWidth, 32.0f)]; + [quitPanel setAutoresizingMask:NSViewWidthSizable]; + [quitPanel addSubview:quitButton]; + + [[m_window contentView] addSubview:quitPanel]; + [m_window orderFront:nil]; + + AddText(PalEntry(255, 0, 0), "\nExecution could not continue.\n"); + AddText(PalEntry(255, 255, 170), message); + AddText("\n"); + + ScrollTextToBottom(); + + [NSApp runModalForWindow:m_window]; +} + + +static const unsigned int THIRTY_FPS = 33; // milliseconds per update + + +template +struct TimedUpdater +{ + explicit TimedUpdater(const Function& function) + { + const unsigned int currentTime = I_msTime(); + + if (currentTime - m_previousTime > interval) + { + m_previousTime = currentTime; + + function(); + + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; + } + } + + static unsigned int m_previousTime; +}; + +template +unsigned int TimedUpdater::m_previousTime; + +template +static void UpdateTimed(const Function& function) +{ + TimedUpdater dummy(function); +} + + +void FConsoleWindow::AddText(const char* message) +{ + PalEntry color(223, 223, 223); + + char buffer[1024] = {}; + size_t pos = 0; + bool reset = false; + + while (*message != '\0') + { + if ((TEXTCOLOR_ESCAPE == *message && 0 != pos) + || (pos == sizeof buffer - 1) + || reset) + { + buffer[pos] = '\0'; + pos = 0; + reset = false; + + AddText(color, buffer); + } + + if (TEXTCOLOR_ESCAPE == *message) + { + const uint8_t* colorID = reinterpret_cast(message) + 1; + if ('\0' == *colorID) + { + break; + } + + const EColorRange range = V_ParseFontColor(colorID, CR_UNTRANSLATED, CR_YELLOW); + + if (range != CR_UNDEFINED) + { + color = V_LogColorFromColorRange(range); + } + + message += 2; + } + else if (0x1d == *message || 0x1f == *message) // Opening and closing bar characters + { + buffer[pos++] = '-'; + ++message; + } + else if (0x1e == *message) // Middle bar character + { + buffer[pos++] = '='; + ++message; + } + else + { + buffer[pos++] = *message++; + } + } + + if (0 != pos) + { + buffer[pos] = '\0'; + + AddText(color, buffer); + } + + if ([m_window isVisible]) + { + UpdateTimed([&]() + { + [m_textView scrollRangeToVisible:NSMakeRange(m_characterCount, 0)]; + }); + } +} + +void FConsoleWindow::AddText(const PalEntry& color, const char* const message) +{ + NSString* const text = [NSString stringWithCString:message + encoding:NSISOLatin1StringEncoding]; + + NSDictionary* const attributes = [NSDictionary dictionaryWithObjectsAndKeys: + [NSFont systemFontOfSize:14.0f], NSFontAttributeName, + RGB(color), NSForegroundColorAttributeName, + nil]; + + NSAttributedString* const formattedText = + [[NSAttributedString alloc] initWithString:text + attributes:attributes]; + [[m_textView textStorage] appendAttributedString:formattedText]; + + m_characterCount += [text length]; +} + + +void FConsoleWindow::ScrollTextToBottom() +{ + [m_textView scrollRangeToVisible:NSMakeRange(m_characterCount, 0)]; + + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; +} + + +void FConsoleWindow::SetTitleText() +{ + static const CGFloat TITLE_TEXT_HEIGHT = 32.0f; + + NSRect textViewFrame = [m_scrollView frame]; + textViewFrame.size.height -= TITLE_TEXT_HEIGHT; + [m_scrollView setFrame:textViewFrame]; + + const NSRect titleTextRect = NSMakeRect( + 0.0f, + textViewFrame.origin.y + textViewFrame.size.height, + textViewFrame.size.width, + TITLE_TEXT_HEIGHT); + + // Temporary solution for the same foreground and background colors + // It's used in graphical startup screen, with Hexen style in particular + // Native OS X backend doesn't implement this yet + + if (DoomStartupInfo.FgColor == DoomStartupInfo.BkColor) + { + DoomStartupInfo.FgColor = ~DoomStartupInfo.FgColor; + } + + NSTextField* titleText = [[NSTextField alloc] initWithFrame:titleTextRect]; + [titleText setStringValue:[NSString stringWithCString:DoomStartupInfo.Name + encoding:NSISOLatin1StringEncoding]]; + [titleText setAlignment:NSCenterTextAlignment]; + [titleText setTextColor:RGB(DoomStartupInfo.FgColor)]; + [titleText setBackgroundColor:RGB(DoomStartupInfo.BkColor)]; + [titleText setFont:[NSFont fontWithName:@"Trebuchet MS Bold" size:18.0f]]; + [titleText setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin]; + [titleText setSelectable:NO]; + [titleText setBordered:NO]; + + [[m_window contentView] addSubview:titleText]; +} + +void FConsoleWindow::SetProgressBar(const bool visible) +{ + if ( (!visible && nil == m_progressBar) + || (visible && nil != m_progressBar)) + { + return; + } + + if (visible) + { + ExpandTextView(-PROGRESS_BAR_HEIGHT); + + static const CGFloat PROGRESS_BAR_X = 2.0f; + const NSRect PROGRESS_BAR_RECT = NSMakeRect( + PROGRESS_BAR_X, 0.0f, + [m_window frame].size.width - PROGRESS_BAR_X * 2, 16.0f); + + m_progressBar = [[NSProgressIndicator alloc] initWithFrame:PROGRESS_BAR_RECT]; + [m_progressBar setIndeterminate:NO]; + [m_progressBar setAutoresizingMask:NSViewWidthSizable]; + + [[m_window contentView] addSubview:m_progressBar]; + } + else + { + ExpandTextView(PROGRESS_BAR_HEIGHT); + + [m_progressBar removeFromSuperview]; + [m_progressBar release]; + m_progressBar = nil; + } +} + + +void FConsoleWindow::ExpandTextView(const float height) +{ + NSRect textFrame = [m_scrollView frame]; + textFrame.origin.y -= height; + textFrame.size.height += height; + [m_scrollView setFrame:textFrame]; +} + + +void FConsoleWindow::Progress(const int current, const int maximum) +{ + if (nil == m_progressBar) + { + return; + } + + UpdateTimed([&]() + { + [m_progressBar setMaxValue:maximum]; + [m_progressBar setDoubleValue:current]; + }); +} + + +void FConsoleWindow::NetInit(const char* const message, const int playerCount) +{ + if (nil == m_netView) + { + SetProgressBar(false); + ExpandTextView(-NET_VIEW_HEIGHT); + + // Message like 'Waiting for players' or 'Contacting host' + m_netMessageText = [[NSTextField alloc] initWithFrame:NSMakeRect(12.0f, 64.0f, 400.0f, 16.0f)]; + [m_netMessageText setAutoresizingMask:NSViewWidthSizable]; + [m_netMessageText setDrawsBackground:NO]; + [m_netMessageText setSelectable:NO]; + [m_netMessageText setBordered:NO]; + + // Text with connected/total players count + m_netCountText = [[NSTextField alloc] initWithFrame:NSMakeRect(428.0f, 64.0f, 72.0f, 16.0f)]; + [m_netCountText setAutoresizingMask:NSViewMinXMargin]; + [m_netCountText setAlignment:NSRightTextAlignment]; + [m_netCountText setDrawsBackground:NO]; + [m_netCountText setSelectable:NO]; + [m_netCountText setBordered:NO]; + + // Connection progress + m_netProgressBar = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(12.0f, 40.0f, 488.0f, 16.0f)]; + [m_netProgressBar setAutoresizingMask:NSViewWidthSizable]; + [m_netProgressBar setMaxValue:playerCount]; + + if (0 == playerCount) + { + // Joining game + [m_netProgressBar setIndeterminate:YES]; + [m_netProgressBar startAnimation:nil]; + } + else + { + // Hosting game + [m_netProgressBar setIndeterminate:NO]; + } + + // Cancel network game button + m_netAbortButton = [[NSButton alloc] initWithFrame:NSMakeRect(432.0f, 8.0f, 72.0f, 28.0f)]; + [m_netAbortButton setAutoresizingMask:NSViewMinXMargin]; + [m_netAbortButton setBezelStyle:NSRoundedBezelStyle]; + [m_netAbortButton setTitle:@"Cancel"]; + [m_netAbortButton setKeyEquivalent:@"\r"]; + [m_netAbortButton setTarget:[NSApp delegate]]; + [m_netAbortButton setAction:@selector(sendExitEvent:)]; + + // Panel for controls above + m_netView = [[NSView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, 512.0f, NET_VIEW_HEIGHT)]; + [m_netView setAutoresizingMask:NSViewWidthSizable]; + [m_netView addSubview:m_netMessageText]; + [m_netView addSubview:m_netCountText]; + [m_netView addSubview:m_netProgressBar]; + [m_netView addSubview:m_netAbortButton]; + + NSRect windowRect = [m_window frame]; + windowRect.origin.y -= NET_VIEW_HEIGHT; + windowRect.size.height += NET_VIEW_HEIGHT; + + [m_window setFrame:windowRect display:YES]; + [[m_window contentView] addSubview:m_netView]; + + ScrollTextToBottom(); + } + + [m_netMessageText setStringValue:[NSString stringWithUTF8String:message]]; + + m_netCurPos = 0; + m_netMaxPos = playerCount; + + NetProgress(1); // You always know about yourself +} + +void FConsoleWindow::NetProgress(const int count) +{ + if (0 == count) + { + ++m_netCurPos; + } + else + { + m_netCurPos = count; + } + + if (nil == m_netView) + { + return; + } + + if (m_netMaxPos > 1) + { + [m_netCountText setStringValue:[NSString stringWithFormat:@"%d / %d", m_netCurPos, m_netMaxPos]]; + [m_netProgressBar setDoubleValue:MIN(m_netCurPos, m_netMaxPos)]; + } +} + +void FConsoleWindow::NetDone() +{ + if (nil != m_netView) + { + ExpandTextView(NET_VIEW_HEIGHT); + + [m_netView removeFromSuperview]; + [m_netView release]; + m_netView = nil; + + // Released by m_netView + m_netMessageText = nil; + m_netCountText = nil; + m_netProgressBar = nil; + m_netAbortButton = nil; + } +} diff --git a/source/platform/posix/cocoa/st_start.mm b/source/platform/posix/cocoa/st_start.mm new file mode 100644 index 000000000..feaa5412f --- /dev/null +++ b/source/platform/posix/cocoa/st_start.mm @@ -0,0 +1,176 @@ +/* + ** st_start.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2015 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include + +#import + +#include "c_cvars.h" +#include "doomtype.h" +#include "st_console.h" +#include "st_start.h" +#include "doomerrors.h" + + +FStartupScreen *StartScreen; + + +CUSTOM_CVAR(Int, showendoom, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0) + { + self = 0; + } + else if (self > 2) + { + self = 2; + } +} + + +// --------------------------------------------------------------------------- + + +FBasicStartupScreen::FBasicStartupScreen(int maxProgress, bool showBar) +: FStartupScreen(maxProgress) +{ + FConsoleWindow& consoleWindow = FConsoleWindow::GetInstance(); + consoleWindow.SetProgressBar(true); + consoleWindow.SetTitleText(); + +#if 0 + // Testing code, please do not remove + consoleWindow.AddText("----------------------------------------------------------------\n"); + consoleWindow.AddText("1234567890 !@#$%^&*() ,<.>/?;:'\" [{]}\\| `~-_=+ " + "This is very very very long message needed to trigger word wrapping...\n\n"); + consoleWindow.AddText("Multiline...\n\tmessage...\n\t\twith...\n\t\t\ttabs.\n\n"); + + consoleWindow.AddText(TEXTCOLOR_BRICK "TEXTCOLOR_BRICK\n" TEXTCOLOR_TAN "TEXTCOLOR_TAN\n"); + consoleWindow.AddText(TEXTCOLOR_GRAY "TEXTCOLOR_GRAY & TEXTCOLOR_GREY\n"); + consoleWindow.AddText(TEXTCOLOR_GREEN "TEXTCOLOR_GREEN\n" TEXTCOLOR_BROWN "TEXTCOLOR_BROWN\n"); + consoleWindow.AddText(TEXTCOLOR_GOLD "TEXTCOLOR_GOLD\n" TEXTCOLOR_RED "TEXTCOLOR_RED\n"); + consoleWindow.AddText(TEXTCOLOR_BLUE "TEXTCOLOR_BLUE\n" TEXTCOLOR_ORANGE "TEXTCOLOR_ORANGE\n"); + consoleWindow.AddText(TEXTCOLOR_WHITE "TEXTCOLOR_WHITE\n" TEXTCOLOR_YELLOW "TEXTCOLOR_YELLOW\n"); + consoleWindow.AddText(TEXTCOLOR_UNTRANSLATED "TEXTCOLOR_UNTRANSLATED\n"); + consoleWindow.AddText(TEXTCOLOR_BLACK "TEXTCOLOR_BLACK\n" TEXTCOLOR_LIGHTBLUE "TEXTCOLOR_LIGHTBLUE\n"); + consoleWindow.AddText(TEXTCOLOR_CREAM "TEXTCOLOR_CREAM\n" TEXTCOLOR_OLIVE "TEXTCOLOR_OLIVE\n"); + consoleWindow.AddText(TEXTCOLOR_DARKGREEN "TEXTCOLOR_DARKGREEN\n" TEXTCOLOR_DARKRED "TEXTCOLOR_DARKRED\n"); + consoleWindow.AddText(TEXTCOLOR_DARKBROWN "TEXTCOLOR_DARKBROWN\n" TEXTCOLOR_PURPLE "TEXTCOLOR_PURPLE\n"); + consoleWindow.AddText(TEXTCOLOR_DARKGRAY "TEXTCOLOR_DARKGRAY\n" TEXTCOLOR_CYAN "TEXTCOLOR_CYAN\n"); + consoleWindow.AddText(TEXTCOLOR_ICE "TEXTCOLOR_ICE\n" TEXTCOLOR_FIRE "TEXTCOLOR_FIRE\n"); + consoleWindow.AddText(TEXTCOLOR_SAPPHIRE "TEXTCOLOR_SAPPHIRE\n" TEXTCOLOR_TEAL "TEXTCOLOR_TEAL\n"); + consoleWindow.AddText(TEXTCOLOR_NORMAL "TEXTCOLOR_NORMAL\n" TEXTCOLOR_BOLD "TEXTCOLOR_BOLD\n"); + consoleWindow.AddText(TEXTCOLOR_CHAT "TEXTCOLOR_CHAT\n" TEXTCOLOR_TEAMCHAT "TEXTCOLOR_TEAMCHAT\n"); + consoleWindow.AddText("----------------------------------------------------------------\n"); +#endif // _DEBUG +} + +FBasicStartupScreen::~FBasicStartupScreen() +{ + FConsoleWindow::GetInstance().SetProgressBar(false); +} + + +void FBasicStartupScreen::Progress() +{ + if (CurPos < MaxPos) + { + ++CurPos; + } + + FConsoleWindow::GetInstance().Progress(CurPos, MaxPos); +} + + +void FBasicStartupScreen::NetInit(const char* const message, const int playerCount) +{ + FConsoleWindow::GetInstance().NetInit(message, playerCount); +} + +void FBasicStartupScreen::NetProgress(const int count) +{ + FConsoleWindow::GetInstance().NetProgress(count); +} + +void FBasicStartupScreen::NetMessage(const char* const format, ...) +{ + va_list args; + va_start(args, format); + + FString message; + message.VFormat(format, args); + va_end(args); + + Printf("%s\n", message.GetChars()); +} + +void FBasicStartupScreen::NetDone() +{ + FConsoleWindow::GetInstance().NetDone(); +} + +bool FBasicStartupScreen::NetLoop(bool (*timerCallback)(void*), void* const userData) +{ + while (true) + { + if (timerCallback(userData)) + { + break; + } + + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; + + // Do not poll to often + usleep(50000); + } + + return true; +} + + +// --------------------------------------------------------------------------- + + +FStartupScreen *FStartupScreen::CreateInstance(const int maxProgress) +{ + return new FBasicStartupScreen(maxProgress, true); +} + + +// --------------------------------------------------------------------------- + + +void ST_Endoom() +{ + throw CExitEvent(0); +} diff --git a/source/platform/posix/dikeys.h b/source/platform/posix/dikeys.h new file mode 100644 index 000000000..4541b0ffd --- /dev/null +++ b/source/platform/posix/dikeys.h @@ -0,0 +1,155 @@ +// ZDoom bases its keycodes on DirectInput's scan codes +// Why? Because it was Win32-only before porting to anything else, +// so this made sense. AFAIK, it's primarily used under Win32 now, +// so it still makes sense. +// +// Actually, these key codes may only be used for key bindings now, +// in which case they're not really necessary--if we tweaked c_bind.cpp. + +enum +{ + DIK_ESCAPE = 1, + DIK_1, + DIK_2, + DIK_3, + DIK_4, + DIK_5, + DIK_6, + DIK_7, + DIK_8, + DIK_9, + DIK_0, + DIK_MINUS, /* - on main keyboard */ + DIK_EQUALS, + DIK_BACK, /* backspace */ + DIK_TAB, + DIK_Q, + DIK_W, + DIK_E, + DIK_R, + DIK_T, + DIK_Y, + DIK_U, + DIK_I, + DIK_O, + DIK_P, + DIK_LBRACKET, + DIK_RBRACKET, + DIK_RETURN, /* Enter on main keyboard */ + DIK_LCONTROL, + DIK_A, + DIK_S, + DIK_D, + DIK_F, + DIK_G, + DIK_H, + DIK_J, + DIK_K, + DIK_L, + DIK_SEMICOLON, + DIK_APOSTROPHE, + DIK_GRAVE, /* accent grave */ + DIK_LSHIFT, + DIK_BACKSLASH, + DIK_Z, + DIK_X, + DIK_C, + DIK_V, + DIK_B, + DIK_N, + DIK_M, + DIK_COMMA, + DIK_PERIOD, /* . on main keyboard */ + DIK_SLASH, /* / on main keyboard */ + DIK_RSHIFT, + DIK_MULTIPLY, /* * on numeric keypad */ + DIK_LMENU, /* left Alt */ + DIK_SPACE, + DIK_CAPITAL, + DIK_F1, + DIK_F2, + DIK_F3, + DIK_F4, + DIK_F5, + DIK_F6, + DIK_F7, + DIK_F8, + DIK_F9, + DIK_F10, + DIK_NUMLOCK, + DIK_SCROLL, /* Scroll Lock */ + DIK_NUMPAD7, + DIK_NUMPAD8, + DIK_NUMPAD9, + DIK_SUBTRACT, /* - on numeric keypad */ + DIK_NUMPAD4, + DIK_NUMPAD5, + DIK_NUMPAD6, + DIK_ADD, /* + on numeric keypad */ + DIK_NUMPAD1, + DIK_NUMPAD2, + DIK_NUMPAD3, + DIK_NUMPAD0, + DIK_DECIMAL, /* . on numeric keypad */ + DIK_OEM_102 = 0x56, /* < > | on UK/Germany keyboards */ + DIK_F11, + DIK_F12, + DIK_F13 = 0x64, /* (NEC PC98) */ + DIK_F14, /* (NEC PC98) */ + DIK_F15, /* (NEC PC98) */ + DIK_KANA = 0x70, /* (Japanese keyboard) */ + DIK_ABNT_C1 = 0x73, /* / ? on Portugese (Brazilian) keyboards */ + DIK_CONVERT = 0x79, /* (Japanese keyboard) */ + DIK_NOCONVERT = 0x7B, /* (Japanese keyboard) */ + DIK_YEN = 0x7D, /* (Japanese keyboard) */ + DIK_ABNT_C2 = 0x7E, /* Numpad . on Portugese (Brazilian) keyboards */ + DIK_NUMPAD_EQUALS = 0x8D, /* = on numeric keypad (NEC PC98) */ + DIK_PREVTRACK = 0x90, /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */ + DIK_AT, /* (NEC PC98) */ + DIK_COLON, /* (NEC PC98) */ + DIK_UNDERLINE, /* (NEC PC98) */ + DIK_KANJI, /* (Japanese keyboard) */ + DIK_STOP, /* (NEC PC98) */ + DIK_AX, /* (Japan AX) */ + DIK_UNLABELED, /* (J3100) */ + DIK_NEXTTRACK = 0x99, /* Next Track */ + DIK_NUMPADENTER = 0x9C, /* Enter on numeric keypad */ + DIK_RCONTROL = 0x9D, + DIK_MUTE = 0xA0, /* Mute */ + DIK_CALCULATOR = 0xA1, /* Calculator */ + DIK_PLAYPAUSE = 0xA2, /* Play / Pause */ + DIK_MEDIASTOP = 0xA4, /* Media Stop */ + DIK_VOLUMEDOWN = 0xAE, /* Volume - */ + DIK_VOLUMEUP = 0xB0, /* Volume + */ + DIK_WEBHOME = 0xB2, /* Web home */ + DIK_NUMPADCOMMA = 0xB3, /* , on numeric keypad (NEC PC98) */ + DIK_DIVIDE = 0xB5, /* / on numeric keypad */ + DIK_SYSRQ = 0xB7, + DIK_RMENU = 0xB8, /* right Alt */ + DIK_PAUSE = 0xC5, /* Pause */ + DIK_HOME = 0xC7, /* Home on arrow keypad */ + DIK_UP = 0xC8, /* UpArrow on arrow keypad */ + DIK_PRIOR = 0xC9, /* PgUp on arrow keypad */ + DIK_LEFT = 0xCB, /* LeftArrow on arrow keypad */ + DIK_RIGHT = 0xCD, /* RightArrow on arrow keypad */ + DIK_END = 0xCF, /* End on arrow keypad */ + DIK_DOWN = 0xD0, /* DownArrow on arrow keypad */ + DIK_NEXT = 0xD1, /* PgDn on arrow keypad */ + DIK_INSERT = 0xD2, /* Insert on arrow keypad */ + DIK_DELETE = 0xD3, /* Delete on arrow keypad */ + DIK_LWIN = 0xDB, /* Left Windows key */ + DIK_RWIN = 0xDC, /* Right Windows key */ + DIK_APPS = 0xDD, /* AppMenu key */ + DIK_POWER = 0xDE, /* System Power */ + DIK_SLEEP = 0xDF, /* System Sleep */ + DIK_WAKE = 0xE3, /* System Wake */ + DIK_WEBSEARCH = 0xE5, /* Web Search */ + DIK_WEBFAVORITES = 0xE6, /* Web Favorites */ + DIK_WEBREFRESH = 0xE7, /* Web Refresh */ + DIK_WEBSTOP = 0xE8, /* Web Stop */ + DIK_WEBFORWARD = 0xE9, /* Web Forward */ + DIK_WEBBACK = 0xEA, /* Web Back */ + DIK_MYCOMPUTER = 0xEB, /* My Computer */ + DIK_MAIL = 0xEC, /* Mail */ + DIK_MEDIASELECT = 0xED /* Media Select */ +}; diff --git a/source/platform/posix/hardware.h b/source/platform/posix/hardware.h new file mode 100644 index 000000000..a7918935f --- /dev/null +++ b/source/platform/posix/hardware.h @@ -0,0 +1,40 @@ +/* +** hardware.h +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __HARDWARE_H__ +#define __HARDWARE_H__ + +#include "i_video.h" +#include "v_video.h" + +#endif // __HARDWARE_H__ diff --git a/source/platform/posix/i_steam.cpp b/source/platform/posix/i_steam.cpp new file mode 100644 index 000000000..dccadb021 --- /dev/null +++ b/source/platform/posix/i_steam.cpp @@ -0,0 +1,227 @@ +/* +** i_steam.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2013 Braden Obrzut +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include + +#ifdef __APPLE__ +#include "m_misc.h" +#endif // __APPLE__ + +#include "doomerrors.h" +#include "d_main.h" +#include "sc_man.h" +#include "cmdlib.h" + +static void PSR_FindEndBlock(FScanner &sc) +{ + int depth = 1; + do + { + if(sc.CheckToken('}')) + --depth; + else if(sc.CheckToken('{')) + ++depth; + else + sc.MustGetAnyToken(); + } + while(depth); +} +static void PSR_SkipBlock(FScanner &sc) +{ + sc.MustGetToken('{'); + PSR_FindEndBlock(sc); +} +static bool PSR_FindAndEnterBlock(FScanner &sc, const char* keyword) +{ + // Finds a block with a given keyword and then enter it (opening brace) + // Should be closed with PSR_FindEndBlock + while(sc.GetToken()) + { + if(sc.TokenType == '}') + { + sc.UnGet(); + return false; + } + + sc.TokenMustBe(TK_StringConst); + if(!sc.Compare(keyword)) + { + if(!sc.CheckToken(TK_StringConst)) + PSR_SkipBlock(sc); + } + else + { + sc.MustGetToken('{'); + return true; + } + } + return false; +} +static TArray PSR_ReadBaseInstalls(FScanner &sc) +{ + TArray result; + + // Get a list of possible install directories. + while(sc.GetToken()) + { + if(sc.TokenType == '}') + break; + + sc.TokenMustBe(TK_StringConst); + FString key(sc.String); + if(key.Left(18).CompareNoCase("BaseInstallFolder_") == 0) + { + sc.MustGetToken(TK_StringConst); + result.Push(FString(sc.String) + "/steamapps/common"); + } + else + { + if(sc.CheckToken('{')) + PSR_FindEndBlock(sc); + else + sc.MustGetToken(TK_StringConst); + } + } + + return result; +} +static TArray ParseSteamRegistry(const char* path) +{ + TArray dirs; + + // Read registry data + FScanner sc; + if (sc.OpenFile(path)) + { + sc.SetCMode(true); + + // Find the SteamApps listing + if (PSR_FindAndEnterBlock(sc, "InstallConfigStore")) + { + if (PSR_FindAndEnterBlock(sc, "Software")) + { + if (PSR_FindAndEnterBlock(sc, "Valve")) + { + if (PSR_FindAndEnterBlock(sc, "Steam")) + { + dirs = PSR_ReadBaseInstalls(sc); + } + PSR_FindEndBlock(sc); + } + PSR_FindEndBlock(sc); + } + PSR_FindEndBlock(sc); + } + } + return dirs; +} + +static struct SteamAppInfo +{ + const char* const BasePath; + const int AppID; +} AppInfo[] = +{ + {"Doom 2/base", 2300}, + {"Final Doom/base", 2290}, + {"Heretic Shadow of the Serpent Riders/base", 2390}, + {"Hexen/base", 2360}, + {"Hexen Deathkings of the Dark Citadel/base", 2370}, + {"Ultimate Doom/base", 2280}, + {"DOOM 3 BFG Edition/base/wads", 208200}, + {"Strife", 317040} +}; + +TArray I_GetSteamPath() +{ + TArray result; + TArray SteamInstallFolders; + + // Linux and OS X actually allow the user to install to any location, so + // we need to figure out on an app-by-app basis where the game is installed. + // To do so, we read the virtual registry. +#ifdef __APPLE__ + const FString appSupportPath = M_GetMacAppSupportPath(); + FString regPath = appSupportPath + "/Steam/config/config.vdf"; + try + { + SteamInstallFolders = ParseSteamRegistry(regPath); + } + catch(class CRecoverableError &error) + { + // If we can't parse for some reason just pretend we can't find anything. + return result; + } + + SteamInstallFolders.Push(appSupportPath + "/Steam/SteamApps/common"); +#else + char* home = getenv("HOME"); + if(home != NULL && *home != '\0') + { + FString regPath; + regPath.Format("%s/.steam/config/config.vdf", home); + // [BL] The config seems to have moved from the more modern .local to + // .steam at some point. Not sure if it's just my setup so I guess we + // can fall back on it? + if(!FileExists(regPath)) + regPath.Format("%s/.local/share/Steam/config/config.vdf", home); + + try + { + SteamInstallFolders = ParseSteamRegistry(regPath); + } + catch(class CRecoverableError &error) + { + // If we can't parse for some reason just pretend we can't find anything. + return result; + } + + regPath.Format("%s/.local/share/Steam/SteamApps/common", home); + SteamInstallFolders.Push(regPath); + } +#endif + + for(unsigned int i = 0;i < SteamInstallFolders.Size();++i) + { + for(unsigned int app = 0;app < countof(AppInfo);++app) + { + struct stat st; + FString candidate(SteamInstallFolders[i] + "/" + AppInfo[app].BasePath); + if(DirExists(candidate)) + result.Push(candidate); + } + } + + return result; +} diff --git a/source/platform/posix/i_system.h b/source/platform/posix/i_system.h new file mode 100644 index 000000000..49fcb9977 --- /dev/null +++ b/source/platform/posix/i_system.h @@ -0,0 +1,116 @@ +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include +#include + +#if defined(__sun) || defined(__sun__) || defined(__SRV4) || defined(__srv4__) +#define __solaris__ 1 +#endif + +#include "doomtype.h" +#include +#include + +struct ticcmd_t; +struct WadStuff; + +#ifndef SHARE_DIR +#define SHARE_DIR "/usr/local/share/" +#endif + + +// Called by DoomMain. +void I_Init (void); + +// Return a seed value for the RNG. +unsigned int I_MakeRNGSeed(); + + +void I_StartFrame (void); + +void I_StartTic (void); + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t *I_BaseTiccmd (void); + +void I_Tactile (int on, int off, int total); + +void I_DebugPrint (const char *cp); + +// Print a console string +void I_PrintStr (const char *str); + +// Set the title string of the startup window +void I_SetIWADInfo (); + +// Pick from multiple IWADs to use +int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad); + +// [RH] Checks the registry for Steam's install path, so we can scan its +// directories for IWADs if the user purchased any through Steam. +TArray I_GetSteamPath(); + +TArray I_GetGogPaths(); + +// The ini could not be saved at exit +bool I_WriteIniFailed (); + +class FTexture; +bool I_SetCursor(FTexture *); + +// Directory searching routines + +struct findstate_t +{ +private: + int count; + struct dirent **namelist; + int current; + + friend void *I_FindFirst(const char *filespec, findstate_t *fileinfo); + friend int I_FindNext(void *handle, findstate_t *fileinfo); + friend const char *I_FindName(findstate_t *fileinfo); + friend int I_FindAttr(findstate_t *fileinfo); + friend int I_FindClose(void *handle); +}; + +void *I_FindFirst (const char *filespec, findstate_t *fileinfo); +int I_FindNext (void *handle, findstate_t *fileinfo); +int I_FindClose (void *handle); +int I_FindAttr (findstate_t *fileinfo); + +inline const char *I_FindName(findstate_t *fileinfo) +{ + return (fileinfo->namelist[fileinfo->current]->d_name); +} + +#define FA_RDONLY 1 +#define FA_HIDDEN 2 +#define FA_SYSTEM 4 +#define FA_DIREC 8 +#define FA_ARCH 16 + +static inline char *strlwr(char *str) +{ + char *ptr = str; + while(*ptr) + { + *ptr = tolower(*ptr); + ++ptr; + } + return str; +} + +inline int I_GetNumaNodeCount() { return 1; } +inline int I_GetNumaNodeThreadCount(int numaNode) { return std::max(std::thread::hardware_concurrency(), 1); } +inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { } + +#endif diff --git a/source/platform/macos/i_specialpaths.mm b/source/platform/posix/osx/i_specialpaths.mm similarity index 100% rename from source/platform/macos/i_specialpaths.mm rename to source/platform/posix/osx/i_specialpaths.mm diff --git a/source/platform/posix/osx/iwadpicker_cocoa.mm b/source/platform/posix/osx/iwadpicker_cocoa.mm new file mode 100644 index 000000000..fd9c60014 --- /dev/null +++ b/source/platform/posix/osx/iwadpicker_cocoa.mm @@ -0,0 +1,470 @@ +/* + ** iwadpicker_cocoa.mm + ** + ** Implements Mac OS X native IWAD Picker. + ** + **--------------------------------------------------------------------------- + ** Copyright 2010 Braden Obrzut + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "cmdlib.h" +#include "d_main.h" +#include "version.h" +#include "c_cvars.h" +#include "m_argv.h" +#include "m_misc.h" +#include "gameconfigfile.h" +#include "doomerrors.h" + +#include +#include + + +CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); + +enum +{ + COLUMN_IWAD, + COLUMN_GAME, + + NUM_COLUMNS +}; + +static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; + +// Class to convert the IWAD data into a form that Cocoa can use. +@interface IWADTableData : NSObject +{ + NSMutableArray *data; +} + +- (void)dealloc; +- (IWADTableData *)init:(WadStuff *) wads num:(int) numwads; + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView; +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex; +@end + +@implementation IWADTableData + +- (void)dealloc +{ + [data release]; + + [super dealloc]; +} + +- (IWADTableData *)init:(WadStuff *) wads num:(int) numwads +{ + data = [[NSMutableArray alloc] initWithCapacity:numwads]; + + for(int i = 0;i < numwads;i++) + { + NSMutableDictionary *record = [[NSMutableDictionary alloc] initWithCapacity:NUM_COLUMNS]; + const char* filename = strrchr(wads[i].Path, '/'); + if(filename == NULL) + filename = wads[i].Path; + else + filename++; + [record setObject:[NSString stringWithUTF8String:filename] forKey:[NSString stringWithUTF8String:tableHeaders[COLUMN_IWAD]]]; + [record setObject:[NSString stringWithUTF8String:wads[i].Name] forKey:[NSString stringWithUTF8String:tableHeaders[COLUMN_GAME]]]; + [data addObject:record]; + [record release]; + } + + return self; +} + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return [data count]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex +{ + NSParameterAssert(rowIndex >= 0 && (unsigned int) rowIndex < [data count]); + NSMutableDictionary *record = [data objectAtIndex:rowIndex]; + return [record objectForKey:[aTableColumn identifier]]; +} + +@end + +static NSDictionary* GetKnownFileTypes() +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + @"-file" , @"wad", + @"-file" , @"pk3", + @"-file" , @"zip", + @"-file" , @"pk7", + @"-file" , @"7z", + @"-deh" , @"deh", + @"-bex" , @"bex", + @"-exec" , @"cfg", + @"-playdemo", @"lmp", + nil]; +} + +static NSArray* GetKnownExtensions() +{ + return [GetKnownFileTypes() allKeys]; +} + +@interface NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath; +@end + +@implementation NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath +{ + NSString* extension = [[filePath pathExtension] lowercaseString]; + NSString* parameter = [GetKnownFileTypes() objectForKey:extension]; + + if (nil == parameter) + { + return; + } + + [self appendFormat:@"%@ \"%@\" ", parameter, filePath]; +} +@end + +// So we can listen for button actions and such we need to have an Obj-C class. +@interface IWADPicker : NSObject +{ + NSApplication *app; + NSWindow *window; + NSButton *okButton; + NSButton *cancelButton; + NSButton *browseButton; + NSTextField *parametersTextField; + bool cancelled; +} + +- (void)buttonPressed:(id) sender; +- (void)browseButtonPressed:(id) sender; +- (void)doubleClicked:(id) sender; +- (void)makeLabel:(NSTextField *)label withString:(const char*) str; +- (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad; +- (NSString*)commandLineParameters; +- (void)menuActionSent:(NSNotification*)notification; +@end + +@implementation IWADPicker + +- (void)buttonPressed:(id) sender +{ + if(sender == cancelButton) + cancelled = true; + + [window orderOut:self]; + [app stopModal]; +} + +- (void)browseButtonPressed:(id) sender +{ + NSOpenPanel* openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowsMultipleSelection:YES]; + [openPanel setCanChooseFiles:YES]; + [openPanel setCanChooseDirectories:YES]; + [openPanel setResolvesAliases:YES]; + [openPanel setAllowedFileTypes:GetKnownExtensions()]; + + if (NSOKButton == [openPanel runModal]) + { + NSArray* files = [openPanel URLs]; + NSMutableString* parameters = [NSMutableString string]; + + for (NSUInteger i = 0, ei = [files count]; i < ei; ++i) + { + NSString* filePath = [[files objectAtIndex:i] path]; + BOOL isDirectory = false; + + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory) + { + [parameters appendFormat:@"-file \"%@\" ", filePath]; + } + else + { + [parameters appendKnownFileType:filePath]; + } + } + + if ([parameters length] > 0) + { + NSString* newParameters = [parametersTextField stringValue]; + + if ([newParameters length] > 0 + && NO == [newParameters hasSuffix:@" "]) + { + newParameters = [newParameters stringByAppendingString:@" "]; + } + + newParameters = [newParameters stringByAppendingString:parameters]; + + [parametersTextField setStringValue: newParameters]; + } + } +} + +- (void)doubleClicked:(id) sender +{ + if ([sender clickedRow] >= 0) + { + [window orderOut:self]; + [app stopModal]; + } +} + +// Apparently labels in Cocoa are uneditable text fields, so lets make this a +// little more automated. +- (void)makeLabel:(NSTextField *)label withString:(const char*) str +{ + [label setStringValue:[NSString stringWithUTF8String:str]]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + [label setEditable:NO]; + [label setSelectable:NO]; +} + +- (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad +{ + cancelled = false; + + app = [NSApplication sharedApplication]; + id windowTitle = [NSString stringWithFormat:@"%s %s", GAMENAME, GetVersionString()]; + + NSRect frame = NSMakeRect(0, 0, 440, 450); + window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; + [window setTitle:windowTitle]; + + NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 384, 402, 50)]; + [self makeLabel:description withString:GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"]; + [[window contentView] addSubview:description]; + [description release]; + + NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 135, 402, 256)]; + NSTableView *iwadTable = [[NSTableView alloc] initWithFrame:[iwadScroller bounds]]; + IWADTableData *tableData = [[IWADTableData alloc] init:wads num:numwads]; + for(int i = 0;i < NUM_COLUMNS;i++) + { + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:[NSString stringWithUTF8String:tableHeaders[i]]]; + [[column headerCell] setStringValue:[column identifier]]; + if(i == 0) + [column setMaxWidth:110]; + [column setEditable:NO]; + [column setResizingMask:NSTableColumnAutoresizingMask]; + [iwadTable addTableColumn:column]; + [column release]; + } + [iwadScroller setDocumentView:iwadTable]; + [iwadScroller setHasVerticalScroller:YES]; + [iwadTable setDataSource:tableData]; + [iwadTable sizeToFit]; + [iwadTable setDoubleAction:@selector(doubleClicked:)]; + [iwadTable setTarget:self]; + NSIndexSet *selection = [[NSIndexSet alloc] initWithIndex:defaultiwad]; + [iwadTable selectRowIndexes:selection byExtendingSelection:NO]; + [selection release]; + [iwadTable scrollRowToVisible:defaultiwad]; + [[window contentView] addSubview:iwadScroller]; + [iwadTable release]; + [iwadScroller release]; + + NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 108, 144, 17)]; + [self makeLabel:additionalParametersLabel withString:"Additional Parameters:"]; + [[window contentView] addSubview:additionalParametersLabel]; + parametersTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 402, 54)]; + [parametersTextField setStringValue:[NSString stringWithUTF8String:osx_additional_parameters]]; + [[window contentView] addSubview:parametersTextField]; + + // Doesn't look like the SDL version implements this so lets not show it. + /*NSButton *dontAsk = [[NSButton alloc] initWithFrame:NSMakeRect(18, 18, 178, 18)]; + [dontAsk setTitle:[NSString stringWithCString:"Don't ask me this again"]]; + [dontAsk setButtonType:NSSwitchButton]; + [dontAsk setState:(showwin ? NSOffState : NSOnState)]; + [[window contentView] addSubview:dontAsk];*/ + + okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 8, 96, 32)]; + [okButton setTitle:@"OK"]; + [okButton setBezelStyle:NSRoundedBezelStyle]; + [okButton setAction:@selector(buttonPressed:)]; + [okButton setTarget:self]; + [okButton setKeyEquivalent:@"\r"]; + [[window contentView] addSubview:okButton]; + + cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 8, 96, 32)]; + [cancelButton setTitle:@"Cancel"]; + [cancelButton setBezelStyle:NSRoundedBezelStyle]; + [cancelButton setAction:@selector(buttonPressed:)]; + [cancelButton setTarget:self]; + [cancelButton setKeyEquivalent:@"\033"]; + [[window contentView] addSubview:cancelButton]; + + browseButton = [[NSButton alloc] initWithFrame:NSMakeRect(14, 8, 96, 32)]; + [browseButton setTitle:@"Browse..."]; + [browseButton setBezelStyle:NSRoundedBezelStyle]; + [browseButton setAction:@selector(browseButtonPressed:)]; + [browseButton setTarget:self]; + [[window contentView] addSubview:browseButton]; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(menuActionSent:) name:NSMenuDidSendActionNotification object:nil]; + + [window center]; + [app runModalForWindow:window]; + + [center removeObserver:self name:NSMenuDidSendActionNotification object:nil]; + + [window release]; + [okButton release]; + [cancelButton release]; + [browseButton release]; + + return cancelled ? -1 : [iwadTable selectedRow]; +} + +- (NSString*)commandLineParameters +{ + return [parametersTextField stringValue]; +} + +- (void)menuActionSent:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + NSMenuItem* menuItem = [userInfo valueForKey:@"MenuItem"]; + + if ( @selector(terminate:) == [menuItem action] ) + { + throw CExitEvent(0); + } +} + +@end + + +EXTERN_CVAR(String, defaultiwad) + +static NSString* GetArchitectureString() +{ +#ifdef __i386__ + return @"i386"; +#elif defined __x86_64__ + return @"x86_64"; +#endif +} + +static void RestartWithParameters(const WadStuff& wad, NSString* parameters) +{ + assert(nil != parameters); + + defaultiwad = wad.Name; + + GameConfig->DoGameSetup("Doom"); + M_SaveDefaults(NULL); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + @try + { + NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)]; + + NSMutableArray* const arguments = [[NSMutableArray alloc] init]; + + // The following value shoud be equal to NSAppKitVersionNumber10_5 + // It's hard-coded in order to build with earlier SDKs + const bool canSelectArchitecture = NSAppKitVersionNumber >= 949; + + if (canSelectArchitecture) + { + [arguments addObject:@"-arch"]; + [arguments addObject:GetArchitectureString()]; + [arguments addObject:executablePath]; + + executablePath = @"/usr/bin/arch"; + } + + [arguments addObject:@"-iwad"]; + [arguments addObject:[NSString stringWithUTF8String:wad.Path]]; + + for (int i = 1, count = Args->NumArgs(); i < count; ++i) + { + NSString* currentParameter = [NSString stringWithUTF8String:Args->GetArg(i)]; + [arguments addObject:currentParameter]; + } + + wordexp_t expansion = {}; + + if (0 == wordexp([parameters UTF8String], &expansion, 0)) + { + for (size_t i = 0; i < expansion.we_wordc; ++i) + { + NSString* argumentString = [NSString stringWithCString:expansion.we_wordv[i] + encoding:NSUTF8StringEncoding]; + [arguments addObject:argumentString]; + } + + wordfree(&expansion); + } + + [NSTask launchedTaskWithLaunchPath:executablePath + arguments:arguments]; + + _exit(0); // to avoid atexit()'s functions + } + @catch (NSException* e) + { + NSLog(@"Cannot restart: %@", [e reason]); + } + + [pool release]; +} + +// Simple wrapper so we can call this from outside. +int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + IWADPicker *picker = [IWADPicker alloc]; + int ret = [picker pickIWad:wads num:numwads showWindow:showwin defaultWad:defaultiwad]; + + NSString* parametersToAppend = [picker commandLineParameters]; + osx_additional_parameters = [parametersToAppend UTF8String]; + + if (ret >= 0) + { + if (0 != [parametersToAppend length]) + { + RestartWithParameters(wads[ret], parametersToAppend); + } + } + + [pool release]; + + return ret; +} diff --git a/source/platform/posix/osx/zdoom-info.plist b/source/platform/posix/osx/zdoom-info.plist new file mode 100644 index 000000000..f0bbbf3d9 --- /dev/null +++ b/source/platform/posix/osx/zdoom-info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + zdoom.icns + CFBundleIdentifier + org.drdteam.gzdoom + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + GZDoom + CFBundlePackageType + APPL + CFBundleShortVersionString + Development Version + CFBundleSignature + ???? + LSApplicationCategoryType + public.app-category.action-games + LSMinimumSystemVersion + 10.9 + CFBundleDocumentTypes + + + CFBundleTypeName + Doom Resource File + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + wad + pk3 + zip + pk7 + 7z + iwad + ipk3 + ipk7 + + + + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/source/platform/posix/osx/zdoom.icns b/source/platform/posix/osx/zdoom.icns new file mode 100644 index 000000000..a0df7f5f7 Binary files /dev/null and b/source/platform/posix/osx/zdoom.icns differ diff --git a/source/platform/posix/readme.md b/source/platform/posix/readme.md new file mode 100644 index 000000000..ce66dab2c --- /dev/null +++ b/source/platform/posix/readme.md @@ -0,0 +1,6 @@ +This directory contains files required to support POSIX-compatible OSes, like GNU/Linux, OS X or BSD. + +Common files are placed in this directory directly. +SDL backend files are in `sdl` subdirectory. +Native OS X backend files are in `cocoa` subdirectory. +Shared files for both OS X backends are in `osx` subdirectory. diff --git a/source/platform/posix/sdl/crashcatcher.c b/source/platform/posix/sdl/crashcatcher.c new file mode 100644 index 000000000..be0e8f599 --- /dev/null +++ b/source/platform/posix/sdl/crashcatcher.c @@ -0,0 +1,425 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#elif defined (__APPLE__) || defined (__FreeBSD__) || defined(__OpenBSD__) +#include +#endif + + +static const char crash_switch[] = "--cc-handle-crash"; + +static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; +static const char pipe_err[] = "!!! Failed to create pipe\n"; +static const char fork_err[] = "!!! Failed to fork debug process\n"; +static const char exec_err[] = "!!! Failed to exec debug process\n"; + +static char argv0[PATH_MAX]; + +static char altstack[SIGSTKSZ]; + + +static struct { + int signum; + pid_t pid; + int has_siginfo; + siginfo_t siginfo; + char buf[4096]; +} crash_info; + + +static const struct { + const char *name; + int signum; +} signals[] = { + { "Segmentation fault", SIGSEGV }, + { "Illegal instruction", SIGILL }, + { "FPU exception", SIGFPE }, + { "System BUS error", SIGBUS }, + { NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { +#ifndef __FreeBSD__ + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { +#ifndef __FreeBSD__ + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { +#ifndef __FreeBSD__ + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, +#endif + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + + +static void gdb_info(pid_t pid) +{ + char respfile[64]; + char cmd_buf[128]; + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + { + fprintf(f, "attach %d\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "thread apply all backtrace full\n" + "detach\n" + "quit\n", pid); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); + printf("Executing: %s\n", cmd_buf); + fflush(stdout); + + system(cmd_buf); + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("!!! Could not create gdb command file\n"); + } + fflush(stdout); +} + +static void sys_info(void) +{ +#ifdef __unix__ + system("echo \"System: `uname -a`\""); + putchar('\n'); + fflush(stdout); +#endif +} + + +static size_t safe_write(int fd, const void *buf, size_t len) +{ + size_t ret = 0; + while(ret < len) + { + ssize_t rem; + if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) + { + if(errno == EINTR) + continue; + break; + } + ret += rem; + } + return ret; +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ + //ucontext_t *ucontext = (ucontext_t*)context; + pid_t dbg_pid; + int fd[2]; + + /* Make sure the effective uid is the real uid */ + if(getuid() != geteuid()) + { + raise(signum); + return; + } + + safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); + if(pipe(fd) == -1) + { + safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); + raise(signum); + return; + } + + crash_info.signum = signum; + crash_info.pid = getpid(); + crash_info.has_siginfo = !!siginfo; + if(siginfo) + crash_info.siginfo = *siginfo; + if(cc_user_info) + cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); + + /* Fork off to start a crash handler */ + switch((dbg_pid=fork())) + { + /* Error */ + case -1: + safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); + raise(signum); + return; + + case 0: + dup2(fd[0], STDIN_FILENO); + close(fd[0]); + close(fd[1]); + + execl(argv0, argv0, crash_switch, NULL); + + safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); + _exit(1); + + default: +#ifdef __linux__ + prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); +#endif + safe_write(fd[1], &crash_info, sizeof(crash_info)); + close(fd[0]); + close(fd[1]); + + /* Wait; we'll be killed when gdb is done */ + do { + int status; + if(waitpid(dbg_pid, &status, 0) == dbg_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) + { + /* The debug process died before it could kill us */ + raise(signum); + break; + } + } while(1); + } +} + +static void crash_handler(const char *logfile) +{ + const char *sigdesc = ""; + int i; + + if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) + { + fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + exit(1); + } + + /* Get the signal description */ + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == crash_info.signum) + { + sigdesc = signals[i].name; + break; + } + } + + if(crash_info.has_siginfo) + { + switch(crash_info.signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stderr); + + if(logfile) + { + /* Create crash log file and redirect shell output to it */ + if(freopen(logfile, "wa", stdout) != stdout) + { + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); + } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf("*** Fatal Error ***\n" + "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + printf("Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stdout); + fflush(stdout); + } + + sys_info(); + + crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; + printf("%s\n", crash_info.buf); + fflush(stdout); + + if(crash_info.pid > 0) + { + gdb_info(crash_info.pid); + kill(crash_info.pid, SIGKILL); + } + + if(logfile) + { + const char *str; + char buf[512]; + + if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) + snprintf(buf, sizeof(buf), "kdialog --title \"Very Fatal Error\" --textbox \"%s\" 800 600", logfile); + else if((str=getenv("GNOME_DESKTOP_SESSION_ID")) && str[0] != '\0') + snprintf(buf, sizeof(buf), "gxmessage -buttons \"Okay:0\" -geometry 800x600 -title \"Very Fatal Error\" -center -file \"%s\"", logfile); + else + snprintf(buf, sizeof(buf), "xmessage -buttons \"Okay:0\" -center -file \"%s\"", logfile); + + system(buf); + } + exit(0); +} + +int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + struct sigaction sa; + stack_t altss; + int retval; + + if(argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(logfile); + + cc_user_info = user_info; + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else + { + getcwd(argv0, sizeof(argv0)); + retval = strlen(argv0); + snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); + } + + /* Set an alternate signal stack so SIGSEGVs caused by stack overflows + * still run */ + altss.ss_sp = altstack; + altss.ss_flags = 0; + altss.ss_size = sizeof(altstack); + sigaltstack(&altss, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + + retval = 0; + while(num_signals--) + { + if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && + *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + return retval; +} diff --git a/source/platform/posix/sdl/gl_sysfb.h b/source/platform/posix/sdl/gl_sysfb.h new file mode 100644 index 000000000..e9491d098 --- /dev/null +++ b/source/platform/posix/sdl/gl_sysfb.h @@ -0,0 +1,49 @@ +#ifndef __POSIX_SDL_GL_SYSFB_H__ +#define __POSIX_SDL_GL_SYSFB_H__ + +#include + +#include "v_video.h" + +class SystemBaseFrameBuffer : public DFrameBuffer +{ + typedef DFrameBuffer Super; + +public: + // this must have the same parameters as the Windows version, even if they are not used! + SystemBaseFrameBuffer (void *hMonitor, bool fullscreen); + + bool IsFullscreen() override; + + int GetClientWidth() override; + int GetClientHeight() override; + + void ToggleFullscreen(bool yes) override; + void SetWindowSize(int client_w, int client_h) override; + +protected: + SystemBaseFrameBuffer () {} +}; + +class SystemGLFrameBuffer : public SystemBaseFrameBuffer +{ + typedef SystemBaseFrameBuffer Super; + +public: + SystemGLFrameBuffer(void *hMonitor, bool fullscreen); + ~SystemGLFrameBuffer(); + + int GetClientWidth() override; + int GetClientHeight() override; + + virtual void SetVSync(bool vsync) override; + void SwapBuffers(); + +protected: + SDL_GLContext GLContext; + + SystemGLFrameBuffer() {} +}; + +#endif // __POSIX_SDL_GL_SYSFB_H__ + diff --git a/source/platform/posix/sdl/hardware.cpp b/source/platform/posix/sdl/hardware.cpp new file mode 100644 index 000000000..9493b7c48 --- /dev/null +++ b/source/platform/posix/sdl/hardware.cpp @@ -0,0 +1,85 @@ +/* +** hardware.cpp +** Somewhat OS-independant interface to the screen, mouse, keyboard, and stick +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include + +#include "i_system.h" +#include "hardware.h" +#include "c_dispatch.h" +#include "v_text.h" +#include "doomstat.h" +#include "m_argv.h" +#include "doomerrors.h" +#include "swrenderer/r_swrenderer.h" + +IVideo *Video; + +void I_RestartRenderer(); + + +void I_ShutdownGraphics () +{ + if (screen) + { + DFrameBuffer *s = screen; + screen = NULL; + delete s; + } + if (Video) + delete Video, Video = NULL; + + SDL_QuitSubSystem (SDL_INIT_VIDEO); +} + +void I_InitGraphics () +{ +#ifdef __APPLE__ + SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "0"); +#endif // __APPLE__ + + if (SDL_InitSubSystem (SDL_INIT_VIDEO) < 0) + { + I_FatalError ("Could not initialize SDL video:\n%s\n", SDL_GetError()); + return; + } + + Printf("Using video driver %s\n", SDL_GetCurrentVideoDriver()); + + extern IVideo *gl_CreateVideo(); + Video = gl_CreateVideo(); + + if (Video == NULL) + I_FatalError ("Failed to initialize display"); +} diff --git a/source/platform/posix/sdl/i_gui.cpp b/source/platform/posix/sdl/i_gui.cpp new file mode 100644 index 000000000..8ad9c8b92 --- /dev/null +++ b/source/platform/posix/sdl/i_gui.cpp @@ -0,0 +1,87 @@ +/* +** i_gui.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include + +#include + +#include "bitmap.h" +#include "v_palette.h" +#include "textures.h" + +bool I_SetCursor(FTexture *cursorpic) +{ + static SDL_Cursor *cursor; + static SDL_Surface *cursorSurface; + + if (cursorpic != NULL && cursorpic->isValid()) + { + auto src = cursorpic->GetBgraBitmap(nullptr); + // Must be no larger than 32x32. + if (src.GetWidth() > 32 || src.GetHeight() > 32) + { + return false; + } + + if (cursorSurface == NULL) + cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0)); + + SDL_LockSurface(cursorSurface); + uint8_t buffer[32*32*4]; + memset(buffer, 0, 32*32*4); + FBitmap bmp(buffer, 32*4, 32, 32); + bmp.Blit(0, 0, src); // expand to 32*32 + memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4); + SDL_UnlockSurface(cursorSurface); + + if (cursor) + SDL_FreeCursor (cursor); + cursor = SDL_CreateColorCursor (cursorSurface, 0, 0); + SDL_SetCursor (cursor); + } + else + { + if (cursor) + { + SDL_SetCursor (NULL); + SDL_FreeCursor (cursor); + cursor = NULL; + } + if (cursorSurface != NULL) + { + SDL_FreeSurface(cursorSurface); + cursorSurface = NULL; + } + } + return true; +} diff --git a/source/platform/posix/sdl/i_input.cpp b/source/platform/posix/sdl/i_input.cpp new file mode 100644 index 000000000..77e6f1860 --- /dev/null +++ b/source/platform/posix/sdl/i_input.cpp @@ -0,0 +1,568 @@ +/* +** i_input.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2005-2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include +#include "doomtype.h" +#include "doomdef.h" +#include "doomstat.h" +#include "m_argv.h" +#include "v_video.h" + +#include "d_main.h" +#include "d_event.h" +#include "d_gui.h" +#include "c_console.h" +#include "c_dispatch.h" +#include "dikeys.h" +#include "events.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "utf8.h" +#include "doomerrors.h" + + +static void I_CheckGUICapture (); +static void I_CheckNativeMouse (); + +bool GUICapture; +static bool NativeMouse = true; + +extern int paused; + +CVAR (Bool, use_mouse, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, m_noprescale, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, m_filter, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + + +extern int WaitingForKey, chatmodeon; +extern constate_e ConsoleState; + +static const SDL_Keycode DIKToKeySym[256] = +{ + 0, SDLK_ESCAPE, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, + SDLK_7, SDLK_8, SDLK_9, SDLK_0,SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_TAB, + SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, + SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, SDLK_RETURN, SDLK_LCTRL, SDLK_a, SDLK_s, + SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, + SDLK_QUOTE, SDLK_BACKQUOTE, SDLK_LSHIFT, SDLK_BACKSLASH, SDLK_z, SDLK_x, SDLK_c, SDLK_v, + SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_KP_MULTIPLY, + SDLK_LALT, SDLK_SPACE, SDLK_CAPSLOCK, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, + SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_NUMLOCKCLEAR, SDLK_SCROLLLOCK, SDLK_KP_7, + SDLK_KP_8, SDLK_KP_9, SDLK_KP_MINUS, SDLK_KP_4, SDLK_KP_5, SDLK_KP_6, SDLK_KP_PLUS, SDLK_KP_1, + SDLK_KP_2, SDLK_KP_3, SDLK_KP_0, SDLK_KP_PERIOD, 0, 0, 0, SDLK_F11, + SDLK_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, SDLK_F13, SDLK_F14, SDLK_F15, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, SDLK_KP_EQUALS, 0, 0, + 0, SDLK_AT, SDLK_COLON, 0, 0, 0, 0, 0, + 0, 0, 0, 0, SDLK_KP_ENTER, SDLK_RCTRL, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, SDLK_KP_COMMA, 0, SDLK_KP_DIVIDE, 0, SDLK_SYSREQ, + SDLK_RALT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, SDLK_PAUSE, 0, SDLK_HOME, + SDLK_UP, SDLK_PAGEUP, 0, SDLK_LEFT, 0, SDLK_RIGHT, 0, SDLK_END, + SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, 0, 0, 0, 0, + 0, 0, 0, SDLK_LGUI, SDLK_RGUI, SDLK_MENU, SDLK_POWER, SDLK_SLEEP, + 0, 0, 0, 0, 0, SDLK_AC_SEARCH, SDLK_AC_BOOKMARKS, SDLK_AC_REFRESH, + SDLK_AC_STOP, SDLK_AC_FORWARD, SDLK_AC_BACK, SDLK_COMPUTER, SDLK_MAIL, SDLK_MEDIASELECT, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const SDL_Scancode DIKToKeyScan[256] = +{ + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, + SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0 ,SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, + SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, + SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, + SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, + SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, + SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_KP_MULTIPLY, + SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, + SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_KP_7, + SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_9, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_KP_4, SDL_SCANCODE_KP_5, SDL_SCANCODE_KP_6, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_KP_1, + SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_3, SDL_SCANCODE_KP_0, SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F11, + SDL_SCANCODE_F12, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_EQUALS, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RCTRL, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_COMMA, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_SYSREQ, + SDL_SCANCODE_RALT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_HOME, + SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_END, + SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_MENU, SDL_SCANCODE_POWER, SDL_SCANCODE_SLEEP, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_AC_SEARCH, SDL_SCANCODE_AC_BOOKMARKS, SDL_SCANCODE_AC_REFRESH, + SDL_SCANCODE_AC_STOP, SDL_SCANCODE_AC_FORWARD, SDL_SCANCODE_AC_BACK, SDL_SCANCODE_COMPUTER, SDL_SCANCODE_MAIL, SDL_SCANCODE_MEDIASELECT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN +}; + +static TMap InitKeySymMap () +{ + TMap KeySymToDIK; + + for (int i = 0; i < 256; ++i) + { + KeySymToDIK[DIKToKeySym[i]] = i; + } + KeySymToDIK[0] = 0; + KeySymToDIK[SDLK_RSHIFT] = DIK_LSHIFT; + KeySymToDIK[SDLK_RCTRL] = DIK_LCONTROL; + KeySymToDIK[SDLK_RALT] = DIK_LMENU; + // Depending on your Linux flavor, you may get SDLK_PRINT or SDLK_SYSREQ + KeySymToDIK[SDLK_PRINTSCREEN] = DIK_SYSRQ; + + return KeySymToDIK; +} +static const TMap KeySymToDIK(InitKeySymMap()); + +static TMap InitKeyScanMap () +{ + TMap KeyScanToDIK; + + for (int i = 0; i < 256; ++i) + { + KeyScanToDIK[DIKToKeyScan[i]] = i; + } + + return KeyScanToDIK; +} +static const TMap KeyScanToDIK(InitKeyScanMap()); + +static void I_CheckGUICapture () +{ + bool wantCapt; + + if (menuactive == MENU_Off) + { + wantCapt = ConsoleState == c_down || ConsoleState == c_falling || chatmodeon; + } + else + { + wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause); + } + + // [ZZ] check active event handlers that want the UI processing + if (!wantCapt && primaryLevel->localEventManager->CheckUiProcessors()) + wantCapt = true; + + if (wantCapt != GUICapture) + { + GUICapture = wantCapt; + ResetButtonStates(); + } +} + +void I_SetMouseCapture() +{ + // Clear out any mouse movement. + SDL_GetRelativeMouseState (NULL, NULL); + SDL_SetRelativeMouseMode (SDL_TRUE); +} + +void I_ReleaseMouseCapture() +{ + SDL_SetRelativeMouseMode (SDL_FALSE); +} + +static void PostMouseMove (int x, int y) +{ + static int lastx = 0, lasty = 0; + event_t ev = { 0,0,0,0,0,0,0 }; + + if (m_filter) + { + ev.x = (x + lastx) / 2; + ev.y = (y + lasty) / 2; + } + else + { + ev.x = x; + ev.y = y; + } + lastx = x; + lasty = y; + if (ev.x | ev.y) + { + ev.type = EV_Mouse; + D_PostEvent (&ev); + } +} + +static void MouseRead () +{ + int x, y; + + if (NativeMouse) + { + return; + } + + SDL_GetRelativeMouseState (&x, &y); + if (!m_noprescale) + { + x *= 3; + y *= 2; + } + if (x | y) + { + PostMouseMove (x, -y); + } +} + +CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) +{ + if (self < 0) self = 0; + else if (self > 2) self = 2; +} + +static bool inGame() +{ + switch (mouse_capturemode) + { + default: + case 0: + return gamestate == GS_LEVEL; + case 1: + return gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_FINALE; + case 2: + return true; + } +} + +static void I_CheckNativeMouse () +{ + bool focus = SDL_GetKeyboardFocus() != NULL; + bool fs = screen->IsFullscreen(); + + bool wantNative = !focus || (!use_mouse || GUICapture || paused || demoplayback || !inGame()); + + if (wantNative != NativeMouse) + { + NativeMouse = wantNative; + SDL_ShowCursor (wantNative); + if (wantNative) + I_ReleaseMouseCapture (); + else + I_SetMouseCapture (); + } +} + +void MessagePump (const SDL_Event &sev) +{ + static int lastx = 0, lasty = 0; + int x, y; + event_t event = { 0,0,0,0,0,0,0 }; + + switch (sev.type) + { + case SDL_QUIT: + throw CExitEvent(0); + + case SDL_WINDOWEVENT: + extern void ProcessSDLWindowEvent(const SDL_WindowEvent &); + ProcessSDLWindowEvent(sev.window); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (!GUICapture) + { + event.type = sev.type == SDL_MOUSEBUTTONDOWN ? EV_KeyDown : EV_KeyUp; + + switch (sev.button.button) + { + case SDL_BUTTON_LEFT: event.data1 = KEY_MOUSE1; break; + case SDL_BUTTON_MIDDLE: event.data1 = KEY_MOUSE3; break; + case SDL_BUTTON_RIGHT: event.data1 = KEY_MOUSE2; break; + case SDL_BUTTON_X1: event.data1 = KEY_MOUSE4; break; + case SDL_BUTTON_X2: event.data1 = KEY_MOUSE5; break; + case 6: event.data1 = KEY_MOUSE6; break; + case 7: event.data1 = KEY_MOUSE7; break; + case 8: event.data1 = KEY_MOUSE8; break; + default: printf("SDL mouse button %s %d\n", + sev.type == SDL_MOUSEBUTTONDOWN ? "down" : "up", sev.button.button); break; + } + + if (event.data1 != 0) + { + D_PostEvent(&event); + } + } + else if ((sev.button.button >= SDL_BUTTON_LEFT && sev.button.button <= SDL_BUTTON_X2)) + { + int x, y; + SDL_GetMouseState(&x, &y); + + event.type = EV_GUI_Event; + event.data1 = x; + event.data2 = y; + + screen->ScaleCoordsFromWindow(event.data1, event.data2); + + if (sev.type == SDL_MOUSEBUTTONDOWN) + { + switch(sev.button.button) + { + case SDL_BUTTON_LEFT: event.subtype = EV_GUI_LButtonDown; break; + case SDL_BUTTON_MIDDLE: event.subtype = EV_GUI_MButtonDown; break; + case SDL_BUTTON_RIGHT: event.subtype = EV_GUI_RButtonDown; break; + case SDL_BUTTON_X1: event.subtype = EV_GUI_BackButtonDown; break; + case SDL_BUTTON_X2: event.subtype = EV_GUI_FwdButtonDown; break; + default: assert(false); event.subtype = EV_GUI_None; break; + } + } + else + { + switch(sev.button.button) + { + case SDL_BUTTON_LEFT: event.subtype = EV_GUI_LButtonUp; break; + case SDL_BUTTON_MIDDLE: event.subtype = EV_GUI_MButtonUp; break; + case SDL_BUTTON_RIGHT: event.subtype = EV_GUI_RButtonUp; break; + case SDL_BUTTON_X1: event.subtype = EV_GUI_BackButtonUp; break; + case SDL_BUTTON_X2: event.subtype = EV_GUI_FwdButtonUp; break; + default: assert(false); event.subtype = EV_GUI_None; break; + } + } + + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); + + D_PostEvent(&event); + } + break; + + case SDL_MOUSEMOTION: + if (GUICapture) + { + event.data1 = sev.motion.x; + event.data2 = sev.motion.y; + + screen->ScaleCoordsFromWindow(event.data1, event.data2); + + event.type = EV_GUI_Event; + event.subtype = EV_GUI_MouseMove; + + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); + + D_PostEvent(&event); + } + break; + + case SDL_MOUSEWHEEL: + if (GUICapture) + { + event.type = EV_GUI_Event; + + if (sev.wheel.y == 0) + event.subtype = sev.wheel.x > 0 ? EV_GUI_WheelRight : EV_GUI_WheelLeft; + else + event.subtype = sev.wheel.y > 0 ? EV_GUI_WheelUp : EV_GUI_WheelDown; + + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); + + D_PostEvent (&event); + } + else + { + event.type = EV_KeyDown; + + if (sev.wheel.y != 0) + event.data1 = sev.wheel.y > 0 ? KEY_MWHEELUP : KEY_MWHEELDOWN; + else + event.data1 = sev.wheel.x > 0 ? KEY_MWHEELRIGHT : KEY_MWHEELLEFT; + + D_PostEvent (&event); + event.type = EV_KeyUp; + D_PostEvent (&event); + } + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + if (!GUICapture) + { + if (sev.key.repeat) + { + break; + } + + event.type = sev.type == SDL_KEYDOWN ? EV_KeyDown : EV_KeyUp; + + // Try to look up our key mapped key for conversion to DirectInput. + // If that fails, then we'll do a lookup against the scan code, + // which may not return the right key, but at least the key should + // work in the game. + if (const uint8_t *dik = KeySymToDIK.CheckKey (sev.key.keysym.sym)) + event.data1 = *dik; + else if (const uint8_t *dik = KeyScanToDIK.CheckKey (sev.key.keysym.scancode)) + event.data1 = *dik; + + if (event.data1) + { + if (sev.key.keysym.sym < 256) + { + event.data2 = sev.key.keysym.sym; + } + D_PostEvent (&event); + } + } + else + { + event.type = EV_GUI_Event; + event.subtype = sev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp; + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); + + if (event.subtype == EV_GUI_KeyDown && sev.key.repeat) + { + event.subtype = EV_GUI_KeyRepeat; + } + + switch (sev.key.keysym.sym) + { + case SDLK_KP_ENTER: event.data1 = GK_RETURN; break; + case SDLK_PAGEUP: event.data1 = GK_PGUP; break; + case SDLK_PAGEDOWN: event.data1 = GK_PGDN; break; + case SDLK_END: event.data1 = GK_END; break; + case SDLK_HOME: event.data1 = GK_HOME; break; + case SDLK_LEFT: event.data1 = GK_LEFT; break; + case SDLK_RIGHT: event.data1 = GK_RIGHT; break; + case SDLK_UP: event.data1 = GK_UP; break; + case SDLK_DOWN: event.data1 = GK_DOWN; break; + case SDLK_DELETE: event.data1 = GK_DEL; break; + case SDLK_ESCAPE: event.data1 = GK_ESCAPE; break; + case SDLK_F1: event.data1 = GK_F1; break; + case SDLK_F2: event.data1 = GK_F2; break; + case SDLK_F3: event.data1 = GK_F3; break; + case SDLK_F4: event.data1 = GK_F4; break; + case SDLK_F5: event.data1 = GK_F5; break; + case SDLK_F6: event.data1 = GK_F6; break; + case SDLK_F7: event.data1 = GK_F7; break; + case SDLK_F8: event.data1 = GK_F8; break; + case SDLK_F9: event.data1 = GK_F9; break; + case SDLK_F10: event.data1 = GK_F10; break; + case SDLK_F11: event.data1 = GK_F11; break; + case SDLK_F12: event.data1 = GK_F12; break; + default: + if (sev.key.keysym.sym < 256) + { + event.data1 = sev.key.keysym.sym; + } + break; + } + if (event.data1 < 128) + { + event.data1 = toupper(event.data1); + D_PostEvent (&event); + } + } + break; + + case SDL_TEXTINPUT: + if (GUICapture) + { + int size; + + int unichar = utf8_decode((const uint8_t*)sev.text.text, &size); + if (size != 4) + { + event.type = EV_GUI_Event; + event.subtype = EV_GUI_Char; + event.data1 = (int16_t)unichar; + event.data2 = !!(SDL_GetModState() & KMOD_ALT); + D_PostEvent (&event); + } + } + break; + + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + if (!GUICapture) + { + event.type = sev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp; + event.data1 = KEY_FIRSTJOYBUTTON + sev.jbutton.button; + if(event.data1 != 0) + D_PostEvent(&event); + } + break; + } +} + +void I_GetEvent () +{ + SDL_Event sev; + + while (SDL_PollEvent (&sev)) + { + MessagePump (sev); + } + if (use_mouse) + { + MouseRead (); + } +} + +void I_StartTic () +{ + I_CheckGUICapture (); + I_CheckNativeMouse (); + I_GetEvent (); +} + +void I_ProcessJoysticks (); +void I_StartFrame () +{ + I_ProcessJoysticks(); +} diff --git a/source/platform/unix/i_joystick.cpp b/source/platform/posix/sdl/i_joystick.cpp similarity index 99% rename from source/platform/unix/i_joystick.cpp rename to source/platform/posix/sdl/i_joystick.cpp index 695a44686..ad3416a10 100644 --- a/source/platform/unix/i_joystick.cpp +++ b/source/platform/posix/sdl/i_joystick.cpp @@ -332,7 +332,7 @@ void I_GetAxes(float axes[NUM_JOYAXIS]) void I_ProcessJoysticks() { - if (use_joystick && JoystickManager) + if (use_joystick) JoystickManager->ProcessInput(); } diff --git a/source/platform/posix/sdl/i_main.cpp b/source/platform/posix/sdl/i_main.cpp new file mode 100644 index 000000000..9004dc0a3 --- /dev/null +++ b/source/platform/posix/sdl/i_main.cpp @@ -0,0 +1,211 @@ +/* +** i_main.cpp +** System-specific startup code. Eventually calls D_DoomMain. +** +**--------------------------------------------------------------------------- +** Copyright 1998-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include + +#include "doomerrors.h" +#include "m_argv.h" +#include "d_main.h" +#include "c_console.h" +#include "version.h" +#include "w_wad.h" +#include "g_level.h" +#include "g_levellocals.h" +#include "cmdlib.h" +#include "r_utility.h" +#include "doomstat.h" +#include "vm.h" +#include "doomerrors.h" +#include "i_system.h" +#include "g_game.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*)); + +#ifdef __APPLE__ +void Mac_I_FatalError(const char* errortext); +#endif + +#ifdef __linux__ +void Linux_I_FatalError(const char* errortext); +#endif + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// The command line arguments. +FArgs *Args; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + + +// CODE -------------------------------------------------------------------- + + + +static int DoomSpecificInfo (char *buffer, char *end) +{ + const char *arg; + int size = end-buffer-2; + int i, p; + + p = 0; + p += snprintf (buffer+p, size-p, GAMENAME" version %s (%s)\n", GetVersionString(), GetGitHash()); +#ifdef __VERSION__ + p += snprintf (buffer+p, size-p, "Compiler version: %s\n", __VERSION__); +#endif + + // If Args is nullptr, then execution is at either + // * early stage of initialization, additional info contains only default values + // * late stage of shutdown, most likely main() was done, and accessing global variables is no longer safe + if (Args) + { + p += snprintf(buffer + p, size - p, "\nCommand line:"); + for (i = 0; i < Args->NumArgs(); ++i) + { + p += snprintf(buffer + p, size - p, " %s", Args->GetArg(i)); + } + p += snprintf(buffer + p, size - p, "\n"); + + for (i = 0; (arg = Wads.GetWadName(i)) != NULL; ++i) + { + p += snprintf(buffer + p, size - p, "\nWad %d: %s", i, arg); + } + + if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL) + { + p += snprintf(buffer + p, size - p, "\n\nNot in a level."); + } + else + { + p += snprintf(buffer + p, size - p, "\n\nCurrent map: %s", primaryLevel->MapName.GetChars()); + + if (!viewactive) + { + p += snprintf(buffer + p, size - p, "\n\nView not active."); + } + else + { + auto& vp = r_viewpoint; + p += snprintf(buffer + p, size - p, "\n\nviewx = %f", vp.Pos.X); + p += snprintf(buffer + p, size - p, "\nviewy = %f", vp.Pos.Y); + p += snprintf(buffer + p, size - p, "\nviewz = %f", vp.Pos.Z); + p += snprintf(buffer + p, size - p, "\nviewangle = %f", vp.Angles.Yaw.Degrees); + } + } + } + + buffer[p++] = '\n'; + buffer[p++] = '\0'; + + return p; +} + +void I_DetectOS() +{ + // The POSIX version never implemented this. +} + +void I_StartupJoysticks(); + +int main (int argc, char **argv) +{ +#if !defined (__APPLE__) + { + int s[4] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS }; + cc_install_handlers(argc, argv, 4, s, GAMENAMELOWERCASE "-crash.log", DoomSpecificInfo); + } +#endif // !__APPLE__ + + printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", + GetVersionString(), GetGitTime(), __DATE__); + + seteuid (getuid ()); + // Set LC_NUMERIC environment variable in case some library decides to + // clear the setlocale call at least this will be correct. + // Note that the LANG environment variable is overridden by LC_* + setenv ("LC_NUMERIC", "C", 1); + + setlocale (LC_ALL, "C"); + + if (SDL_Init (0) < 0) + { + fprintf (stderr, "Could not initialize SDL:\n%s\n", SDL_GetError()); + return -1; + } + + printf("\n"); + + Args = new FArgs(argc, argv); + + // Should we even be doing anything with progdir on Unix systems? + char program[PATH_MAX]; + if (realpath (argv[0], program) == NULL) + strcpy (program, argv[0]); + char *slash = strrchr (program, '/'); + if (slash != NULL) + { + *(slash + 1) = '\0'; + progdir = program; + } + else + { + progdir = "./"; + } + + I_StartupJoysticks(); + + const int result = D_DoomMain(); + + SDL_Quit(); + + return result; +} diff --git a/source/platform/posix/sdl/i_system.cpp b/source/platform/posix/sdl/i_system.cpp new file mode 100644 index 000000000..9b0f8e8f1 --- /dev/null +++ b/source/platform/posix/sdl/i_system.cpp @@ -0,0 +1,418 @@ +/* +** i_system.cpp +** Main startup code +** +**--------------------------------------------------------------------------- +** Copyright 1999-2016 Randy Heit +** Copyright 2019-2020 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "i_system.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "doomerrors.h" + +#include "doomtype.h" +#include "doomstat.h" +#include "version.h" +#include "doomdef.h" +#include "cmdlib.h" +#include "m_argv.h" +#include "m_misc.h" +#include "i_sound.h" +#include "x86.h" + +#include "d_main.h" +#include "d_net.h" +#include "g_game.h" +#include "c_dispatch.h" + +#include "gameconfigfile.h" + +extern "C" +{ + double SecondsPerCycle = 1e-8; + double CyclesPerSecond = 1e8; +} + +#ifndef NO_GTK +bool I_GtkAvailable (); +int I_PickIWad_Gtk (WadStuff *wads, int numwads, bool showwin, int defaultiwad); +void I_ShowFatalError_Gtk(const char* errortext); +#elif defined(__APPLE__) +int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad); +#endif + +double PerfToSec, PerfToMillisec; + +ticcmd_t emptycmd; +ticcmd_t *I_BaseTiccmd(void) +{ + return &emptycmd; +} + +void I_BeginRead(void) +{ +} + +void I_EndRead(void) +{ +} + + +// +// I_Init +// +void I_Init (void) +{ + CheckCPUID (&CPU); + DumpCPUInfo (&CPU); +} + +// +// I_Error +// +extern FILE *Logfile; + +#ifdef __APPLE__ +void Mac_I_FatalError(const char* errortext); +#endif + +#ifdef __linux__ +void Linux_I_FatalError(const char* errortext) +{ + // Close window or exit fullscreen and release mouse capture + SDL_Quit(); + + const char *str; + if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) + { + FString cmd; + cmd << "kdialog --title \"" GAMESIG " " << GetVersionString() + << "\" --msgbox \"" << errortext << "\""; + popen(cmd, "r"); + } +#ifndef NO_GTK + else if (I_GtkAvailable()) + { + I_ShowFatalError_Gtk(errortext); + } +#endif + else + { + FString title; + title << GAMESIG " " << GetVersionString(); + + if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, errortext, NULL) < 0) + { + printf("\n%s\n", errortext); + } + } +} +#endif + + +void I_ShowFatalError(const char *message) +{ +#ifdef __APPLE__ + Mac_I_FatalError(message); +#elif defined __linux__ + Linux_I_FatalError(message); +#else + // ??? +#endif + +} + + +void I_SetIWADInfo () +{ +} + +void I_DebugPrint(const char *cp) +{ +} + +void I_PrintStr(const char *cp) +{ + // Strip out any color escape sequences before writing to debug output + TArray copy(strlen(cp) + 1, true); + const char * srcp = cp; + char * dstp = copy.Data(); + + while (*srcp != 0) + { + if (*srcp != 0x1c && *srcp != 0x1d && *srcp != 0x1e && *srcp != 0x1f) + { + *dstp++ = *srcp++; + } + else + { + if (srcp[1] != 0) srcp += 2; + else break; + } + } + *dstp = 0; + + fputs(copy.Data(), stdout); + fflush(stdout); +} + +int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + int i; + + if (!showwin) + { + return defaultiwad; + } + +#ifndef __APPLE__ + const char *str; + if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) + { + FString cmd("kdialog --title \"" GAMESIG " "); + cmd << GetVersionString() << ": Select an IWAD to use\"" + " --menu \"" GAMENAME " found more than one IWAD\n" + "Select from the list below to determine which one to use:\""; + + for(i = 0; i < numwads; ++i) + { + const char *filepart = strrchr(wads[i].Path, '/'); + if(filepart == NULL) + filepart = wads[i].Path; + else + filepart++; + // Menu entries are specified in "tag" "item" pairs, where when a + // particular item is selected (and the Okay button clicked), its + // corresponding tag is printed to stdout for identification. + cmd.AppendFormat(" \"%d\" \"%s (%s)\"", i, wads[i].Name.GetChars(), filepart); + } + + if(defaultiwad >= 0 && defaultiwad < numwads) + { + const char *filepart = strrchr(wads[defaultiwad].Path, '/'); + if(filepart == NULL) + filepart = wads[defaultiwad].Path; + else + filepart++; + cmd.AppendFormat(" --default \"%s (%s)\"", wads[defaultiwad].Name.GetChars(), filepart); + } + + FILE *f = popen(cmd, "r"); + if(f != NULL) + { + char gotstr[16]; + + if(fgets(gotstr, sizeof(gotstr), f) == NULL || + sscanf(gotstr, "%d", &i) != 1) + i = -1; + + // Exit status = 1 means the selection was canceled (either by + // Cancel/Esc or the X button), not that there was an error running + // the program. In that case, nothing was printed so fgets will + // have failed. Other values can indicate an error running the app, + // so fall back to whatever else can be used. + int status = pclose(f); + if(WIFEXITED(status) && (WEXITSTATUS(status) == 0 || WEXITSTATUS(status) == 1)) + return i; + } + } +#endif + +#ifndef NO_GTK + if (I_GtkAvailable()) + { + return I_PickIWad_Gtk (wads, numwads, showwin, defaultiwad); + } +#endif + +#ifdef __APPLE__ + return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); +#endif + + if (!isatty(fileno(stdin))) + { + return defaultiwad; + } + + printf ("Please select a game wad (or 0 to exit):\n"); + for (i = 0; i < numwads; ++i) + { + const char *filepart = strrchr (wads[i].Path, '/'); + if (filepart == NULL) + filepart = wads[i].Path; + else + filepart++; + printf ("%d. %s (%s)\n", i+1, wads[i].Name.GetChars(), filepart); + } + printf ("Which one? "); + if (scanf ("%d", &i) != 1 || i > numwads) + return -1; + return i-1; +} + +bool I_WriteIniFailed () +{ + printf ("The config file %s could not be saved:\n%s\n", GameConfig->GetPathName(), strerror(errno)); + return false; + // return true to retry +} + +static const char *pattern; + +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED < 1080 +static int matchfile (struct dirent *ent) +#else +static int matchfile (const struct dirent *ent) +#endif +{ + return fnmatch (pattern, ent->d_name, FNM_NOESCAPE) == 0; +} + +void *I_FindFirst (const char *filespec, findstate_t *fileinfo) +{ + FString dir; + + const char *slash = strrchr (filespec, '/'); + if (slash) + { + pattern = slash+1; + dir = FString(filespec, slash-filespec+1); + } + else + { + pattern = filespec; + dir = "."; + } + + fileinfo->current = 0; + fileinfo->count = scandir (dir.GetChars(), &fileinfo->namelist, + matchfile, alphasort); + if (fileinfo->count > 0) + { + return fileinfo; + } + return (void*)-1; +} + +int I_FindNext (void *handle, findstate_t *fileinfo) +{ + findstate_t *state = (findstate_t *)handle; + if (state->current < fileinfo->count) + { + return ++state->current < fileinfo->count ? 0 : -1; + } + return -1; +} + +int I_FindClose (void *handle) +{ + findstate_t *state = (findstate_t *)handle; + if (handle != (void*)-1 && state->count > 0) + { + for(int i = 0;i < state->count;++i) + free (state->namelist[i]); + state->count = 0; + free (state->namelist); + state->namelist = NULL; + } + return 0; +} + +int I_FindAttr(findstate_t* const fileinfo) +{ + dirent* const ent = fileinfo->namelist[fileinfo->current]; + bool isdir; + + if (DirEntryExists(ent->d_name, &isdir)) + { + return isdir ? FA_DIREC : 0; + } + + return 0; +} + +void I_PutInClipboard (const char *str) +{ + SDL_SetClipboardText(str); +} + +FString I_GetFromClipboard (bool use_primary_selection) +{ + if(char *ret = SDL_GetClipboardText()) + { + FString text(ret); + SDL_free(ret); + return text; + } + return ""; +} + +// Return a random seed, preferably one with lots of entropy. +unsigned int I_MakeRNGSeed() +{ + unsigned int seed; + int file; + + // Try reading from /dev/urandom first, then /dev/random, then + // if all else fails, use a crappy seed from time(). + seed = time(NULL); + file = open("/dev/urandom", O_RDONLY); + if (file < 0) + { + file = open("/dev/random", O_RDONLY); + } + if (file >= 0) + { + read(file, &seed, sizeof(seed)); + close(file); + } + return seed; +} + +TArray I_GetGogPaths() +{ + // GOG's Doom games are Windows only at the moment + return TArray(); +} + diff --git a/source/platform/posix/sdl/i_system.mm b/source/platform/posix/sdl/i_system.mm new file mode 100644 index 000000000..50faf94a8 --- /dev/null +++ b/source/platform/posix/sdl/i_system.mm @@ -0,0 +1,19 @@ +#include +#include "SDL.h" + +void Mac_I_FatalError(const char* errortext) +{ + // Close window or exit fullscreen and release mouse capture + SDL_Quit(); + + const CFStringRef errorString = CFStringCreateWithCStringNoCopy( kCFAllocatorDefault, + errortext, kCFStringEncodingASCII, kCFAllocatorNull ); + if ( NULL != errorString ) + { + CFOptionFlags dummy; + + CFUserNotificationDisplayAlert( 0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, + CFSTR( "Fatal Error" ), errorString, CFSTR( "Exit" ), NULL, NULL, &dummy ); + CFRelease( errorString ); + } +} diff --git a/source/platform/posix/sdl/sdlglvideo.cpp b/source/platform/posix/sdl/sdlglvideo.cpp new file mode 100644 index 000000000..39931c28e --- /dev/null +++ b/source/platform/posix/sdl/sdlglvideo.cpp @@ -0,0 +1,760 @@ +/* +** sdlglvideo.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2005-2016 Christoph Oelckers et.al. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "doomtype.h" + +#include "i_module.h" +#include "i_system.h" +#include "i_video.h" +#include "m_argv.h" +#include "v_video.h" +#include "version.h" +#include "c_console.h" +#include "c_dispatch.h" +#include "s_sound.h" + +#include "hardware.h" +#include "gl_sysfb.h" +#include "gl_load/gl_system.h" +#include "r_defs.h" + +#include "gl/renderer/gl_renderer.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/shaders/gl_shader.h" + +#ifdef HAVE_VULKAN +#include "rendering/vulkan/system/vk_framebuffer.h" +#endif + +#include "rendering/polyrenderer/backend/poly_framebuffer.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern IVideo *Video; + +EXTERN_CVAR (Int, vid_adapter) +EXTERN_CVAR (Int, vid_displaybits) +EXTERN_CVAR (Int, vid_defwidth) +EXTERN_CVAR (Int, vid_defheight) +EXTERN_CVAR (Int, vid_preferbackend) +EXTERN_CVAR (Bool, cl_capfps) + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CUSTOM_CVAR(Bool, gl_debug, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + Printf("This won't take effect until " GAMENAME " is restarted.\n"); +} +CUSTOM_CVAR(Bool, gl_es, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + Printf("This won't take effect until " GAMENAME " is restarted.\n"); +} + +CVAR(Bool, i_soundinbackground, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CVAR (Int, vid_adapter, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(String, vid_sdl_render_driver, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + Printf("This won't take effect until " GAMENAME " is restarted.\n"); +} + +CCMD(vid_list_sdl_render_drivers) +{ + for (int i = 0; i < SDL_GetNumRenderDrivers(); ++i) + { + SDL_RendererInfo info; + if (SDL_GetRenderDriverInfo(i, &info) == 0) + Printf("%s\n", info.name); + } +} + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +namespace Priv +{ + FModule library("SDL2"); + +#define SDL2_OPTIONAL_FUNCTION(RESULT, NAME, ...) \ + static TOptProc NAME("SDL_" #NAME) + + SDL2_OPTIONAL_FUNCTION(int, GetWindowBordersSize, SDL_Window *window, int *top, int *left, int *bottom, int *right); +#ifdef HAVE_VULKAN + SDL2_OPTIONAL_FUNCTION(void, Vulkan_GetDrawableSize, SDL_Window *window, int *width, int *height); + SDL2_OPTIONAL_FUNCTION(SDL_bool, Vulkan_GetInstanceExtensions, SDL_Window *window, unsigned int *count, const char **names); + SDL2_OPTIONAL_FUNCTION(SDL_bool, Vulkan_CreateSurface, SDL_Window *window, VkInstance instance, VkSurfaceKHR *surface); +#endif + +#undef SDL2_OPTIONAL_FUNCTION + + static const uint32_t VulkanWindowFlag = 0x1000'0000; + + SDL_Window *window; + bool vulkanEnabled; + bool softpolyEnabled; + bool fullscreenSwitch; + + void CreateWindow(uint32_t extraFlags) + { + assert(Priv::window == nullptr); + + // Set default size + SDL_Rect bounds; + SDL_GetDisplayBounds(vid_adapter, &bounds); + + if (win_w <= 0 || win_h <= 0) + { + win_w = bounds.w * 8 / 10; + win_h = bounds.h * 8 / 10; + } + + FString caption; + caption.Format(GAMESIG " %s (%s)", GetVersionString(), GetGitTime()); + + const uint32_t windowFlags = (win_maximized ? SDL_WINDOW_MAXIMIZED : 0) | SDL_WINDOW_RESIZABLE | extraFlags; + Priv::window = SDL_CreateWindow(caption, + (win_x <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_x, + (win_y <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_y, + win_w, win_h, windowFlags); + + if (Priv::window != nullptr) + { + // Enforce minimum size limit + SDL_SetWindowMinimumSize(Priv::window, VID_MIN_WIDTH, VID_MIN_HEIGHT); + } + } + + void DestroyWindow() + { + assert(Priv::window != nullptr); + + SDL_DestroyWindow(Priv::window); + Priv::window = nullptr; + } + + void SetupPixelFormat(int multisample, const int *glver) + { + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + if (multisample > 0) { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisample); + } + if (gl_debug) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + + if (gl_es) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + } + else if (glver[0] > 2) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glver[0]); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glver[1]); + } + else + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + } + } +} + +class SDLVideo : public IVideo +{ +public: + SDLVideo (); + ~SDLVideo (); + + DFrameBuffer *CreateFrameBuffer (); + +private: +#ifdef HAVE_VULKAN + VulkanDevice *device = nullptr; +#endif +}; + +// CODE -------------------------------------------------------------------- + +#ifdef HAVE_VULKAN +void I_GetVulkanDrawableSize(int *width, int *height) +{ + assert(Priv::vulkanEnabled); + assert(Priv::window != nullptr); + assert(Priv::Vulkan_GetDrawableSize); + Priv::Vulkan_GetDrawableSize(Priv::window, width, height); +} + +bool I_GetVulkanPlatformExtensions(unsigned int *count, const char **names) +{ + assert(Priv::vulkanEnabled); + assert(Priv::window != nullptr); + return Priv::Vulkan_GetInstanceExtensions(Priv::window, count, names) == SDL_TRUE; +} + +bool I_CreateVulkanSurface(VkInstance instance, VkSurfaceKHR *surface) +{ + assert(Priv::vulkanEnabled); + assert(Priv::window != nullptr); + return Priv::Vulkan_CreateSurface(Priv::window, instance, surface) == SDL_TRUE; +} +#endif + +namespace +{ + SDL_Renderer* polyrendertarget = nullptr; + SDL_Texture* polytexture = nullptr; + int polytexturew = 0; + int polytextureh = 0; + bool polyvsync = false; + bool polyfirstinit = true; +} + +void I_PolyPresentInit() +{ + assert(Priv::softpolyEnabled); + assert(Priv::window != nullptr); + + if (strcmp(vid_sdl_render_driver, "") != 0) + { + SDL_SetHint(SDL_HINT_RENDER_DRIVER, vid_sdl_render_driver); + } +} + +uint8_t *I_PolyPresentLock(int w, int h, bool vsync, int &pitch) +{ + // When vsync changes we need to reinitialize + if (polyrendertarget && polyvsync != vsync) + { + I_PolyPresentDeinit(); + } + + if (!polyrendertarget) + { + polyvsync = vsync; + + polyrendertarget = SDL_CreateRenderer(Priv::window, -1, vsync ? SDL_RENDERER_PRESENTVSYNC : 0); + if (!polyrendertarget) + { + I_FatalError("Could not create render target for softpoly: %s\n", SDL_GetError()); + } + + // Tell the user which render driver is being used, but don't repeat + // outselves if we're just changing vsync. + if (polyfirstinit) + { + polyfirstinit = false; + + SDL_RendererInfo rendererInfo; + if (SDL_GetRendererInfo(polyrendertarget, &rendererInfo) == 0) + { + Printf("Using render driver %s\n", rendererInfo.name); + } + else + { + Printf("Failed to query render driver\n"); + } + } + + // Mask color + SDL_SetRenderDrawColor(polyrendertarget, 0, 0, 0, 255); + } + + if (!polytexture || polytexturew != w || polytextureh != h) + { + if (polytexture) + { + SDL_DestroyTexture(polytexture); + polytexture = nullptr; + polytexturew = polytextureh = 0; + } + if ((polytexture = SDL_CreateTexture(polyrendertarget, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h)) == nullptr) + I_Error("Failed to create %dx%d render target texture.", w, h); + polytexturew = w; + polytextureh = h; + } + + uint8_t* pixels; + SDL_LockTexture(polytexture, nullptr, (void**)&pixels, &pitch); + return pixels; +} + +void I_PolyPresentUnlock(int x, int y, int width, int height) +{ + SDL_UnlockTexture(polytexture); + + int ClientWidth, ClientHeight; + SDL_GetRendererOutputSize(polyrendertarget, &ClientWidth, &ClientHeight); + + SDL_Rect clearrects[4]; + int count = 0; + if (y > 0) + { + clearrects[count].x = 0; + clearrects[count].y = 0; + clearrects[count].w = ClientWidth; + clearrects[count].h = y; + count++; + } + if (y + height < ClientHeight) + { + clearrects[count].x = 0; + clearrects[count].y = y + height; + clearrects[count].w = ClientWidth; + clearrects[count].h = ClientHeight - clearrects[count].y; + count++; + } + if (x > 0) + { + clearrects[count].x = 0; + clearrects[count].y = y; + clearrects[count].w = x; + clearrects[count].h = height; + count++; + } + if (x + width < ClientWidth) + { + clearrects[count].x = x + width; + clearrects[count].y = y; + clearrects[count].w = ClientWidth - clearrects[count].x; + clearrects[count].h = height; + count++; + } + + if (count > 0) + SDL_RenderFillRects(polyrendertarget, clearrects, count); + + SDL_Rect dstrect; + dstrect.x = x; + dstrect.y = y; + dstrect.w = width; + dstrect.h = height; + SDL_RenderCopy(polyrendertarget, polytexture, nullptr, &dstrect); + + SDL_RenderPresent(polyrendertarget); +} + +void I_PolyPresentDeinit() +{ + if (polytexture) + { + SDL_DestroyTexture(polytexture); + polytexture = nullptr; + } + + if (polyrendertarget) + { + SDL_DestroyRenderer(polyrendertarget); + polyrendertarget = nullptr; + } +} + + + +SDLVideo::SDLVideo () +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError()); + return; + } + + // Load optional SDL functions + if (!Priv::library.IsLoaded()) + { + Priv::library.Load({ "libSDL2-2.0.so.0", "libSDL2-2.0.so", "libSDL2.so" }); + } + +#ifdef HAVE_VULKAN + Priv::vulkanEnabled = vid_preferbackend == 1 + && Priv::Vulkan_GetDrawableSize && Priv::Vulkan_GetInstanceExtensions && Priv::Vulkan_CreateSurface; + Priv::softpolyEnabled = vid_preferbackend == 2; + + if (Priv::vulkanEnabled) + { + Priv::CreateWindow(Priv::VulkanWindowFlag | SDL_WINDOW_HIDDEN); + + if (Priv::window == nullptr) + { + Priv::vulkanEnabled = false; + } + } + else if (Priv::softpolyEnabled) + { + Priv::CreateWindow(SDL_WINDOW_HIDDEN); + } +#endif +} + +SDLVideo::~SDLVideo () +{ +#ifdef HAVE_VULKAN + delete device; +#endif +} + +DFrameBuffer *SDLVideo::CreateFrameBuffer () +{ + SystemBaseFrameBuffer *fb = nullptr; + + // first try Vulkan, if that fails OpenGL +#ifdef HAVE_VULKAN + if (Priv::vulkanEnabled) + { + try + { + assert(device == nullptr); + device = new VulkanDevice(); + fb = new VulkanFrameBuffer(nullptr, fullscreen, device); + } + catch (CVulkanError const&) + { + if (Priv::window != nullptr) + { + Priv::DestroyWindow(); + } + + Priv::vulkanEnabled = false; + } + } +#endif + + if (Priv::softpolyEnabled) + { + fb = new PolyFrameBuffer(nullptr, fullscreen); + } + + if (fb == nullptr) + { + fb = new OpenGLRenderer::OpenGLFrameBuffer(0, fullscreen); + } + + return fb; +} + + +IVideo *gl_CreateVideo() +{ + return new SDLVideo(); +} + + +// FrameBuffer Implementation ----------------------------------------------- + +SystemBaseFrameBuffer::SystemBaseFrameBuffer (void *, bool fullscreen) +: DFrameBuffer (vid_defwidth, vid_defheight) +{ + if (Priv::window != nullptr) + { + SDL_SetWindowFullscreen(Priv::window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + SDL_ShowWindow(Priv::window); + } +} + +int SystemBaseFrameBuffer::GetClientWidth() +{ + int width = 0; + + if (Priv::softpolyEnabled) + { + if (polyrendertarget) + SDL_GetRendererOutputSize(polyrendertarget, &width, nullptr); + else + SDL_GetWindowSize(Priv::window, &width, nullptr); + return width; + } + +#ifdef HAVE_VULKAN + assert(Priv::vulkanEnabled); + Priv::Vulkan_GetDrawableSize(Priv::window, &width, nullptr); +#endif + + return width; +} + +int SystemBaseFrameBuffer::GetClientHeight() +{ + int height = 0; + + if (Priv::softpolyEnabled) + { + if (polyrendertarget) + SDL_GetRendererOutputSize(polyrendertarget, nullptr, &height); + else + SDL_GetWindowSize(Priv::window, nullptr, &height); + return height; + } + +#ifdef HAVE_VULKAN + assert(Priv::vulkanEnabled); + Priv::Vulkan_GetDrawableSize(Priv::window, nullptr, &height); +#endif + + return height; +} + +bool SystemBaseFrameBuffer::IsFullscreen () +{ + return (SDL_GetWindowFlags(Priv::window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; +} + +void SystemBaseFrameBuffer::ToggleFullscreen(bool yes) +{ + SDL_SetWindowFullscreen(Priv::window, yes ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + if ( !yes ) + { + if ( !Priv::fullscreenSwitch ) + { + Priv::fullscreenSwitch = true; + fullscreen = false; + } + else + { + Priv::fullscreenSwitch = false; + SetWindowSize(win_w, win_h); + } + } +} + +void SystemBaseFrameBuffer::SetWindowSize(int w, int h) +{ + if (w < VID_MIN_WIDTH || h < VID_MIN_HEIGHT) + { + w = VID_MIN_WIDTH; + h = VID_MIN_HEIGHT; + } + win_w = w; + win_h = h; + if ( fullscreen ) + { + fullscreen = false; + } + else + { + win_maximized = false; + SDL_SetWindowSize(Priv::window, w, h); + SDL_SetWindowPosition(Priv::window, SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter), SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter)); + SetSize(GetClientWidth(), GetClientHeight()); + int x, y; + SDL_GetWindowPosition(Priv::window, &x, &y); + win_x = x; + win_y = y; + } +} + + +SystemGLFrameBuffer::SystemGLFrameBuffer(void *hMonitor, bool fullscreen) +: SystemBaseFrameBuffer(hMonitor, fullscreen) +{ + // NOTE: Core profiles were added with GL 3.2, so there's no sense trying + // to set core 3.1 or 3.0. We could try a forward-compatible context + // instead, but that would be too restrictive (w.r.t. shaders). + static const int glvers[][2] = { + { 4, 6 }, { 4, 5 }, { 4, 4 }, { 4, 3 }, { 4, 2 }, { 4, 1 }, { 4, 0 }, + { 3, 3 }, { 3, 2 }, { 2, 0 }, + { 0, 0 }, + }; + int glveridx = 0; + int i; + + const char *version = Args->CheckValue("-glversion"); + if (version != NULL) + { + double gl_version = strtod(version, NULL) + 0.01; + int vermaj = (int)gl_version; + int vermin = (int)(gl_version*10.0) % 10; + + while (glvers[glveridx][0] > vermaj || (glvers[glveridx][0] == vermaj && + glvers[glveridx][1] > vermin)) + { + glveridx++; + if (glvers[glveridx][0] == 0) + { + glveridx = 0; + break; + } + } + } + + for ( ; glvers[glveridx][0] > 0; ++glveridx) + { + Priv::SetupPixelFormat(0, glvers[glveridx]); + Priv::CreateWindow(SDL_WINDOW_OPENGL | (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); + + if (Priv::window == nullptr) + { + continue; + } + + GLContext = SDL_GL_CreateContext(Priv::window); + if (GLContext == nullptr) + { + Priv::DestroyWindow(); + } + else + { + break; + } + } +} + +SystemGLFrameBuffer::~SystemGLFrameBuffer () +{ + if (Priv::window) + { + if (GLContext) + { + SDL_GL_DeleteContext(GLContext); + } + + Priv::DestroyWindow(); + } +} + +int SystemGLFrameBuffer::GetClientWidth() +{ + int width = 0; + SDL_GL_GetDrawableSize(Priv::window, &width, nullptr); + return width; +} + +int SystemGLFrameBuffer::GetClientHeight() +{ + int height = 0; + SDL_GL_GetDrawableSize(Priv::window, nullptr, &height); + return height; +} + +void SystemGLFrameBuffer::SetVSync( bool vsync ) +{ +#if defined (__APPLE__) + const GLint value = vsync ? 1 : 0; + CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval, &value ); +#else + if (vsync) + { + if (SDL_GL_SetSwapInterval(-1) == -1) + SDL_GL_SetSwapInterval(1); + } + else + { + SDL_GL_SetSwapInterval(0); + } +#endif +} + +void SystemGLFrameBuffer::SwapBuffers() +{ + SDL_GL_SwapWindow(Priv::window); +} + + +void ProcessSDLWindowEvent(const SDL_WindowEvent &event) +{ + switch (event.event) + { + extern bool AppActive; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + S_SetSoundPaused(1); + AppActive = true; + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + S_SetSoundPaused(i_soundinbackground); + AppActive = false; + break; + + case SDL_WINDOWEVENT_MOVED: + if (!fullscreen && Priv::GetWindowBordersSize) + { + int top = 0, left = 0; + Priv::GetWindowBordersSize(Priv::window, &top, &left, nullptr, nullptr); + win_x = event.data1-left; + win_y = event.data2-top; + } + break; + + case SDL_WINDOWEVENT_RESIZED: + if (!fullscreen && !Priv::fullscreenSwitch) + { + win_w = event.data1; + win_h = event.data2; + } + break; + + case SDL_WINDOWEVENT_MAXIMIZED: + win_maximized = true; + break; + + case SDL_WINDOWEVENT_RESTORED: + win_maximized = false; + break; + } +} + + +// each platform has its own specific version of this function. +void I_SetWindowTitle(const char* caption) +{ + if (caption) + { + SDL_SetWindowTitle(Priv::window, caption); + } + else + { + FString default_caption; + default_caption.Format(GAMESIG " %s (%s)", GetVersionString(), GetGitTime()); + SDL_SetWindowTitle(Priv::window, default_caption); + } +} + diff --git a/source/platform/posix/sdl/st_start.cpp b/source/platform/posix/sdl/st_start.cpp new file mode 100644 index 000000000..7e673d961 --- /dev/null +++ b/source/platform/posix/sdl/st_start.cpp @@ -0,0 +1,331 @@ +/* +** st_start.cpp +** Handles the startup screen. +** +**--------------------------------------------------------------------------- +** Copyright 2006-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include + +#include "st_start.h" +#include "doomdef.h" +#include "i_system.h" +#include "c_cvars.h" +#include "doomerrors.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class FTTYStartupScreen : public FStartupScreen +{ + public: + FTTYStartupScreen(int max_progress); + ~FTTYStartupScreen(); + + void Progress(); + void NetInit(const char *message, int num_players); + void NetProgress(int count); + void NetMessage(const char *format, ...); // cover for printf + void NetDone(); + bool NetLoop(bool (*timer_callback)(void *), void *userdata); + protected: + bool DidNetInit; + int NetMaxPos, NetCurPos; + const char *TheNetMessage; + termios OldTermIOS; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +FStartupScreen *StartScreen; + +CUSTOM_CVAR(Int, showendoom, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) self = 0; + else if (self > 2) self=2; +} + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static const char SpinnyProgressChars[4] = { '|', '/', '-', '\\' }; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FStartupScreen :: CreateInstance +// +// Initializes the startup screen for the detected game. +// Sets the size of the progress bar and displays the startup screen. +// +//========================================================================== + +FStartupScreen *FStartupScreen::CreateInstance(int max_progress) +{ + return new FTTYStartupScreen(max_progress); +} + +//=========================================================================== +// +// FTTYStartupScreen Constructor +// +// Sets the size of the progress bar and displays the startup screen. +// +//=========================================================================== + +FTTYStartupScreen::FTTYStartupScreen(int max_progress) + : FStartupScreen(max_progress) +{ + DidNetInit = false; + NetMaxPos = 0; + NetCurPos = 0; + TheNetMessage = NULL; +} + +//=========================================================================== +// +// FTTYStartupScreen Destructor +// +// Called just before entering graphics mode to deconstruct the startup +// screen. +// +//=========================================================================== + +FTTYStartupScreen::~FTTYStartupScreen() +{ + NetDone(); // Just in case it wasn't called yet and needs to be. +} + +//=========================================================================== +// +// FTTYStartupScreen :: Progress +// +// If there was a progress bar, this would move it. But the basic TTY +// startup screen doesn't have one, so this function does nothing. +// +//=========================================================================== + +void FTTYStartupScreen::Progress() +{ +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetInit +// +// Sets stdin for unbuffered I/O, displays the given message, and shows +// a progress meter. +// +//=========================================================================== + +void FTTYStartupScreen::NetInit(const char *message, int numplayers) +{ + if (!DidNetInit) + { + termios rawtermios; + + fprintf (stderr, "Press 'Q' to abort network game synchronization."); + // Set stdin to raw mode so we can get keypresses in ST_CheckNetAbort() + // immediately without waiting for an EOL. + tcgetattr (STDIN_FILENO, &OldTermIOS); + rawtermios = OldTermIOS; + rawtermios.c_lflag &= ~(ICANON | ECHO); + tcsetattr (STDIN_FILENO, TCSANOW, &rawtermios); + DidNetInit = true; + } + if (numplayers == 1) + { + // Status message without any real progress info. + fprintf (stderr, "\n%s.", message); + } + else + { + fprintf (stderr, "\n%s: ", message); + } + fflush (stderr); + TheNetMessage = message; + NetMaxPos = numplayers; + NetCurPos = 0; + NetProgress(1); // You always know about yourself +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetDone +// +// Restores the old stdin tty settings. +// +//=========================================================================== + +void FTTYStartupScreen::NetDone() +{ + // Restore stdin settings + if (DidNetInit) + { + tcsetattr (STDIN_FILENO, TCSANOW, &OldTermIOS); + printf ("\n"); + DidNetInit = false; + } +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetMessage +// +// Call this between NetInit() and NetDone() instead of Printf() to +// display messages, because the progress meter is mixed in the same output +// stream as normal messages. +// +//=========================================================================== + +void FTTYStartupScreen::NetMessage(const char *format, ...) +{ + FString str; + va_list argptr; + + va_start (argptr, format); + str.VFormat (format, argptr); + va_end (argptr); + fprintf (stderr, "\r%-40s\n", str.GetChars()); +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetProgress +// +// Sets the network progress meter. If count is 0, it gets bumped by 1. +// Otherwise, it is set to count. +// +//=========================================================================== + +void FTTYStartupScreen::NetProgress(int count) +{ + int i; + + if (count == 0) + { + NetCurPos++; + } + else if (count > 0) + { + NetCurPos = count; + } + if (NetMaxPos == 0) + { + // Spinny-type progress meter, because we're a guest waiting for the host. + fprintf (stderr, "\r%s: %c", TheNetMessage, SpinnyProgressChars[NetCurPos & 3]); + fflush (stderr); + } + else if (NetMaxPos > 1) + { + // Dotty-type progress meter. + fprintf (stderr, "\r%s: ", TheNetMessage); + for (i = 0; i < NetCurPos; ++i) + { + fputc ('.', stderr); + } + fprintf (stderr, "%*c[%2d/%2d]", NetMaxPos + 1 - NetCurPos, ' ', NetCurPos, NetMaxPos); + fflush (stderr); + } +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetLoop +// +// The timer_callback function is called at least two times per second +// and passed the userdata value. It should return true to stop the loop and +// return control to the caller or false to continue the loop. +// +// ST_NetLoop will return true if the loop was halted by the callback and +// false if the loop was halted because the user wants to abort the +// network synchronization. +// +//=========================================================================== + +bool FTTYStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata) +{ + fd_set rfds; + struct timeval tv; + int retval; + char k; + + for (;;) + { + // Don't flood the network with packets on startup. + tv.tv_sec = 0; + tv.tv_usec = 500000; + + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + + retval = select (1, &rfds, NULL, NULL, &tv); + + if (retval == -1) + { + // Error + } + else if (retval == 0) + { + if (timer_callback (userdata)) + { + fputc ('\n', stderr); + return true; + } + } + else if (read (STDIN_FILENO, &k, 1) == 1) + { + // Check input on stdin + if (k == 'q' || k == 'Q') + { + fprintf (stderr, "\nNetwork game synchronization aborted."); + return false; + } + } + } +} + +void ST_Endoom() +{ + throw CExitEvent(0); +} diff --git a/source/platform/posix/unix/gtk_dialogs.cpp b/source/platform/posix/unix/gtk_dialogs.cpp new file mode 100644 index 000000000..697d5a721 --- /dev/null +++ b/source/platform/posix/unix/gtk_dialogs.cpp @@ -0,0 +1,444 @@ +/* +** gtk_dialogs.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2008-2016 Braden Obrzut +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef NO_GTK + +#if !DYN_GTK +// Function addresses will never be NULL, but that's because we're using the +// same code for both dynamic and static. +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#include +#if GTK_MAJOR_VERSION >= 3 +#include +#else +#include +typedef enum +{ + GTK_ALIGN_FULL, + GTK_ALIGN_START, + GTK_ALIGN_END, + GTK_ALIGN_CENTER, + GTK_ALIGN_BASELINE +} GtkAlign; +#endif + +#include "c_cvars.h" +#include "d_main.h" +#include "i_module.h" +#include "i_system.h" +#include "version.h" + +EXTERN_CVAR (Bool, queryiwad); + +namespace Gtk { + +FModuleMaybe GtkModule{"GTK"}; +static int GtkAvailable = -1; + +#define DYN_GTK_SYM(x) const FModuleMaybe::Req x{#x}; +#define DYN_GTK_REQ_SYM(x, proto) const FModuleMaybe::Req x{#x}; +#if GTK_MAJOR_VERSION >= 3 +#define DYN_GTK_OPT2_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#define DYN_GTK_OPT3_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#else +#define DYN_GTK_OPT2_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#define DYN_GTK_OPT3_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#endif + +DYN_GTK_SYM(g_main_context_iteration); +DYN_GTK_SYM(g_signal_connect_data); +DYN_GTK_SYM(g_type_check_instance_cast); +DYN_GTK_SYM(g_type_check_instance_is_a); +DYN_GTK_SYM(g_value_get_int); +DYN_GTK_SYM(g_value_unset); +DYN_GTK_SYM(gtk_box_get_type); +DYN_GTK_SYM(gtk_box_pack_end); +DYN_GTK_SYM(gtk_box_pack_start); +DYN_GTK_SYM(gtk_box_set_spacing); +DYN_GTK_SYM(gtk_button_box_get_type); +DYN_GTK_SYM(gtk_button_box_set_layout); +DYN_GTK_SYM(gtk_button_new_with_label); +DYN_GTK_SYM(gtk_cell_renderer_text_new); +DYN_GTK_SYM(gtk_check_button_new_with_label); +DYN_GTK_SYM(gtk_container_add); +DYN_GTK_SYM(gtk_container_get_type); +DYN_GTK_SYM(gtk_container_set_border_width); +DYN_GTK_SYM(gtk_init_check); +DYN_GTK_SYM(gtk_label_new); +DYN_GTK_SYM(gtk_list_store_append); +DYN_GTK_SYM(gtk_list_store_new); +DYN_GTK_SYM(gtk_list_store_set); +DYN_GTK_SYM(gtk_toggle_button_get_type); +DYN_GTK_SYM(gtk_toggle_button_set_active); +DYN_GTK_SYM(gtk_tree_model_get_type); +DYN_GTK_SYM(gtk_tree_model_get_value); +DYN_GTK_SYM(gtk_tree_selection_get_selected); +DYN_GTK_SYM(gtk_tree_selection_select_iter); +DYN_GTK_SYM(gtk_tree_view_append_column); +// Explicitly give the type so that attributes don't cause a warning. +DYN_GTK_REQ_SYM(gtk_tree_view_column_new_with_attributes, GtkTreeViewColumn *(*)(const gchar *, GtkCellRenderer *, ...)); +DYN_GTK_SYM(gtk_toggle_button_get_active); +DYN_GTK_SYM(gtk_tree_view_get_selection); +DYN_GTK_SYM(gtk_tree_view_get_type); +DYN_GTK_SYM(gtk_tree_view_new_with_model); +DYN_GTK_SYM(gtk_main); +DYN_GTK_SYM(gtk_main_quit); +DYN_GTK_SYM(gtk_widget_destroy); +DYN_GTK_SYM(gtk_widget_grab_default); +DYN_GTK_SYM(gtk_widget_get_type); +DYN_GTK_SYM(gtk_widget_set_can_default); +DYN_GTK_SYM(gtk_widget_show_all); +DYN_GTK_SYM(gtk_window_activate_default); +DYN_GTK_SYM(gtk_window_get_type); +DYN_GTK_SYM(gtk_window_new); +DYN_GTK_SYM(gtk_window_set_gravity); +DYN_GTK_SYM(gtk_window_set_position); +DYN_GTK_SYM(gtk_window_set_title); +DYN_GTK_SYM(gtk_window_set_resizable); +DYN_GTK_SYM(gtk_dialog_run); +DYN_GTK_SYM(gtk_dialog_get_type); + +// Gtk3 Only +DYN_GTK_OPT3_SYM(gtk_box_new, GtkWidget *(*)(GtkOrientation, gint)); +DYN_GTK_OPT3_SYM(gtk_button_box_new, GtkWidget *(*)(GtkOrientation)); +DYN_GTK_OPT3_SYM(gtk_widget_set_halign, void(*)(GtkWidget *, GtkAlign)); +DYN_GTK_OPT3_SYM(gtk_widget_set_valign, void(*)(GtkWidget *, GtkAlign)); +DYN_GTK_OPT3_SYM(gtk_message_dialog_new, GtkWidget* (*)(GtkWindow*, GtkDialogFlags, GtkMessageType, GtkButtonsType, const gchar*, ...)); + +// Gtk2 Only +DYN_GTK_OPT2_SYM(gtk_misc_get_type, GType(*)()); +DYN_GTK_OPT2_SYM(gtk_hbox_new, GtkWidget *(*)(gboolean, gint)); +DYN_GTK_OPT2_SYM(gtk_hbutton_box_new, GtkWidget *(*)()); +DYN_GTK_OPT2_SYM(gtk_misc_set_alignment, void(*)(GtkMisc *, gfloat, gfloat)); +DYN_GTK_OPT2_SYM(gtk_vbox_new, GtkWidget *(*)(gboolean, gint)); + +#undef DYN_GTK_SYM +#undef DYN_GTK_REQ_SYM +#undef DYN_GTK_OPT2_SYM +#undef DYN_GTK_OPT3_SYM + +// GtkTreeViews eats return keys. I want this to be like a Windows listbox +// where pressing Return can still activate the default button. +static gint AllowDefault(GtkWidget *widget, GdkEventKey *event, gpointer func_data) +{ + if (event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Return) + { + gtk_window_activate_default (GTK_WINDOW(func_data)); + } + return FALSE; +} + +// Double-clicking an entry in the list is the same as pressing OK. +static gint DoubleClickChecker(GtkWidget *widget, GdkEventButton *event, gpointer func_data) +{ + if (event->type == GDK_2BUTTON_PRESS) + { + *(int *)func_data = 1; + gtk_main_quit(); + } + return FALSE; +} + +// When the user presses escape, that should be the same as canceling the dialog. +static gint CheckEscape (GtkWidget *widget, GdkEventKey *event, gpointer func_data) +{ + if (event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Escape) + { + gtk_main_quit(); + } + return FALSE; +} + +static void ClickedOK(GtkButton *button, gpointer func_data) +{ + *(int *)func_data = 1; + gtk_main_quit(); +} + +static int PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + GtkWidget *window; + GtkWidget *vbox = nullptr; + GtkWidget *hbox = nullptr; + GtkWidget *bbox = nullptr; + GtkWidget *widget; + GtkWidget *tree; + GtkWidget *check; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + GtkTreeIter iter, defiter; + int close_style = 0; + int i; + char caption[100]; + + // Create the dialog window. + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + mysnprintf(caption, countof(caption), GAMESIG " %s: Select an IWAD to use", GetVersionString()); + gtk_window_set_title (GTK_WINDOW(window), caption); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_gravity (GTK_WINDOW(window), GDK_GRAVITY_CENTER); + gtk_container_set_border_width (GTK_CONTAINER(window), 10); + g_signal_connect (window, "delete_event", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect (window, "key_press_event", G_CALLBACK(CheckEscape), NULL); + + // Create the vbox container. + if (gtk_box_new) // Gtk3 + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + else if (gtk_vbox_new) // Gtk2 + vbox = gtk_vbox_new (FALSE, 10); + + gtk_container_add (GTK_CONTAINER(window), vbox); + + // Create the top label. + widget = gtk_label_new (GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"); + gtk_box_pack_start (GTK_BOX(vbox), widget, false, false, 0); + + if (gtk_widget_set_halign && gtk_widget_set_valign) // Gtk3 + { + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_valign (widget, GTK_ALIGN_START); + } + else if (gtk_misc_set_alignment && gtk_misc_get_type) // Gtk2 + gtk_misc_set_alignment (GTK_MISC(widget), 0, 0); + + // Create a list store with all the found IWADs. + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); + for (i = 0; i < numwads; ++i) + { + const char *filepart = strrchr (wads[i].Path, '/'); + if (filepart == NULL) + filepart = wads[i].Path; + else + filepart++; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, filepart, + 1, wads[i].Name.GetChars(), + 2, i, + -1); + if (i == defaultiwad) + { + defiter = iter; + } + } + + // Create the tree view control to show the list. + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("IWAD", renderer, "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Game", renderer, "text", 1, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); + gtk_box_pack_start (GTK_BOX(vbox), GTK_WIDGET(tree), true, true, 0); + g_signal_connect(G_OBJECT(tree), "button_press_event", G_CALLBACK(DoubleClickChecker), &close_style); + g_signal_connect(G_OBJECT(tree), "key_press_event", G_CALLBACK(AllowDefault), window); + + // Select the default IWAD. + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); + gtk_tree_selection_select_iter (selection, &defiter); + + // Create the hbox for the bottom row. + if (gtk_box_new) // Gtk3 + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + else if (gtk_hbox_new) // Gtk2 + hbox = gtk_hbox_new (FALSE, 0); + + gtk_box_pack_end (GTK_BOX(vbox), hbox, false, false, 0); + + // Create the "Don't ask" checkbox. + check = gtk_check_button_new_with_label ("Don't ask me this again"); + gtk_box_pack_start (GTK_BOX(hbox), check, false, false, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(check), !showwin); + + // Create the OK/Cancel button box. + if (gtk_button_box_new) // Gtk3 + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + else if (gtk_hbutton_box_new) // Gtk2 + bbox = gtk_hbutton_box_new (); + + gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX(bbox), 10); + gtk_box_pack_end (GTK_BOX(hbox), bbox, false, false, 0); + + // Create the OK button. + widget = gtk_button_new_with_label ("OK"); + + gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); + + gtk_widget_set_can_default (widget, true); + + gtk_widget_grab_default (widget); + g_signal_connect (widget, "clicked", G_CALLBACK(ClickedOK), &close_style); + g_signal_connect (widget, "activate", G_CALLBACK(ClickedOK), &close_style); + + // Create the cancel button. + widget = gtk_button_new_with_label ("Cancel"); + + gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); + g_signal_connect (widget, "clicked", G_CALLBACK(gtk_main_quit), &window); + + // Finally we can show everything. + gtk_widget_show_all (window); + + gtk_main (); + + if (close_style == 1) + { + GtkTreeModel *model; + GValue value = { 0, { {0} } }; + + // Find out which IWAD was selected. + gtk_tree_selection_get_selected (selection, &model, &iter); + gtk_tree_model_get_value (GTK_TREE_MODEL(model), &iter, 2, &value); + i = g_value_get_int (&value); + g_value_unset (&value); + + // Set state of queryiwad based on the checkbox. + queryiwad = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(check)); + } + else + { + i = -1; + } + + if (GTK_IS_WINDOW(window)) + { + gtk_widget_destroy (window); + // If we don't do this, then the X window might not actually disappear. + while (g_main_context_iteration (NULL, FALSE)) {} + } + + return i; +} + +static void ShowError(const char* errortext) +{ + GtkWidget *window; + GtkWidget *widget; + GtkWidget *vbox = nullptr; + GtkWidget *bbox = nullptr; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW(window), "Fatal error"); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_gravity (GTK_WINDOW(window), GDK_GRAVITY_CENTER); + gtk_window_set_resizable (GTK_WINDOW(window), false); + gtk_container_set_border_width (GTK_CONTAINER(window), 10); + g_signal_connect (window, "delete_event", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect (window, "key_press_event", G_CALLBACK(CheckEscape), NULL); + + // Create the vbox container. + if (gtk_box_new) // Gtk3 + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + else if (gtk_vbox_new) // Gtk2 + vbox = gtk_vbox_new (FALSE, 10); + + gtk_container_add (GTK_CONTAINER(window), vbox); + + // Create the label. + widget = gtk_label_new ((const gchar *) errortext); + gtk_box_pack_start (GTK_BOX(vbox), widget, false, false, 0); + + if (gtk_widget_set_halign && gtk_widget_set_valign) // Gtk3 + { + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_valign (widget, GTK_ALIGN_START); + } + else if (gtk_misc_set_alignment && gtk_misc_get_type) // Gtk2 + gtk_misc_set_alignment (GTK_MISC(widget), 0, 0); + + // Create the Exit button box. + if (gtk_button_box_new) // Gtk3 + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + else if (gtk_hbutton_box_new) // Gtk2 + bbox = gtk_hbutton_box_new (); + + gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX(bbox), 10); + gtk_box_pack_end (GTK_BOX(vbox), bbox, false, false, 0); + + // Create the cancel button. + widget = gtk_button_new_with_label ("Exit"); + gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); + g_signal_connect (widget, "clicked", G_CALLBACK(gtk_main_quit), &window); + + // Finally we can show everything. + gtk_widget_show_all (window); + + gtk_main (); + + if (GTK_IS_WINDOW(window)) + { + gtk_widget_destroy (window); + // If we don't do this, then the X window might not actually disappear. + while (g_main_context_iteration (NULL, FALSE)) {} + } +} + +} // namespace Gtk + +int I_PickIWad_Gtk (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + return Gtk::PickIWad (wads, numwads, showwin, defaultiwad); +} + +void I_ShowFatalError_Gtk(const char* errortext) { + Gtk::ShowError(errortext); +} + +bool I_GtkAvailable() +{ + using namespace Gtk; + + if(GtkAvailable < 0) + { + if (!GtkModule.Load({"libgtk-3.so.0", "libgtk-x11-2.0.so.0"})) + { + GtkAvailable = 0; + return false; + } + + int argc = 0; + char **argv = nullptr; + GtkAvailable = Gtk::gtk_init_check (&argc, &argv); + } + + return GtkAvailable != 0; +} + +#endif diff --git a/source/platform/unix/i_specialpaths.cpp b/source/platform/posix/unix/i_specialpaths.cpp similarity index 100% rename from source/platform/unix/i_specialpaths.cpp rename to source/platform/posix/unix/i_specialpaths.cpp diff --git a/source/platform/posix/zdoom.xpm b/source/platform/posix/zdoom.xpm new file mode 100644 index 000000000..daf54d701 --- /dev/null +++ b/source/platform/posix/zdoom.xpm @@ -0,0 +1,8613 @@ +/* XPM */ +static char * zdoom_xpm[] = { +"256 256 8354 2", +" c None", +". c #161616", +"+ c #000000", +"@ c #111111", +"# c #656565", +"$ c #434343", +"% c #282828", +"& c #0D0D0D", +"* c #080808", +"= c #222222", +"- c #3D3D3D", +"; c #5F5F5F", +"> c #444444", +", c #262626", +"' c #0F0F0F", +") c #0A0A0A", +"! c #202020", +"~ c #373737", +"{ c #4B4B4B", +"] c #141414", +"^ c #1E1E1E", +"/ c #0E0E0E", +"( c #050505", +"_ c #171717", +": c #060606", +"< c #040404", +"[ c #070707", +"} c #121212", +"| c #000001", +"1 c #000002", +"2 c #000003", +"3 c #1C1C1C", +"4 c #010105", +"5 c #02020C", +"6 c #020412", +"7 c #040618", +"8 c #05081D", +"9 c #060821", +"0 c #060925", +"a c #060A28", +"b c #080B2B", +"c c #080C2E", +"d c #080C2F", +"e c #080D31", +"f c #090D33", +"g c #090D34", +"h c #080D2F", +"i c #060A25", +"j c #060922", +"k c #030513", +"l c #02030D", +"m c #010207", +"n c #303030", +"o c #333333", +"p c #02020B", +"q c #030514", +"r c #05081E", +"s c #070A28", +"t c #080D30", +"u c #090E35", +"v c #0A0F39", +"w c #0B103E", +"x c #0B1142", +"y c #0C1246", +"z c #0D1349", +"A c #0D144C", +"B c #0D144E", +"C c #0E154D", +"D c #0D144B", +"E c #0D144A", +"F c #0E154E", +"G c #0D144D", +"H c #0D134A", +"I c #0C1243", +"J c #0B103F", +"K c #0A0F3A", +"L c #090E36", +"M c #090D31", +"N c #070A2A", +"O c #050821", +"P c #040617", +"Q c #03040D", +"R c #000102", +"S c #3C3C3C", +"T c #000104", +"U c #02020A", +"V c #060923", +"W c #080C30", +"X c #0A103B", +"Y c #0C1245", +"Z c #0D1348", +"` c #0D1347", +" . c #0C1346", +".. c #0C1244", +"+. c #0B113F", +"@. c #0A103C", +"#. c #090E34", +"$. c #060A26", +"%. c #04071A", +"&. c #02030C", +"*. c #010104", +"=. c #1F1F1F", +"-. c #02030E", +";. c #070B2B", +">. c #090F39", +",. c #0C1142", +"'. c #0C1347", +"). c #0B103D", +"!. c #080C2D", +"~. c #05071E", +"{. c #020310", +"]. c #010209", +"^. c #151515", +"/. c #040619", +"(. c #070B29", +"_. c #0C1349", +":. c #080B2D", +"<. c #05071C", +"[. c #030410", +"}. c #212121", +"|. c #030512", +"1. c #080C31", +"2. c #0B1040", +"3. c #0E144D", +"4. c #090D35", +"5. c #060926", +"6. c #040616", +"7. c #03030F", +"8. c #0C1141", +"9. c #0C1143", +"0. c #0C1144", +"a. c #090F38", +"b. c #070B2A", +"c. c #010208", +"d. c #060820", +"e. c #0A0F37", +"f. c #0C1041", +"g. c #0B0F40", +"h. c #0C1042", +"i. c #0C1145", +"j. c #0D1448", +"k. c #0C1448", +"l. c #0B1345", +"m. c #0C1549", +"n. c #0E1A53", +"o. c #0F1F5C", +"p. c #0F215D", +"q. c #0F225F", +"r. c #0F2260", +"s. c #112869", +"t. c #102764", +"u. c #0E245B", +"v. c #0E245A", +"w. c #102965", +"x. c #0F265F", +"y. c #0E2355", +"z. c #0E275E", +"A. c #0F2760", +"B. c #102862", +"C. c #0F255C", +"D. c #0F235A", +"E. c #102663", +"F. c #112868", +"G. c #0F2664", +"H. c #112668", +"I. c #0F205E", +"J. c #0F1D59", +"K. c #0D174F", +"L. c #0C1043", +"M. c #0B1041", +"N. c #010106", +"O. c #030515", +"P. c #0B1141", +"Q. c #0B0F3E", +"R. c #0B0F3F", +"S. c #0C1247", +"T. c #0D154B", +"U. c #0D184F", +"V. c #0D1850", +"W. c #102262", +"X. c #12286A", +"Y. c #122A6B", +"Z. c #102A68", +"`. c #0F2860", +" + c #10285D", +".+ c #102A5F", +"++ c #12316D", +"@+ c #102C62", +"#+ c #0F285B", +"$+ c #102B62", +"%+ c #12306B", +"&+ c #102A5E", +"*+ c #102D61", +"=+ c #112D64", +"-+ c #122F68", +";+ c #0F2A5D", +">+ c #0F295B", +",+ c #112F68", +"'+ c #112D66", +")+ c #102B60", +"!+ c #102D64", +"~+ c #133574", +"{+ c #133473", +"]+ c #112F6A", +"^+ c #11306A", +"/+ c #112E66", +"(+ c #12306D", +"_+ c #12306E", +":+ c #13316F", +"<+ c #102962", +"[+ c #102A65", +"}+ c #112C6D", +"|+ c #102668", +"1+ c #10215F", +"2+ c #0D1A52", +"3+ c #0C174D", +"4+ c #0E174E", +"5+ c #0D154D", +"6+ c #0C1248", +"7+ c #0B0E3F", +"8+ c #0B0E3E", +"9+ c #02030A", +"0+ c #0A0D3B", +"a+ c #0B0F43", +"b+ c #0E1752", +"c+ c #0F1B56", +"d+ c #0E1D53", +"e+ c #0E1E55", +"f+ c #102867", +"g+ c #112E6F", +"h+ c #123271", +"i+ c #123371", +"j+ c #112F69", +"k+ c #102D65", +"l+ c #102B61", +"m+ c #102C65", +"n+ c #102C63", +"o+ c #11316E", +"p+ c #112D65", +"q+ c #102B5F", +"r+ c #0F295D", +"s+ c #113270", +"t+ c #0F2C61", +"u+ c #0E2858", +"v+ c #0E2656", +"w+ c #0F2B61", +"x+ c #12306C", +"y+ c #102C61", +"z+ c #122F6A", +"A+ c #102E67", +"B+ c #112E67", +"C+ c #112E68", +"D+ c #102D62", +"E+ c #102E65", +"F+ c #102D66", +"G+ c #102963", +"H+ c #0F2765", +"I+ c #0D2059", +"J+ c #0E1B56", +"K+ c #0E1850", +"L+ c #0C1249", +"M+ c #030411", +"N+ c #2C2C2C", +"O+ c #060A29", +"P+ c #0B1140", +"Q+ c #0E144C", +"R+ c #0C0E3F", +"S+ c #0D154C", +"T+ c #102766", +"U+ c #122C6E", +"V+ c #122E6C", +"W+ c #0F2C62", +"X+ c #0E2958", +"Y+ c #11316B", +"Z+ c #12336F", +"`+ c #12316E", +" @ c #112E6A", +".@ c #133170", +"+@ c #0F295C", +"@@ c #122F69", +"#@ c #123270", +"$@ c #102E66", +"%@ c #0E2658", +"&@ c #0E2859", +"*@ c #102E68", +"=@ c #0E2757", +"-@ c #0D2655", +";@ c #133476", +">@ c #112C63", +",@ c #0F2A60", +"'@ c #112F6B", +")@ c #0E2758", +"!@ c #0F2959", +"~@ c #0F2C5F", +"{@ c #123472", +"]@ c #112F6D", +"^@ c #112A67", +"/@ c #0E235A", +"(@ c #0E1E56", +"_@ c #0E1E59", +":@ c #0D1650", +"<@ c #0B0F42", +"[@ c #0C1040", +"}@ c #070C2F", +"|@ c #272727", +"1@ c #080C32", +"2@ c #0B1243", +"3@ c #0B1143", +"4@ c #0C164E", +"5@ c #102363", +"6@ c #0F2663", +"7@ c #112B66", +"8@ c #14367B", +"9@ c #14397F", +"0@ c #133776", +"a@ c #12336C", +"b@ c #112E64", +"c@ c #133576", +"d@ c #11306B", +"e@ c #12326E", +"f@ c #133475", +"g@ c #11306C", +"h@ c #112C64", +"i@ c #12316C", +"j@ c #0D2451", +"k@ c #0F2859", +"l@ c #12326F", +"m@ c #0E2655", +"n@ c #10306C", +"o@ c #11306D", +"p@ c #0F295E", +"q@ c #0E285A", +"r@ c #0F2B62", +"s@ c #0E285B", +"t@ c #102B63", +"u@ c #112F66", +"v@ c #102E64", +"w@ c #102E63", +"x@ c #112F6C", +"y@ c #102A67", +"z@ c #0F255D", +"A@ c #0E1A56", +"B@ c #0A0E36", +"C@ c #04071C", +"D@ c #1B1B1B", +"E@ c #080D33", +"F@ c #0B1144", +"G@ c #0C1044", +"H@ c #0E1851", +"I@ c #0E2260", +"J@ c #0E2762", +"K@ c #0F2B63", +"L@ c #12377A", +"M@ c #123574", +"N@ c #11316D", +"O@ c #113472", +"P@ c #133980", +"Q@ c #133679", +"R@ c #102F6A", +"S@ c #10306B", +"T@ c #0F2D63", +"U@ c #11316C", +"V@ c #10316D", +"W@ c #13377A", +"X@ c #13397D", +"Y@ c #123576", +"Z@ c #10306E", +"`@ c #0E295E", +" # c #0E295C", +".# c #0F2D65", +"+# c #0F2D62", +"@# c #0E2B60", +"## c #0E2A5F", +"$# c #0F2C63", +"%# c #0F2B60", +"&# c #0E295B", +"*# c #0E2A60", +"=# c #102F69", +"-# c #11326F", +";# c #0F2A5F", +"># c #0F2A5E", +",# c #102F68", +"'# c #0F2D64", +")# c #0F2D66", +"!# c #0F2E65", +"~# c #0F2C60", +"{# c #123473", +"]# c #102E6B", +"^# c #102D6E", +"/# c #0F2465", +"(# c #0C0F40", +"_# c #0A0F38", +":# c #080D32", +"<# c #0B1245", +"[# c #0C103F", +"}# c #0B0D3D", +"|# c #0C134A", +"1# c #0C164C", +"2# c #0D1F5C", +"3# c #112E70", +"4# c #102F6B", +"5# c #103068", +"6# c #11326D", +"7# c #10306A", +"8# c #10306D", +"9# c #10316E", +"0# c #123676", +"a# c #113373", +"b# c #123677", +"c# c #0E2A5D", +"d# c #0F2F69", +"e# c #113576", +"f# c #133A81", +"g# c #12377B", +"h# c #113475", +"i# c #0E2A5E", +"j# c #0F2E68", +"k# c #0D2757", +"l# c #0F2B5F", +"m# c #11316F", +"n# c #113370", +"o# c #10326F", +"p# c #0E2C62", +"q# c #0F2E66", +"r# c #102E69", +"s# c #0E2B62", +"t# c #0F2C64", +"u# c #0F2E67", +"v# c #113371", +"w# c #0F2E69", +"x# c #0F2A68", +"y# c #0E225F", +"z# c #0D184E", +"A# c #0B0F41", +"B# c #04071B", +"C# c #242424", +"D# c #080B2F", +"E# c #0C0F3D", +"F# c #0E205E", +"G# c #102B70", +"H# c #113477", +"I# c #10326B", +"J# c #0E2959", +"K# c #123374", +"L# c #0E2B5E", +"M# c #113474", +"N# c #113271", +"O# c #10316C", +"P# c #113372", +"Q# c #0D2A5D", +"R# c #0D2756", +"S# c #0D295D", +"T# c #0D2758", +"U# c #0D285A", +"V# c #0E2C60", +"W# c #113473", +"X# c #102F67", +"Y# c #12367A", +"Z# c #0F2F68", +"`# c #0E2B5F", +" $ c #103370", +".$ c #123476", +"+$ c #10326E", +"@$ c #112E72", +"#$ c #0D205C", +"$$ c #0E1952", +"%$ c #0C0F41", +"&$ c #0B0E3C", +"*$ c #090F36", +"=$ c #040516", +"-$ c #01030C", +";$ c #0B1043", +">$ c #0E1A55", +",$ c #0F2564", +"'$ c #0F2D6D", +")$ c #11377A", +"!$ c #113673", +"~$ c #0B2450", +"{$ c #0D2A5C", +"]$ c #0F2F66", +"^$ c #0F316D", +"/$ c #0F306B", +"($ c #0E2D65", +"_$ c #103371", +":$ c #0E2C63", +"<$ c #0E2D64", +"[$ c #0D2858", +"}$ c #0D2B5F", +"|$ c #0D2A5E", +"1$ c #0E2E66", +"2$ c #0F2F6A", +"3$ c #10326D", +"4$ c #0D295E", +"5$ c #0D2859", +"6$ c #0E2C61", +"7$ c #102F6C", +"8$ c #10336E", +"9$ c #0F326E", +"0$ c #103276", +"a$ c #102A6D", +"b$ c #0E1C59", +"c$ c #0C1146", +"d$ c #0C0F3F", +"e$ c #101010", +"f$ c #0D1E5B", +"g$ c #0D265F", +"h$ c #103274", +"i$ c #123A80", +"j$ c #0F326C", +"k$ c #0F316B", +"l$ c #0B234E", +"m$ c #103373", +"n$ c #0F306C", +"o$ c #103576", +"p$ c #103472", +"q$ c #0E3068", +"r$ c #0F316E", +"s$ c #0E2F68", +"t$ c #0E2B61", +"u$ c #0E2F69", +"v$ c #0E306A", +"w$ c #103475", +"x$ c #0E2D63", +"y$ c #0D2C62", +"z$ c #0E2F6A", +"A$ c #0F3371", +"B$ c #113676", +"C$ c #0F2F67", +"D$ c #0F306A", +"E$ c #0D2B60", +"F$ c #0C2756", +"G$ c #0D2C5F", +"H$ c #0D2C61", +"I$ c #0E2C64", +"J$ c #0D2B5E", +"K$ c #0C2758", +"L$ c #113678", +"M$ c #0F3270", +"N$ c #0E2E67", +"O$ c #113677", +"P$ c #0E2E65", +"Q$ c #0B2552", +"R$ c #10316F", +"S$ c #0F336F", +"T$ c #0F3069", +"U$ c #103372", +"V$ c #0F326F", +"W$ c #103473", +"X$ c #11387A", +"Y$ c #11357A", +"Z$ c #0F2C6C", +"`$ c #0F2262", +" % c #070B2D", +".% c #0F2366", +"+% c #0F2B69", +"@% c #0F3370", +"#% c #0F336E", +"$% c #0D2B61", +"%% c #0D2D64", +"&% c #0C2654", +"*% c #0B234F", +"=% c #0E2D66", +"-% c #0E2E64", +";% c #0F316C", +">% c #103574", +",% c #0E2F67", +"'% c #0C2857", +")% c #0C2858", +"!% c #0D295B", +"~% c #0F3271", +"{% c #113679", +"]% c #0C2859", +"^% c #0F316F", +"/% c #0F2F6B", +"(% c #0E306B", +"_% c #103270", +":% c #113779", +"<% c #103575", +"[% c #0F3472", +"}% c #12347B", +"|% c #0F2567", +"1% c #0D1750", +"2% c #050820", +"3% c #000103", +"4% c #353535", +"5% c #070C2D", +"6% c #0C144B", +"7% c #0E205B", +"8% c #11337B", +"9% c #0D2C60", +"0% c #0E3069", +"a% c #0D2A5F", +"b% c #0C2653", +"c% c #103679", +"d% c #103474", +"e% c #103374", +"f% c #11397E", +"g% c #0D2E67", +"h% c #0D295A", +"i% c #0F2F6C", +"j% c #0C295B", +"k% c #0C295C", +"l% c #0E326E", +"m% c #103577", +"n% c #11367A", +"o% c #103677", +"p% c #113278", +"q% c #102768", +"r% c #0B0D3A", +"s% c #020202", +"t% c #0B1242", +"u% c #0D1449", +"v% c #0D215B", +"w% c #11347D", +"x% c #0D2D63", +"y% c #113575", +"z% c #0D2A60", +"A% c #0B2554", +"B% c #0C2552", +"C% c #0D295C", +"D% c #0E316D", +"E% c #11377C", +"F% c #113778", +"G% c #0E306C", +"H% c #0C285B", +"I% c #11387E", +"J% c #0C2655", +"K% c #0C2759", +"L% c #0D2C63", +"M% c #0D2E66", +"N% c #0C285A", +"O% c #11377B", +"P% c #113578", +"Q% c #0F3372", +"R% c #11387D", +"S% c #12397D", +"T% c #103778", +"U% c #103478", +"V% c #112A6F", +"W% c #0E1A57", +"X% c #0C1348", +"Y% c #060A27", +"Z% c #000105", +"`% c #0D134C", +" & c #0E215E", +".& c #103072", +"+& c #113776", +"@& c #0F306D", +"#& c #10377A", +"$& c #12397E", +"%& c #103678", +"&& c #0E2E68", +"*& c #0E3067", +"=& c #11387C", +"-& c #11397F", +";& c #0B2452", +">& c #133A83", +",& c #0F3471", +"'& c #0B2757", +")& c #0D2D65", +"!& c #0F326D", +"~& c #0F3473", +"{& c #12397F", +"]& c #133D88", +"^& c #123B81", +"/& c #123B83", +"(& c #103675", +"_& c #13418E", +":& c #11357C", +"<& c #0E2667", +"[& c #0C0F3E", +"}& c #090F3A", +"|& c #232323", +"1& c #181818", +"2& c #0D1E5C", +"3& c #0F2D6E", +"4& c #113A82", +"5& c #103779", +"6& c #0E316E", +"7& c #0E306D", +"8& c #10387B", +"9& c #0F3576", +"0& c #103980", +"a& c #0E316C", +"b& c #123E8B", +"c& c #10377B", +"d& c #123C85", +"e& c #0E3371", +"f& c #0F3574", +"g& c #0C2B60", +"h& c #0D2F68", +"i& c #0E326F", +"j& c #0F3272", +"k& c #0C2C62", +"l& c #0D2F69", +"m& c #0F3475", +"n& c #0F3373", +"o& c #0F3474", +"p& c #0F3577", +"q& c #10377C", +"r& c #10397E", +"s& c #113980", +"t& c #10387D", +"u& c #113C87", +"v& c #10387A", +"w& c #0E2F70", +"x& c #0D2262", +"y& c #0B1244", +"z& c #010107", +"A& c #0D1B57", +"B& c #0F2E73", +"C& c #10397F", +"D& c #10397D", +"E& c #0D306B", +"F& c #0E3472", +"G& c #0C2C60", +"H& c #0D306A", +"I& c #0D306C", +"J& c #0D326D", +"K& c #10387C", +"L& c #0F3679", +"M& c #0D2F67", +"N& c #0F387B", +"O& c #0D316C", +"P& c #0E3474", +"Q& c #103B81", +"R& c #0E316B", +"S& c #0C2A5D", +"T& c #0E3473", +"U& c #0E3471", +"V& c #0E326D", +"W& c #0C2D64", +"X& c #0C2D63", +"Y& c #0F377A", +"Z& c #0F3678", +"`& c #0E3370", +" * c #0D2F66", +".* c #0D3069", +"+* c #0D2F6A", +"@* c #0F3575", +"#* c #0E336F", +"$* c #0F3677", +"%* c #103C84", +"&* c #113A7F", +"** c #113B83", +"=* c #10387E", +"-* c #113D86", +";* c #113E88", +">* c #10337A", +",* c #0B1044", +"'* c #0A0E37", +")* c #020410", +"!* c #0A103D", +"~* c #0D164E", +"{* c #0F2666", +"]* c #0F3374", +"^* c #123F89", +"/* c #134494", +"(* c #0F3676", +"_* c #0E3270", +":* c #103A81", +"<* c #123E8A", +"[* c #0E3576", +"}* c #0F387D", +"|* c #0E3676", +"1* c #103578", +"2* c #0C2B5F", +"3* c #0C2A5E", +"4* c #0C2B5E", +"5* c #0F3778", +"6* c #0C2B61", +"7* c #0E3373", +"8* c #113C85", +"9* c #103A80", +"0* c #113C86", +"a* c #13408F", +"b* c #12408E", +"c* c #134495", +"d* c #102C72", +"e* c #0D164C", +"f* c #0C0D3E", +"g* c #0C1345", +"h* c #050822", +"i* c #0C1F5C", +"j* c #0E2F71", +"k* c #0F3476", +"l* c #0D2F6B", +"m* c #113D87", +"n* c #123F8F", +"o* c #123F8D", +"p* c #0E3476", +"q* c #0E3271", +"r* c #0D316D", +"s* c #0E316F", +"t* c #103A83", +"u* c #0A2759", +"v* c #0A2451", +"w* c #0D306D", +"x* c #10377D", +"y* c #0E3372", +"z* c #0B2A5D", +"A* c #0B2A5F", +"B* c #0E3272", +"C* c #0E3475", +"D* c #0F377C", +"E* c #0F367A", +"F* c #0F3477", +"G* c #0E3171", +"H* c #0F387F", +"I* c #0D316E", +"J* c #11387F", +"K* c #0B295C", +"L* c #0C2C63", +"M* c #0F387E", +"N* c #0B2C62", +"O* c #0B295E", +"P* c #0E3374", +"Q* c #0D316F", +"R* c #0C2E67", +"S* c #0C2E68", +"T* c #0C2D65", +"U* c #0F3578", +"V* c #0C2E66", +"W* c #0C2C64", +"X* c #0C306D", +"Y* c #0E3375", +"Z* c #0E3679", +"`* c #113B85", +" = c #0F3980", +".= c #0F397F", +"+= c #103C86", +"@= c #103B84", +"#= c #0F3579", +"$= c #113C88", +"%= c #124191", +"&= c #113E89", +"*= c #12418F", +"== c #123D8D", +"-= c #0F276B", +";= c #0B0C3A", +">= c #404040", +",= c #0E2868", +"'= c #0E3577", +")= c #0D3370", +"!= c #0F387C", +"~= c #0E387D", +"{= c #0D3272", +"]= c #0F3A83", +"^= c #0C2D67", +"/= c #103F8D", +"(= c #103E8C", +"_= c #0F377D", +":= c #0D3374", +"<= c #0C2F6B", +"[= c #0C306C", +"}= c #0E3678", +"|= c #0D3270", +"1= c #0B2B5F", +"2= c #0A295C", +"3= c #0B2B61", +"4= c #0B285B", +"5= c #0A2655", +"6= c #0A2756", +"7= c #092655", +"8= c #103A82", +"9= c #0E377A", +"0= c #0D3271", +"a= c #0D326F", +"b= c #0C316D", +"c= c #0B2D64", +"d= c #0B2B60", +"e= c #0F367B", +"f= c #0B2D65", +"g= c #0C2F68", +"h= c #0F397E", +"i= c #0D3474", +"j= c #0D3373", +"k= c #0B295D", +"l= c #0B2A60", +"m= c #0B2658", +"n= c #0B2A5E", +"o= c #0C316B", +"p= c #0C2F6A", +"q= c #0C2F69", +"r= c #0E3578", +"s= c #0E3575", +"t= c #0F377B", +"u= c #0B285C", +"v= c #0C2D66", +"w= c #0D3372", +"x= c #103C87", +"y= c #113F8C", +"z= c #0E367A", +"A= c #0F3A81", +"B= c #0E397F", +"C= c #10408D", +"D= c #114191", +"E= c #11388B", +"F= c #0D1D5E", +"G= c #0C0E3E", +"H= c #3F3F3F", +"I= c #0A0F3C", +"J= c #0D205E", +"K= c #0D3274", +"L= c #0E3A81", +"M= c #0F3B85", +"N= c #0E377B", +"O= c #0C306A", +"P= c #0B2859", +"Q= c #082048", +"R= c #081F47", +"S= c #0A2553", +"T= c #0D326E", +"U= c #113E8B", +"V= c #0E3579", +"W= c #0E3779", +"X= c #0C2C65", +"Y= c #0C2D68", +"Z= c #0D3371", +"`= c #0E377C", +" - c #0E377D", +".- c #0F3B82", +"+- c #0F3C85", +"@- c #0F3A7E", +"#- c #114291", +"$- c #103A86", +"%- c #0E286E", +"&- c #0D134B", +"*- c #05071B", +"=- c #0E144A", +"-- c #0C154D", +";- c #102D74", +">- c #0F3A7F", +",- c #0E367B", +"'- c #0F3A84", +")- c #0C2B62", +"!- c #0D3475", +"~- c #0C306B", +"{- c #0C316E", +"]- c #0A2858", +"^- c #0A285A", +"/- c #0B295B", +"(- c #0A2656", +"_- c #092451", +":- c #0B2C61", +"<- c #0F3981", +"[- c #0D2E68", +"}- c #0C3270", +"|- c #0B2C64", +"1- c #0E387C", +"2- c #0E387F", +"3- c #0F3B83", +"4- c #103D8A", +"5- c #0E397E", +"6- c #0F3A80", +"7- c #0F3C86", +"8- c #103685", +"9- c #0D1A55", +"0- c #090909", +"a- c #060823", +"b- c #0D1E5F", +"c- c #0E3277", +"d- c #103E89", +"e- c #0B2D63", +"f- c #0D3170", +"g- c #0B2F6A", +"h- c #0D3476", +"i- c #0C3069", +"j- c #0A2B60", +"k- c #0A2554", +"l- c #0A285B", +"m- c #103D88", +"n- c #103D87", +"o- c #0D3473", +"p- c #0D3577", +"q- c #0E3677", +"r- c #0B2C63", +"s- c #0B2D66", +"t- c #0C2B63", +"u- c #0B2E68", +"v- c #0D3576", +"w- c #0F3D88", +"x- c #0F3B86", +"y- c #103982", +"z- c #103F8A", +"A- c #0F3B80", +"B- c #0E367E", +"C- c #0E2569", +"D- c #191919", +"E- c #0C1147", +"F- c #0D266A", +"G- c #103B87", +"H- c #0B2B62", +"I- c #0A2758", +"J- c #09224E", +"K- c #0A2350", +"L- c #0D367A", +"M- c #103E8A", +"N- c #0F3A82", +"O- c #103B85", +"P- c #0D3678", +"Q- c #0C3370", +"R- c #0D3575", +"S- c #0A2859", +"T- c #0C2E69", +"U- c #0F3C87", +"V- c #103F8C", +"W- c #0F3B84", +"X- c #0D2868", +"Y- c #0D154E", +"Z- c #020209", +"`- c #0D1751", +" ; c #0D2F76", +".; c #0F3F8C", +"+; c #0D3778", +"@; c #0C3576", +"#; c #0F3D8B", +"$; c #103E8B", +"%; c #0B2E67", +"&; c #0B2D67", +"*; c #092351", +"=; c #0D387C", +"-; c #0C3371", +";; c #0B306B", +">; c #0C316F", +",; c #0A2A5D", +"'; c #104090", +"); c #0B2F6C", +"!; c #0C3271", +"~; c #0D3579", +"{; c #0D3679", +"]; c #0E387E", +"^; c #124599", +"/; c #0C3474", +"(; c #0D377A", +"_; c #104192", +":; c #0A2A5F", +"<; c #0B306A", +"[; c #0C3374", +"}; c #0E3981", +"|; c #0D3779", +"1; c #0B2E69", +"2; c #0C326F", +"3; c #0B2F69", +"4; c #0B326E", +"5; c #0D387D", +"6; c #0A295B", +"7; c #0A295A", +"8; c #0E3B85", +"9; c #0F3E8C", +"0; c #0E3B84", +"a; c #0E3980", +"b; c #0F3D89", +"c; c #0F3D8C", +"d; c #0B3069", +"e; c #0C2A6A", +"f; c #0D1E5E", +"g; c #0B0F3D", +"h; c #03040F", +"i; c #030303", +"j; c #0D1E5A", +"k; c #0D347B", +"l; c #0F418E", +"m; c #10408F", +"n; c #0E3C86", +"o; c #0F3E8A", +"p; c #0D397F", +"q; c #0F3F8D", +"r; c #0D3A81", +"s; c #0C3472", +"t; c #0A2C62", +"u; c #08224D", +"v; c #0C316C", +"w; c #0B316D", +"x; c #0D377B", +"y; c #104293", +"z; c #09285A", +"A; c #0A2C63", +"B; c #0C3373", +"C; c #0B316C", +"D; c #0C3577", +"E; c #0E3C88", +"F; c #0C3372", +"G; c #0D387F", +"H; c #0C3779", +"I; c #092A5E", +"J; c #0B306D", +"K; c #0C326E", +"L; c #0A2B61", +"M; c #0B2F66", +"N; c #0A2A5E", +"O; c #0B306C", +"P; c #0B316E", +"Q; c #09295C", +"R; c #0A2E65", +"S; c #0A2E67", +"T; c #0C3272", +"U; c #0F3F8E", +"V; c #0D3980", +"W; c #0B2F68", +"X; c #0D3478", +"Y; c #0D2669", +"Z; c #0B0E40", +"`; c #0C205F", +" > c #0E3A80", +".> c #0F3E8B", +"+> c #0E3A7F", +"@> c #092757", +"#> c #0A295E", +"$> c #0E3B81", +"%> c #0E3A82", +"&> c #0F408F", +"*> c #0F408D", +"=> c #104190", +"-> c #0D3677", +";> c #0B3371", +">> c #0E3D87", +",> c #0B2E66", +"'> c #0B2E6A", +")> c #0D3578", +"!> c #0D3880", +"~> c #0C3575", +"{> c #0A2B62", +"]> c #0A2B5F", +"^> c #09285B", +"/> c #0F3D87", +"(> c #0C3477", +"_> c #0E3B82", +":> c #0D397E", +"<> c #0E3A84", +"[> c #0E3D88", +"}> c #0D3A80", +"|> c #0B3270", +"1> c #0B2460", +"2> c #0C0E3D", +"3> c #0C1242", +"4> c #02030F", +"5> c #0C2565", +"6> c #0F4190", +"7> c #0C397D", +"8> c #0F4191", +"9> c #0C377B", +"0> c #0B316F", +"a> c #0F3E89", +"b> c #0C3677", +"c> c #0F4293", +"d> c #0E3B83", +"e> c #0B3372", +"f> c #0C377C", +"g> c #0C3473", +"h> c #0A2D65", +"i> c #0D367B", +"j> c #0D3B82", +"k> c #0A2D64", +"l> c #0C3475", +"m> c #0B306E", +"n> c #0C3679", +"o> c #0A2A5C", +"p> c #0C3678", +"q> c #092A5C", +"r> c #0B336F", +"s> c #092A5F", +"t> c #0D377D", +"u> c #092B5F", +"v> c #0B2E65", +"w> c #0F408E", +"x> c #0B3373", +"y> c #0E3F8C", +"z> c #0D2C70", +"A> c #0E1957", +"B> c #313131", +"C> c #0A2763", +"D> c #0C397C", +"E> c #0E3F8D", +"F> c #0E3E88", +"G> c #0B3474", +"H> c #0B3678", +"I> c #0C3676", +"J> c #0B326F", +"K> c #0D3C85", +"L> c #0D3E8A", +"M> c #0E408E", +"N> c #0C387C", +"O> c #0F4292", +"P> c #0C3578", +"Q> c #0A2E69", +"R> c #0A316E", +"S> c #082759", +"T> c #0E418F", +"U> c #0B3370", +"V> c #09295A", +"W> c #0A2F67", +"X> c #0B3575", +"Y> c #0C367B", +"Z> c #0C377A", +"`> c #0B3472", +" , c #0B3574", +"., c #0A316C", +"+, c #0A306B", +"@, c #0C3A81", +"#, c #0C3A80", +"$, c #0B3271", +"%, c #0D397D", +"&, c #0B3473", +"*, c #092D65", +"=, c #0A2D66", +"-, c #0C387F", +";, c #0A2E66", +">, c #0A306A", +",, c #0A2C61", +"', c #092D63", +"), c #0A2D62", +"!, c #0A2C65", +"~, c #092A5D", +"{, c #0A306C", +"], c #092C61", +"^, c #072149", +"/, c #092B60", +"(, c #0A3069", +"_, c #0D3D86", +":, c #0D3F8A", +"<, c #0E4190", +"[, c #0D3B85", +"}, c #0D3B83", +"|, c #0D377C", +"1, c #0B3576", +"2, c #0D3B84", +"3, c #092D64", +"4, c #0E408B", +"5, c #0F3A88", +"6, c #0C1A55", +"7, c #0B0E3D", +"8, c #0C2E73", +"9, c #0D3D85", +"0, c #0D3A82", +"a, c #0C3A7F", +"b, c #0C397F", +"c, c #0D3D88", +"d, c #0D3981", +"e, c #0A3270", +"f, c #0B3475", +"g, c #082858", +"h, c #0A2F69", +"i, c #0A2E68", +"j, c #0A326E", +"k, c #0B3577", +"l, c #0E3C85", +"m, c #0F459B", +"n, c #0B3578", +"o, c #0D387B", +"p, c #0A2E64", +"q, c #0B3679", +"r, c #0C3574", +"s, c #0D3C86", +"t, c #092B61", +"u, c #08285A", +"v, c #09295B", +"w, c #08244F", +"x, c #0E3E8A", +"y, c #0C387D", +"z, c #0E408F", +"A, c #0C397E", +"B, c #0D3D89", +"C, c #0F4496", +"D, c #0B367A", +"E, c #0F418F", +"F, c #0E3F8B", +"G, c #0C397A", +"H, c #0C357B", +"I, c #0C1B56", +"J, c #0B0D3E", +"K, c #1A1A1A", +"L, c #0E144B", +"M, c #0C0D3B", +"N, c #0D1B55", +"O, c #0C347B", +"P, c #0D418E", +"Q, c #0D3C84", +"R, c #0C3B84", +"S, c #0D3E87", +"T, c #0C3A83", +"U, c #0F469A", +"V, c #0B377B", +"W, c #0A316B", +"X, c #0A326F", +"Y, c #0C3C86", +"Z, c #0C387A", +"`, c #0C387B", +" ' c #0B3677", +".' c #0B3779", +"+' c #0C3B82", +"@' c #092C62", +"#' c #0C3980", +"$' c #0A2F68", +"%' c #082859", +"&' c #092C5F", +"*' c #092C64", +"=' c #082756", +"-' c #092859", +";' c #082654", +">' c #082552", +",' c #082755", +"'' c #092C63", +")' c #0E3E89", +"!' c #0B377A", +"~' c #0F4395", +"{' c #0A326B", +"]' c #0B357A", +"^' c #0C2060", +"/' c #0C0D3D", +"(' c #0D1C5B", +"_' c #0D3782", +":' c #0E3F8A", +"<' c #0E408D", +"[' c #0C3B81", +"}' c #0A2F6A", +"|' c #072452", +"1' c #0A316D", +"2' c #0C387E", +"3' c #0C377D", +"4' c #092E65", +"5' c #0B3374", +"6' c #0A306D", +"7' c #092E66", +"8' c #072047", +"9' c #08295C", +"0' c #0A2D63", +"a' c #092E67", +"b' c #0B316B", +"c' c #0F4497", +"d' c #0E3D89", +"e' c #0E4394", +"f' c #0E4293", +"g' c #0B3471", +"h' c #092E63", +"i' c #07234D", +"j' c #0B3676", +"k' c #0C205C", +"l' c #0C0F42", +"m' c #454545", +"n' c #01030A", +"o' c #0C0D3C", +"p' c #0D1F5F", +"q' c #0D3C8A", +"r' c #0C3778", +"s' c #0D3A7F", +"t' c #0F4396", +"u' c #0D3E89", +"v' c #0B3675", +"w' c #0C3B83", +"x' c #0D3B81", +"y' c #0A2F66", +"z' c #0C3A7E", +"A' c #0D3D87", +"B' c #0A326D", +"C' c #0C3C85", +"D' c #0B387A", +"E' c #092C60", +"F' c #082757", +"G' c #0A3371", +"H' c #0F469B", +"I' c #0C3C84", +"J' c #0F469C", +"K' c #0D3F8B", +"L' c #0D3E88", +"M' c #092D61", +"N' c #092F69", +"O' c #08234F", +"P' c #0C2668", +"Q' c #0C1344", +"R' c #0B1E5C", +"S' c #0D3B86", +"T' c #0C3D84", +"U' c #0A3372", +"V' c #0F479C", +"W' c #0D3F8D", +"X' c #0B387C", +"Y' c #092C65", +"Z' c #082655", +"`' c #0B397D", +" ) c #0B377C", +".) c #092F68", +"+) c #0D4190", +"@) c #0A3370", +"#) c #09306A", +"$) c #0A3473", +"%) c #0A3475", +"&) c #0A3576", +"*) c #0B3476", +"=) c #0A316F", +"-) c #082A5E", +";) c #092F67", +">) c #09306B", +",) c #0E459A", +"') c #104AA5", +")) c #0C3E89", +"!) c #0E4292", +"~) c #092E68", +"{) c #082A5F", +"]) c #0A3676", +"^) c #0A205C", +"/) c #030412", +"() c #0E3F90", +"_) c #0B3A7D", +":) c #0B3B83", +"<) c #092F6A", +"[) c #0B397E", +"}) c #0C3B85", +"|) c #0D418F", +"1) c #0D4497", +"2) c #0B3C86", +"3) c #0B3C84", +"4) c #0B397F", +"5) c #0B3D88", +"6) c #09326F", +"7) c #082C62", +"8) c #082B5F", +"9) c #0A3472", +"0) c #0E4395", +"a) c #0C3D89", +"b) c #0B387D", +"c) c #0B387B", +"d) c #0A3577", +"e) c #0A3373", +"f) c #0A3574", +"g) c #0B3980", +"h) c #0C3D87", +"i) c #0B3A80", +"j) c #0B387E", +"k) c #0A377A", +"l) c #093270", +"m) c #0A3474", +"n) c #0A3678", +"o) c #0B3A81", +"p) c #0A3677", +"q) c #09306C", +"r) c #082D65", +"s) c #082B60", +"t) c #082C60", +"u) c #0C3A82", +"v) c #093370", +"w) c #08295B", +"x) c #0C418F", +"y) c #0E479C", +"z) c #0D408E", +"A) c #0B3B82", +"B) c #0D4293", +"C) c #0D4395", +"D) c #0C408E", +"E) c #08295A", +"F) c #082A5C", +"G) c #09326E", +"H) c #0B2466", +"I) c #02040E", +"J) c #343434", +"K) c #0C0C3C", +"L) c #0B1D5A", +"M) c #0C3981", +"N) c #0D408D", +"O) c #0D4090", +"P) c #0C3F8C", +"Q) c #0D4191", +"R) c #0D4192", +"S) c #0C3E8C", +"T) c #0B3C85", +"U) c #072653", +"V) c #072859", +"W) c #0A3575", +"X) c #0C3E8B", +"Y) c #0C3D88", +"Z) c #0F4AA5", +"`) c #0A377B", +" ! c #082B5E", +".! c #082C61", +"+! c #072757", +"@! c #082A5B", +"#! c #0A2F6B", +"$! c #0A3778", +"%! c #0A3679", +"&! c #093069", +"*! c #0B3981", +"=! c #0D408F", +"-! c #0B3375", +";! c #093371", +">! c #092D66", +",! c #0C3F8A", +"'! c #0D3E8B", +")! c #09326D", +"!! c #0A1F5A", +"~! c #0D1145", +"{! c #0C1E5C", +"]! c #0B3780", +"^! c #0A3675", +"/! c #0B387F", +"(! c #0B3579", +"_! c #0C3C87", +":! c #0D4499", +"~ c #0B3E8C", +",~ c #07295C", +"'~ c #0C3C88", +")~ c #0A397F", +"!~ c #093170", +"~~ c #0B3F8C", +"{~ c #0A3982", +"]~ c #0D4294", +"^~ c #0D47A2", +"/~ c #093577", +"(~ c #093575", +"_~ c #08326F", +":~ c #082F69", +"<~ c #093475", +"[~ c #0B3880", +"}~ c #093679", +"|~ c #09306E", +"1~ c #0A1C56", +"2~ c #030516", +"3~ c #0D1A54", +"4~ c #0B3376", +"5~ c #093470", +"6~ c #0C4396", +"7~ c #0A397D", +"8~ c #0C4395", +"9~ c #0E4DAC", +"0~ c #0A3B84", +"a~ c #0C3F8B", +"b~ c #0B3F8B", +"c~ c #0A387C", +"d~ c #0B3D87", +"e~ c #082E69", +"f~ c #0B3C88", +"g~ c #07295E", +"h~ c #072552", +"i~ c #0B408F", +"j~ c #093473", +"k~ c #093576", +"l~ c #0A3C85", +"m~ c #082C64", +"n~ c #07295D", +"o~ c #08316D", +"p~ c #0A3A81", +"q~ c #0A3579", +"r~ c #0A3779", +"s~ c #0C4292", +"t~ c #0D449A", +"u~ c #0B3D89", +"v~ c #0A3A83", +"w~ c #0A3D88", +"x~ c #0C2162", +"y~ c #050823", +"z~ c #0C1750", +"A~ c #0B3278", +"B~ c #08336F", +"C~ c #08316C", +"D~ c #0C4295", +"E~ c #0D469D", +"F~ c #0A3B85", +"G~ c #0A3B83", +"H~ c #082F6B", +"I~ c #082E65", +"J~ c #07285B", +"K~ c #07295B", +"L~ c #06285A", +"M~ c #072B60", +"N~ c #082D66", +"O~ c #09316C", +"P~ c #0A3B82", +"Q~ c #0A3C83", +"R~ c #0A3C86", +"S~ c #062756", +"T~ c #093678", +"U~ c #08295E", +"V~ c #062149", +"W~ c #093476", +"X~ c #0A3B86", +"Y~ c #0C459A", +"Z~ c #0B4090", +"`~ c #0B3F8D", +" { c #093578", +".{ c #0D4396", +"+{ c #0A3A7E", +"@{ c #0B3984", +"#{ c #0B1952", +"${ c #090E37", +"%{ c #0B2D71", +"&{ c #093A7F", +"*{ c #0E4AA5", +"={ c #0B3C87", +"-{ c #0C408F", +";{ c #09377B", +">{ c #093778", +",{ c #0D459A", +"'{ c #072759", +"){ c #062552", +"!{ c #072A5D", +"~{ c #0C4293", +"{{ c #0B3F8A", +"]{ c #082D63", +"^{ c #08306A", +"/{ c #0A387B", +"({ c #09387B", +"_{ c #0A3A80", +":{ c #0C4497", +"<{ c #0C4394", +"[{ c #0B3F8E", +"}{ c #0A3A84", +"|{ c #072A5E", +"1{ c #093573", +"2{ c #0B3781", +"3{ c #0C0E3C", +"4{ c #0A2868", +"5{ c #09387A", +"6{ c #0A3F8C", +"7{ c #0C4498", +"8{ c #0B4190", +"9{ c #0A3C87", +"0{ c #0B3E8B", +"a{ c #0A3C84", +"b{ c #0A3A7F", +"c{ c #093677", +"d{ c #072B5F", +"e{ c #082F67", +"f{ c #082758", +"g{ c #062655", +"h{ c #083270", +"i{ c #083371", +"j{ c #0B4191", +"k{ c #0B3D8B", +"l{ c #083372", +"m{ c #072A60", +"n{ c #09377A", +"o{ c #0C4192", +"p{ c #09387D", +"q{ c #0A397E", +"r{ c #072C63", +"s{ c #0C4493", +"t{ c #0D3A8B", +"u{ c #05081C", +"v{ c #0A2668", +"w{ c #073574", +"x{ c #072F68", +"y{ c #072C62", +"z{ c #072E67", +"A{ c #09397E", +"B{ c #0A4191", +"C{ c #093981", +"D{ c #0A3E8B", +"E{ c #0D4DAB", +"F{ c #093B84", +"G{ c #09397F", +"H{ c #0B4599", +"I{ c #0B408D", +"J{ c #093779", +"K{ c #093676", +"L{ c #0A3F8D", +"M{ c #0A3C88", +"N{ c #07306C", +"O{ c #083473", +"P{ c #08326E", +"Q{ c #072D64", +"R{ c #072D65", +"S{ c #062A5F", +"T{ c #093579", +"U{ c #08316F", +"V{ c #072F69", +"W{ c #09387F", +"X{ c #07316D", +"Y{ c #083373", +"Z{ c #09377D", +"`{ c #0A3D86", +" ] c #0B4293", +".] c #0B459A", +"+] c #09377C", +"@] c #083575", +"#] c #083679", +"$] c #083678", +"%] c #083271", +"&] c #09387E", +"*] c #08316B", +"=] c #072D66", +"-] c #072C61", +";] c #082F68", +">] c #09387C", +",] c #093980", +"'] c #0A408F", +")] c #0A3D89", +"!] c #0B459C", +"~] c #083474", +"{] c #093273", +"]] c #093A82", +"^] c #072C64", +"/] c #083577", +"(] c #072D62", +"_] c #0A3D87", +":] c #0D3484", +"<] c #03030D", +"[] c #0B2160", +"}] c #083472", +"|] c #0C4499", +"1] c #093C87", +"2] c #0A4090", +"3] c #0A408D", +"4] c #093D86", +"5] c #0C489F", +"6] c #072F6A", +"7] c #07306D", +"8] c #062859", +"9] c #072F6B", +"0] c #07316C", +"a] c #072B61", +"b] c #083370", +"c] c #083475", +"d] c #0C4091", +"e] c #093F8C", +"f] c #093C85", +"g] c #083677", +"h] c #083779", +"i] c #0D53B8", +"j] c #093B83", +"k] c #07306A", +"l] c #072E66", +"m] c #093B85", +"n] c #072D63", +"o] c #083576", +"p] c #093A81", +"q] c #08367A", +"r] c #0C48A1", +"s] c #0B4294", +"t] c #093574", +"u] c #08397D", +"v] c #062B5F", +"w] c #083476", +"x] c #0A418F", +"y] c #0B276A", +"z] c #04061A", +"A] c #1D1D1D", +"B] c #090E38", +"C] c #0C1D59", +"D] c #083272", +"E] c #072F67", +"F] c #093A7E", +"G] c #0B4394", +"H] c #0B469C", +"I] c #093B82", +"J] c #093A80", +"K] c #083374", +"L] c #05234E", +"M] c #083069", +"N] c #0A4292", +"O] c #0A3F8B", +"P] c #0A3E88", +"Q] c #09397D", +"R] c #093C84", +"S] c #072B62", +"T] c #0A3E8D", +"U] c #083574", +"V] c #062E65", +"W] c #0A3E89", +"X] c #08397E", +"Y] c #062A60", +"Z] c #0A3E8C", +"`] c #0A205E", +" ^ c #0A1041", +".^ c #0C1449", +"+^ c #092C6C", +"@^ c #08397B", +"#^ c #08377B", +"$^ c #0A3E8A", +"%^ c #093A83", +"&^ c #0A4294", +"*^ c #07316E", +"=^ c #072B5E", +"-^ c #072756", +";^ c #062758", +">^ c #062C62", +",^ c #072E65", +"'^ c #07326F", +")^ c #0A3B81", +"!^ c #072E68", +"~^ c #062858", +"{^ c #062A5E", +"]^ c #0B4396", +"^^ c #0A3F8A", +"/^ c #072A5F", +"(^ c #062E63", +"_^ c #072C65", +":^ c #040515", +"<^ c #0A2663", +"[^ c #093D88", +"}^ c #08326D", +"|^ c #06295B", +"1^ c #083578", +"2^ c #0C469D", +"3^ c #083170", +"4^ c #083375", +"5^ c #062A5D", +"6^ c #062657", +"7^ c #07306B", +"8^ c #0A3D8A", +"9^ c #0A4190", +"0^ c #0B3E89", +"a^ c #073067", +"b^ c #0A3278", +"c^ c #050922", +"d^ c #0A0E3B", +"e^ c #0D1042", +"f^ c #0B2263", +"g^ c #073373", +"h^ c #08377C", +"i^ c #093E8A", +"j^ c #093F8B", +"k^ c #093982", +"l^ c #06295C", +"m^ c #07316B", +"n^ c #062C63", +"o^ c #062D64", +"p^ c #062B61", +"q^ c #052451", +"r^ c #06295A", +"s^ c #0A4192", +"t^ c #083573", +"u^ c #07306E", +"v^ c #08387D", +"w^ c #0A3F8E", +"x^ c #093D89", +"y^ c #093E8B", +"z^ c #08397F", +"A^ c #0A4293", +"B^ c #073576", +"C^ c #0B4598", +"D^ c #08387C", +"E^ c #052551", +"F^ c #083E85", +"G^ c #092869", +"H^ c #02040F", +"I^ c #0D1041", +"J^ c #0C1A56", +"K^ c #073070", +"L^ c #063068", +"M^ c #08387E", +"N^ c #073371", +"O^ c #073271", +"P^ c #0A4396", +"Q^ c #093D8A", +"R^ c #09408C", +"S^ c #093E8C", +"T^ c #083980", +"U^ c #083A7F", +"V^ c #083A81", +"W^ c #083A82", +"X^ c #0A4498", +"Y^ c #083C86", +"Z^ c #083B84", +"`^ c #08377A", +" / c #062E68", +"./ c #062D65", +"+/ c #062B60", +"@/ c #052755", +"#/ c #052552", +"$/ c #052654", +"%/ c #05234F", +"&/ c #062E66", +"*/ c #062F68", +"=/ c #062F6A", +"-/ c #073069", +";/ c #073474", +">/ c #08387B", +",/ c #073577", +"'/ c #093C86", +")/ c #083B82", +"!/ c #09408E", +"~/ c #093D87", +"{/ c #08387F", +"]/ c #073473", +"^/ c #062D63", +"// c #073677", +"(/ c #062C64", +"_/ c #052C61", +":/ c #073372", +"( c #06316D", +",( c #073778", +"'( c #07397D", +")( c #083C87", +"!( c #094293", +"~( c #083E88", +"{( c #08408E", +"]( c #07377B", +"^( c #073679", +"/( c #083E89", +"(( c #083B85", +"_( c #073A7F", +":( c #083F8C", +"<( c #06306B", +"[( c #062F69", +"}( c #052C63", +"|( c #052A5F", +"1( c #052959", +"2( c #052758", +"3( c #042756", +"4( c #052A5D", +"5( c #052554", +"6( c #05295C", +"7( c #062E64", +"8( c #052B5F", +"9( c #063576", +"0( c #073A80", +"a( c #073676", +"b( c #073B82", +"c( c #073A82", +"d( c #083E8A", +"e( c #073B81", +"f( c #0A469C", +"g( c #062C61", +"h( c #063373", +"i( c #063371", +"j( c #07387C", +"k( c #073A81", +"l( c #083F8B", +"m( c #062F67", +"n( c #062F66", +"o( c #06316B", +"p( c #07397F", +"q( c #052A5C", +"r( c #063270", +"s( c #05295A", +"t( c #0A4BA7", +"u( c #07387B", +"v( c #063474", +"w( c #052858", +"x( c #063069", +"y( c #093C83", +"z( c #07367A", +"A( c #063574", +"B( c #0A2769", +"C( c #08337A", +"D( c #06326F", +"E( c #073C83", +"F( c #094497", +"G( c #073578", +"H( c #094393", +"I( c #052757", +"J( c #052B5E", +"K( c #042654", +"L( c #06336F", +"M( c #063271", +"N( c #063370", +"O( c #07397C", +"P( c #08408C", +"Q( c #07377D", +"R( c #09408D", +"S( c #073C85", +"T( c #073B85", +"U( c #07377C", +"V( c #09479D", +"W( c #052C60", +"X( c #062C5E", +"Y( c #06326E", +"Z( c #052A5B", +"`( c #073A7E", +" _ c #063573", +"._ c #06316A", +"+_ c #083F8A", +"@_ c #084190", +"#_ c #08408F", +"$_ c #073675", +"%_ c #0B1953", +"&_ c #0C0E3B", +"*_ c #05071A", +"=_ c #0C0E42", +"-_ c #09296B", +";_ c #063A7F", +">_ c #073573", +",_ c #083E8B", +"'_ c #084192", +")_ c #07387E", +"!_ c #063372", +"~_ c #05295D", +"{_ c #062B62", +"]_ c #052C5F", +"^_ c #063473", +"/_ c #063578", +"(_ c #094192", +"__ c #083F8D", +":_ c #05285C", +"<_ c #063575", +"[_ c #083C83", +"}_ c #083D8A", +"|_ c #0A48A0", +"1_ c #073C86", +"2_ c #063475", +"3_ c #0A479E", +"4_ c #08479C", +"5_ c #0B419D", +"6_ c #0D0E3D", +"7_ c #0B1B56", +"8_ c #07357C", +"9_ c #073C87", +"0_ c #063272", +"a_ c #07387F", +"b_ c #083A84", +"c_ c #094395", +"d_ c #094396", +"e_ c #09408F", +"f_ c #073982", +"g_ c #09459A", +"h_ c #084293", +"i_ c #094295", +"j_ c #073B84", +"k_ c #05295E", +"l_ c #062C65", +"m_ c #052453", +"n_ c #06306D", +"o_ c #063170", +"p_ c #063679", +"q_ c #073477", +"r_ c #083982", +"s_ c #094190", +"t_ c #073880", +"u_ c #062E69", +"v_ c #073171", +"w_ c #063374", +"x_ c #073981", +"y_ c #073A83", +"z_ c #073A84", +"A_ c #093E8D", +"B_ c #063577", +"C_ c #06377A", +"D_ c #084191", +"E_ c #094498", +"F_ c #083E8C", +"G_ c #0B2365", +"H_ c #090E39", +"I_ c #063B82", +"J_ c #084090", +"K_ c #073980", +"L_ c #083F8E", +"M_ c #09469B", +"N_ c #0A49A4", +"O_ c #073579", +"P_ c #052E66", +"Q_ c #052A5E", +"R_ c #052B60", +"S_ c #094294", +"T_ c #09469D", +"U_ c #093F8E", +"V_ c #052F69", +"W_ c #073B83", +"X_ c #06316F", +"Y_ c #052C62", +"Z_ c #052A60", +"`_ c #052D64", +" : c #094193", +".: c #083B86", +"+: c #05316E", +"@: c #0A4BA8", +"#: c #052F67", +"$: c #09367E", +"%: c #0C1953", +"&: c #091E59", +"*: c #05326E", +"=: c #08377D", +"-: c #0948A2", +";: c #09479F", +">: c #084498", +",: c #084396", +"': c #052555", +"): c #052B62", +"!: c #06306E", +"~: c #09469E", +"{: c #0A4AA5", +"]: c #08377E", +"^: c #073E8D", +"/: c #063476", +"(: c #05306D", +"_: c #052F68", +":: c #052759", +"<: c #04234E", +"[: c #052C64", +"}: c #042452", +"|: c #052859", +"1: c #052C65", +"2: c #073E8B", +"3: c #084294", +"4: c #0A50B2", +"5: c #07418F", +"6: c #0A2C73", +"7: c #0B164D", +"8: c #063274", +"9: c #073D86", +"0: c #06387F", +"a: c #083F8F", +"b: c #08469B", +"c: c #0948A4", +"d: c #073D88", +"e: c #063B83", +"f: c #063678", +"g: c #084292", +"h: c #073E8A", +"i: c #06387E", +"j: c #06377B", +"k: c #07408E", +"l: c #05306C", +"m: c #04316E", +"n: c #053271", +"o: c #053579", +"p: c #063A82", +"q: c #04387D", +"r: c #042E66", +"s: c #042B60", +"t: c #03295D", +"u: c #032554", +"v: c #03295E", +"w: c #04306B", +"x: c #022E6A", +"y: c #042E68", +"z: c #032A5E", +"A: c #032C63", +"B: c #042D67", +"C: c #04306D", +"D: c #043272", +"E: c #043271", +"F: c #04306A", +"G: c #05377A", +"H: c #042F6B", +"I: c #032B60", +"J: c #032D66", +"K: c #04306C", +"L: c #032D67", +"M: c #04316F", +"N: c #053578", +"O: c #043679", +"P: c #053779", +"Q: c #053679", +"R: c #05367A", +"S: c #043473", +"T: c #05387E", +"U: c #043577", +"V: c #05377C", +"W: c #053B84", +"X: c #053A82", +"Y: c #053981", +"Z: c #05397F", +"`: c #053D88", +" < c #053C86", +".< c #06408E", +"+< c #053A83", +"@< c #07459B", +"#< c #053F8C", +"$< c #05377D", +"%< c #053B86", +"&< c #053E8A", +"*< c #063E8C", +"=< c #05387C", +"-< c #05377B", +";< c #053C85", +">< c #053D86", +",< c #064293", +"'< c #063E8D", +")< c #053478", +"!< c #042E67", +"~< c #043474", +"{< c #053980", +"]< c #043576", +"^< c #043270", +"/< c #053373", +"(< c #04326F", +"_< c #063E8A", +":< c #043372", +"<< c #032F6A", +"[< c #032F69", +"}< c #043374", +"|< c #053476", +"1< c #053475", +"2< c #064193", +"3< c #04397F", +"4< c #063A83", +"5< c #074292", +"6< c #021E43", +"7< c #022450", +"8< c #043475", +"9< c #042C64", +"0< c #04316D", +"a< c #022859", +"b< c #03285B", +"c< c #042F68", +"d< c #032859", +"e< c #042C63", +"f< c #032E67", +"g< c #022351", +"h< c #042D65", +"i< c #032E66", +"j< c #042B62", +"k< c #043373", +"l< c #032E65", +"m< c #042B61", +"n< c #03316D", +"o< c #04316C", +"p< c #042A5D", +"q< c #063980", +"r< c #073D8B", +"s< c #074191", +"t< c #073C88", +"u< c #073E89", +"v< c #074291", +"w< c #073983", +"x< c #0D1346", +"y< c #092B72", +"z< c #06377C", +"A< c #06377D", +"B< c #084295", +"C< c #08469D", +"D< c #073D8A", +"E< c #073E8C", +"F< c #06387C", +"G< c #05326D", +"H< c #073F8C", +"I< c #06387D", +"J< c #043578", +"K< c #083273", +"L< c #0C3476", +"M< c #0C367A", +"N< c #0D3A83", +"O< c #0C3982", +"P< c #0D3C88", +"Q< c #0D3C87", +"R< c #0C3579", +"S< c #074396", +"T< c #06367A", +"U< c #073D89", +"V< c #093786", +"W< c #0B1C58", +"X< c #063D88", +"Y< c #08479F", +"Z< c #074193", +"`< c #06397F", +" [ c #063981", +".[ c #08489E", +"+[ c #073D87", +"@[ c #123A82", +"#[ c #133C85", +"$[ c #153B86", +"%[ c #153B85", +"&[ c #163B84", +"*[ c #173C86", +"=[ c #173C85", +"-[ c #173D87", +";[ c #173D88", +">[ c #173D89", +",[ c #173E89", +"'[ c #173E8A", +")[ c #173C87", +"![ c #183E8A", +"~[ c #183E89", +"{[ c #183E8B", +"][ c #173C88", +"^[ c #173D8A", +"/[ c #173E8B", +"([ c #173F8D", +"_[ c #173B83", +":[ c #173F8C", +"<[ c #173B84", +"[[ c #173A82", +"}[ c #183D8A", +"|[ c #183F8C", +"1[ c #173E8C", +"2[ c #18408E", +"3[ c #173F8E", +"4[ c #183E8C", +"5[ c #173B85", +"6[ c #183D89", +"7[ c #183F8D", +"8[ c #183C84", +"9[ c #153B84", +"0[ c #08479D", +"a[ c #052F6A", +"b[ c #073C89", +"c[ c #053270", +"d[ c #063D89", +"e[ c #0C2A74", +"f[ c #0C0F43", +"g[ c #082C6F", +"h[ c #063B84", +"i[ c #063677", +"j[ c #05387F", +"k[ c #063F8D", +"l[ c #063C87", +"m[ c #063E8B", +"n[ c #063A81", +"o[ c #063B86", +"p[ c #073F8B", +"q[ c #07408F", +"r[ c #07459A", +"s[ c #133B86", +"t[ c #153D87", +"u[ c #193E8A", +"v[ c #183D88", +"w[ c #183D87", +"x[ c #193D88", +"y[ c #153F8D", +"z[ c #052F6B", +"A[ c #05316F", +"B[ c #063D86", +"C[ c #074296", +"D[ c #063D85", +"E[ c #083783", +"F[ c #0C154E", +"G[ c #000101", +"H[ c #3A3A3A", +"I[ c #0C0C3A", +"J[ c #0B1D5C", +"K[ c #053B81", +"L[ c #053473", +"M[ c #053576", +"N[ c #074090", +"O[ c #063D8A", +"P[ c #064192", +"Q[ c #07469B", +"R[ c #063C84", +"S[ c #053474", +"T[ c #063D87", +"U[ c #04387C", +"V[ c #113B84", +"W[ c #163E89", +"X[ c #143C85", +"Y[ c #063A84", +"Z[ c #05326F", +"`[ c #064397", +" } c #0848A3", +".} c #08469C", +"+} c #06408F", +"@} c #0A286D", +"#} c #0D0C3B", +"$} c #070926", +"%} c #0D1246", +"&} c #072B6B", +"*} c #03326E", +"=} c #06397E", +"-} c #08479E", +";} c #074395", +">} c #074598", +",} c #053677", +"'} c #074293", +")} c #063C85", +"!} c #063676", +"~} c #04397E", +"{} c #143D87", +"]} c #184090", +"^} c #184191", +"/} c #184192", +"(} c #053472", +"_} c #074294", +":} c #0848A0", +"<} c #062E72", +"[} c #0C0C3B", +"}} c #0A1F5D", +"|} c #053575", +"1} c #074394", +"2} c #074393", +"3} c #05397E", +"4} c #063F8B", +"5} c #06418F", +"6} c #053B85", +"7} c #0749A3", +"8} c #053A7F", +"9} c #05397D", +"0} c #153980", +"a} c #143577", +"b} c #133271", +"c} c #112C62", +"d} c #15397E", +"e} c #183F8B", +"f} c #153C86", +"g} c #054190", +"h} c #063A80", +"i} c #063779", +"j} c #084DAB", +"k} c #074496", +"l} c #07469C", +"m} c #063E89", +"n} c #063C86", +"o} c #053F89", +"p} c #0A276A", +"q} c #0D0E40", +"r} c #060924", +"s} c #07317A", +"t} c #06387B", +"u} c #074498", +"v} c #05306B", +"w} c #06397D", +"x} c #074295", +"y} c #153A80", +"z} c #0E2551", +"A} c #0D224C", +"B} c #0C2047", +"C} c #0C1F44", +"D} c #0B1D41", +"E} c #0B1C3E", +"F} c #0B1C3D", +"G} c #0B1A39", +"H} c #143473", +"I} c #153E8A", +"J} c #05387D", +"K} c #07469A", +"L} c #0748A1", +"M} c #053577", +"N} c #064290", +"O} c #073B8A", +"P} c #0B1750", +"Q} c #0C0E40", +"R} c #0A1E5D", +"S} c #063F8C", +"T} c #06408D", +"U} c #053F8D", +"V} c #06418E", +"W} c #053678", +"X} c #064190", +"Y} c #053C84", +"Z} c #043E8B", +"`} c #043C85", +" | c #143E8B", +".| c #193D89", +"+| c #163B83", +"@| c #0F2757", +"#| c #0D2149", +"$| c #0B1D40", +"%| c #0B1D3F", +"&| c #0C1E41", +"*| c #0C1E42", +"=| c #0C1F43", +"-| c #0C1E40", +";| c #143575", +">| c #18408F", +",| c #143C86", +"'| c #043B83", +")| c #063B85", +"!| c #053E87", +"~| c #064191", +"{| c #043C83", +"]| c #0A286E", +"^| c #04061B", +"/| c #09337D", +"(| c #053B80", +"_| c #05408E", +":| c #053D87", +"<| c #053B83", +"[| c #064497", +"}| c #063F8E", +"|| c #064292", +"1| c #064597", +"2| c #05408F", +"3| c #054294", +"4| c #044395", +"5| c #163D89", +"6| c #153A82", +"7| c #0D234C", +"8| c #0B1E41", +"9| c #0C1F45", +"0| c #0D224A", +"a| c #0E234D", +"b| c #0E234E", +"c| c #0D224B", +"d| c #043E89", +"e| c #064396", +"f| c #0649A2", +"g| c #064496", +"h| c #053E88", +"i| c #064498", +"j| c #083E93", +"k| c #070A27", +"l| c #0B1C57", +"m| c #053A80", +"n| c #042F6A", +"o| c #053D8A", +"p| c #06459A", +"q| c #05459B", +"r| c #0749A2", +"s| c #074CAB", +"t| c #07489F", +"u| c #064599", +"v| c #064395", +"w| c #054396", +"x| c #104292", +"y| c #163D87", +"z| c #0A204A", +"A| c #041942", +"B| c #001136", +"C| c #000828", +"D| c #00011F", +"E| c #000013", +"F| c #000010", +"G| c #00000F", +"H| c #00000C", +"I| c #00000B", +"J| c #00000A", +"K| c #000011", +"L| c #00000D", +"M| c #000009", +"N| c #000008", +"O| c #000007", +"P| c #000006", +"Q| c #000005", +"R| c #000822", +"S| c #0D2247", +"T| c #0C2045", +"U| c #143C88", +"V| c #053D89", +"W| c #053D85", +"X| c #074DAC", +"Y| c #053E8B", +"Z| c #064394", +"`| c #0B2771", +" 1 c #0C113F", +".1 c #2F2F2F", +"+1 c #09317C", +"@1 c #054499", +"#1 c #053E8C", +"$1 c #064090", +"%1 c #064294", +"&1 c #074FB1", +"*1 c #074AA7", +"=1 c #053E89", +"-1 c #053F8A", +";1 c #113D88", +">1 c #183C86", +",1 c #143678", +"'1 c #0D2552", +")1 c #0B1B3C", +"!1 c #0C1D3F", +"~1 c #0A1F47", +"{1 c #061A41", +"]1 c #000C2A", +"^1 c #071225", +"/1 c #242B38", +"(1 c #393E47", +"_1 c #4B4D52", +":1 c #5C5C5E", +"<1 c #5F5D5A", +"[1 c #605D58", +"}1 c #65625C", +"|1 c #6F6D67", +"11 c #726F69", +"21 c #75726C", +"31 c #787570", +"41 c #7A7772", +"51 c #6F6D68", +"61 c #6E6C68", +"71 c #6A6A66", +"81 c #595A56", +"91 c #575856", +"01 c #565755", +"a1 c #555654", +"b1 c #5B5B58", +"c1 c #585855", +"d1 c #575754", +"e1 c #595956", +"f1 c #5F5F5C", +"g1 c #5D5C59", +"h1 c #5A5A56", +"i1 c #5B5B57", +"j1 c #5E5D5A", +"k1 c #676662", +"l1 c #6D6C67", +"m1 c #696863", +"n1 c #6C6B67", +"o1 c #6C6A65", +"p1 c #716E69", +"q1 c #726F6A", +"r1 c #74726D", +"s1 c #76736E", +"t1 c #7A7872", +"u1 c #716E68", +"v1 c #797771", +"w1 c #7D7A74", +"x1 c #6F6D66", +"y1 c #7B7872", +"z1 c #797671", +"A1 c #77746F", +"B1 c #7C7973", +"C1 c #7D7A75", +"D1 c #807D78", +"E1 c #7F7D77", +"F1 c #817F79", +"G1 c #85827D", +"H1 c #84817C", +"I1 c #87857F", +"J1 c #8A8781", +"K1 c #87847E", +"L1 c #8A8782", +"M1 c #827F7A", +"N1 c #888680", +"O1 c #83807B", +"P1 c #898781", +"Q1 c #86847E", +"R1 c #817E78", +"S1 c #898681", +"T1 c #8B8883", +"U1 c #888580", +"V1 c #82807A", +"W1 c #85827C", +"X1 c #7F7C77", +"Y1 c #86837D", +"Z1 c #83807A", +"`1 c #807D77", +" 2 c #827F79", +".2 c #84817B", +"+2 c #817E79", +"@2 c #7B7973", +"#2 c #807E78", +"$2 c #7C7974", +"%2 c #7F7C76", +"&2 c #7E7B76", +"*2 c #7E7B75", +"=2 c #84827C", +"-2 c #7D7B75", +";2 c #918D85", +">2 c #36383E", +",2 c #040C2F", +"'2 c #0D2147", +")2 c #04387E", +"!2 c #06489F", +"~2 c #053F8B", +"{2 c #05387B", +"]2 c #06469B", +"^2 c #074CA7", +"/2 c #064495", +"(2 c #054598", +"_2 c #08419B", +":2 c #0B1954", +"<2 c #053A86", +"[2 c #054394", +"}2 c #06479F", +"|2 c #054192", +"12 c #043D88", +"22 c #054090", +"32 c #05418F", +"42 c #054395", +"52 c #043F8C", +"62 c #113B86", +"72 c #0C1E43", +"82 c #0A1E45", +"92 c #061A40", +"02 c #06193C", +"a2 c #0E1B31", +"b2 c #343842", +"c2 c #5B5D5F", +"d2 c #787774", +"e2 c #888681", +"f2 c #8F8D8A", +"g2 c #9D9B99", +"h2 c #B3B2B0", +"i2 c #B8B7B7", +"j2 c #BABABA", +"k2 c #BFBFBF", +"l2 c #D5D5D5", +"m2 c #DADADA", +"n2 c #E2E2E2", +"o2 c #DBDBDC", +"p2 c #D2D5D7", +"q2 c #C0C4C8", +"r2 c #AEB4B9", +"s2 c #A6ACB1", +"t2 c #A1A8AD", +"u2 c #AAB1B7", +"v2 c #A4AAAF", +"w2 c #A4ABAF", +"x2 c #B0B7BB", +"y2 c #B3BABE", +"z2 c #B4B9BE", +"A2 c #B0B4B8", +"B2 c #B4B9BC", +"C2 c #BABFC2", +"D2 c #C2C5C8", +"E2 c #C1C4C7", +"F2 c #CFD3D5", +"G2 c #CDD1D3", +"H2 c #CBCDCF", +"I2 c #D0D2D3", +"J2 c #DEE0E1", +"K2 c #E2E3E4", +"L2 c #D6D7D8", +"M2 c #DEDFDF", +"N2 c #E5E4E5", +"O2 c #DBDBDB", +"P2 c #DDDDDD", +"Q2 c #EBEBEA", +"R2 c #E9E9E8", +"S2 c #E3E3E3", +"T2 c #DBDADA", +"U2 c #E3E2E2", +"V2 c #EBEBEB", +"W2 c #F3F3F3", +"X2 c #EEEEEE", +"Y2 c #F0F0F0", +"Z2 c #F6F6F6", +"`2 c #F5F5F5", +" 3 c #F8F8F8", +".3 c #F9F9F9", +"+3 c #FFFFFF", +"@3 c #FEFEFE", +"#3 c #FCFCFC", +"$3 c #FBFBFB", +"%3 c #F2F2F2", +"&3 c #FAFAFA", +"*3 c #F1F1F1", +"=3 c #ECECEC", +"-3 c #F4F4F4", +";3 c #F7F7F7", +">3 c #EFEFEF", +",3 c #EDEDED", +"'3 c #666C75", +")3 c #000020", +"!3 c #03377D", +"~3 c #04377A", +"{3 c #043B82", +"]3 c #043678", +"^3 c #054498", +"/3 c #054497", +"(3 c #054295", +"_3 c #064499", +":3 c #064CA9", +"<3 c #06469D", +"[3 c #0B2468", +"}3 c #0D0D3C", +"|3 c #08296E", +"13 c #043A80", +"23 c #04397D", +"33 c #054292", +"43 c #043E8A", +"53 c #064AA5", +"63 c #043677", +"73 c #044396", +"83 c #044090", +"93 c #103C88", +"03 c #0C1D41", +"a3 c #0B1F46", +"b3 c #02173F", +"c3 c #06193D", +"d3 c #1E283C", +"e3 c #4F5053", +"f3 c #7C7C7B", +"g3 c #8B8986", +"h3 c #939290", +"i3 c #9B9A9A", +"j3 c #9F9F9F", +"k3 c #959595", +"l3 c #949494", +"m3 c #969696", +"n3 c #9C9C9C", +"o3 c #ABABAB", +"p3 c #B5B5B5", +"q3 c #C4C4C4", +"r3 c #C0C0C0", +"s3 c #CACACA", +"t3 c #D1D1D1", +"u3 c #C0C1C2", +"v3 c #B0B2B4", +"w3 c #A3A8AA", +"x3 c #9A9EA2", +"y3 c #92989D", +"z3 c #8E9499", +"A3 c #888E92", +"B3 c #898F93", +"C3 c #959CA0", +"D3 c #A0A7AA", +"E3 c #9BA1A5", +"F3 c #969CA0", +"G3 c #A0A5A8", +"H3 c #A8ADAF", +"I3 c #B2B6B9", +"J3 c #AEB2B4", +"K3 c #B1B4B7", +"L3 c #B9BCBE", +"M3 c #BBBEC0", +"N3 c #BBBDBE", +"O3 c #C1C3C4", +"P3 c #C5C7C7", +"Q3 c #C0C1C1", +"R3 c #C2C2C3", +"S3 c #C6C6C6", +"T3 c #C5C4C4", +"U3 c #C5C5C6", +"V3 c #C5C5C4", +"W3 c #C7C7C6", +"X3 c #CECECD", +"Y3 c #D0CFCF", +"Z3 c #C1C1C0", +"`3 c #CBCCCB", +" 4 c #D3D3D3", +".4 c #D6D6D6", +"+4 c #D8D8D8", +"@4 c #E1E1E1", +"#4 c #E8E8E8", +"$4 c #E4E4E4", +"%4 c #D9D9D9", +"&4 c #DFDFDF", +"*4 c #E0E0E0", +"=4 c #E5E5E5", +"-4 c #D7D7D7", +";4 c #CFCFCF", +">4 c #D2D2D2", +",4 c #DEDEDE", +"'4 c #DCDCDC", +")4 c #D0D0D0", +"!4 c #D4D4D4", +"~4 c #F3F1EE", +"{4 c #5D626B", +"]4 c #000023", +"^4 c #084395", +"/4 c #044190", +"(4 c #053B82", +"_4 c #054291", +":4 c #05459A", +"<4 c #0752B8", +"[4 c #0549A2", +"}4 c #054599", +"|4 c #0650AF", +"14 c #083582", +"24 c #0D164F", +"34 c #063173", +"44 c #043D83", +"54 c #05397A", +"64 c #064BA2", +"74 c #064392", +"84 c #053A7E", +"94 c #0649A0", +"04 c #064AA3", +"a4 c #05408D", +"b4 c #054391", +"c4 c #054290", +"d4 c #064AA2", +"e4 c #074EAA", +"f4 c #064CA6", +"g4 c #06499D", +"h4 c #053C82", +"i4 c #053F88", +"j4 c #043572", +"k4 c #03408D", +"l4 c #163A83", +"m4 c #0E2552", +"n4 c #081C44", +"o4 c #00133B", +"p4 c #16233C", +"q4 c #4C4E52", +"r4 c #858381", +"s4 c #908F8E", +"t4 c #8C8B8B", +"u4 c #8C8C8C", +"v4 c #888888", +"w4 c #8F8F8F", +"x4 c #979797", +"y4 c #929292", +"z4 c #9B9B9B", +"A4 c #A5A5A5", +"B4 c #B0B0B0", +"C4 c #BEBEBE", +"D4 c #C3C3C3", +"E4 c #C2C3C3", +"F4 c #BBBEBF", +"G4 c #ABAFB2", +"H4 c #8E9498", +"I4 c #8A9094", +"J4 c #8C9397", +"K4 c #939A9E", +"L4 c #989FA3", +"M4 c #979CA1", +"N4 c #949A9E", +"O4 c #9CA1A5", +"P4 c #A8ADB0", +"Q4 c #A9AEB0", +"R4 c #AEB2B5", +"S4 c #AFB3B6", +"T4 c #B0B3B5", +"U4 c #B6B8B9", +"V4 c #C4C6C7", +"W4 c #C4C7C9", +"X4 c #BEBFC0", +"Y4 c #B6B7B7", +"Z4 c #C5C5C5", +"`4 c #C9C9C9", +" 5 c #D2D1D1", +".5 c #EAEAEA", +"+5 c #E7E7E7", +"@5 c #E6E6E6", +"#5 c #CDCDCD", +"$5 c #F9F7F4", +"%5 c #143D89", +"&5 c #044492", +"*5 c #064595", +"=5 c #053E86", +"-5 c #05428E", +";5 c #06489C", +">5 c #06479B", +",5 c #064291", +"'5 c #0749A1", +")5 c #0756BC", +"!5 c #0753B5", +"~5 c #074BA3", +"{5 c #064393", +"]5 c #054696", +"^5 c #0749A4", +"/5 c #0C0D39", +"(5 c #0D0C38", +"_5 c #092367", +":5 c #033785", +"<5 c #023987", +"[5 c #033C90", +"}5 c #03429B", +"|5 c #033885", +"15 c #02347C", +"25 c #033E93", +"35 c #033B8E", +"45 c #033783", +"55 c #033276", +"65 c #023379", +"75 c #033781", +"85 c #03357B", +"95 c #034098", +"05 c #0343A0", +"a5 c #033F94", +"b5 c #033C8D", +"c5 c #033A8B", +"d5 c #02357E", +"e5 c #03347D", +"f5 c #02367F", +"g5 c #01357F", +"h5 c #0D234D", +"i5 c #0D2044", +"j5 c #071B44", +"k5 c #05173A", +"l5 c #363D4B", +"m5 c #757370", +"n5 c #8F8F8E", +"o5 c #858585", +"p5 c #808080", +"q5 c #848484", +"r5 c #868686", +"s5 c #878787", +"t5 c #989898", +"u5 c #939393", +"v5 c #9E9E9E", +"w5 c #A0A0A0", +"x5 c #A4A4A4", +"y5 c #A3A3A3", +"z5 c #BCBCBC", +"A5 c #CECECE", +"B5 c #CCCCCC", +"C5 c #C5C6C6", +"D5 c #BBBCBE", +"E5 c #ADB0B3", +"F5 c #979C9F", +"G5 c #8B9195", +"H5 c #989FA4", +"I5 c #959BA1", +"J5 c #8D9296", +"K5 c #93999C", +"L5 c #989EA2", +"M5 c #A7ADB0", +"N5 c #AAADB0", +"O5 c #B4B7BA", +"P5 c #B8BDBE", +"Q5 c #B1B5B5", +"R5 c #BEC0C3", +"S5 c #C6C9CB", +"T5 c #C0C2C3", +"U5 c #C2C3C4", +"V5 c #CDCDCE", +"W5 c #BDBDBD", +"X5 c #C2C2C2", +"Y5 c #D2D0D0", +"Z5 c #CBCACA", +"`5 c #E9E9E9", +" 6 c #F6F4F1", +".6 c #60656E", +"+6 c #143A83", +"@6 c #053781", +"#6 c #02337A", +"$6 c #033378", +"%6 c #02357D", +"&6 c #023279", +"*6 c #033E94", +"=6 c #023885", +"-6 c #03367D", +";6 c #033A8A", +">6 c #0449AD", +",6 c #0447A6", +"'6 c #033F97", +")6 c #033B8C", +"!6 c #034096", +"~6 c #033C8C", +"{6 c #033E92", +"]6 c #072972", +"^6 c #0D0D3A", +"/6 c #020207", +"(6 c #0D1141", +"_6 c #022670", +":6 c #003793", +"<6 c #003086", +"[6 c #003083", +"}6 c #002A78", +"|6 c #00276E", +"16 c #002B78", +"26 c #00276F", +"36 c #002D7E", +"46 c #002D7A", +"56 c #002B76", +"66 c #002A74", +"76 c #002466", +"86 c #002973", +"96 c #003289", +"06 c #002B7A", +"a6 c #002C7A", +"b6 c #002971", +"c6 c #00276C", +"d6 c #002873", +"e6 c #05307F", +"f6 c #173B86", +"g6 c #0D234E", +"h6 c #13223E", +"i6 c #595C60", +"j6 c #8C8A85", +"k6 c #8B8A8A", +"l6 c #7A7A7A", +"m6 c #7D7D7D", +"n6 c #818181", +"o6 c #909090", +"p6 c #919191", +"q6 c #9A9A9A", +"r6 c #A1A1A1", +"s6 c #B8B8B8", +"t6 c #CBCBCB", +"u6 c #CDCECE", +"v6 c #B5B7B9", +"w6 c #A3A6A9", +"x6 c #94989C", +"y6 c #94999E", +"z6 c #969DA1", +"A6 c #8E959A", +"B6 c #91979B", +"C6 c #999FA2", +"D6 c #A1A7AB", +"E6 c #A6ABAE", +"F6 c #A6ABAD", +"G6 c #A9ACB0", +"H6 c #B0B3B6", +"I6 c #BABEBF", +"J6 c #B5B8BA", +"K6 c #B8BABB", +"L6 c #C6C8C8", +"M6 c #CDCDCF", +"N6 c #CBCCCD", +"O6 c #C0C0C1", +"P6 c #FFFEFB", +"Q6 c #61666F", +"R6 c #000022", +"S6 c #193E89", +"T6 c #143B87", +"U6 c #02296C", +"V6 c #002363", +"W6 c #00266D", +"X6 c #002A76", +"Y6 c #002B77", +"Z6 c #003084", +"`6 c #002C79", +" 7 c #003087", +".7 c #00328A", +"+7 c #00338E", +"@7 c #00338F", +"#7 c #002F85", +"$7 c #002E7F", +"%7 c #003187", +"&7 c #003286", +"*7 c #002F86", +"=7 c #091753", +"-7 c #0E103D", +";7 c #05071D", +">7 c #0E0F3B", +",7 c #071A5A", +"'7 c #00318A", +")7 c #002F7D", +"!7 c #00286B", +"~7 c #003081", +"{7 c #003185", +"]7 c #00307F", +"^7 c #002F7E", +"/7 c #002B74", +"(7 c #002E7D", +"_7 c #003288", +":7 c #002F80", +"<7 c #003184", +"[7 c #002A72", +"}7 c #002B73", +"|7 c #002F7F", +"17 c #003082", +"27 c #002C78", +"37 c #002A71", +"47 c #023287", +"57 c #143B8A", +"67 c #163C85", +"77 c #0E2553", +"87 c #0A1E43", +"97 c #00153F", +"07 c #232F4A", +"a7 c #717170", +"b7 c #8F8D89", +"c7 c #838383", +"d7 c #7B7B7B", +"e7 c #7C7C7C", +"f7 c #828282", +"g7 c #898989", +"h7 c #AFAFAF", +"i7 c #B6B6B6", +"j7 c #CCCCCD", +"k7 c #B5B7B8", +"l7 c #949B9F", +"m7 c #8A8F94", +"n7 c #888D92", +"o7 c #878D90", +"p7 c #979DA1", +"q7 c #969C9F", +"r7 c #A2A7AA", +"s7 c #A7ACAF", +"t7 c #A4A7AA", +"u7 c #A5A8AB", +"v7 c #B4B7B9", +"w7 c #B8BBBD", +"x7 c #BABCBE", +"y7 c #BABBBC", +"z7 c #CDCECF", +"A7 c #C3C3C4", +"B7 c #BFC0BF", +"C7 c #C3C4C4", +"D7 c #D3D3D2", +"E7 c #D6D6D5", +"F7 c #FAF8F5", +"G7 c #143A84", +"H7 c #032E77", +"I7 c #002A75", +"J7 c #002E7C", +"K7 c #003287", +"L7 c #002C77", +"M7 c #003A9D", +"N7 c #003A9B", +"O7 c #00348E", +"P7 c #00358F", +"Q7 c #003183", +"R7 c #002D78", +"S7 c #00338A", +"T7 c #003BA1", +"U7 c #061F64", +"V7 c #0E0D39", +"W7 c #0D0E3E", +"X7 c #042168", +"Y7 c #003690", +"Z7 c #00348B", +"`7 c #003388", +" 8 c #003080", +".8 c #002D7B", +"+8 c #002E7A", +"@8 c #003182", +"#8 c #002E79", +"$8 c #002B75", +"%8 c #0F3989", +"&8 c #193E88", +"*8 c #102A5D", +"=8 c #00133D", +"-8 c #2A3751", +";8 c #848383", +">8 c #959391", +",8 c #797979", +"'8 c #7F7F7F", +")8 c #8D8D8D", +"!8 c #8E8E8E", +"~8 c #9D9D9D", +"{8 c #A6A6A6", +"]8 c #B3B3B3", +"^8 c #BDBEBD", +"/8 c #BEBEC0", +"(8 c #B7BBBE", +"_8 c #A2A7AB", +":8 c #8B9094", +"<8 c #94999D", +"[8 c #9AA0A4", +"}8 c #989DA1", +"|8 c #A3A8AB", +"18 c #ABB0B2", +"28 c #B1B5B8", +"38 c #B1B5B7", +"48 c #BEC0C1", +"58 c #C9CBCC", +"68 c #C9CBCB", +"78 c #BEBFBF", +"88 c #C1C1C2", +"98 c #C4C4C3", +"08 c #D2D2D1", +"a8 c #D3D3D1", +"b8 c #CFCFCE", +"c8 c #5C616A", +"d8 c #000024", +"e8 c #143C8B", +"f8 c #03307E", +"g8 c #002D79", +"h8 c #003389", +"i8 c #002E7B", +"j8 c #003284", +"k8 c #00358D", +"l8 c #00358E", +"m8 c #003999", +"n8 c #003A99", +"o8 c #022B80", +"p8 c #0B1248", +"q8 c #0D1140", +"r8 c #040512", +"s8 c #0D103F", +"t8 c #0A154E", +"u8 c #002978", +"v8 c #00348A", +"w8 c #003792", +"x8 c #003387", +"y8 c #00348C", +"z8 c #00307E", +"A8 c #003C9F", +"B8 c #002970", +"C8 c #002A6F", +"D8 c #002C76", +"E8 c #002768", +"F8 c #083586", +"G8 c #183F8E", +"H8 c #00133C", +"I8 c #2D3B55", +"J8 c #9C9C9A", +"K8 c #8B8B8B", +"L8 c #767676", +"M8 c #737373", +"N8 c #A8A8A8", +"O8 c #B7B7B7", +"P8 c #C1C1C1", +"Q8 c #C7C7C7", +"R8 c #C4C7C8", +"S8 c #898F94", +"T8 c #8C9297", +"U8 c #9A9FA4", +"V8 c #93989C", +"W8 c #A9ADB2", +"X8 c #ADB0B1", +"Y8 c #B9BBBC", +"Z8 c #C6C8CA", +"`8 c #C6C8C9", +" 9 c #BDBEBF", +".9 c #C3C3C2", +"+9 c #F5F3F0", +"@9 c #5E636C", +"#9 c #143B86", +"$9 c #03317D", +"%9 c #002F7C", +"&9 c #003285", +"*9 c #00348D", +"=9 c #003B9D", +"-9 c #003893", +";9 c #003698", +">9 c #091855", +",9 c #0D0F3C", +"'9 c #060921", +")9 c #091851", +"!9 c #00368F", +"~9 c #003794", +"{9 c #003894", +"]9 c #003998", +"^9 c #00358C", +"/9 c #00296E", +"(9 c #002A70", +"_9 c #022B73", +":9 c #193F8A", +"<9 c #143578", +"[9 c #0B1E44", +"}9 c #0B1E40", +"|9 c #0D2045", +"19 c #01163E", +"29 c #273551", +"39 c #A09E9D", +"49 c #8A8A8A", +"59 c #7E7E7E", +"69 c #999999", +"79 c #B2B2B2", +"89 c #D5D6D5", +"99 c #C1C3C5", +"09 c #A3A7AA", +"a9 c #9BA1A4", +"b9 c #919699", +"c9 c #9CA2A6", +"d9 c #A5A9AD", +"e9 c #A8ACAF", +"f9 c #A6A9AC", +"g9 c #B2B5B7", +"h9 c #BBBDBD", +"i9 c #BDBDBE", +"j9 c #DCDCDB", +"k9 c #DBDBDA", +"l9 c #D1D0D1", +"m9 c #143B85", +"n9 c #03358A", +"o9 c #002A73", +"p9 c #003A98", +"q9 c #003DA1", +"r9 c #062169", +"s9 c #03246F", +"t9 c #00399B", +"u9 c #003592", +"v9 c #00348F", +"w9 c #00389B", +"x9 c #003691", +"y9 c #002669", +"z9 c #003795", +"A9 c #00338B", +"B9 c #0D3784", +"C9 c #1A3F8A", +"D9 c #04183F", +"E9 c #172949", +"F9 c #909195", +"G9 c #AFADAB", +"H9 c #777777", +"I9 c #747474", +"J9 c #D1D2D2", +"K9 c #B9B9BB", +"L9 c #A8ACAE", +"M9 c #9FA5AA", +"N9 c #979DA2", +"O9 c #8E969B", +"P9 c #8E9599", +"Q9 c #9BA0A4", +"R9 c #ADB0B2", +"S9 c #BFC2C5", +"T9 c #BABCBD", +"U9 c #B9BBBB", +"V9 c #C3C4C3", +"W9 c #C8C8C8", +"X9 c #F4F2EF", +"Y9 c #5F646D", +"Z9 c #032F7C", +"`9 c #002F81", +" 0 c #003AA1", +".0 c #003897", +"+0 c #003692", +"@0 c #002D85", +"#0 c #0B1349", +"$0 c #0240A2", +"%0 c #004CB3", +"&0 c #00439D", +"*0 c #003E95", +"=0 c #00439F", +"-0 c #0045A2", +";0 c #00419A", +">0 c #003F94", +",0 c #00357E", +"'0 c #00347B", +")0 c #003A89", +"!0 c #003C8C", +"~0 c #003F95", +"{0 c #003B8B", +"]0 c #003C8F", +"^0 c #003C8E", +"/0 c #00367F", +"(0 c #003783", +"_0 c #003988", +":0 c #043783", +"<0 c #17408E", +"[0 c #102D63", +"}0 c #0B1B3B", +"|0 c #06183F", +"10 c #071A41", +"20 c #808590", +"30 c #C2C0BC", +"40 c #B0AFAF", +"50 c #A7A7A7", +"60 c #AAAEB1", +"70 c #979DA0", +"80 c #91969B", +"90 c #A0A6AA", +"00 c #A1A6A9", +"a0 c #B4B8BA", +"b0 c #AFB2B4", +"c0 c #B3B5B7", +"d0 c #D0D1D1", +"e0 c #CCCBCB", +"f0 c #D9D9DA", +"g0 c #FBF9F6", +"h0 c #03367E", +"i0 c #004098", +"j0 c #0049AE", +"k0 c #003E94", +"l0 c #004095", +"m0 c #003F96", +"n0 c #003B8C", +"o0 c #003E91", +"p0 c #003884", +"q0 c #003A88", +"r0 c #003379", +"s0 c #003986", +"t0 c #0044A3", +"u0 c #003C8D", +"v0 c #003A8D", +"w0 c #0A164E", +"x0 c #0548A2", +"y0 c #044CA7", +"z0 c #044FAF", +"A0 c #0450B1", +"B0 c #03479C", +"C0 c #0451B2", +"D0 c #0449A2", +"E0 c #033E89", +"F0 c #033676", +"G0 c #04469C", +"H0 c #034497", +"I0 c #044598", +"J0 c #034191", +"K0 c #03469A", +"L0 c #044599", +"M0 c #04418F", +"N0 c #033D86", +"O0 c #033C84", +"P0 c #02397E", +"Q0 c #091C42", +"R0 c #000D36", +"S0 c #5C6679", +"T0 c #CBC9C4", +"U0 c #B6B6B5", +"V0 c #ADAEAE", +"W0 c #787878", +"X0 c #A9A9A9", +"Y0 c #B4B4B4", +"Z0 c #B9B9B9", +"`0 c #C7C7C9", +" a c #969B9F", +".a c #878C91", +"+a c #8B9296", +"@a c #B3B7BA", +"#a c #AFB2B3", +"$a c #B4B5B6", +"%a c #C1C2C2", +"&a c #D4D5D4", +"*a c #D4D4D3", +"=a c #5B6069", +"-a c #0349A2", +";a c #044CA6", +">a c #044BA4", +",a c #034599", +"'a c #044497", +")a c #034393", +"!a c #034496", +"~a c #044494", +"{a c #03408F", +"]a c #033F8A", +"^a c #04489E", +"/a c #04479F", +"(a c #04479D", +"_a c #0449A1", +":a c #03418F", +"b c #003078", +",b c #003586", +"'b c #003A93", +")b c #003382", +"!b c #00317D", +"~b c #003484", +"{b c #00307A", +"]b c #003890", +"^b c #133374", +"/b c #05193F", +"(b c #0B1D43", +"_b c #898E97", +":b c #C6C4C1", +"c c #003995", +",c c #002E78", +"'c c #00296B", +")c c #003180", +"!c c #002E77", +"~c c #003487", +"{c c #0E3883", +"]c c #152649", +"^c c #9DA0A4", +"/c c #BEBDBB", +"(c c #727272", +"_c c #CBCACB", +":c c #8E9398", +"d c #9BA0A3", +",d c #9DA1A4", +"'d c #A4A7A9", +")d c #B4B6B6", +"!d c #BCBCBD", +"~d c #D8D7D7", +"{d c #E3E2DF", +"]d c #565B64", +"^d c #000225", +"/d c #143C8A", +"(d c #033180", +"_d c #003A96", +":d c #003B9C", +"e c #02408E", +",e c #024396", +"'e c #023D8A", +")e c #023D8C", +"!e c #0249A2", +"~e c #024AA5", +"{e c #024091", +"]e c #024191", +"^e c #024092", +"/e c #023F8E", +"(e c #023D88", +"_e c #02408F", +":e c #023F8D", +"f c #E9E8E4", +",f c #E5E4E0", +"'f c #E8E7E3", +")f c #EAE9E6", +"!f c #F4F3F0", +"~f c #EFEDEA", +"{f c #F9F8F4", +"]f c #F5F4F0", +"^f c #E7E6E3", +"/f c #D6D5D2", +"(f c #D3D1CE", +"_f c #E0DEDB", +":f c #E7E6E2", +"g c #666970", +",g c #696C74", +"'g c #6E7179", +")g c #787B82", +"!g c #7A7D84", +"~g c #84878E", +"{g c #9A9DA4", +"]g c #A2A5AC", +"^g c #8C929B", +"/g c #767F89", +"(g c #7A838D", +"_g c #7A818B", +":g c #7E848D", +"h c #0E0C3A", +",h c #0E0D3A", +"'h c #082063", +")h c #003591", +"!h c #003898", +"~h c #002E7E", +"{h c #003896", +"]h c #07368C", +"^h c #133372", +"/h c #384560", +"(h c #ACAAA7", +"_h c #989693", +":h c #505663", +"i c #022C7F", +",i c #0D1040", +"'i c #3E3E3E", +")i c #032878", +"!i c #003B97", +"~i c #003B9B", +"{i c #013488", +"]i c #123A87", +"^i c #0D244F", +"/i c #18294B", +"(i c #9C9EA3", +"_i c #BBBBBA", +":i c #C1C0BD", +"j c #0D0E3B", +",j c #030310", +"'j c #091853", +")j c #003BA2", +"!j c #003EA5", +"~j c #0C3A8C", +"{j c #0B1E42", +"]j c #8D93A1", +"^j c #DEDDDC", +"/j c #9B9A97", +"(j c #32373E", +"_j c #19408F", +":j c #194294", +"k c #0156BD", +",k c #0153B7", +"'k c #014BA6", +")k c #014FAE", +"!k c #0155BB", +"~k c #0151B3", +"{k c #133F8B", +"]k c #283859", +"^k c #DADBDD", +"/k c #ECEBEA", +"(k c #A6A5A5", +"_k c #5A5B5C", +":k c #05132C", +"l c #D6D4D0", +",l c #8A8F98", +"'l c #172749", +")l c #133370", +"!l c #1A3C85", +"~l c #154291", +"{l c #044DAC", +"]l c #004EAC", +"^l c #014EAB", +"/l c #014CA8", +"(l c #014294", +"_l c #01469C", +":l c #013F8B", +"m c #003DA2", +",m c #0040A9", +"'m c #0044B4", +")m c #003A9C", +"!m c #083B95", +"~m c #001239", +"{m c #9198A6", +"]m c #E8E8E6", +"^m c #E1E1E0", +"/m c #1C1D1F", +"(m c #001033", +"_m c #0C234D", +":m c #113F93", +"n c #E1E0DE", +",n c #E2E0DC", +"'n c #273651", +")n c #000C36", +"!n c #081B41", +"~n c #1A3E89", +"{n c #123984", +"]n c #03388D", +"^n c #003E9F", +"/n c #002A6C", +"(n c #0042AA", +"_n c #0044AF", +":n c #0040A4", +"o c #093582", +",o c #00327F", +"'o c #002C72", +")o c #02287C", +"!o c #04071D", +"~o c #092266", +"{o c #0053BC", +"]o c #0058C8", +"^o c #0053BE", +"/o c #004CAD", +"(o c #0050B7", +"_o c #00469E", +":o c #0054BF", +"p c #0051AE", +",p c #0050AD", +"'p c #014BA3", +")p c #133F8C", +"!p c #153779", +"~p c #56627A", +"{p c #D8D8D6", +"]p c #C4C3C2", +"^p c #1D1E20", +"/p c #0E4496", +"(p c #0054B5", +"_p c #0050AE", +":p c #004AA1", +"

Q c #06244F", +",Q c #062759", +"'Q c #062452", +")Q c #062450", +"!Q c #062350", +"~Q c #05244E", +"{Q c #08245A", +"]Q c #0C154C", +"^Q c #020309", +"/Q c #0C3C89", +"(Q c #0E51B4", +"_Q c #08295D", +":Q c #07295A", +"R c #08295F", +",R c #082A61", +"'R c #08285C", +")R c #082554", +"!R c #0E4498", +"~R c #082C65", +"{R c #082657", +"]R c #072350", +"^R c #061E45", +"/R c #06204A", +"(R c #06234C", +"_R c #07234F", +":R c #05224A", +"S c #08224F", +",S c #07214A", +"'S c #06234B", +")S c #092457", +"!S c #0D1851", +"~S c #0A275E", +"{S c #092D62", +"]S c #0E4191", +"^S c #0A2B5E", +"/S c #072049", +"(S c #07214C", +"_S c #072148", +":S c #092759", +"T c #0B2656", +",T c #08214B", +"'T c #0B2858", +")T c #0A2555", +"!T c #0A2450", +"~T c #09224C", +"{T c #09224D", +"]T c #09244E", +"^T c #082652", +"/T c #0C215B", +"(T c #0D306E", +"_T c #071B3C", +":T c #061B3C", +"U c #0D2E65", +",U c #0C2A5B", +"'U c #0D3068", +")U c #0B2959", +"!U c #0B2857", +"~U c #0D2D62", +"{U c #0C2B5D", +"]U c #0B2A5C", +"^U c #0A224B", +"/U c #0B2653", +"(U c #0C2A5C", +"_U c #0B2756", +":U c #09224B", +"V c #0B2855", +",V c #0B2153", +"'V c #0E1F5C", +")V c #0D2C65", +"!V c #0E2F66", +"~V c #12387B", +"{V c #0D2656", +"]V c #0C2755", +"^V c #091D40", +"/V c #081937", +"(V c #0A2148", +"_V c #091E45", +":V c #0C2A5A", +"W c #02030B", +",W c #0C164B", +"'W c #0D2055", +")W c #0D2553", +"!W c #0C214B", +"~W c #0C1F48", +"{W c #0D234F", +"]W c #0C2049", +"^W c #0E2657", +"/W c #0B1C40", +"(W c #0C2048", +"_W c #091A3B", +":W c #0B1D44", +"X c #0C1E48", +",X c #0B1B3E", +"'X c #0F2552", +")X c #0E224D", +"!X c #0E224E", +"~X c #0A1A39", +"{X c #0A1939", +"]X c #091735", +"^X c #0A193A", +"/X c #0F2452", +"(X c #081633", +"_X c #0E214A", +":X c #13306D", +"Y c #0F224C", +",Y c #0D1C41", +"'Y c #0D1D40", +")Y c #0B1937", +"!Y c #0C1C41", +"~Y c #0F214F", +"{Y c #0E1E53", +"]Y c #0C1547", +"^Y c #0D1248", +"/Y c #0C1B4A", +"(Y c #0C1B42", +"_Y c #0E1F46", +":Y c #0F234D", +"Z c #0D174C", +",Z c #0C1046", +"'Z c #0D1648", +")Z c #0C1446", +"!Z c #0E1A4E", +"~Z c #0F1E4F", +"{Z c #0E1E4A", +"]Z c #0E1E46", +"^Z c #0E1F45", +"/Z c #0D1D3F", +"(Z c #0C1A39", +"_Z c #0F2148", +":Z c #0E1D3F", +" , ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) ! ~ { ", +" ] ^ / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( _ % ", +" * : < + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * [ } ", +" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | 1 1 2 2 1 1 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 ", +" - + + + + + + + + + + + + + + + + + + + + + + + + + + 4 5 6 7 8 9 0 a b c d e f f g g f f e h c b a i j 8 7 k l m | + + + + + + + + + + + + + + + + + + + + + + + + + n ", +" o ] + + + + + + + + + + + + + + + + + + + + + | p q r s t u v w x y z A B C A A D D D E E E E D D D A A C F G H y I J K L M N O P Q R + + + + + + + + + + + + + + + + + + + + + * S ", +" ! * + + + + + + + + + + + + + + + + + + T U P V W X J x Y z D D z Z ` .Y Y ......................................Y .` Z z E D H y I +.@.#.$.%.&.*.| + + + + + + + + + + + + + + + + + + =. ", +" * + + + + + + + + + + + + + + + + + m -.%.;.>.,.Y Y '.Z ` .y Y ....................................................................Y Y .` Z Z y ..I ).!.~.{.].| + + + + + + + + + + + + + + + + [ . ", +" ^.+ + + + + + + + + + + + + + + + + l /.(.v '.z ` .Y Y Y ..............................................................................................Y Y y ` z _.w :.<.[.2 + + + + + + + + + + + + + + + + [ ", +" }.+ + + + + + + + + + + + + + + + |.j 1.2.A D ` ........................................................................................................................ .E 3...4.5.6.R + + + + + + + + + + + + + + + ^. ", +" % + + + + + + + + + + + + + + + 7.5.u 8.A A ` ..............................I ,.,.9.9.9.9.9.9.0.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.,.9.9.,.9.I .............................. .D G ..a.b.q | + + + + + + + + + + + + + + ^ ", +" 3 + + + + + + + + + + + + + | c.d.e.x Z D ` ..........................9.8.f.g.h.i.Z j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.H.r.I.J.K.l. .k.'.y L.M.f.f.,........................... .E z 9.K $.l R + + + + + + + + + + + + + . ", +" [ + + + + + + + + + + + + + N.O.c P.y ` '.Y ......................9.J Q.R.S.A T.U.V.W.X.Y.Z.`. +.+++@+#+$+%+&+*+=+-+;+>+,+'+)+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+2+3+4+5+6+7+8+2.,.......................Y .Z y I g %.9++ + + + + + + + + + + + + < ", +" ] + + + + + + + + + + + + + &.O v _.Z Y ........................8.Q.0+a+b+c+d+e+f+g+h+^+i+,+j+k+l+m+n+!+o+p+l+l+q+l+r+@+n+s+t+u+v+.+w+x+y+%+i+j+j+z+A+B+^+A+C+D+E+F+G+H+I+e+J+K+L+7+Q.f.........................Y '.H +.$.M++ + + + + + + + + + + + + < ", +" N++ + + + + + + + + + + + + |.O+P+Q+Z ........................,.R.R+S+J.q.T+U+V+W+X+Y+Z+,+`+ @x+.@B++@k+@@`+#@$@r+t+k+v+%@&@*@,+!+x+u+=@=@-@A+;@>@p+!+@+n+x+'+,@n+x+'@)@-@!@~@{@]@^@/@(@_@:@<@[@8......................... .5+..}@7 + + + + + + + + + + + + + |@ ", +" o ^.+ + + + + + + + + + + + O.1@2@A Z ..................I ,.,.9.3@4@5@6@7@8@9@0@a@b@^+c@C+d@e@++]+#@f@i+h+!+`+g@h@B+i@;+)@>+u+j@)@k@A+>@(+l@m@=@n@o@B+*@W+A+p@l+q@)+n+^+r@s@F+t@k+k+u@v@w@Y+x@y@z@r.A@..9.9.,.9................... .A S.B@C@| + + + + + + + + + + + + =. ", +" D@+ + + + + + + + + + + 1 q E@F@H Z ..................8.g.G@j.H@I@J@K@o+L@M@++^+N@O@P@Q@R@S@T@n@s+U@V@W@X@Y@Z@`@ #.#o@+#@###$#%#j@-@&#%#u+*#W+A+s+S@=#o+-#A+N@;#>#%#]+,#'#*#A+A+)#*@r@F+=#$#!#W+~#{#W@]#^#/#2+ .Y (#f.I ................'.E y _#<.*.+ + + + + + + + + + + . ", +" + + + + + + + + + + + + q :#<#'. .................I [#}#|#1#2#3#4#T@5#6#7#V@7#'#8#9#0#a#Y@b#c# #O@d#$@e#f#g#L@h#.#%#W+'#i#j#$#r+k#.#4#l#.#q@##&#.#=#m#n#o#s+'#r+R@N@s+n@7#V@p#q#r#s#t+t#n@'#T@'#p#u#.#o+v#{@7#w#x#y#z#_.A#Q.,.................y ` y K B#T + + + + + + + + + + + C# ", +" & + + + + + + + + + + + M+D#6+Z Y ................I E#7+K.F#G#H#I#J#W+S@W+9#K#T@L#S@M#Y@Y@N@N#d@'#-@$@U@=#O#{#P#*@7#a#O#t+F+u#s#Q#R#$#L@S@S#T#U#V#d@$#@#p#$@W#X#.#.#b#Y#P#o#Z#=#,#S@T@S@,#7#r#w+i#`#W+)#a#d@ $.$S@'#o++$9#@$#$$$%$&$9...................'.H *$=$+ + + + + + + + + + + + ", +" + + + + + + + + + + + -$b.Y A ..................I [@;$>$,$'$)$!$'#`#~${$-#d#j#u#p#R@h#V@{#W#]$s+^$p#@#p#p#/$u#,#($O#h#_$j#d#:$Z#<$Q# #n@/$`###[$i#}$@#|$!#w#c#|$1$V@2$s+L@3$p#4$5$7#9#S@a#Z#q#u#W+`#k#6$V@d#s+P#V@7$/$W#d#+$8$9$0$a$b$c$d$9...................E z e k + + + + + + + + + + + = ", +" e$+ + + + + + + + + + *.0 P+A '...............I 8.f.S.f$g$h$i$j$k$@#p#u#l$}$m$n$<$<$o#o$p$q$9#2$o$r$/$6$s$t$u$v$w$^$x$y$z$A$^$B$C$<$9$D$:$/$E$F$}$@#G$($H$p#I$J$K$`#L$M$N$}$_$O$P$Q$:$Z#:$^$R$D$D$u$S$p#I$/$T$s$U$o#/$n$v$/$n@n$V$W$B$X$Y$Z$`$D f.f.,...............y D .. %].+ + + + + + + + + + [ ", +" ( + + + + + + + + + | =$v z z Y ............I [@h.D .%+%<$@%#%w$^$$%%%_$&%*%u$=%:$E$($_$S$r$ $<$S@n$D$q#-%e#;%u$s$>%h#2$/$V$n$O@>%R$/$I$,%d#u$;%'%F$)%!%N$~%{%n@<$V$Z#D$;%]%}$j#$%^%9#^$E$r$Z#Z#/$/%9$(%n$C$<$V@1$<$D$;%U$V$e#_%($P#:%D$o#<%[%M$}%|%1%0.d$,.Y ............Z z +.2%3%+ + + + + + + + + + 4% ", +" % + + + + + + + + + + 9+5%y '.Y ..............d$h.6%7%8%O$9%x$o$0%y$a%u$($I$b%]%j#p#I$^%d#L$s$($u$r$^%z$^$_%2$^%n$x$V$m$o$c%d%V$r$/$d#e%f%2$S#K$a%/$g%|$h%=%i%;%D$I$N$_%<$y$,%|$@#u#!#S#j%($U$r$u$q#2$r$z$9#r$k%1$y$|$($l%_%m%n%9#s$9$m$9#A$D$A$L$:%o%c%p%q%H@%$r%................'.'.u M++ + + + + + + + + + s% ", +" s%+ + + + + + + + + + r t%u%................8.8+E v%w%;%,%r$x%y%2$z%A%D$_%9$Q#B%|$C%/$B$d%{%o$N$d#M$D%r$w$E%F%G%y$H%z%1$($o${%V$<$p#I$o#I%{%P$E$J%K%p#/$|$S#;%/$n$z$x$ $N$[$(%9$d%2$,%u#|$s$/%s$9#_%y%p$L%d#Z#M%N$:$N%N$w$U$O%L$^$S@P%A$^$h#Q%p$R%S%>%U$_$d%T%U%V%W%}#[#................X%y Y%Z%+ + + + + + + + + + ", +" + + + + + + + + + + l :#`%X%................[#'. &.&i$+&N$@&#&0%$&]%s#m%:%^$(%F$k#d#%&<%f%&&o%p$V$^$*&u$=&-&O%O$V$2$N$/$;&u$>&9$r$/$)$f%,&W$9$+$s$u$E$'&C$1$E$)&P$!&[%;%D$1$u$~&A$D$1$z$D$P$!&&&z$n$w$O$o#_$($:$($p#6$)#V$Q%{&]&V$v$^&:%r$9$V$o${&m%o$-&n%O%/&~&(&_&:&<&K.[&9............... .A }&O.+ + + + + + + + + + |& ", +" 1&+ + + + + + + + + + r 2.D Y ............,.f.;$2&3&4&5&s$A$y$r$6&0%E$D%z$7&%&l%^$u$x%D%6&8&9&(%~&[%,%(%z$/$5&0&Q%[%V$a&a&(%j%$%b&c&Q%m%I%d&o%w$e&m%a&e&f&}$g&g%)&l%M$1$h&y$i&s$[%j&%%0%g&V$D%k&l&h&z$i&A$(%^%D%)&^$x%k&M%u$D%m&c&c%%&M$~&l%D%i&A$n&o&p&q&r&4&s&c&t&u&5&v&8&w&x&_.[@,...............D y&5.*.+ + + + + + + + + D@ ", +" } + + + + + + + + + z&c '.Z ............I (#%$A&B&C&D&r&E&l&F&h&%&k&G&w$D&o&~&i&l&v$H&(%I&J&K&L&n&i&M%M&J&D%D%v&N&n&D%O&~&P&Q%a&H&Q&F&K&T%E&R&C&r&~&i&6&6&s$S&)&H&%%9&T&U&D%u$9&V&i&h&a&W&%%v$k&X&e&a&l%f&Y&i&Z&`&M%P$ *.*+*V$%%%%h&@*Z&D%z$M&i&q&f&D%#*$*D&%*Z&p&&*&*Y&**=*f&-*;*>*2&,*[&9.............` z '*)*+ + + + + + + + + + ", +" + + + + + + + + + + q !*z Y ..........Y ,.8+~*{*]*^*/*K&(*K&t&.*k%%%(%)&4&s&M%@%e&o%~&[%o$e&%%R%C&a&M&l&_*c&c&K&:*A$~&_*o&t&<*K&~%n&M$~&9&l%9&[%[*}*l%0%@*-&m&F&%%M%M%+*H&9&|*1*q&@%s$2*%%_*0%M%3*4*~&R&(%_*_*9$A$g%+*,%x%0%5*D%6*h&h&u$D%M&G%]*l%i&o&i&7*Y&8*9*#&0*#&L&C&5&$*0*a*b*c*0*d*e*f*[@g*..........Y z 2@~.+ + + + + + + + + + ", +" + + + + + + + + + + h*y Z ..............[&0.i*j*[%k*0&l*+*m*n*o*p*q*p&r*s*t*n&u*v*q*w*q*x*K&q*q*9&y*z*A*g&x%I&B*c%q&C*D%D*=*I&Z&E*Y&F*l&C&s*G*H*p&I*q*r*9&J*E*_*y*n&w*K*L*7&q*]*M*D*P&C*N*O*g&h&P*Q*R*S*x*E&P&l&T*z$U*s*V*)&9&I&W*w*X*L*l*T*Y*E*L&E&I*B*Z*`* =.=+=u&0&D*@=m&Z&p&#=p*$=%=&=*===-=h.;=.............. ._. %N.+ + + + + + + + + >= ", +" + + + + + + + + + ].d H y I ..........I d$S+,='=)=!=~={=R*I*]=O&^=I*S*t*/=(=_=:=<=L*[=r*D*C*}=|=1=2=2=3=3=4=5=6=7=7*!=8=D*9=0=|=T&a=Z*.=b=]=c=d=[=7*s*H&'=e=}='=w*f=e&g=P*h=:=i=e&j=7*[=k=l=m=n=o=l=p=y*q=I&|=r=k=r*s=t=Q*<=q=u=+*+*j=I*K*v=w=+=q*I&C*B*a= = =x=y=!=D*z=M* =}*A==*B=h=]=Z*P* =C=D=E=F=G=8...............D v )*+ + + + + + + + + 1& ", +" H=+ + + + + + + + + 6 I=A Y ..........I f.Y J=K=L=a=y*L*q=M=N=.=U*q=q*^=f=C*[*}*t=j=j=h&d=W&U*w=O=P=Q=R=S=^=S*T=I&W&W*x=U=9&'=7*I*Z*D*0=Z*i=I*%*I*H&I&|=w*T&~=P*i=.=V=a=W*d=D*p&A=t*W=p=I&X=V*I&Q*k&V*E&M*7*E&O&Z*Y=W*d=Z=}=r=V*n=R*a=S*v=a=S*p=w=l&r*Z*'=}=C*I&N=`=s=Y&D* -9=Z*.-[*8=+-~= =z=Z*9= =@-#-$-%-|#f.9.............&-2@*-+ + + + + + + + + }. ", +" o + + + + + + + + + B#3@=-Y ........Y 8.d$--;- =>-.=s=,-I*p=@='-E&)-E&s=p*!-'=l&~-{-I*r*~-]-^-W&/-(-_-n=W&:-X=T=Z=a=)=0=+=@=a=P*.=P&<-M=t=b=T*T=[*g=S*I*E*[*}=w=I*C*.=i=w=Q*i=Z*W&R*[-z=D*}-Z=g=o=^-Z*q=)=Z*[=O&R*H&p*X*^-r*r=:-c=c=V*V*|-X=y*P&N*k&4=I&a=1-~=9=`=Z=0=E&P&P*2-.=[*3-M*4-5-Z*i=6-A=W=j=[*t=|*7-8-9-d$[@............z ` i | + + + + + + + + 0- ", +" |@+ + + + + + + + + a- .'.............Q.f*b-c->-d-z=e-W&N=}=z=.=w=q=I&~-}=f-g-q=I&v=h-i-j-^-k-I&1=4=+*l-R*a=[*I*{-i=[=I&m-6-p=Q*P*~=T*_*n-o-e&|=e&{=Q*l&w=Z&}=p-p*)='=~=q- =x=8=C*h&C*Q*P*q=q-P&B*Q*r-t=|=7*y*N*T*v=~-[-s-P&6*^-A*I*p=O=A*q={=Q*W&p=a=v=t-u-6*{-8=.=0=Z=T*2=A*v-L&w-x-`=y-M*'=C*M=N=`=!-V=D* =z-A-B-C-0.[&I ..........Y _. %T + + + + + + + + 0- ", +" D-+ + + + + + + + 3%;.H Y ............[&E-F-G-q-'= =P&f=H-w='=Z=P&P&}=m-|=C*3=I-I*^-J-q=c=4=r*K-C*Y&[=S*d=S*P&L-q=:-^=z=}=]=M-I*'=N-@=N-+*:=|=Z=P&Q*,-O-r=0=r=Q*Z*[=I&E* ='=]=E*@=M*|-r-P-Q-w=Q*S*'-R-'=N-w='=q=W&Q*q=:-z*q=a=k=l=O=g%S-4=p=R-X&4=H-Z*r*X*S*W*W*I*R*O=[*`=}=T-0=y*Z&/=}* =~=p*!=x=U-j=}=}= -V-m-'=z=6-W-X-Y-[&..............H u Z-+ + + + + + + + : ", +" @ + + + + + + + + z&1@A Y ............[#`- ;.;+;'={-{-j=u-i=z=w=@;#;L-U-$;%;q=&;X*g-*;=;-;;;>;,;^-';L-|-);{-h-!-!;)=P-B=z=~;{;[*];{;]=^;/=2-/;!;(;L=!-5-_;!-:;s-<;[;b=q=};~=];p-|;}=`=`=1;1;2;[;~=p-{;3;s-[=4;5;v-f=X*%;I*%;u=6;[=q=f=%;1=R*0=7;3=p=q=q=r=f=u-v-:;O=Z=%;~-!;P-r=8;h-C*M*R-(;];9;0;$;a;Z*z=N=b;b;w={;c;v-%;d;e;f;Q...............D g;h;+ + + + + + + + i; ", +" / + + + + + + + + p _#A Y ..........,.G=j;k;l;m;n;o;o-2;/;!-(;}-`=|;p;q;r;{;s;c=t;7=u;v;=;w;L=};x;};y;a;z;A;P-B;!;s-C;o;q;/;5;5-5;N=P-B=x;(;b;P-R-};D;!-r;E;F;:-,;v;3;F;(;(;G;L-F;H;;;B=W-];!;B;{;n;I;j-u-b=F;{;p-a;o-J;K;;;%;N*z;L;M;c=2;=;v-r-2=X*<;N;O;p-p-F;P;Q;u-}-R;3;;;{-~;@;3-1-p-S;T;U;w-b;a;F;H;V;b;a;-;n;0;a;v-O;s-W;X;Y;;$8...........Y D P.O.+ + + + + + + + i; ", +" D@+ + + + + + + + l K D Y ..........[@Z;`;x- > >o;.>5-5;a;+>~=;;j=o;n;a;a;}-f=v-L=W;@>#>N;{=T;{;{;x;};V-7-$>F;{-}-%>8;(;&>*>x;%>=>i=(;->>;;>}-0;>>/;{-D;B=r;!;P;!;,>t;'>x;x;B;)>j=1-s;:=L=x-,-J;h-)>n;[;3;@;O;->!>3=O;+-~>!;f=R;b=1;{>]>2=%;=;!-S-N*q=[=3;^>A;/>R-W;u-r-O;F;2;%;r--;(>_>R-i=B;:>1-a;<>z=v-[>.>7-~=}>%>|;/=x;~-}-2;s;|>1>y 2>I ..........z 3>=$+ + + + + + + + i; ", +" C#+ + + + + + + + 4>!*X%............d$c$5>q;6>:>x;->|;x;}>}>D;-;;>7>8>w-8;>>!-9>x;p;5;0>P;a;T;j-/;u--;=;.;a>r;=;O;~-b>@;0;c>d>w;p-L=(;d>R-e>f>g>%;h>~>i>d>d>x;/;@;<;3;A;!;j>->/;}-0>/;@;5;H;:>B;<;k>/;x;W;3;0;l>g>}-m>n>s;o>p>{;g-k>O;,>q>;;j-I;r>2;W;P;>;Q;s>;;c=t>[;X*c=u>v>~-O;Q;u>P;b>s;@;b>w>(;x>=;=;d>[>n;M=y>L=5;(;9>/;(;4;S;g-k>j-z>A>2>............y I /.+ + + + + + + + ) ", +" B>+ + + + + + + + 4>+.X%..........Y d$Y-C>D>7>E>F>G>g>G>H>I>f=J>K>n>L>M>N>9>O>7>V;9>H;P>Q>R>x;S>w;J>P;A;C;T>U>V>%;@;H>N>9>G>W>~>G>:>;><;X>Y>b>H;Z>J>e>/;n>`> ,n>e>/;.,p>+,N>@,H;g>w;D;Z>=;:>#,Z>$,%,&,*,H;.,=,H;R>J>J>-,r;;>;,%;>,O;*,0>g>R;I;]>u>,,p;n>',),!,~,S;l>J;W>w;{,],^,^>;;;;/,(,D;n;:>_,Z>X>[>:,<,[,},L>y>|,1,n>n>2,Q-+,{,3,S;-;1,4,5,6,7,g*.......... .Y %.+ + + + + + + + / ", +" H=+ + + + + + + + M+P.H ............[&V.8,9,&,|>0,n>n>|;:>a,5;w;b,K>V;c,c,`>D;P;{,}>:>&,d,9>e,f,.,1;j-g,J;@;9>$,h,i,R>X>/;;>;,j,k,l,m,h,O;G>n,9>9>V;b,%,o,~>>,e>;;p,b>q,},D;&,x>4;1,r,r;:>/;9>p>H>{,v-1,G>4;P;s,b>+,J>&,k,~>p>e>,,p,W>;,b,|>t,u,Q;A;N;J;X>:>;;v,w,h>n>b>R;j-',k>h>;,t,A;L;;>{;x,y,9>e>|>M>z,A,B,C,z,@,d,D,{,E,F,',C;R;I;h,n>e,b>G,H,I,J,I ..........Z X%%.+ + + + + + + + K, ", +" + + + + + + + + M+P.L,..........9.M,N,O,P,_,f,G>f>r;Q,L>j>y,d,H;2,R,p>N>>>S,}>4;T,Z>H;U,z,J>P>S;t;V>6;k>+,0>f>H;V,W,&,0,n>H;x>n>U>f>2,X,y,Y,D,G>a,Q,},Z,f>Z>1,&,P;j,Z>p>`, 'F;;;&,q,p>y,&,/;.'a,+'b>@;p>l>L;@'H;D;n>y,;>J>#'r;`>@'Q;k>e>~,Q;$'%'&'*'='-'y,p>S;;'>','''p>R>u>/;w;;>&,/,j-;>.,@;b>e,`,b,2,)'2,9>s,},}><,E>s,!'~'@,X>h>4;f,e>%'z;k>/,{']'^'R.,...........z Z B#+ + + + + + + + D- ", +" + + + + + + + + 4>w u%..........,./'('_'a,+'K>$'$'y,@,~>A,}>c,B,f>s,y>9>e>H>c,s,:'<'y,Z>['#'|>@;$'Q;>'}'R;|'u>b,&,u>4;c,@;$,0,B,D;$'!'1'X>N>Z>y,b,y,_,c,X>A,!'p>.,4;2'A,x>e>e>J>S;1,X>k,3'x;N>z,},@,o,G>N>V;W;4'e>n>5'6'p>b>0,D;],I;k>7'I;8'Q;X>;,;'@>h,W;h>X>q,s>9't;0'a'$'>,2;A;S;i,g-C;G>],&,b'1'c'~'d'y>e'+'@,b,},x,<,f'Z>@,b,k,A,Z>g'h';'i';'I;0'j'd,k'l'8...........Z Y /.+ + + + + + + + m' ", +" + + + + + + + + n'X Z ............o'p'q'S,r'I>/;Z>k>W,y,h,s'<'M>_,p;t'C,u's,v'f>w'2,a,2,r'G>;>1'}'J>X>/,4'/,p>Z>x'H>:,K>j'y'W;1,z'{,|>t'7>9>N>}>A'~'b>H;H;@;;>(,S;&,p>A'n>C;O;;>e>1,G>B'b>a,K>C'K>Z>@;&,f,9>j>/;P;@;J>$';>D'b>N>/,1'&,=,i,4;3,3,,,E'>'F'e,>,j'[>c,h>s>h>`>G'>,g>~>J>S;h>G'Z> ,E'S;$'<'H'+'I'J'K'Q,#,|,L'Q,M>x'f>+'K'L>2,1,M'N'~,O'E'G>I;W>J>k,P'i.8...........y I O.+ + + + + + + + ", +" + + + + + + + + Z-K Z ..........Q'}#R'S'T'#'e,J>q,1,;,U'>,G>V'W'X'@,@,z,w'K>R,R,q,X>2'x>w'y,Y'',t,q>N'f,S;Z'u,1,G>f'`'>,G> )b,+,|>.)X>q,K'&,1,+'2'K'+)G'f> ,@)#)t,G>X>1,!'U'$)`>%)&)#'@,G>D;n,@,a,N>Z,G>y,V,G>k,u'L'G'@)X>1,G>*)@'B'1,1'u>I;k,=)u,t,-)>,''v,}'`>@;b,X>{,],i,X>h,>,1'e>;) 'q, '>)7'X>,)')))+)K>Y,A'u's,A,L's,u'C'L'A'c,!)Z>k,$'~)h,{)*,.,.,$)G>C'])^)i.,...........'.x /)+ + + + + + + + ", +" : + + + + + + + N.B@D ..........I 7,`;()_):)X'@)<)e,1'X>[)&)@,}) '|)1)2)3)[)4) )5)C'#,G>R>6)a'e,7)R>8)',',#)+,4';)9)0)a)V,@)1'b)c)d)$,e)T,f)G'd)g)h)i)j)$)c)4)k)l)H>Y,m)$)m)H>b,b,n)X>4)o)X>X,@)>,>,p)$)n)&)U'U'X':)X'e, 'e,&,j,q)4'.)a'-)-)>,N'E's>r)s)t);)e,>)1'3,m)H>u)9>j)#)@'G' , 'v)$)j,w)I;n)))L'x)y)z)c)A)h)B)0)Y,h)A)))a)C)D)#'V,v)@).'e,l)1'],E)U'4)i)j)F)G)H)L.,...........u%P.I)+ + + + + + + + ", +" J)+ + + + + + + | }@A Y ........9.K)L)M)N)V,7> )b)1,@)X,U'O)P)X>Q)B,R)B)S)o)o)P)L>T)4)o) 'U'+,N'/,n)G'g,U)%'V)q)y,A,W)z)+'X)k)7'+,+'!'e,Y)Z)b)G>$)#,X)P)b,G>!'n)b) )+'V,U'e)$,`)X'X'#,X>m) !],.!+!@!%'-)6'#!R>@)u)$!U'%!w'$,.,',N'n)#)3,.,.!],@'@'u>H>&!s)>,q)I;~)=)3,@'*!=!-!G';! )7'H>X'<)>!-)>)9)1,+, 'A'P)b)L>i)X>+),!4)u)u)j)'!Q)C'k,n,j)e,C'4)o)C'>,e,w'w'G'>){,f))!!!~!8...........D X U + + + + + + + < ", +" + + + + + + + + s D Y ........9./'{!]!^!j) )n)/!(!U'q,D,})_!2'4):!})*!j)_!D,k,&)6!/!C'c!{,>!-)g,0!d!.)e!<)@'f!c!b)H>d)g)/![)d)R>8!*'e,m)~)g!m)<)''i,.!*'a'h!e,i!''=)=)j!k!n)%)c!X,{, )k,2)1,#!j)b)})1,{,/!$)m) )g)m!n!j)g)_!/!y,b)o!D,i)i)1, )e)m)p!j)~)q!e)}'r!s!t!u!%$,...........D E@2 + + + + + + + 4% ", +" + + + + + + + + v!_.Y ..........2>w!x!X'e,y!z!A!7!k!`)B!C!M!j!l)O!P!g)Q!n!l)z!N!N!n!>)R!S!T!U!6!X)6!V!n!e)}!G'2)W!H!D!X!A!Y!%!Z!`! ~.~ ~9!|!m)l)+~a'L!%)@~#~j)n!$~l)%~&~J!&)j!j!8!r)l)2!*~Y'R!=~J!-~>);~Q!>~r!,~e,k)%)*!b)Y!}!8!t!2)'~)~!~Q!~~9!{~]~]~^~V!j!Y!`)&)2)5)&)z!/~$)T)X!n!%!(~U!_~9!O!:~<~b)[~}~+~|~E!5)t!1~G=............_.;.+ + + + + + + + ", +" ) + + + + + + + 2~Y g*........Y [&3~4~5~L!U!}~}~`):)9!X!6~7~8~9~0~z!z!H!)%)9!:~f~a'*~w)~)g~h~|'s)<)d)q)d)t!N!i~K!:~>)D,e)#~`)z!}~;!q)Q!j~k~k~l~#~d)4)T)#~1!/!`)W)&)9!6)6)m)k~;!Z!r)m~6)d)1!`)6)%!d);! ~l);!2!q)e~E!}!V)3,`) ~L!~)>)f!n~o~S!d).!6)l)}!p~U!q~|!z!b~X'l)0~m):~r~z!g)H!s~t~G'e!2)v~w~/~+~N'x~G=g*.........._.y~+ + + + + + + + ", +" N++ + + + + + + l P+X%..........[&z~A~B~U!L!C~7~Q!2)O!a)D~H!E~p!o)S!H!3)5)k)5)F~4)<~G~k)1!o)<~u~O!7'N'E!m)j!&~4)H~I~}!9!s)J~n~;~K~L~M~N~2!Q!}!O!/~}!U!o~}!z!O~j~l~l)e)$~U!1!k~P~:).......... .'.7 + + + + + + + s% ", +" + + + + + + + T ${D ..........[#A %{&{Q!:~5!g!m)1!c~F~1)A!D)*{T))~={p~@~T)-{={H!;{H!>{O!E!)>)j~`)k~r~(~r)r)~)]{r)J!L!-)7)Q!J!L!>!N'^{d)r) !b)Y!%)j~l)k)<~0~`);{/{F!*!n)({_{1!6!:{<{[{)~p!}{/~$~G)r)~)2!E!;!2!A!]G~-~x{l)z{l{p~>{C~_~C~^{G{j~-]o~0~g!Q!G{,]j~(~'])]w~i~!]L{+]~]{]+~r)<~z!/~z{~];{p{]]p~o~Q{e~r{^]j~k~/];{}!P{]{~]y{(]Q!p{}~o~_]:]H [@........Y D b.+ + + + + + + 0- ", +" + + + + + + + <]2@` ..........d$[]}]o~L!M~E!V{n{|]/~&];{1]j{a{2]C!~~6{3]({F~R~4]+]@~Y{O{5]@~_~6]U!~]7]6{`~_~_~8]9]C~:~d{%]<~%~o~^{/~Z!_~o~E!0]Z!a]:~O{_~}~l{h{b]P{$]c]1!i~d]G~6{j{e]0~f]h{g]p{U!U!1!{~h{l{p~W~%~h]7~l{}~T~p{)]i]E{j],]p~Z{1!_~k]n{>]*]l]a]L~g{|{H!m]T~n]6)-]:~}]o]k~O{g]$]C~Y{l)j~p]W{G~l{q]o]o]L{A{`~r]s]t]b](~R~_~@]}~G~;!|{k~T~;{g!z{p{L!M~n]-~Z!;!u]U!&~A{P~ {a]~]h{}]^{v]w]x]y]l'............z z]+ + + + + + + A] ", +" + + + + + + + Z%B]_.I ........[@C]D]E]6)6]/]o~n]&]w~F]Y~G~p{}~_]G],]G~H]_]n{p]I]_]A{J]p]>]}~~]n{K]_~`{;{7~Z!6]R{M~L!!{){L]z{n]-~Q{M]k~k]}~c]h{Z!l{0~ {N]F{k~l{({c{}~T~c{J]j]0~O]w~F{({;{}~2!b]@]~]$]p{#]K]>]P]T~_~Q]J]}~U{_~Q{l]n{8{p{J]R]p{c{p{G{G{c]Z!;]L!Z!y{y{S]T]9{c]M~]{o~p{U]V]9{_]k]Q{q{b]o~K]+]N{y{%~!{%~R]D{N] ]w~~]t]n{@~;]W]%~i{$]o~X]G~Y]Q{R]Y{z{P{o~p{T!P{Y{V{D{D{_~Q{Z]/~w]%~h]n{-]E]%~`][#..........` ^l + + + + + + + ", +" ' + + + + + + + Y%.^..........(#--+^@^P{U{O{R~]]%~_{w~g]>]#^_]>]$^p~%^$^&^v~G~f]G{p]J]N]c{C~%~@]]]l{']D{*^r{n]Q{:~=^-^!{;^>^Q{v]o~i{O{%~,^O{'^&~:~i{_]O!&]}~~]h{K]k~Q]0~G{&]j])^]]N!p{&]c]_~}~h{6]G{,]i{J{a{0~I]T~e~%~k]k~h{g]V{o~o~!^%]G{/~T!p{p]G{Y{Q!U!%~R{-~,^~^*^i{o]c]a]{^z{Y{_~C~/]%~n]Z!Z!%~o]O{l{~]i{n{~]Z!O]B{p]]^$^o]V{ {K]n]:~g!:~l{p{-~O{T!T~j~;]U{l{k~/~U{R{q)+]^^y{&~%~'^/^l~R~o~,~l]E!(^_^w![&.........._.g R + + + + + + + ", +" + + + + + + + :^.. .......Y [@G@<^O{_~ ~%~P{;{<~K]6{>]o~Q!G{D{R]R~8{M{p~v~W{f]G~w~[^)]]]Q!%~}^C~~]1!,]:~|{-]V{_~V{a]/^*^|^S{-]Q{R{d{l]/^8]M~_~i{h{&]%~z{%]Y{ {x{N{T!p]F{j])]m]T~l{/~G{~];{+]1^p]p{@~h{/~2^i~p{T~~]3^!^+~}~*^r)U!6]<~:~;]n{#]T!Y{%~W~>]p{&~z{4^%~o~g!|{5^o~+~,~6^z{-~%^G{$]9{c]p{C~7^n{k~r{%~<~V{b]@]Z~8^W{`{[^n{%]D]r)&]n{d{o~9^o~r{>]w~h{o~'^}~1!0]T!~]0~0^P{*]z{Z!Q{5^7^T~l]a]/^M~]]!^a^b^6%[&........Y Z c^+ + + + + + + =. ", +" + + + + + + + 4 d^z ........,.e^f^b]b]g^l{V{N{Y{;{o~n]'^j]m]3]f]h^[^9^]]`{G{p{i^I]j{j^}~~]J]k^=]#]9^$]l]5^l]h{M~!{{^l^6]Z!S~*~^]m^n^o^p^q^r^!^l)A{m]m]&]j~i{i{%]<~Z{,]q]p{,]]]>]G{h^q]p{p{]],]9^B{I]~]$]A{']s^I]o]c]~]8]6]G{o~p{+]V{t^9]z{>]T!u^9]@]G{+]p{o]$]v^>]J{x{J!#^&]5^8]o~N{o~Z!h^w^_]x^&^']~]%]R{/]q]/~R~6{+]Y{T~#]!^~]y^p]R{,]F{h{~]z^7^;^y{h^A^,]*^B^6{T~Q{Q!N]C^D^~]$]O{{^#]h{p]r)E^R{&]g]>]O{}~F^G^/'[@........Z ..H^+ + + + + + + ", +" / + + + + + + + b.D Y ........I^J^K^L^7]#^M^N^i{p]N^Q{6]O^P^e]m]Q^R^S^T^U^p]V^W^X^Y^#]#]w]X{Z^V^`^i^*^*^l] /./R{8]+/@/#/$/%/#/r^&/*/p^V{=/v]n^-/U];/>/q]K]W{N^0]n{p{V^,/#^`^1^q]v^'/]]V^#]#^)/f]f]p{`^f]Y^h^#]!/~/{/,],]o]7^*^0]]/6]^/k]1^1^D^$]1^~]//;/B^M^>]'^n](/z^6]k],/#]&/>^*^_/*^@]{^1^7^;/w]!^:/'^z{=/]]1]j]//~]g^v^:/n^h^M^^{^'^x/X{y///B^q]'^z/V{M^X{V{o]#]Q{&/$]F{A/O^p{/]+/h{p]@]v^e]c]'/`^~]l]m/c]]/*^N^$]]]L{f]l{f]'/-~X]B/D^w/C/Q{l{R{r{;^0]6]c]Z^1^D/E/#{[@..........A j + + + + + + + F/ ", +" G/+ + + + + + 4 v H ..........,.H/I/V{n^8]k]W^>/X{*^'^j]J/e]!/K/R]#]v^z^#]V^L/]]V^M^M/z^3/Z^z^e]j^h]N/x{V{z{O/V{h]P/Q/./R/S/5^>^6/T/>^x{S{v]E]g^U/]/b]V/*^*^;/*^N/Y{N^)/M^;/g]D^#^h^`^W/B/T^e]z^#^F{V^`^V^v^,]V^;/v^v^M/Z^;/z^#]7^k]U/&//o]w]D^Y{f/#^Z/l{*^M^U/O{]/l{'^`/X{(/o//C/6]x{y{E]/]~]N{x{'^-/N{#^h]`^:/6]-/$];/>/Y/'^L{x^O^l^n^Q{`^>/V] /'/ (X{n]h].(j]7^Z/+(>/c]!^V^]/@(n]+/o^h]/]N^0]p{v^#($(%(,.........'.I l + + + + + + &( ", +" + + + + + + + *(H ........g*2>=(g^-(n^&/]/;(W^>(5/,('()(!(K/h/~(Z/o]'(Z^//D/{(](^(](/((([/_(:(M///]/'^<([(}(U/n^,/X/|(1(2(3(z/4(5(6(7(2/V]8(^/&/<(o/9(0(&/./.(^[/t(!(u(//$]g^v(^(A/;(@(t/:/[(^/w(n^O^x(y(Y^z($]A(`/u]Z^B(/'..........Z 1.+ + + + + + + = ", +" s%+ + + + + + &.x Y ........[#_.C(0//B^q(^/v]n^*/I(J(g(K(#/I(|(>^g(_/*/N{`/B^`/L(;/M(Y/.(.(v(N(Y/O(.(]/J/e(;(P([/.()/M/V^e(Q(.(V^R(J/S(l(e]>/T(;(X];(U(](o/+/7(x(U/;/v^+(U^j(V^f/#^V(Y^>^J(n^./N/W(_/V{g(X(N/.()/O(Y(Z(J(7^7^7^Z/<(x(;(,/0(`(j(N/]/Y/i(n(J/;(5^o^g^&/ _^/Z/)(/(O(f/._~/J/+_b(n/'^w/,(f/e]o/;^X/B^*^.()/7^o^+_@_B^s/#_f/;/d(D^$_^(%_&_Y ......Y S.*_+ + + + + + + ", +" + + + + + + + :#Z ........I =_-_;_>_n^X{D^=/*/x/M/^(u(K/,_k(Y(9/M^Z^}/M/L/3/J/Y^p(:(+(K/'_d(e]f/D^'^.(P/[()_A/!_ /~_o^8({_N{[(]_8]I(@(V{r(8(+/<(8( /g^:/f/;/('^.(U(g].(r/;/X{1/h/:/'^w/<(*^1/*/S{^X{<(`/(k(0(Y^Z^((U(^(3_Z^A(:/4_5_%$[#........'.@.N.+ + + + + + ' ", +" + + + + + + + /.S.y ........6_7_8_,(;(9_q]=/t/t/0_a_b_c_d_e_f_]/c(,_p(d(!(g_}_a_,_Q(k(h_i_:(j_T^.(Q(b_h(x/)_Q(X/N/./k_ /m(l_n^V]p^m_I(g^z( /y/P/n_g^*^o_v^p_|/>(1/]/ /|/;/U(v(7]q_((A/e_9/G(r_r/]()_Y^s/Z^K/s_((4/#_T(p(:(B^9/M/,_^(z^t_M(u_N/g^G(^(c(9/f_](M^r/](a(z(!_;/;/./v_,/X/^(w_^(x_g^N/V/V/N/g^U/X/./)_,/./w/M(M(|/f/b_9/4/y_M(f/./>^o^7]{_<(G(x(X/z_A_z(o_B_d(V^B^,/](Q(g^N/C_;(c()(G(>^=/D_Z^q_E_d(U(.(F_d(;/.(Y^!(0(G_G=9.........H a + + + + + + + ", +" + + + + + + 3%H_H ........[@|#$(I_s/s_K/.(>(7^w/.(s/!({(4/,_J_}_r/)_K_L_{(#_((!(d_W^M_g_N_Y^l(F_T^s/](,/j(O_!_P_>^[(N/[(8(6(Q_<(t/j/(/6/#/g({_R_ /G(z(>(a(N/[(M(z(0_N/i(w/N(;/f/;(T^c(V^](V^T^)_c(9/f/V^)((_S_3/Y^T_y_M/;()_c(U_y_f/K_}_^(V_D(K_<(Z/w_K_p(W_0(,/V/B^:/k(](^_o/U(|/(U/./!_{(d_K/,/Z_=/`_E^{^R_:/X/[(=/z(;/ :.:f/t/:x_J_,:F(____,_j_U(w/J/[/z(r([(N^v(^(i(P/[(Q_':):r(>(N/T/&/Y(V_Z_ /./!_B^D([(!:](B_N/(B^)_j(c(K_U(Z^r/U(O_b_l(K_K_F_y^s/J_](~:{:]:j_W^^:y_Z^((T(s/Y^)_^()_A/.(O_/:A/)/,_.(N^.(j()_j_z^|/p(;((:r(>(|/2_^(|/M(G(U(p(9(A/:/v(U(F_K_N^0_!:'(d_z(`_R_{^_:+/K(::Z_I(<:m_[:j(=/T/n^./}:|(::|:w(_/n^6(X/v]](9(2(1:2:3:&/:/#^D(p(4/A/x_3/h_4:c(g^B_r(5:6:R+,.........A b + + + + + + + ", +" ! + + + + + + N.w z ........,.7:8:9:z_X_x_0:P/i(a:b:c:d:U(p_A/K_B_`/d:e:B_f:g:h:j_2:,:>:T(i:W_i:j:p(<_e:k:0:0_l:m:n:o:p:q:r:s:t:u:v:w:x:y:z:A:B:C:D:E:z:y:m:F:G:H:I:J:K:L:M:N:O:P:Q:R:S:Q:T:U:T:V:W:X:X:Y:Z:`: <.<+<@<#<$<%<&<*<=<-<,<'<.<)(W_sP;|>$,x>O;P;x>F;J>l>L/;b>n>Z>x>;>D;MN2'0,y,A,w'},OR)=![,Y>P>9>D;b,[,y,D;3'[,R<9>[,+)Q)[,Y)L>P<#'#'2'd,X>9>2'-,['`,[,u'C'B,3'b,W'b,y,z)D;;,P;[;p>Rb,9>n>@,L>M<-,L>y,n>(>2'g>e>n>`,2'N>/;N>u)B;2'Y,p>@;N>[;9>X)z,n>Z/p:S[,['[;[)[![;[![>[!['[-[-[,[>[~['[;[;[>[>[;[{[,[)[>[)[>['[;[;[-[>[;[-[>[>[;[>[;['[,[*[][*['['[;[^['[)[>[>[>['[*[*[;['[{[>[>[;[>[-[;[>[;[>['[^[>[=[-[/[(['[;[>['[;[{[;[)[*['[-[_[)[:['[)[)['[>[<[[[;[:[:[}[;[;[|[^[1[)[>[{[2[>[<[:[3[>[;[;[<[4[/['[![>['[*[5[;[{[|[;[)[6[;[![7[/[8[9[D{0[I}l[X[{[7[2[]}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}^}/}^}/[-[;[6[9[j^P[I_q}'}_}S<:}K[<}`-6_........` E@+ + + + + + + ", +" + + + + + + m J Z ........[}}}e:=}|}N:M[k[Y<1}2}q[3}1<9:4}s&{[x[;[;[;[;[;['[|[2[/[0}a}b}%+/+p+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+c}p+d}e};[v[f}!(g}h})}i:i}T[j}k}j}l}m}f:n}o}p}q}3>......%}0.k + + + + + + } ", +" + + + + + + + r}Z Y ......(#_.s}o}p[t}z<)}n}1}u}k})}v}v}M[w}B[s<@<'}x}@[{['[>[y}e@;+z}A}B}C}D}E}E}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}F}G}F}H}]};[v[I})/J}=}p:Z:G:X[7[1[+|#@@|#|C}$|E}%|&|*|*|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|-|=|;|>|;[6[,|__'|)|d[7} |6|@@7|8|&|&|*|=|C}9|B}#|0|a|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b|c|C}C}&|C};|>|;[v[I})(d|T:)}e|S}f|g|d[`:<|:|h|.|y|^+A}F}*|C}C}C}C}#|z|A|B|C|D|E|F|G|H|H|I|J|J|H|H|H|F|F|K|K|F|K|F|F|G|G|F|F|G|L|H|L|H|H|I|I|I|J|M|I|H|J|M|I|H|J|J|J|M|M|M|N|N|N|O|O|P|P|P|P|Q|O|P|P|O|P|O|N|O|O|P|P|Q|P|O|P|P|O|M|M|O|N|N|N|M|O|N|N|N|N|O|N|N|N|O|N|N|O|P|N|N|N|N|N|N|O|N|N|N|N|N|M|M|N|N|M|N|M|N|M|M|M|M|N|O|O|O|N|O|N|N|N|N|M|M|N|Q|R|S|T|&|C};|>|;[v[U|U(U[[|Y}V|#1;[;[;['[2[,1'1)1!1C}C}C}C}~1{1]1^1/1(1_1:1<1[1}1|111213141516171819101a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q1r1s1t1s1u1v1w1q1x1y1z1A1w1B1C1D1E1F1G1H1I1J1J1K1L1M1N1J1O1P1Q1R1O1Q1J1S1T1N1H1U1N1V1w1w1W1M1X1M1C1Y1Z1`1R1 2H1X1X1M1.2R1F1.2S1+2+2 2+2D1M1Y1R1+2+2O1E1w1@2M1#2$2%2&2X1B1*2B1$2#2O1G1=2D1O1`1+2V1M1w1-2M1;2>2,2'2&|C};|>|;[6[,|^()2Y| 3*3Z2Z2`2-3&3$3$3;3.3`2X2>3,3W2&3W2*3Y2Y2%3>3*3Y2%3-3#3+3 3Z2Z2%3Z2`2;3W2`2&3+3'3)3'2&|C};|>|;[v[I}z1;[;[;[7[+|>+03$|C}C}a3b3c3d3e3f3g3h3i3j3k3l3m3j3n3o3p3q3r3s3t3u3v3w3x3y3z3A3B3C3D3E3F3G3H3I3J3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3Y3Z3`3l2 4l2.4+4+4m2l2@4V2#4*3n2@4>3=3,3-3*3$4@4X2V2%4&4*4n2@4@4&4=4V2P2-4+4.4m2O2t3;4t3>4;4>4>4.4m2,4-4t3'4'4)4t3!4!4P2 4.4l2+4'4t3;4-4!4%4m2%4.4.4%4-4+4-4P2,4.4&4'4O2+4+4+4O2%4,4@4%4m2~4{4]4'2&|C};|>|;[v[,|^4/433(48<'|;<<|U}22Z}_4'|:4<4[4}4|414R+I ........X%/)+ + + + + + H= ", +" 4%+ + + + + + %.H ........[@243444546474849404a4a4b4c4d4d4e4f4g4h432i4j4>.|;[;[;[>|l4m4%|=|C}C}n4o4p4q4r4s4t4u4v4w4x4x4y4y4z4n3A4B4C4D4+4!4E4F4G4G3H4I4J4K4L4M4N4O4P4Q4R4S4T4U4V4W4X4Y4S3q3D4s3S3Z4`4 5s3s3t3 4P2'4+4-4%4O2,4.5+5=4=4S2=4@5=3,3#4@5.5X2>3n2@4,4*4'4*4S2$4&4+5@5!4 4P2'4l2!4!4.4)4#5;4!4-4#5-4'4-4&4&4;4>4'4.4 4>4%4.4l2%4l2'4+4-4.4-4%4%4+4O2 4.4'4&4P2O2O2l2-4m2%4%4O2&4@4'4-4=4$5{4]4'2&|C};|>|;[w[%5P(&5*5=5{2!|-5c4;5>5,5'5)5!5~5{5~5]5^5x~/5........` ;.+ + + + + + D- ", +" / + + + + + | #.` ......Y (5_5:5<5[5}5|515253545556575859505a5b5c5d5e5f5g5R,e}v[;[;[>|+|h5F}C}C}i5j5k5l5m5n5o5p5q5r5s5u4l3t5u5v5w5x5y5B4z5Z4A5B5C5D5E5F5G5y3H5I5J5K5L5M5H3N5O5P5Q5R5S5T5U5V5S3W5X5S3S3`4Y5Z5#5t3)4@4@4O2P2+4@4S2@4S2m2,4=3.5S2S2#4S2n2#4`5.5`5=4n2=4=4'4'4#4'4.4@4&4!4>4t3>4l2.4 4>4)4-4!4+4.4 4l2.4!4%4O2'4l2m2!4A5&4+5+4%4&4O2P2+4.4+4%4P2!4.4.4!4+4+4P2'4P2%4l2l2P2P2P2@4*4+4P2,4@5 6.6]4'2&|C};|>|;[}[+6@6#6$6%6|5&665*6=6-6;6>6,6'6)645!6~6{6]6^6I ......y P+/6+ + + + + + ", +" + + + + + + -...Y ......(6y _6:6<6[6}6|6162636465666768696062686a6b6c6d6e6f6v[;[;[7[&[g6)1C}C}C}A|h6i6j6k6l6m6m6n6r5v4u4o6p6y4q6r6j3y5s6r3X5A5t6u6v6w6x6y6z6A6B3B6C6D6E6F6G6H6I6J6N3K6L6M6N6q3O6k2V3A5B5l2+4t6!4P2+4-4@5-4P2%3V2S2&4O2'4+5.5@5=4%4@4#4.5n2#4n2'4+4m2,4@4S2=4m2,4S2P2'4 4>4%4+4.4;4t3m2'4;4>4;4l2%4%4)4;4,4-4!4%4.4,4@5*4-4m2m2m2.4%4-4l2m2%4 4>4!4%4-4,4*4,4P2.4-4&4,4P2+5@4.4&4$4m2*4P6Q6R6'2&|C};|>|;[S6T6U6V6W6X6Y6Z6`6d6 7.7+7@7#7$7a6%7&716Z6*7=7-7........y ;7+ + + + + + m' ", +" o + + + + + + 0 S.........>7,7'7)7!7~7{7]7^7/7(7^7_7:7<7(7[7}7(7|71727374757S6;[;[1[6777F}C}C}879707a7b7c7m6d7e7p5f7s5g7s5w4o6p6u5z4z4y5h7i7S3#5+4j7k7E5D6l7m7n7o7p7q7r7s7t7u7v7w7x7y7V4z7A7B7C7q3V3B5B5D7E7>4O2n2,4-4O2*4&4*4#4n2P2m2,4n2`5#4n2,4*4$4+5n2n2*4m2*4l2t3O2O2%4P2'4'4+4&4&4m2m2O2.4t3t3 4*4 4t3A5B5t3 4!4;4.4+4A5!4=4n2'4,4&4%4l2;4O2-4%4'4.4+4+4!4!4 4-4,4=4*4O2%4%4-4m2'4&4,4%4O2$4&4P2,4F7.6R6'2&|C};|>|;[S6G7H7I7^7J7K7_737L7M7N7:7O7P7Q7R7{7&7{7S7T7U7V7........Z u + + + + + + : ", +" + + + + + + + >.Z ......I W7X7Y7[7[7Z7`7O7]7 8.8Z7:617+837}717@8#8/7$8Z6%8&8;[;[^[>[*8%|C}C}87=8-8;8>8n6l6,8l6e7'8n6v4s5s5)8!8y4k3~8j3~8{8]8Z4)4)4^8/8(8_8G5n7:8<8[8}8O4|8182838k7485868788898D4B5D708a8b8D7l2+4'4@5+4%4+5$4@4,4O2%4 4P2+5$4&4*4&4#4$4#4'4O2O2m2O2%4t3>4m2!4%4.4O2t3%4!4,4'4>4>4%4>4t3+4;4>4>4.4)4l2!4>4t3!4.4m2=4%4t3,4%4l2!4 4+4!4P2m2+4.4l2%4!4-4.4m2S2'4+4-4'4m2,4P2+4*4*4P2&4@4P2@4$5c8d8'2&|C};|>|;[&8e8f8g8h8Q7i8<7j8&7<7)7K7K7~7k8^7R7Y7l8m8n8o8p8q8......` P+&.+ + + + + + ", +" + + + + + + r8x '.......s8t8u8]7v8w8j8x8y8z817K7A8v8B8C8D8<7~7E8+8}7.8F88[~[;[;[G8C+D}72C}C}H8I8)8J8K8f7m6L8M8l6n6m6'8n6s5s5)8)8k3z4q6j3N8O8P8Q8B5B5R8P4y6S8T8H4z6U8V8F3W82828X8Y8Z8`888 9.988Q8D7P2!4A5>4m2 4;4+4'4!4*4.5*4S2S2*4>4.4=4,4O2O2,4$4*4&4,4%4O2m2-4m2@4%4>4-4%4*4*4%4'4P2O2*4,4+4.4&4>4)4-4A5t3A5`4B5m2S2O2l2O2+4-4+4#5;4m2;4+4%4O2%4l2%4-4m2%4%4P2-4 4.4l2 4.4m2,4,4@4P2l2O2@4%4O2,4&4&4`5+9@9]4'2&|C};|>|;[S6#9$917^7g8+8M7:6)7:7%9&9*9~717(7Y7&9^7=9-9;9>9,9......Y S.'9+ + + + + + ", +" + + + + + + k|z ........s8)9~7`7!9~9Z7Q7l8{9Y7]9^9v8@8@8^7 8/9(9&7S7_9>&:9;[-[>|<9[9}9C}|91929)839w449q559,8L8'859m6m6p5r5r5u4)8o6k3u569A4i779S389.49909a9C3A6B6L4b9c9d9e9f9g9w7T5h9i9W5P8D4s3 4j9k9l9!4,4'4@4-4!4,4P2+5,4 4@5V2P2l2%4'4'4&4=4@4@5+4-4m2P2'4+4+4,4@4.4t3!4.4P2&4S2P2%4+4%4*4m2&4'4t6>4)4!4.4)4%4>4%4.4>4.4m2 4>4-4!4 4.4%4;4s3@4'4l2-4.4O2-4'4'4!4%4-4;4!4O2O2*4S2O2.4O2P2m2P2+4m2$4$4n2 6c8]4'2&|C};|>|;[&8m9n9$7o9^7k8k8[6|7+8 8v8v8~7)7x8l8g8{7:6p9q9r9d$I ......D f + + + + + + + ", +" + + + + + + 2 ${D ......,.0.s9t9u9O7v9w9v9:7x9K7.8y9(7y8z9{766i8h8A9$7B9C9;[;[4[67g6F}C}i5D9E9F9G9k3w4u4s5r5c7e7H9I9,8e7p5f7v4w4K8k3k369q6v5y5p3q3)4J9K9L9M9N9O9P9J5Q9w2Q4f9R9w7S9T9U9V9A7W9V5l2!4)4D7o2.4!4@4'4>4n2=4@4S2.4l2$4.5m2t3m2O2n2`5@4@5*4%4%4O2%4O2,4-4%4%4>4!4>4)4.4*4&4%4 4 4P2$4,4m2>4t3)4#5>4 4!4@4>4.4%4!4O2.4B5%4O2&4+4l2.4)4m2+4.4'4O2@4O2 4l2)4l2.4A5;4P2P2$4S2%4+4n2P2%4P2'4P2S2,4m2,4X9Y9]4'2&|C};|>|;[S6m9Z9`9 0.0{7|727`7J7I7L7_7_7{7+0[6`9:7O7M7A9@0#08.......j. 1&.+ + + + + + ", +" + + + + + + M+3>` ......[@.^$0%0&0*0=0-0;0>0,0'0)0!0~0{0]0^0/0(0)0_0:0;[~[;['[<0[0}0=||9|01020304050z4p6p6k3g7e7L8,8l6d7'8'8q5r5g7!8m3q6l369o3p3k2#5X348607080A3A3p790_800N5a0b0c0K678U3`4t6l2d0s3e0.4f0m2>4%4%4-4@5*4%4*4=3#4+5`5P2 4*4=4=4+5+4*4,4m2+4!4-4S2=4+4+4+4+4%4l2t6.4+5m2%4%4$4@5*4P2'4 4 4!4;4#5l2-4A5;4.4O2+4.4!4-4P2%4.4!4.4m2>4%4.4l2&4%4O2O2O2l2;4t3>4)4>4O2n2.5P2.4n2@4%4-4%4P2n2,4O2&4@5g0Y9]4'2&|C};|>|;[.|X[h0i0j0k0l0m0n0)0_0o0p0q0{0r0s0)0_0^0t0~0u0v0w0f.......Y D ~.+ + + + + + ", +" + + + + + + h*A Y ......f.L)x0y0z0A0B0C0D0E0F0E0G0H0I0J0K0L0M0N0O0P0x=![;[;[G88@C}039|Q0R0S0T0U0V0{8v5n3z4m3K8r5c7'8W0e7c759q5o5s549!8w4y4n3X0Y0Z0)4!4`018 a.a+aC3L4Q900Q4@aH6#a$aK6y7%at6B5 4&a*a 4+4n2%4!4'4&4O2$4+4.4`5X2n2$4=4,4O2P2P2@5P2%4P2m2!4.4.4+4&4=4%4.4m2+4-4l2A5!4.5&4,4&4*4@4,4S2@4'4!4P2%4;4t3;4!4>4.4l2t3%4-4O2'4 4.4;4%4-4>4.4!4m2P2%4+4+4O2O2+4!4+4 4+4'4P2P2*4m2+4-4.4m2O2+4m2O2'4*4=4X9=ad8'2&|C};|>|;[w[I}p|-a;a>a,aB0'aN0)aA0!a~a{a]a!a^a/a(a_a:a[k@%|C}C}kalamanaj2oapaqaX069l3t5p6g7f759n6f7'8c7v4r559q5)8x4t5r6pai7Z4b8ra18satauavawaxa|8yazaAaBaCaU4C7s3t6q3DaEaP2*4,4O2l2!4=4m2-4n2@4P2,3=3&4n2*4*4O2m2m2S2S2O2-4>4l2m2-4+4n2,4P2.4O2*4,4O2%4+4@4+4@4,4l2O2*4O2O2+4m2-4t3t3!4%4*4.4%4O2-4P2%4-4!4%4 4 4!4t3)4m2-4'4'4O2'4%4l2t3t3!4!4!4'4;4)4%4P2.4!4>4l2P2O2l2!4+4P2@4@4FaGa]4'2&|C};|>|;[>1y[42Ha6aIaJaKaLaMaNaOaPaQaRaSaTaUaVaVaWaXaLa2aYad$........Z Zac.+ + + + + [ ", +" [ + + + + + U `a` ......Y [& b.b+b@b#b$b%b&b*b=b-b;b>b,b'b)b!b~b{b]bc;w[;[;[{[^b&|C}C}/b(b_b:b4-4P2!4'4%4.4.4.4t3>4>4.4l2 4 4;4!4>4>4+4!4t3.4!4l2m2&4'4@4dbeb]4'2&|C};|>|;[fbgbhbibjbkb~blb,bmbnbobpbqb,brbsbmbtb{bobsbubvb]7wbI ......g*H q + + + + + s% ", +" &(+ + + + + =$&-........xbybzbAbBbCb_7[7<7J7y9DbEb$846$8`9i846K7FbGbHbv[;[,[-[m4&|C}=|IbJbKbLbY0MbC4k2Z0O8Nboay5x4q6u5s5n6p5f7l6W0H9'8s5!8)8)8l3paD4A5S3X438ObPbQbRbSbTbUbVbWbXbYbZb`bZ0z5`4)4*a'4O2A5m2=3=3X2@4=4O2,3=3n2'4t6m2n2.4O2@4m2%4%4+4t6#5B5#5>4l2P2*4'4+4P2%4%4P2@4&4P2l2$4!4+4&4'4P2-4m2n2'4P2'4.4+4>4#5l2O2m2*4&4m2m2t3#5#5 4%4+4l2>4%4>4l2.4B5%4*4%4t3.4 4B5%4.4s3 4B5l2O2+4t3t3!4-4&4'4-4,4O2 c.cd8'2&|C};|>|;[+cT6@cu9:7(7|7&7~7`9h8{7:7y8N7#cx8)7<7$c$7 8 8{7]9%c&c........S+*c+ + + + + + .1 ", +" .1+ + + + + + 0 A ........,9=c-c;c>ck8 8+8^7x8,c'c(9j8)c!c,ci8<7~cJ7{cv[;[;[|[b}*|72C}Q0]c^c/cs6k2D4z5j2r3D4O8NbX0r6v5k3u5s5f7L8(cL8e7c7o5)8g7o6l3[bi7D4;4_c7b:c4'4'4.4;4!4.4m2 4A5A5`4)4!4!4t6s3 4O2l2;4 4t3;4!4.4'4'4-4+48c9c0c'2&|C};|>|;[&8gbacK7&7bccc-9k8!9-9!9!9dcw8ec)cfcn8gcQ7hc!9Y7icjc6_........Z kc3%+ + + + + 3 ", +" 3 + + + + + 3%g Z ........lcmcncocx8}7~7ec&9h8pcqcgc^9(9qc)7+8)crcsctcS6;[;[:[ucvcC}|9wcxcyczcMbW5W5MbW5Z4S3C4k2Y0qa50n3v5!8'8I9L8H9e759'8c7u4l3u5x4oa#5cbAc7bp7BcQbCcDc|cEc7bk7FcGcQ3HcA5E7*a!4`4.4@5$4@5&4P2,4>3n2n2`5@4O2O2.4l2&4.4>4A5S3P8P8`4)4 4.4l2 4t3#4+5O2P2+4m2%4O2,4,4.4-4@4l2.4.4%4,4O2&4'4+4'4,4-4t3%4.4$4n2P2+4!4t3>4.4+4>4A5.4@4-4!4m2.4t3)4`4!4+4;4`4s3#5B5B5#5A5B5#5W9t6 4 4.4)4t6t3!4t3t3l2IcJcKc'2&|C};|>|;[&8gbLc<7>cMcNcOcPc-9n8PcQc!9)7#8]7hc`7`7x8Rcdc^7-bScTc8....... .x ].+ + + + + e$ ", +" Uc+ + + + + Vcx g*......f.wbWcXcqc(9Yc&7z8 8+8Zc 8 8`c d.dfc+d%937@dw[~[;[2[#d$dD}C}%d&d*d=d]8qa79O8z5C4q3q3q3q3s6O8B4h7z4u4K8'8,8W0e7H9l6c749!8!8t5h7k2D4P8Xb-dua;d a>d,d'd4cO3)d!dB5A5!4.4~d,4l2m2`5$4-4%4*4S2.5'4P2,4*4&4O2A5Q8)4Q8S3`4Z4;4 4;4>4-4%4.4-4O2@4P2+4@4n2O2P2,4%4A5>4+4%4t3;4-4&4@4.4-4P2%4m2m2l2'4P2 4+4t3%4&4-4m2A5>4D4%4!4t3.4t3A5B5A5t3)4S3>4A5P8t6;4W9t6Q8A5>4Z4S3D4Z4`4;4t3t3l2B5`4s3Q8t6{d]d^d'2&|C};|>|;[&8/d(dh8_dbc>cNchc:d|6d7dC}|98d9d0dad5050NbMbj2O8Z4P8D4P8r3P8W5]8~8o6w4K8p5m6e7H9p5c7o5u4q6x5w5qaW9O6yabdcdQbdd>dedfd4cU4gdZ4B5hdY3Q8;4'4O2S2S2,4n2n2*4=4@4l2A5)4!4m2t3r3W9+4t6A5l2B5t3-4,4l2!4+4l2!4+5@4>4-4O2m2.4&4@4.4-4%4+4+4+4l2-4O2,4!4!4m2,4*4*4P2V2&4l2!4+4+4;4-4!4+4.4;4+4)4l2 4`4t6A5#5q3B5A5S3P8W9#5B5Z4q3`4t6X5W9t6Q8Z4q3k2D4)4t3t6t6Q8D4q3id=a^d'2&|C};|>|;[x[U|jdv8{9Oc{9rc>cnckdq9n8^9v8l8&7ldmdndod#8)7z8)7pdqdrd........Z a-+ + + + + + sd ", +" td+ + + + + + V ` ........udvd.dwdqbxd%bydzdAdBdCdXcDd#brbkbzd*b}7EdFdw[;[GdHdA}IdC}JdKdLdMdNdw5r6h7p379O8P8s6S3P8W9D4z5]8X0[bu5p6v4'8W0d7e7d7f7)8p6u5z4Y0Q8O6ya-dOdQbx6PdQdRdBaSds3q3A5E7t3B5t3O2,4,4=4S2>3>3'4P2-4+4#5X5S3l2t3A5W9>4 4O2)4#5 4%4m2>4;4>4P2m2n2S2-4%4'4$4*4l2m2!4%4m2l2&4=4m2l2%4O2!4 4%4'4@4m2&4&4P2O2#5;4!4 4!4#5O2 4t3+4>4 4#5Q8A5A5q3Z4S3D4S3W9Z4S3k2X5s3q3W5r3t6`4Q8D4D4r3S3D4P8Q8t6B5t6S3Td@90c'2&|C};|>|;[S6UdVdpdWd-b%bXdYdZdub_d`d e)c.eqb+e@efc-b!b#e$eC8%e&e^60....... .*eG[+ + + + + B> ", +" 4%+ + + + + | e .......I =e-e;e>e,e'e)e!e~e{e8aXa]e^e/eUa(e_e:e4 4t3>4)4 4!4%4@5s3;4O2,4>4>4+4%4+4!4l2&4@4m2O2O2m2,4,4S2@5&4'4%4+4&4,4+4,4`5.4s3;4!4 4+4-4+4'4.4l2!4!4O2l2;4A5)4.4>4l2t6Q8s3t6-4>4S3W9s3S3P8r3P8W9D4C4S3Z4W9Z4W5D4X5P8W9S3k2s3z5r3W9D4X5Z4S3#5#5Ic0e^d'2&|C};|>|;[aebe|2cedeeefegehe]eiejekele_emeneoeJaaapePaqereseteueve[&......Y +.*.+ + + + + 3 ", +" |&+ + + + + *.wey ......d$xeyezeAeBeCeDeEeFeGeCeHeIeJeKeLeMeNeOePeQex[;['['[Re7dC}SeTeUeVez4w550r6y5paN8o33eMbz5z5s6O8r3z5Mbp3Nbx5q6w4c7d7m6WeXeYeZe`e`e f.f+f@f#f$f%f&f*f=fna-f;f>f,f'f)f!f~f8c!f{f+3+3$5F7]fIc^f/f(f_f:f:f|;[aeqfrfsftfufvfwfxfyfzfCeAfBfCfDfEfAeufFfGfHfvfIfIfJfKfLflc......Y ..I)+ + + + + + ", +" G/+ + + + + &.9.Y ......^6MfNfOfPfQfRfSfTfUfVfWfXfYfZfPf`f g.g+g@g#g~[;[Gd$g#|8|C}%g&g*g=g~8v5[b~8{8n3v5x5qaB4X0h7h7O8MbW5W5Z0p3Nbr6t5u5w4-g;g>g,g'g)g!g~g{g]g^g/g(g_g:g|;[aeSgTg gUgVgWgXgYgZg`g h.h+hVg@h#hYg$hSf%h&h*h`fPf=h-h;h>h......Y ..B#+ + + + + + ", +" + + + + + + /...Y ......,h'h)hJ7]9*9[6~7.8!h~7~h*9y817)h{h:6Z7A9]h&8;[;[<0^hF}C}C}H8/h(hA4r6v5[bw5~8m3[bv5[b[bj3y5h7Z0]8O8Z0z5s6p37979A4_h:h|;[&8#9ChDhl8Eh&9)7$8Fh1727~h|7g8g8.8k8A9 8y8&7Z7*9(7J7j8Gh&cI ...... .k|+ + + + + + Hh ", +" Ih+ + + + + + 0 y ......I JhKhLh^9k8YcY7%9k8Ncpc d{7Y7OcMhNhNc>ck8Ohv[;['[,[)+IdC}Q0PhQhRho3r650j3n3q6~8~8k3l3x4z4r6X050X03ei7Y0]8Z0W5Z0ShThUhVhJdJd{1{192WhXhIbD9YhYhYhWhD9XhIbIbZh`h i i i i i.i.i.i+i.i i i i i i i i i i i i i i i i.i i i i i i i i i.i i i i i i i i i i i i i i i i i i i`h`h`h`h i`hZhZh`hZhZhZhZhZhIb%g@i#i$i%iA5Z4W9s3W9q3X5Z4P8MbC4D4`4C4j2s6MbW5z5q3q3r3Z0Nbi7C4q3k2Mbs6W5W579k2k2z5S3&i*i^d'2&|C};|>|;[=iT6-im8Oc!9bcx8j8!9kdbcQ7Wd46h8)c_7~7icld~9bcQ7+8gc;i>i<#,i......Z f + + + + + + 'i ", +" td+ + + + + + W '.......(6F@)i!i!9bcocy8x8{9x8`7ic^9~i~irc+dbcrc{i]iv[;[/[&[^i*|C}D9/i(i_iqa{8oa[b50~8z4q6m3l3t5j3v5v5n3w5X0{8paZ0Q8s6:i|;[&8U|6iv8m8Ocbcx8bc7i8ihc+dYcj8fcpchcK7`7oc9iZ70igcRcaibicis8......Z Za1 + + + + + di ", +" B>+ + + + + + H_Z ......,ieifi~cv8LhYcZ7giDdz8hip9_d;cRcockdYcbciiji;[;[e}kiC}=|C}liminiP8MbO8h7Y0Y0w5v5~8x4k3r6x4y4l3w5w5q6n33eB4p3MboipiVhT|9|*|7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d&|C}C}C}C}C}qirisitiuiviabk2D4Z4P8z5q3W9W9X5i7Z0k2s6]8MbMbO8p3s6i7D4P8r3i7i7s6C4k2X5C4k2C4z5C4q3P8q3D4wixiyi'2&|C};|>|;[=iziAix9BibcA8!9 efc]7pcNh)cz8$eicx9j8hc~cZ7&7pdhcaiCiDiEiFi......` w p + + + + + & ", +" A]+ + + + + m GiZ ......HiIiJiKi:6ic#8hcrcMhDdhcfcw8Lincp9~i>c:6MiNi;[;[/[l@*|C}Oi10PiQiW9k2k2C4X5O8o350A4paN8x5z4m3j3n369v5j3z4z4{8h2RiSiTiC}*|C}Uik@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@k@q+Vi=|C}C}C}WiXiYiZi`i>4X5z5S3S3k2C4MbD4S3P8W5k2k2k2z5h7k2k2j2s6r3C4z5X5)4W5j2k2k2W5D4s3Z4k2Mbr3k2W9q3 j.j+j@jT|&|C};|>|;[fb#j$jn8!9Mc%j!9fc&j#eYc^7&j%9v8j8&7*j~c,cKii8z8p9=jLh-j;j>j......'.x O.+ + + + + + ", +" ( + + + + + ,jP+'.......Fi'j)j!j]9oc!9p9!9~917i8(7A8;i{9kdz9l8&9~j~[;[>[>[l+*|C}{j87]j^j#5)4>4s3W9P8r3r3X5Z0Nb3eo3pax5q6A4{8v5p6m3v5/j(j{1C}C}D}&+_jG8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G8G87[G8:j|;[fb7j8j`7#c9jv8@8)7v8S7Y7]7rcNcw8 8v8k8@8 8j8~7&7QcOc0jajbj[&......Y Y cj+ + + + + + ", +" + + + + + + /.I .......}3djejfjgjhjijjj*0kjljmjnjojpjqjmjqjrjsjc;v[;[>[>[77&|C}Q0tjujvj+4&4+4>4;4B5;4!4!4k2Z0X5W5O8h7i7oaA4v5t5u5wjf7xj82C}*|Ti0}e}>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[:[7[yj72!1C}T|[9.izjAjBjB5X5W9;4W9X5Z4Q8Q8W9k2z5z5X5Mbj2C4W5P8MbC4r3z5j2k2X5P8`4Q8D4)4t6X5k2S3Q8X5Q8S3s3CjDjEjFjJd|9C}Gj=|a}>|;[HjziIjJjKjpjLj_0MjrjNjOjPjQjkjjjRjOjSjTj_0UjVj]0WjXjYjZj`j0.,.....Y Z k+ + + + + + ", +" + + + + + + O y Y ....I W7.k+k@k#k$k%k&k*k=k#k-k;k>k,k'k)k!k~k'k{kae;[,[&[0|&|C}92]k^k/kS2n2,4O2l2m2P2+4!4A5t3;4Q8B5;4`4O8Y0B4A4y5(k_k:k[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[-[;[;[;[;[;[;[;[;[;[;[7[;[}k%|$|C}|k1k2k3k4k5kD4k2X5`4S3W9t6D4C4k2q3S3k2C4Q8r3W5r3MbMbD4Q8Mbk2q3P8r3r3X5D4#5>4s3t6q3X5Z4P8P86k7k8k9k0kaki5C}%|72bk^[>[;[ck |dkekfkgkhkikjkkklkmknkokpkqkrksktkukvkwkxkykzkAk)kBkCkS.8.......D c + + + + + + ", +" + + + + + + a z Y ....,.DkEkFkGk~kHkIkJkKkLk)kteAkMkNk'kOkPkQkRkSkw[;['[Tk'2*|C}IbUkVkWkS2n2,4#4.5V2&4!4P2O2O2%4m2%4.4t3`4S3#5P8Y0XkYkZk`kC}-|h+/[;[;[-[+c+cS6fb+c&8+c l l l&8+cw[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[-[.l:9+l@l>[;[;[;[;[;[;[2[+|#l$|*|C}$l%l&l*l=l-lr3W9s3Q8Z4W9;4B5`4X5r3W5q3Z4k2Z4S3C4Z0Mbz5k2P8X5S3q3W5q3t6W9Z4S3W9Z4q3S3Q8s3Q8;l>l,l'lUhSeC}C}E}Ti)l|[,[;[;[!l~l{l]l^lJk/lLk)kPk/luk(l_l:l[DlElvlFlGlw[;[;[;[;[]}kiHl!1C}C}|9IlJlKl`i)4Z4C4S3S3X5B5A5B5`4s3s3W9k2D4W9q3Z4Z4r3D4C4j2k2A5t6P8D4Z4P8S3A5t6S3t6W9q3`4)4LlMlNlOlPl19C}C}C}F}Qlm,m~iOcl8icccoc:d'mOc~i]9)mNc!m;[w[;[7[B+72=|C}~m{m]m^m&4n2+5O2'4O2@4&4l2@4$4+5.4Q8 4+4 4#5>4'4!4W3/m(m$d*|_m#g{[;[@l:mbc$cY7:6{h=9ccj8|717Ncldt{+l;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[w[[v[;[;[,[>||m1mvcC}C}|92m3m4m5m#5Z4t6s3P8C4s3#5)4t6S3s3`4X5Z4B5W9s3Q8Z4`4Z4MbC4#5`4Z4Z4q3S3P8S3#5q3s3S3;4#5s3#5a86m7m8mJd|9C}72F}9m&[0m;[;[;[w[ambmQ7<7&9OcA8q9n8[dy8]7Y7&7 d^9^9#8Wd~7WdOc9i!9~iococAbA8cm*mdm......z emfm+ + + + + 0- ", +" + + + + + | L E ......8.gmhmimubndjmq9hiecp9ndndbcxdimnd:d+d ekm,[;[;[7[lm$|=|C}8dmmnmP2@4+5,4 4%4,4+4.4m2P2t3-4>4`4&4+4W9t6!4Q8S3ompmqm$d&|rm+|{[;[=ism;cdc etm_dPcicicDdgi%j%jumfb;[;[;['[/['['['['['['['['['['['['['['['['['['['['['['['['['['['['['['['['[/[>[;[;[w[vmwmxmymfb;[;[;[/[1[i@{jE}C}T|zmAmBmCmDm#5`4t3A5`4t6`4k2Z4t6t6q3s3#5B5W9#5W9X5q3Q8Q8P8P8S3S3q3W9q3Z4k2X5q3z5s3W9s3A5W9`4EmFmGmHmImi5C}&|Gj&+5|2[;[;[;[x[#9Jmq9p9KmBi eAdCiccMhic%bNhz8Cd*j&jLm.d~c>cNhMh%jMmNm4)4-4%4`4l2'4O2#5S3)4B5`4A5P8D4Q8k2P8B5`m nql$d&|m4&[{[;[=i.n;c%j>cMc`d+dtmictm-9SmMh+n=i;[>[)[@n;||m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m|m;|#n'[;[w[$n%n&nx[;[;[;[7[-[>@&|$|C}|k*n=n-n;n>nA5S3)4>4t6A5)4s3q3W5r3W9Q8t6)4A5A5t6q3X5D4D4D4s3q3k2D4Z4Z4C4r3S3`4A5X5#5)4#5`4b8,nt7'n)n!nC}C}$|C}-+{[|[-[;[v[~n{n]nA8`dndPcXd^nKmtm*j eic*jYc~b)c.dLm/nXdAd%bgcMh(n_n:ncBiSm;j[@......` 8.M++ + + + + + ", +" ) + + + + + ].@.z ......8.[nldc`dZdndndib3nCi4n.l;[;[/[m4E}C}5n6n7n8n!4S3q3!4-4)4A5 4A5)4Z4q3>4B5Z4P8q3C4s6MbX5r39n0nql$d&|m4&[{[;[=i.nLhnd%jPcPc e)c*jobMhBitm.n+c;[e}&[an%|C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}*|$|bncn;[;[;[jiv[;[;[;[2[+|k@Gj*|C}|92mdnenfngn-4A5Q8X5Z4t6A5t6`4Q8;4k2A5;4>4#5#5t3A5W9W9X5D4#5s3D4D4Z4`4t6Q8k2X5Z4t3t3A5A5Da-lhninjnkaSeT|C}E}kn^hln/[;[;[v[&8mnnnx8;cMh>cYc!i3n@bPc`don^9cc#e.eWdec$egi~c,cpd:nnd~iKmq9pnLiqnCiU78.......'...6.+ + + + + + ", +" + + + + + + l J Z ......[@w0rnsnCi^nndZdLhLiNh%b>cndWm}dCiimndtnunvn;[,[*[wn%|C}.imixnynS3Q8-4 4 4t3>4S3Z4`4X5B5l2s3X5k2P8r3D4Z4s3A5-lzn(m$d&|m4&[{[;[fb.nCiim`d@bAntm!b*j~bDdecndBnfb;[e}&[Cnvc=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|=|&|%|bncn;[;[;[;[;[;[,[]}DnViEn=|C}|9}iFnGnHnm2)4.4l2B5B5#5t6s3!4#5#5P2&4l2;4t3B5#5>4)4S3Q8s3s3`4P8r3P8#5 4 4#5`4q3`4W9A5s34i3iInJnKnWhC}C}=|7dLnMnG8>[;[;[v[u[~jBi e%btm~c&jgi efc%b-9Km:dLhhi;bgi!c*j]bpchckbgcKm_dPcWmkdimSmimNnOn[@...... .S.z]+ + + + + + ", +" + + + + + + [.8.` ......[@PnjmQnRnLinc3nAnYd~cAn(n%jndSnVmTnpn{9Un=i;[{[6|Ql$|C}AmVnWnXns3t3>4>4%4!4;4Q8`4 4)4;4Z4;4`4X5D4Q8B5s3;4,4YnZn`n$d&|m4&[{[;[fb oonLiBifcAnBi~cCdpd+8Sm,m.o&8;[e}&[an%|C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}+oC}C}C}*|$|bncn;[;[;[;[;['[G8c@B}F}C}C}$l%g@o#o$o%o!4#5t6t3!4;4 4;4t6A5;4!4+4Y2X2+4l2.4 4!4B5`4t3>4t6`4P8D4r3D4>4.4t3S3A5W9W9V5A5&o*o=o-oJdi5C}72F};o+|>|;[;[;[v[][>ox8LhYc$e*j~cpnNh&9 e]7$e]bBiAn,onbim#ez8;b dMhKmec'oDdKmpnMh;iPcMcib)of.......Y z !o+ + + + + + ", +" + + + + + + k I ` ......d$~o{o]o^o/o(o_o:o4-4m2codo(m$d&|m4&[{[;[=ieofogohoiojokolomonooopohoeo=i;[e}&[an%|C}C}+o%dqo!n!nro!n!n!n!n!n!nqososoqoqoYmYmYmYmYmYmtotouoakT|C}*|$|bncn;[;[;[;[/[/[vo=|E}9|9|82Fjwoxodfyo%4-4#5)4.4)4)4!4!4l2>4)4Q8W9t3'4l2l2+4 4)4!4l2!4;4s3W9W9t6B5P8S3.4.4q3S3t6zo*aHnp,p'p)p8o;[|[!p#|$|C}8d~pm2{pl2O2,4O2-4+4&4>4!4&4+4%4'4#5)4)4;4A5;4'4l2 4]p^p`n$d&|m4&[{[;[8o/p(p_p:px[;[e}&[an%|C}Jd4p5p6p7p8p9p0papbpbpcpdpepfpgphpipjpkplpkpmpnpopppqprpspc|*|$|bncn;[;[;[|[)[@+*|-|9||9tp=nupvpwp>4 4 4)4l2.4l2 4t6Q8s3A5;4 4t6s3#5`4#5l2>4>4 4.4l2;4A5t3t6S3t6q3W9t6>4)4S3X5A708xpypzp8dJd|9C}$|Ap@@'[|[;[;[v[u[BpCpDpEpFpGpHp|pIpJpKpLpMp3pNpOpPpQpNpRpSpTpUp[pVpOpHpWp3pXpJp(pYpZp`p.p(pDp qf.........G j + + + + + + ", +" + + + + + + P y .......[@.q+q@q1o#q$q%q&q[oRo*q=q-q;qq,q'q)q8o;[7[|mB}&|C}!q~qwp{q$4@4S2,4+4S2+5O2l2'4&4!4+4;4`4S3;4 4>4>4)4#5]q^q`n$d&|m4&[{[;[Hj/q,q(q_q[>|hqiq!1*|C}|9jqkqlqmqnqoqt3#5#5,4*4!4 4t3#5Q8W9W9A5t6;4s3A5s3W9>4>4>4;4B5)4 4t3t6t3A5W9S3Q8X5s3B5S3r3Q8hnpqE9qq87|9=|F}rqsq:['[;[;[6[S6t*tquqRojololovqwqooxqyqVozqfoAqrg6E}=|C},r2m'r)r!r.4%4%4B5A5 4-4%4P2-4;4t3X5S3B5#5 4 4`4;4t6Q8t3 4t6s3;4!4 4;4)4!4>4t6t6Q8W9)4t3k2X3~r{rtoWhC}C}72]r^r/r>|>[;[;[~[x[(rpd-bzd_rvbqn:r%b*jXd|-[;[;[u[-[qr#8j8p9~c.eon[dpctmBiDdNhBiydR7WdhcNhAnC8Xcgi;b)c.dxd1n}dBipcDd.eDdLhp9`dtm;conp9rr8.........C Y%+ + + + + + ", +" + + + + + + B#X%y ......8.sr%jxdLi^n}dNmtr3nncWmUmRmpnWmnc}dRcur$n@l;[2[)lC}*|C}Wqvr2fwr,4n2V2n2%4m2S2&4%4-4-4!4%4m2 4s3#5s3s3s3t3'4Ml`q(m$d&|m4&[{[;[x[xr`dPcyrzrVmSmSm:rCi^n%becxr&8;[e}&[an%|C}wcArBrCrz4x4!8p6!84949q5r5r5s5g749!8!8y4m3x4x5{8w5y5r3DrEr7|*|$|^hFrz+C}vc9|C}GrHrIrJrid 4s3P8D4q3`4s3W9Q8Z4#5#5t3A5A5s3P8Z4t6D4q3s3s3s3)4#5t3)4`4A5A5#5Q8A5.4#5s3t3B5s35mLbKrBoWh|9C}*|$|Lr-[G8-[;[;[u[MrNr]7Cd e~cgcYc~cx8qb)bhiWd$eKikb dWdkb!bpdOr~cMckb.eMc;cMc`d_d*jrbqbYc{9ic#e>cTmononPr,.........C k|+ + + + + + ", +" + + + + + + C@_.Y ......8.Qrnd(nMcnd%j}d(nTm[dVmRrSrSqonimVm%jTrUr@l;[2[VrC}*|C}WrXrYrZr&4'4@4&4@4P2-4,4'4l2.4P2&4A5t6t6D4s3)4 4)4%4`rdo s$d&|m4&[{[;[fb o.s+s.btnRcMcib(n%jubMhonBnfb;[e}&[an%|C}wc@s#s$sw5n3~8j3l3!8K849v4r5s5r5r549g749!8y4k369q6~8]8%s&s7|*|*|`+j+-|GjC}*s=s2k-s;s>sX5D4C4C4D4D4X5P8q3`4W9S3s3#5B5q3W9q3`4q3q3X5Z4X5S3B5t6t6A5t6B5;4`4`4-4 4S3S3t6)4,s's)ska%d|9C}!1*|z+/[:[;[;[~[:9!s~s%9Wdzdpdpd-b!crb~cKm+dXdgcDdyd'oWd{s~izdKiBijm!i@rububp9Adrb]bDd)b]b%bWd,c ep9Sm@rub]s8.........C k|+ + + + + + ", +" + + + + + + B#X%y ......f.Qr;iWmndndibTnndTmnc%j.rim`dibtr4;4>4.4_s^p`n$d&|m4&[{[;[vn:smyr=9kslsmsw[;[2[#@C}*|C}8rnsospsl2>4*4'4P2*4-4 4'4&4'4 4>4>4!4.4>4#5l2-4P2'4qs^p s$d&|m4&[{[;[rssstsaidctsaj-cec]9Qckd=9w8us&8;[e}&[an%|C}wcvsws9np3Nbo3n3v5z4t5k3o6w4K8u4g7g7s5K849g7u4)8p6!8N8xsys7|C}C}72C}C},rzsAsBsCsDsEsh7h7]8p3i7O8i7i7MbW5X5P8z5k2D4P8W9X5r3D4r3p3O8j2k2X5S3S3Q8Q8`4S3W9>4q3W5P8i9;lFsGsHsIsC}C}C}F}b|8@G8;[;[;[&8>1JsD8KsLs`cMsZc(9Ks#8@8{9w8.0 eod#8 e&j`ci8^7/7 eech8Wdv8Z7ic(7%9`7l8Z7x9%946%9%9&j&9k8rcpcy8Ns,.........C Y%+ + + + + + ", +" + + + + + + z]6+ .......f.OsPsQsRsSs3rTsUsVlVsWsXsVsYsZs`s t.t+ty[>1;[2[)lC}*|C}8r@t chdB5)4A5'4P2O2n2P2+4O2-4-4%4O2'4.4>4.4%4m2%4 4#t/m`n$d&|m4&[{[;[fb$t%t&t*t*t=tvb-tilflRs;t>t,tck;[e}&[an%|C}wc't)t!tZ0qaN8y5[by5r6~869u5w4!8m3l3)849w4o6w4)8o6o6y5~t{t7|C}C}C}C}9|&r]t^t/tY4(tx5{8X03eoaB4B4Nb]8p3j2C4k2MbP8k2j2Z0z5C4z5s6i7r3W5O8C4q3q3S3S3D4q3s3B5P8q3X5Xnmq_t:tb3|9C}C}vc@||-[;[;[v[*[[t}tlb>b|t=b1t2t3t4t5t#m6tblhl7t3t8t9t0tatbtYlctdt@metftgthtit%ectdtjt8t7tctktftgtatltbtftmtctnt,.........F 0 + + + + + + ", +" + + + + + + 7 y .......[@otptqtNkrtstNk&kttut=kvtwtxt5lptytzt73At@l;[2[^h9|&|C}BtCtDtEts3Z4 4 4%4,4S2&4+4l2P2*4!4m2%4-4-4!4>4%4l2m2Zq/m`n$d&|m4&[{[;[FtGtHtItJtNkststrtIkKtLtMtIk6>8o;[e}&[an%|C}TeNterOtoaoapaX050x5y5w5n3k3l3l369w4o6u4u4u5y4u5o6)8[bPt3s7|C}C}C}[9+iQtRtStTt~8j3w5[b[b50X0x5X03ep3Nbp3W5MbY0Mbp3i7]8i7j2j2W5Mbi7Z0s6W5O8C4X5r3r3D4D4P8X5X5UtVtWtXtYt4sC}&|Ztl+;[2[;[;[;[=i`t$1Soja u.u+u@u#unk$u%u&u*u=u-u;umk>u,u'uyq)u$ukk!umk(l~u{u]u^u/u(u_uzk>utk~u$u:uu|u1u2uf.........F V + + + + + + ", +" + + + + + + 6.Y .......[@3uGkGk4u5ust@k5uqt)k$k6u7u8u5l5u9u0uaubucu;[G8ir$d&|C}dueufugu 4Z4A5;4>4P2l2-4>4l2+4%4l2%4-4>4 4s3#5m2m2l2hu/m`n$d&|m4&[{[;[Ftt'iuju4lkuGklumunuJk1lou~kpuck;[e}&[an%|C}19qurusu{8paoaB4o3A4[b{8A4v5~8q6u5t5m3w4)8o6y4u5)8u4A4tu3s7|C}|9uuvuwuxuyu69x4q6z4z4z4j33ex5v5x5paqaoapaY079B4h7]8]8Nbh7]8p3Mbs6]8p3]8p3i7O8W5k2k2z5j2zuC4AuBuCukaSei5C}E}*|j+([:[;[;[v[&8b*/4DuEuFu>uGuHuIu,uHu~u#kJuKuLuwk1lMu:lNuokukOu}uPuqkmk~uQu=uRu^uqk/l;ugkSu;uTukkIu:l'k4l_lukwkUuFuVu,.........G j + + + + + + ", +" + + + + + + q I ` ......f.WuXuYuZui0`u v.vXj+v@v#vKjMj$vlj%vMj&v)q8o;[7[|mB}&|C}*v=v-vgul2S3t6t6#5 4+4O2t3t3O2 4m2+4-4!4A5W9!4A5#5;v>v^q`n$d&|m4&[{[;[fb,vmj=0'v)v!vj0pj~v`lgj{v]v^vvn;[e}&[an%|C}/v(v_v:vw5X0NbB4h7pa[br6A450[bq6~8j369l3m3y4l3p6!8o6N84#5;4IvJv`n$d&|m4&[{[;[&8KvY7~9]9N7)mM7]9:6Oc=9m8q9Lv.l;[e}&[an%|C}Is(vMvAjx4v5y5o3oaX050x5o3{8x5j3j3z4v5n3~8t5k3y4l3u5{8Nv3sOv2mPvQvRvx4!8!8w4y4y4u5k3k3k369~8z4q6q6j3[by5X0{850X050X0paX0oaqah7NbY03epaqa79qaB4]8B4h7SvTvUvVvb3i5C}C}F}wnWvG8;[;[;[8v&8Xv|7J7R7/7)70iWd dYv0iqc&9pcZvKsD8/7L7)7[7o9b6gc{9i8`vJ727 wLsw8h8y8&9Z7!7i8&7{7`7Y7x9w8R7.w+wLs}7Yv[7)7@w,.......Y E #w+ + + + + + ", +" + + + + + + l J Z ......8.$w+0bckdTn;cndjmnc{9Oc>m!9{9Oconkdmd!9%w=i;[e}ki0|$|C}&wUk*w^jm2'4#5#5+4t3+4l2.4n2 4-4)4t3t3#5)4>4W9A5>4!4=w-wqm$d&|m4&[{[;[=ismon{9kd7ixd9ifcbckdMh=9.s;w.l;[e}&[an%|C}>w,w'w)w49w4x4w5y5A4paX0x5503e50{850y5A4v5z4x4j369p6A4Pt3s!w~w{w]w^ww4o6p6o6w4o6o6p6u5u5m3x4696969j3n369y5X0y5x550[by5{8{8qah7X03eX0paoa50pao3qah7/w(w_w:w|;[;[;[v[ji[w`7i8.d.d d/9Kigi]7Cdkb.dx8$eEbod}wE8Ddz8,cqcXcpchcgi'oMs37|wC8qcw8j8`v 8ZcDdonecCd&jgiic,c1wec`v2wWd#8,c3w,....... .y z]+ + + + + + ", +" 0-+ + + + + U 4wz ......f.5wm8:6v8aiCi>mAnw8>cNh~cn8[d^9v8,mock8&n&8;[/[#nh5%|C}ka6w7w8w%4)4l2+4P2@4 4!4-4,4+4#5l2-4.4B5;4m2;4S3Q89w0waw`n$d&|m4&[{[;[x[bwNcYcpndc,m;iYcbconYc!jcwLv.l;[e}&[an%|C}ridwewfwq5g7y4t5~8A4{850A4o33eo3NbB4y5y5r6y5w5y5z4m3pagwK|hwiwjwkww4p6p6o6p6p6)8o6k3p6w4p6y4m3m3x4t5m3m3q6j3v5v5r6y5[by550X0[bA4x5N850A4N8X0{83elwk3mwnwowi5C}03-|pwqwG8;[;[;[S6zirw:6Dd#8Ki.d!7Or^7+8%9kb@8Zcpcx8`v#8)70i&jkbz8.d!cz8hc,c2w|wXc0i0i`v]7kb#8]7gcgi`7odpcWd(9l8Oc{9]7Zc dpcWdXdsw,....... .Y P + + + + + + ", +" 1&+ + + + + m K z ......8.gm96icccdcq9Bioctw~ix8Mc,m[dbrA8Ocgc~9uw+c;['[*[wn%|C}vwwwxwyw=4%4@4.5%4&4,4>4l2l2)4B5m2$4m2t6B5;4j7W9V36kzwAwqm$d&|m4&[{[;[=i;wYcn8kdbc{9>c-9rcock8:6A8Bnfb;[e}&[an%|C}ridwBwCw'8o5K8p669j3w5y5A450pao3B43e50{8X0{8A4y5~8q6qaDwEwFwGwHwy4p6p6p6o6y4o6o6u5l3!8o6p6y4m3p6y4p6u5m3x4~8w5v5~8~8y5A4y5r6[by5{8x5y5r6x5r6IwJwKwLwkaMwC}C}IdC}Nwln|[;[;[~[u[OwPw^7ecpd`c%9%9]7Qw.dpd%9R7.dLm%9]7hc^7`v(9qcWd#8 d^9]9w8J7QwZcRwC8#8qcWd)cec)c)7g8i8%9Wd3dodonv8%jA8 8`c#8z8]7Sw,.......` 3>|.+ + + + + + ", +" + + + + + 1 B@E ......8.TwUw^9braibc e!9Z7giv8RcVwajVwbr{h`7-jWwck;[>[lnz}E}C}XwYwZw`wO2*4+5@5+4%4O2m2.4>4t3-4%4t6s3`4`4#5Q8 xX5s3.x+xql$d&|m4&[{[;[=i.n>cNc.0n8bc{9]9Nck8k8 eY7@x=i;[e}&[an%|C}#x$x%x&x,8'8g7v4w4t5v5r6y5oaqaqaqaX0X0NbY0N8x5r6w5w5{8d7*x=x69m3m3y4u5y4o6y4l369t5y4w4l3u5l3p6K8!8p6u5u5k3y5w569m3n3w5z4z4z4r6x5w5~8r6n3m3-x;x>x,xIl[9C}C}F}#|'x:[,[;[;[~[)xxr!x$8 d^70i~x{x#8}7|7#80i`7 8R7)7 dv8@8}7]xQw`vR7z8WdLi!9|7x8 8RwRw^x/xod!cz8l8h8`vJ7pcqc37]7#8v8p9Y7n8p9`v+8qc}7(x,.......Z +.l + + + + + + ", +" + + + + + + E@D ......,._x:xM7[;[;[v[gxhxo9o9(7{7|7b656&7w8@8^7I7Yv(7O7K756L7Z7(766B8B8I78646ix^737.817|7J7(7c6y9Ls27/7^7[7B8&9 866%7{h 866v9v9|78ix9jxB8kxlx,.......z 4wZ-+ + + + + * ", +" + + + + + + h D ......8.S.mx gYfnxoxpxqxrxsxtxuxvxwxxxyx-hzxAxBx*[-[;[7[l#8|=|C}8dCxDxExl2>3S2+4>4+4 4 4&4O2Q8S3t3#5A5B5cqX5Z4`4W9Fx nql$d&|z}&[{[;[>1GxHxIxJxnxKxLxMxNxOxPxQxo0};S6;[e}&[an%|C}Rx$xSxTxUxH9l6q5s5v4y4y4x4j3{8N8Nbi7h7h7B4h73e3eo3pa{8r6j3j3n3n3z4n3l3u5x4m3m3q6~869x4u5w4p6u5o6w4y4l3x4m3u5u5m3m3l3m3t5t5x4t569n3v5w5~8VxWx8mowi5C}*|EnXxYx>|;[;[;[v[qwZx`x ypx.y+yVf@h@y.yTf#y$y%y&yPf%y*y=y-yPfqxpxZgXg@y;yPf>yVf`g,ySfrx,y'y)y!y~y{y.y*y.y]y]y-y^y/y(yPf_ynxPf:y y1lymyny4yoypyqyrysyvftypk:ld'x[;[e}&[an%|C}#xuyvywyxyUxW0l6n649w4o6m3~8r6X0B4B4NbNbh7oaY0793eo3N8x5w5[bj3j3v5z469z4q6t569x4z4m3o6y4y4p6p6!8w4l3x4t5u5o6x4p6p6m3x4t5m3k3m3x469z4yyfwzy-oAyC}C}03By)+,[2[;[;[;[w[Cyk:DyEy(eFyGyPeHyIyIyJyKyLyMyNysyOyPyKyQyGyFfRySyTy5y5yMeryNyUyxfMeIfVyWyQyXyYyufZy`yGfFyZySapy zoyNyOy.z+z@zxf#z$zEe%z&zY 8.......D f + + + + + + ", +" + + + + + + *z'.Y ....9.,.=z-z;zhe>zqe,z'z)z!z~z{zqe2a]z^z/zge(zjiw[;[{[Vr9|*|C}ll_z:z#'K'ezfz:!gz2zgzW'hziz_!=!i~jzkzhelzmznzozpzqzrz_eszpetzuz!z!zXapevzwzre(ecaIaxzyzX%8.......D d + + + + + + ", +" + + + + + + %... .......>jzz2r$rAzqbKmBdlbpd,o@r+b%b+bBzCz*jLhDzf}v[;['[[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[e}&[an%|C}Rx$x;A>ANzNzNzNz,A(cd7s5v4v4p6t5m3v5A4o3pao3qaoaX0oapa50X0o3h7X0A4{8x5~8~8r6v5k3z4y5w5q6m3k3l3x4k3x4x4l3u5k3x4m3u5k3l3m3k3l3v5'A)Ak5uu*sC}C}F}Qld}G8Ni;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[S6!A~A^7ec)7ZvZcD8+8D8^7 8(9/7^7]7 dqcQc(7Q7y88iZ7R7^7]7{Af.......Y .v!+ + + + + + ", +" Uc+ + + + + fmOmZ ......s8]A^ADdl8+wQw`7,m=9Rc/An8pchc!9)c]7^9=j(A@l;[>[;[;o*|C}ak|9_A:AO2&4,4 4l2t3)4l2>4Q8W36x98q3P8V388k2[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[|[hqan%|C}ridw4A5Axy6A7A^t6AUxW0d759f749u4u5q6r6X0paX0X0oapaX0qaB4paoaqaoao350paX0x5j3j3v5z4w5n3q669n369t5t5q6k3l3t5t5m3k3k3x4l3k3l38A9A0AaAbAC}C}72GjiqYxcn>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[;[;[;[S6T6(d(7Xc.d.d0iqc 8<746D8)7%9^7D8)7.d&9z8Zc`c>caj)cececcA&c......'.x dA+ + + + + + ", +" n + + + + + + eAZ ......,ifAgABi`v!7Zc^7*bpcMh,m!j'm@*|C}SeiAjA{q,4n2,4t3t6>4.4l2)4#5A5P8k2.9.9P8kAO8Y4lAmAnAoApAqArA=|$|)+sA;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[e}&[an%|C}ridwtA5z7A7Axyxy6A7A6AI9l6p5r5)8m3k369y5N8x5x5o3o3paoaqa3eoaqaB43eo3X0{8y5A4A4v5n3j3n3~8j3z4~8~8q6t5t5x4t5x4m369m3t5m3z4uAvAwAxA1Ai5C}C}Apl+y|7[-[-[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[=iyAzAQ7z8(9%9+8^7z8]7 8v8@8pc@8#8+8Z7^7+8RwQwz8j80i,cK7AA&c......` +.&.+ + + + + + ", +" m'+ + + + + + M ` ......(6_xBAzd2wKsi8}7hc@8+8%9~9:dp9+#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l>+k@NA*|C}ridwBw5z7A7AxyM8Nz^txyM8H9'8q5!8u5l3t5q6~8r6r6A4X0X0qaB4Nb]87979NbqaN8N8X0{8j3r6r6r6j3[bw5~8v5~86969z469t5t569m3m3t5OAPAQARASAAyi5C}C}=|TA#l&+#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#l#lUi#lUA7[;[&8gbVA|7`7kb@8#8QwC8^7gc~70ihcic@8]7]7i8giod`cD8`v3dhcWAXAs8......Z @.3%+ + + + + ! ", +" YA+ + + + + + k| .......I s8ZA 8`v[7Ks37@8g8D8S7Z7P7+0]9l8`A B0j.Bvm;[;[{[kiApC}C}vw+B^j@B!4>4.4!4>4'4 4$A6xbbt6r3z5Z0q3#BLb$B%B&B3c*B=B-B;B>B=|=|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|&|*|=|C}C}ridw;gwy^t,AUx(cNz,ANzNzL8e7p5c7r5g7u5x4q6n3n3n3[b{8X0B4p3B4Y0]8h7X0X0o3x5x550x5y5j3r6y5A4[bv5w5n3v5z4z4q6t5x4t5z4,B'B)B!BYtC}C}C}C}C}C}C}72*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|En*|~B>|;[S6#9{BK7[6l8~7@866<7[6L7D837i8S7!9&9Q7R7g8D8 w}7]B^BLm/B#0,i......Z u + + + + + + (B ", +" + + + + + + ^|Y Y ......_B:BCd{b@r{b4l2!4vi4B;47xC4GcGzZ0zuGA5B6B7B8B9BH60BaBbBTiC}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}ricBvydB(c(c(c(c,A6A7AxyH9,8H9d7f7v4K8!8l3m3q6y5x5x5paY0]8]8Y0B4X0papaN8N8X0N8N8A4A43eoax5r6y5r6j3r6r6w5j3z4z4j3eBfBgBhB1kC}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}&|C};|>|;[&8#9iB~c$erbKi#egizseNaKatBuB;zvBwBxByBzBme]~>1;[>[,[n+%|C}!nABBB3f!4.4!4 4#5;4Y3CB6xX5Z3D4DBomEBlAFBGBk7v3HBIBJBE5KBK|LB82MwakMwakakakakQ0Q0Q0%d%d%dQ0Q0Q0Q0akMwMwMwSeMwIl$xMBNBL8UxNzNzNz6A7A7AL8I9I9m6q5r5s5s5!8u5v5N8r6A4A450oaB4oaqa]8qapa{8pa50qaoaX0X0[bA4N8r6[bx5r6{8{8[bw5r6w5X0OB0APBQBSeQ0Q0Q0Q0Q0Q0Q0Q0Q0akMwakakMwMwakakMwMwMwMwakakQ0Q0akMwQ0MwMwakQ0MwakakMwakakakakMwakQ0Q0Q0[9C}C}&|C};|>|;[>1RBSBTBUBVBWBXBYBuzYBZB`B C.C CpecaUBWBbareaamzszvz+C@C#C......Y Y ;7+ + + + + + (c ", +" ! + + + + + *.J y ......d$4@$C%C&C*CM0M0=C-C;C;C>C,C'C)C!C_a~C{Cg_>1;[;[2[ir]CC}C}R0^C/CO2;4t3B5Z4(Cb8S3W5_Cr3D4gd|;[v[,|T[OyiC)ajCkClCmCnCoCpCqCrCsCiCN0{atCpCoCuCvCd|wCxCyCJh......Y I )*+ + + + + + ", +" o + + + + + G[E@ .......I #}zCDoACBCpx=y'yCCXftx gDCKx+hUf>ytx~0EC7rw[;[7[DnFC&|C}kaYwGCHCB5t6B5`3FA6kZ4CBB5R3D4k2ICJCKCKCCaLCMCNCOCPCQC,dRCSCTCUCVCWCXCYCZC`C D.DBu+D@D#D$D%D&D*DKw=D-D;D>DBw,D'D59c7d7)DI9(cUx,AM8M8xyxyNzUx)Dd7p5q5)8r5g7t5q6x4v5[br6x5pao3o3X0pa3e3eoa3eB4paN8pa3epaX0X0X050x5{8x5A4[b[b!D~D{D]D]D^D*DFw^D/D(D/D(D_D(D:DKw|;[x[%59D(0:y0DaDVfbDcD0t&y`fdDeDfD.yaDgDhD@yaD=yiDjDkDlDmD[&......Y P+N.+ + + + + K, ", +" m'+ + + + + + nD` ........udoD[6/7b6|7pDA9h8qD+0rD$c@8`7[x)hzbP7P7sD)x;[![/[*8tDC}%duDvDwD#Bj7xDe0.9q3U3q3yD5cD4jyLbzDADBD9b8eHBCDDDBuEDFDGDHDIDHDJDKDLDMDNDODPDQDY4RDSDomTDUDVDWDXDfCYDZD`DxuZD Ec7e7W0I9)DUxUxI9(c,Axy,ANzNzM8L8e7'8o5u4)8p6t5q669q6v5A450N8o3{8pah7B4B4oa3eo3h73eo3X0o3o3X0pao350X0paA4.Ex5+E@E#E$EeCeCwj%ECr&E*E*E=E-E;E;EOz>E,E;E'E)E)EOz!E!EOz~E7q}A{E]E^E!EOz/E}AOz2e&E(E)E_E}A,E:E]E]E|;[S6!A1E2EFb[7273E[7:7{7Ls46]B4E]B wL7<796/9!75E66:7/96Ee^9....... .f R + + + + + .1 ", +" < + + + + + 2~z ........1d7EWAD8[xT7Z7@8y8:7~9rDK7l8:6Q7l8*9[6.78E9E;[;[^}VrE}=||9Wq0E#5aECBFAbEcEt6Q8X5O6Z4SdU58s:CJAdEX8eE8BfEgEhE7eSbiEjEkElEz3H4G5KDmEnEoEpEqErE|;[u[vEwE`9Lsqc$856(717J7xE wyE{x}7i8h8~7g8!7xE17<7 8[6zE>7........Z i + + + + + + > ", +" @ + + + + + AE3@g*......f.BECE^7rD27|7<7y8:6*9O72x+0x9_7+0l8pDDEEEFE=i;[|[+|^iE}C}GEHEIE,sJEj9t6KE>4bbjyLE=fMENE[A%BOEPEX8QERE6bSETEUEVEWEXEJ5YEZE`E F.F+F80@F#F$Fs6j21vr3p3N8v5k3w4w4o6)8s5q5c7d7e7,8)DI9,8H9L8I9Nz7AxyUxUx,A,AL8W0e7q5g7)8)8k3q6n3j3r6A4paX0{8qaoaoa]8i7i7i779qai7]8B4qaNbs6p3B4]8B4Nb79oaX0X0qaX0A4{8N8{8X0paN8x5v5y5A4z4q6z4z4l3t5v5t5t5j3w5x4~869t5x4u569l369t5k3m3q6q6z4x4y4k3q6p6u5l3u5%F&F*F'2&|C};|>|;[S6!A=F66:7/9i8^7i8/9]B4E/x37Ls]7^7+8.8Q7Ls4E.8~7Q7Fh-F-7........H O.+ + + + + + ", +" D@+ + + + + *.u Z ........&c;F&9:756L7@8l8P7&7+0{h17<7l8_7A9Y7A9Z6>F l;[;[]}y+F}=||9,F'F)F!Fcb>4bbS3~FV5D4W5{F6B]FOE9eRd^F/FfE(F@F_F:FOD<8.D|;[u[+6H7Y6/7B846 d(9C8}74ER7Q7i8^7Q7/x)746^B/9Zcb6DdSc#0,.......Y F@9++ + + + + / ", +" iF+ + + + + + jFD ........HikF46&9(70i)7y8Y7x9 8`7pc~7O7S7_7v8R7&7lFw[w[;[7[Dv$d-|C}!nmFnFoFt3cbW9Z4pFZ4Q8qFFBrFsF$FtFws5eWb'duFvFuFwFxFkEyF:8zF.aBcAFBF:8CFDFJBmA:Ci2Mbj2i7qaA4r669y4K8o5n6n6m6'859m659l6L8)DUxL8W0,AM8,ANz,A(c)D,8W0H9YAEF5FnB4F6FnBnB0FFF59p3GFdFHhcFbFfFfFbF{ HF49i7q3l64%0FaFHhcFIFHh$ eFqa79Nbs5> 6FfFHhcFfFbFJFnBm3j3l3fFtdnB0FHh0FnBnB$ )Dj3r6f7S 4FHh4FnB0FHhJFJF0FnBcFnBsdaFw4x4k3o6`eKFuE'2&|C};|>|;[u[G7LF]B!7Yv/7`vZc!7 wKs%9i8D8+8Qw5E`c.8Ls!7EbDb`vMF&c........Z u *.+ + + + + D@ ", +" &(+ + + + + 7 A ........(6SwNFL7.8xEOFP7P7A9PFO796K7O7$7a6<6QFRFSFTF&8;[;[|[uc%|C}2AllUFiyZ3r3A5j7 xC4VFK9WFXFYF]FZF`F G.G+G@G#GuFwF$GkE%GI4ZEG6AZ0k2`4,8S oaX5i7O8z5z5n6'ip6D450,G'GqaY0qaoaNbh7w47F)G3eI9m'e7[br6A4w550A4GFtd!8o3n6!Gs5A4k3v5z469nB4F~869q6n3Ux$ M8z4o6!8~G{GuE'2&|C};|>|;[u[m9]G^G/G(GxE$83Eb6c6Yv$8`6_G(G76:G_G`686X6[7 wI7=Nzf7n6s5g7K8w4m3l3q6~8r6w5[b{8N8B4B4; EF50P8Z0W9d7(BX0Z0]8s6h7C4n3o c7D4A4(Bd7r3h7h7o3qao3oaDGIFp3EG$ 69x5[bv5j3y5X0l6!Gg7pap5H[e7w5t5n3p6y46FnBm3p6m3n3g7- 7Al3u4x4FGGGHG'2&|C};|>|;[~[IGJGKGLGMGACLGNGOGPG4G4GQGRGSGTGAC4GUGVGWGXGYGZGfA,.......g*&-2~+ + + + + s% ", +" 0-+ + + + + | e A ........,.`G H.H+H!|@H#H$H%H&H*HY}=H-H;H>Hg4!2,H'H5|>1;[>[>[or&|C}C}ll)H;lZ4zoW9Mb!H~H@f6B{H]H/FWb|C^H/H(H_H:H z4j3x5j3r6j3N8W0(Bo63eq56Hp5r669x4q6696FnBz4l3k369v4'i,Aq6o6u5xo7HuE'2&|C};|>|;[w[ |8H9H0HaH@HbH!|cH@H=5dHeHcHfH{3 HgHhHiHj4jHkHlH[#........Z w Vc+ + + + + : ", +" + + + + + + V A ........,.mHnH~}q:]3#<[2w|^3`:3=M8C4qa3epao3qaX0DG; i76A$ l3N8{8r6y5y5N8d7(Bw43eo5~ r5x5y4v5v5z44F0Fw5l3u569s5- )Dj3m3t5FHKF|E'2&|C};|>|;[6[GHT(HHX:IH:|U}Y|fH&b1rLHAz_rMH8t&b,oNHOHPHQHRHSHTHUHBzVH;[;[;['[,[>+F}C}C}UhWHXHYHZH$azu`H%BmAyH I.I7b2CIB+IvF:FDF@Ibd#INDB3$IJ5%IvGJ4xGMD&ITE'd%BW5*IW5P8X5s6NboaA4~8l3x4p6o6u4v4s5s5c7'8e7d7,8l6I9UxNz^t6Axy,A7A6AI9# > 8Fd7n6xy>GtdEFJFEFxyz4m3q6z4v5x5; 7Fn3i7h7Y079Q8'8(Bo3C4i7i7Y0s6n3J)g7W950>=I9X5Nbqapa3eB450=IYAj26A$ t5A4[b[bN8v5o3d7H[)83eo56H'8r669j3~8n35FcFz4l3m3t5r5'iUxj3z4t5-I;I>I'2&|C};|>|;[u[+6,Iwd'I)I!I&blt~I4t{I_r=b]I&b!I2t^I/I(I_I:I$e '8B43eqaNbp3B5m6(BX0k2s6]8qaC4j3J)49W9N8H=H9D4B43eB4o3X0X0FF# X5^t$ j3pa50y5{8~879,8H[o6O8v4!Gn650n3q6~8j34F4Fn3l3w4z4r5- M8x5m3k3dIeIuE'2&|C};|>|;[:9fIgIYv{xo956.8Yv{x37o9hIhI:GxEb6iIxE37hI dLsjIkI,.......Z dm4>+ + + + + + ", +" 3 + + + + + + lIE ........s8mIQ7~7(7J7i8D846~hg817S7x9Cbm8 8x9~9~hJ7nIoI8v;[>[>|pIqI=|C}YmrIsI`iJCtIIA$BZHuI8Bws'dvI/HwIxI=)DW5qaB4h7o33e3e=IGFQ8,A>Gn3oay5oaA4[bh7d76Hz4i7r5~ s5p3~8n3x5w54FnBt5p6y4z4o5'i(c[bm3p6EIFIuE'2&|C};|>|;[GIHIIIc6Ls27o9xEKsE8KsLs wJI{x^B{xiIZc/9yE)c17oD9.I ......D g | + + + + + + ", +" + + + + + + dA2@'.......s8KILIO7{7(7J7_7Fb$8$8~717MIS7zbh8l8<7[6P7NIOIPI;[;[|[ji77F}C}4sQIRISITIzDIAOEUIVIXbWIXIYIZI`IVEVE@I JMD.JMDZE+JKDzG@J#J$J%J&JCD*JBaabom=Jr3C4MbMbY050r6m3u5p6!8w4!8)8g7o5c7'8m6m6W0I9H9H9UxM8)DM8,AM8H98F$ gFH9H9l6,8m6o5W0HF7Al3u469n3'GEFp6[bA4{8pa79i7D4e7(B[bj2Y0NbZ0W5k3J)K8s3X0H=)Dr3Nbh7B4B4Y0qaFFIhP8,AHFv5B4N83eN8N8]8596Hm37949~ v4B4v5q6~8p65F4Ft5l3l3~8K8'ixy[bw5v5-J}E;J'2&|C};|>|;[GIvE>J]B<7$8E8iIYvJI/7Lsy9 w{x/xB8L7i8~hLs37,J'J)J......Y X%r}+ + + + + + ", +" + + + + + + 1 !JZ ......I ~J{J^9K7Z617J7 8|7$8.8+0P7pD%796`9FbO7h8zb]J>&&8;[-[2[^J|k%|C}|9wc/J(J_J:JLC}CX8Xb8BGeFd7L8H9d7e7'8)DHF7Au5g7k3W0S W0x5v5[bX0o3pao3r3l6S r6Y0j2]8B4k2n3o v4`4A4S W0X5qa3eNbp3Z0i7=IYAZ4xy>Gpaqapa50{8X0P8v4J)u5z5p64%v4h7z4q6x4j3nBcFj3u5t5r6u4'iNzx5n3z4(h5J>I'2&|C};|>|;[:96J7Ja6L7y9/94EB83756{x5E]B[7[727D8g817278J9JTw(6......'.P.0J+ + + + + + ", +" D-+ + + + + + (. .........>7aJ.7%7<6Fbo9c6OFI7bJFbZ6qD`6kxWARFDi<627cJdJv[&8;[;[GdazGjeJC}[9vwfJgJhJiJxHjJkJXIEclJtGmJx3mJ}JnJ&IJ5yFKDwGG5T8xGoJJDWESbpJ'dPE!Hi7qJQ8S3W5p3h73e[b~8x4y4k3o6w4o6)84949r5n6'8p5,8,8H9I9I9M8(cUxM8W08F$ 8F59e7c7q5p5r5e7>GEG!8g7c7DGIFv5{8X03e3ep3i73eMbl6S o3P8C4Z0D4Q8k3rJ49W9N8>=,Aq3D4X5P8W9`4Nb{ YA`4I9>=50r3Z0Y0j2X5W9L86Hq6D4!8F/v4B4z4j3v5N8cF4Fj3w5~8t5v4- (cN8z4w5sJtJuJ'2&|C};|>|;[S6vJwJ(Gy9 wxJyJ]BLs]BB8b6c6/G(Gc656a636b6yJzJAJ........Z *$+ + + + + + < ", +" + + + + + + M+Y Y ......(6i.BJ&j(7.8`6{7Z7P7y8b6/7S766D8v8+0S7h8Y7`7P7CJ8o;[;[,[/[.+E}C}C}Oio4DJ3JEJFJ GGJ/HhE(FYIHJx3IJwF a<8sa6e[c$JKDPbJJ # g7r5; >GgF59d7n6p5f7q5s5A4Y0)D'io5g7c7w4u5495FFFA4r3O8UxH=p5t5!8o6l3w4EGHFg7r3q6m'cI!8u449)8p6n6>GNJo3Z0K8~ q5pay5r6{8{84F0FN8x5w5j3s5'iNzj3N8N8-IKF>I'2&|C};|>|;[S6T6OJyE!7PJQJ(9y9y9{x$8`6!7Yv}7c6g8}7`v~xRJEis8........y 2%+ + + + + + H= ", +" + + + + + + | _#x<......Y SJTJUJVJh0WJXJYJZJ`J K.K`J+K@K#K#K`J$K%K&K*K=Kbux[;[;[/[][iqF}9|C}Ap-o-K;K>K*J'dkJLJlJ[J,KaI'K)K!KWEjE}HG5:cH4JDuaOdK4~K a(H5e{KKC/878D4D4P8Mbi7B450j3m3k3q6l3K8u4u4g7g7v4f759d7e7l6W0H9H9l6W0M8)DW0e7^t6F> HF>G>G>G>G> HFMJg7)G$ tdHF(BS H[H[!G4%(Bw4z5Ux~ !G~ !GJ)rJrJ=I69C4O8W5oa)G!GN+rJrJrJB>EFo5p3i7j2s5FFB>J)4%J)n ~ # [b]8O8p6]Kl6paj3~8N8[b{ =Ir6m3m3z4q5o NzA4v5t5^K7HuE'2&|C};|>|;[![Ud/K(K_K_K:KI'2&|C};|>|;[![Udi{(}CKDK>(-(EKEK;(FKGK;(E(j(HK`(IKJKd:KKM,........` c + + + + + + . ", +" + + + + + + 9+9.Y ........o'LKMKNKOKPKQKRKSKTKUKVKWKXKYKZK`K L.LTK+L@L%K#L>1~[;[;[G8l4m4)172C}C}5vOi$L%L&L%BDD,d*L=LF5<8WEMD-L}HH4KD%J;L>Lm7:8V8wF,K,Lv3'Ls6gdr3Z4q3j2Z079paX0x5~8m3x4w449)849v4c7c7n6n6'8e7m659e7,8W0H9H9,8L8L8,8W0W0d7d7,8l659,8H9I9e7s5!8u4g7y4l3k3j3q6t5z4k3~8[b[bw5X0oaNbZ0W5Z0Y0]8j2C4k2s3s3`4S3P8z5k2j279B479P8X5Z0j2Z0q3k2s6s679qaN8y5h7X0o3y5x5paoaN8x5t5u5~8r650X0y4u5u4p67z)L>I'2&|C};|>|;[![!L~L{L]LH:^L^L/L(L_L:L$<|;[GI+6gLhLiLxJ^BjLjLB8c6I7.8a6374EJI{x/9]BkL&c........Z Za4 + + + + + i; ", +" |@+ + + + + + 2~H Y ........8.lL`6c6YvX6v9mL86a6^GnL4Lo916WAFhI766iI(7`6oLpLqLC9~[;[;[/[Yx;+E}&|9|C}C}JdrLsLtLuLvLwL/H}JV83HV8z3%JxLP99IxGA3JDyLHJcL5e{KeLj2zuP8W9Z4W5MbZ03e50N8r6x4k3k3w4K849v4v4o5p5f7o5n6f7p5e759m6d7d7W0W0L8)D)DM8(cH9,8,8H9L8)Dd7d7H9l6n6q5r5v4s5u4K8y4y4t5z4j3q6t5q6v5x5paNbY0W5C4P8O8s6C4i7p3Z0i7p379O8Q8s6i779B4C4Z0pao3O8O8Nbh7oay5oaN85079i7qa50y5[b~8y5qaX0z4z4x4t5~8r6q6zLALBL'2&|C};|>|;[:9#9CLDLxJPJyJELELKsY6`6jL[7`6xEFLNFb6GLkI9.........A c^+ + + + + + . ", +" s%+ + + + + HLu D ........,.ILEh66Y6Eh%7:Gb6$7a656[6kx27$8Y6:Ga6]BFb16WA'7I7JL:9;[;[;[1[([az9|IdC}C}C}C} i0kKLLLMLNLwFOLPLQLRL8Ib9jEwG)K8I[HSL#fRETLULGzW5VLXnna/cdqWLzwXLh2YL;s;sXD5qZL`L M3qYD.M+M@M#M$M.M.M%M&M$M*M=M=M-M;M>MRt,M*M-M'M)MdC!M~M-MCw-M{M&M$M]M^M`L M2q/MZL~E7q(MCm_M-r:M|;[GI+6lMmMPJnMoMpMqMrMJIxEyJjxy9iLjL]BYvsM8.........tM,.uM+ + + + + + ", +" & + + + + + + r c/Y ......8.'.vMa6hIYvFLo9wMxM^GYvyM56`6OFo916zMzMEhX636,JAMBMCM![~[;[;[/[>|Wvan$|&|=|C}C}Oi iDMWhEMFMGMHMIMJMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZM`M N N.N+N@N#N$N%N&N*N=N-N;N>N,N'N)N)N'N!N~N{N!N]N^N/N(N_N:NNaN*N=NbNcNdNeNfNgNhNiNjNkNkNlNmNnNoNpNqNrNsNtNuNvNwNxNmNlNwNmNyNmNzNANBNANzNCNDNENwNFNhNGNHNGNINJNGN%N&NKNLNMNeNNNONPNQNRN8mT|&|C};|>|;[SN6JTNUNVNWNPJhLjLV6XNYNhIV6xJjLjLjxZNTwxb........G b.+ + + + + + &( ", +" + + + + + + m !*H ........I^`N_Gy9b6Y6FLY6nL|63E`616 O06_G%7FL#7%7I7X6kx66}666.O~[~[;[;[,[7[+OazA}&|vc*|C}C}C}%dIbwc}j{1Se4s@O7C#O0CmFmF#O#O$O#O%O#O9C$O&O#O&O&O9C9C*O$O=O#O9C0C#O0C#O7C7C7C7C#O7C%O#O0C%O-O#O0C@O0C%O@O0C%O%O%O@O@O%O@O%O%O%O@O@O;O-O%O%O0C0C0C%O7C0C0C#O0C9C7C9C=O=O9C9C$O9C9C#O#O9C9C$O9C$O9C=O$O9C&O#O#O&O=O>O#O$O$O#O9C&O&O=O9C9C9C&O$O9C&O9C9C9C$O&O*O0C#O&O9C9C9C9C&O$O$O0C,O`kC}&|C};|>|;[SN'O)O!OYN~O!O{O{OUN]OhL~xV6jLNFjx!7^O0.I ......` Y |.+ + + + + + ", +" + + + + + + + a z Y ......,i$w/OB8^GwM O|6FLoL(O3LFLwM_O}6^GNF:O,J}6RJ:O:OnLwM|;[GI6J}O|O1OrM2O3O4OUNUNELrM5O6O1OELFL7Os8........H kc+ + + + + + + ", +" + + + + + + ].[@` ........8O9OVz0OaObOcOdOeOfOgOhOiOjOkOlOmOnOoOpOqOrOsOtOuOvO1wwOxO}[;[;[;[;[,[>|5[%+yO#|[e)1%|*|=|C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}C}&|C};|>|;[SNzOAOBOCODOEOFOGOHOIOJOKOLOLOMONOOOi.xb......%}Y 7 + + + + + + & ", +" + + + + + + + b.` ........(#E POQOw{)(5:DK+___ROSOTOD/UOSOe]VO[^qKWO$_XOYO]/X]A/Z:{/ZO;[v[;[;[;[;['[cn4[9o`Ol+an3A$|F}%|%|%|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|$|7d$|Tz]};[![!LK{G^o^m($P%P&P6_........Z '*| + + + + + + ", +" rJ+ + + + + + uMP.Y ......Y [}*P#^=P-P~/x^O]'];PN]_]>P;P3],P'P~/N]H]u]B/)~B/$]J{5{;(a([)Sgaev[;[;[;[;['[2[)P:[+|>rDvVqx+,+B+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+}kp+!P/[;[![f}6)V]O/z/l/~P8]8]{P|^]Pi{'^$_^Pq}I ......Y .7 + + + + + + & ", +" + + + + + + + (.z ........,.~J/P(P)/W/[^-PJ]Z^W/_P:PW]F{J/B/RO~/V^U^#^j]W^Y{g]#]~]D^V^o]h-,|;[6[v[;[;[;[;['[|[7[2[7[/['['['[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,[,['[,[;[;[;[{[6|I~n^&/a]|^+/o^{P/y^K/!/i^p]~/y^|P1P3/z^X]B^I]2P/]'/Z^Y^3Pw];/g^h^2,4Pqw6[S6v[;[;[;[;[;[;[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[>[;[;[;[![9[}!_/Q{z/M~Q{{^5P#Po^n^n^>(6Pd$..........z 7P+ + + + + + * ", +" + + + + + + + $.Q+..........R+8P9P$]o])]B/.]B{1]h^;{B/Z]fy&]W~&]/]}]X]`^G~N]z^[^1^T!>]o]l{A/0(M)@=0PaP{[S66[6[6[>[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[;[![9[N'j/Q{z/|^r^l/#/z/d{bPx{cPc/I ........E u dP+ + + + + + ", +" 1&+ + + + + + Z-dmZ ........[@6,eP@]s]fPW]@~,],]+]q{a{)]T~gP}]$]J{/~&]p{p~q/T~5{T~g]c]W~R]~/6{,/X_i^i)v-hP#[,|f}f6~[~[~[~[6[v[6[~[6[6[v[v[v[v[w[v[![v[v[v[6[w[v[~[6[6[v[w[6[6[w[6[![~[~[![![v[v[~[![~[![![6[~[v[~[![6[v[~[![![{[![{[6[v[6[~[![{[6[~[![![![~[![![~[![{[![~[v[6[~[~[~[~[~[~[w[ae~[![![{[e}{[![![e}|[|[{[{[![![{[{[![{[![{[{[{[{[{[|[|[{[{[{[{[{[{[{[![![{[{[{[![|[{[![{[{[{[|[{[{[{[{[![{[{[{[iPYx~)v]S~8]jPjPk/8]~^kPV)o^W~=L=+-V;x;d>},rP+-sPy>tP8;!-8;sP[>_>uPx,];V;B=.;uP};B=8>d,L-p; -{;l>*>vPx;v-a;L-r,5-~=>>1-P-L=[>};(;h--;v-Z==;wPn;5;g>F;rP2-|;P-z=xPv-P>5;)>yP[; -W-};a;x;2-];B=];zP~'n>!-p-{-3;v;N=1-i-3;&;{-[={;|,o-APj=>;i=C;>;Z=I*O=,><;B;b=w=}-Z=K;{-o-/;Z=v;b=h-1;{-x;B;j={-g-2;w=T;B;p-!;{-T;K;g-e{(]l/L~h~+!L~6^R/n]BPCPDP9.........A d | + + + + + + ", +" _ + + + + + + N.g;H ........d$5+EPFP1]GP;{2]fP;{C{&]>]p{ ~&~Y{c]o~n{n{%]&~;{T~O{_]c{c]h{k~F~)~$]W{w~a{;{c]&][/#^w/A{h^Y{v^Z]'^]({/1^S_.]T^m]A_i^z^M^f/r/q/}/p]>]w]y^HP{/h^1]A^&]x/O^M^l{1^G]:/'^N^6]X{$]v^^S{7^w/]]M^$]$]Y{N^N{0]JP]]IPKP'/x/ /{_n^V{{^@(l^p^|:*^(/{^v]!^Y]z/l^&/=]5^{^n^I(L~Q_LP^/S{i/_/!^S{#PQ_N{MPj/l^O^{_5^j/2(g(n^t/S{5(+/S{@(|:>^R{z{6]a]K~k/8]|^g{NPg~OPf.........Z ..M++ + + + + + s% ", +" + + + + + + + ;7Z y ........o'l|s^B/i~P~q/~]h{,]n{PPc]U!%~R{3^b]:~R{&~}!E!%]l{,]o]r{Z!/~Z{;{~]G{F~)~)~R~@~f]%]<~0~p{j~c]0~G{9{+]9{QP$^#^&]9^W]9{%^$^p{+]_{}~,]j{$^%^)]T~G~)^D]N{o~P]l{m]_~d{;{~];]Y{>]R~ {Q]G~O{M~%~o~-~y{l]o]p{]]k/RPq]J!f!7^Z!/~;{i{ ~l{{^m{Q{,~7^+]c]%~$]]]l{h{ {~]o~j!<~z{&]SPR~n{T~h{S{J!z{J!M~^]!{J~l/S]l]l^~^!{){Z/e~h{L~RPL!TP8]l^n~l{h{y{V{l]o~V{|^|{V{#/5Ph{UP!{ ~~^k/VP;^R/V)m/M~;^NPy{+!|{NPn]/^WPs(XPYP,.........E ZP+ + + + + + + ", +" % + + + + + + | u X%........,.R+`PH{x]&];{}]/~t!U!O{ Q ~9!&~_~-~.Q6);!i{-~U!+]3^(~#~p{^{}~v~@~_~k~&]/~T~;{G~J{F~p{j~-~O!n{F~D);{=~H!`~9{)]0~%~v~={C!U!_~Q!0^b~/~}!l~+QT!>]<~U!G{@Qj{n{L!n~-^8)i{l{6)E!t!0~p~(~8!#Qo~:~r{&~-{D]H!L~<~;]$Q6)|~J!j!1!Q!}]E!9!5^,~N~m{8)&~J!j~Z{}~%~@~@~@]W~}~g!r{=]%Qy{_{p~0~w~{]&Q*~5^n~*Q=Qg{d{=Q-Q6^;Q>Q,Q*~'{'QS~|{*~V)bPM!-^6)S!k~bP*~8)f!Z!!{NP7)&~'QjPM~|^&~l^*Qa]*Q4!;^g{-^)Q;Q;Q*~M!!Q~Q{Q]Q[@........` +.^Q+ + + + + + * ", +" + + + + + + + `z0.Y ......Y [&6%~jx]n{p~&)%!6)&)/~}~W~n)Q!2!l)z!#~f!&~Q! ~9!E!$)Q!q)&)1!=~k)W~j!p~n{=]/Q*!#~v)s)^{D~r~d~(Q`~{~E!o)8~,{]~c~~:)2)m)}~1!<~d)<~H!F!K!`)r{%',~%'L!:)%!7)&)N!+]$)k~A!k~_Qf!J!7{u~{)2!r)l)*~W~d)m)k~e~f!e,c~-~}!W)Z!9'&~-Q:Qn!)6){)N'J!q)j!>]k~K!/~q)6)E!N!%!4)A!H!`))~V!6!t!p~o~%!C!bQ[{cQdQj{/!`)5)2)O!d~p!B!V!&~8!$~i)`'U!o{))#)E!N!&~l)o~k~}~]{n~M~.!l)+~#)W~c~n!V!>):~r{k)0~|{bP<~}!)-]K~+!3!!{r)M!h~-^4!5P|'iQjQkQ~!I ........Z 4.1 + + + + + + 3 ", +" : + + + + + + m weu%I ......I 9.lQa{j~.QL!d!m~S!2);]r).)>)6)h!l)m)}!6)d)k)U!Q!~`)N'U!/~}!={~~U'Q!Y! {&)j~&~|~O!l)~)J!<)d{,~2!h!L!'{d)1!%)5!m)&)L!:~8)=QsQ{~,~tQc~5)n~s)}!Z!g!q)5!m~%)k)O~z!}{_{ {U{,~uQ;~k/M!Q!%!9!;!a'.!a]r)f!5!vQN~wQ;~>)H!N~Z!&!V)M!7)r)d~[{m)#)e{-^xQk/J~3Qg)M!hQV)NPjPn~h!<)s)L!h!w)4!M!*~J~Y'L!*~*~bPM!5Q>Q6QV)8Q2Q'Q;^*~hQ-Q6QyQzQAQBQ[QCQ-m,......... .y M++ + + + + + + ", +" + + + + + + + /.E y ........f.DQEQM]r)-]L!_~#)8!U'Q!I~]{q)FQa'l)L!6)8!>)d)e,:~^{N'h!l~:)0{={5)*!={A!2)6!T!)~:)B!A)GQqQHQIQT)T)%)7{<~g!f!/~JQY):)~~(~-)f!6)U{G'7)+]F~KQg!H~J!8)s)N'2!s)L!l)M~'{-)m~~)5!+!8)>)=~*~&Qu,JQ*!%QN'm)!~H~r)/^5!&~n!l)z!`)i)E!l)H~n~=Qr)9'G'U!`)a'V)r{H~g!hQF',~J~+!.!*~3!L!Q!M~7'a'N~d{LQ;Q*~2)T)l)f!|'MQNQf!OQa'3!jP){5Q7)9'PQ;]|{6^+!V)d!f!J~*~-Q-Q4!M!+!uQuQQQ}Qh!1!M!;QV~xQRQ|Q}QSQTQUQI,,...........S+k|+ + + + + + + 3 ", +" } + + + + + + | c Q+..........h.VQWQ5^+~:~#)r)m~6)l)%~e,>!r)N~&~>)e)6)%)6)Q!$~%~;]n!A!5)n!k),!1!E!z!`~C!3zXQYQY~!|{h{^{Q!7)!{g!/^fQ R|{q{%!K~jQ,~;~#)D~H~Z!l)r)!{8!L!K~-)O~E!:)p~9!M]H!j!q);]e{2!h!>)z!%!s)N~.!-Q-)V)hQ;'K~5!,~;]&~~)L!8!k,j!|{V)uQfQ.RV~xQd)+Rl)G~}!5Q)QjPs)bP>)e~'{V);~@R)Qn~n]r)|{+~ !g,K~jP+!#R7)hQ;~V!6!p]X)8)-Q*~$R%R'Q)QV~){&R..9.......I D X N.+ + + + + + i; ", +" + + + + + + + m J z ........,.0K*RO~5!=R-)*'-)m~*,l)%!z!<)2!e!U'FQ}!N'm~e!|~6)|!u)k,%!e!D,z!6)m):)m)z!G!6!dz-R})iz[!c!R)Y,H>/!0!N'O!S!8!q)W);R`!X,>,=)a'T,X)e,h,>R,Rr)6)|!j!FQ{)|~<)*'3,*'''{)8Q>)o)X)&QK~9'9'.)T,'R7)j!a' R{)s)5!%'_Q1'e!%!l)*'>)e)-)}'[)H>w)+,m)7'v,5!u,/,)RxQu,w)S>;~4Q,R!R7'N~~Rm~<)|{f{f{{RM!'{4Qw)]RZ'%)A!6'=)e,f{K~hQr)R>(!>)F'f{;~-)7)&QNQ;~u,n~_QhQhQU~9'9)=) '}'.,7'N'm)N'#)$)b)q,.'4'b) ']{1,X'#,`'w'})A'w'2R3Ra~B)+)Q)4Re,R>X'!'U'%)`'$)X'V,G'vQa'.,*)o)p);)&'E'3!a'#'H>/,.,$)G'/,.,G'{,9'w)5!$)V,3,f{.)7'@R$)K'.R',@)9'5R6Q[QeQ5Qu>*)%)7'{)e,6Rg,>,&,1,1'''.)e!],;'s>V)%'hQ7R4!%'_Qh~fQ9'8R;)W><)1'-)9R'{w)-)5QS>w)E)='-^F)E)9'>)b,;)E)%'_QF)(,.,',-Qt,#)4'%'%'g,|'%'3!F'h~*~0R3,N'Z'aRhQ4QbR_R)RcRi'h~dR1Q4@h.........Y u%k|+ + + + + + + K, ", +" ) + + + + + + + %c/..........[@eRfRgR*'%'F)4'',9'*,H>H>>,N'e,1,s!hRU'U'&!b) )1'&)X'X'&,$)e,k)P)3zw'L>gz2)iRK'+)Q)y)V,n)9)b,c,K'&,+,jRq)@).'j,O~1'X'A,8).)',F)3,',e!<);)*,e,7'~)>).)U'kR5R$)@,lR%'8)7'K~>,g!,!/,g,='3!s)$QuQ_QE)r)X,{,e,1,X,>)F)mR%)&,G)@)1'j,.)u,@'3,F)$RNQ:QnRE)U)>Q(R5RW)O~.)',3,3!t,@'F)3!9R-Q9R5Rw)/,]{|RhQ+!@'-^g,h,g!7R4!f{>)g!3,1'|RV)5!3!,'%'*,w)u>%'g,7'='@R,'bPRQF'hQh~$QeQoR4QpRh.I ........z v 4 + + + + + + + ", +" + + + + + + + c.@.z I ........[@f$qRw)~,@'8)7'3,]{e,W>a'k>X,1,e,h,(,e,n,})V,X'C'Y,@,#'3'e)3zL'N>;>I'A's,'!u'L>f'W'&)q,+'K'iR=! )+,G>e>$,#)R>N>~)N';,w)''@'q)k,P;;)-)q){,*,3,%)!'7)='w)$'Z>%'3QI;;,f{F)R; '#)='g,9RF'u,-)/,N>w'4)k,X,V,9>1,m)}!G'R>*)&,G>$'N'/,I;I;9R_RrRF's)F'yQZ'Z'5Rh~lR.)3!u>@'@' !;,3!OQZ'sRtRu,8);,#!],Z'4!hQZ'g,lR;'Z')RQ;h,C'{,,,g,3!t,F)~,3,-)rRhQS>hQ;~eQF'4Q9Ru>E)$QE)u,V>)QuRh...........y ..vR+ + + + + + + ", +" + + + + + + + k y y ..........(#wR1'3;w)%'V>,,-)I;xRt;@'p>C;$'/,<;y,-,R,Z>j>GtyRu'X>G> 'K>#,[,e>j>N2'#,K>AR9>H>s,9>U>D;e>$'h,Z>X>h>S;N;z;j-<;.,j->,P;&,BR%'3,e>f,f,-'+,1,$'V>',@'S;/,CR$Qw;K>;'_QlRz;],$,G;n>G>H>y,9>J>b>x>2'PR>|>DR}'A;L;ERFRGR;'Q;sRj-Z'bRsRHR9R;,],E)w)IRf{JRv,hQKR9R)R~,OQ''q>LRMRCRg,>'8'>'_RNRQ;%'{>OR/;=,~,v,;'>'3!,,N;GRPRhQ~,;'^,rR>'3!t,lRZ'F)-'~,-^QRS.I ..........E 2%+ + + + + + + 1& ", +" @ + + + + + + + j =-Y ........3>i.RR1R-'~,]>)R^>]>g,]>Q>S;h,S;6RSR2,E>TRZ>9>c>URT>2'N1,u)A'f,9>#':>e>A'}>n>y,N>0,Q,Y>-;|>H;@;O;.,P;U>N>h>VR*,~,S>@'h>P;Z'a'C;K;h>;>6'Q;3,3,,,j>|>@>@'d;N;k>h,cR>'[,|>Q;U>|>S;$'N>y,/;~>i,H>G>;;b>X>|>$,H>/;j,P;*)@;=,1'>!|R9RWRXRz;;'YROR/,;'tQWRQ;',g,',I;$Qu;)Rf{2QsRI;~,OQt,Q;w)2QLRWR;'9RZR`RZ';'3!'')RWRtQQ;u, S^>''{,-'u;.SO'Z'Q;g-+S^,CRv,u,u,;'f{;'MR@S4@8...........D d + + + + + + + i; ", +" + + + + + + + + W A ..........8.H #S7R/,t;~,k>Q;~,''R;Q;P;Ln>!'K>},7>&S=;h,}'s'P;P>Z>9>j,a'J;/;&>*Se>&,s,#'P;+,p>k,p>J>M0'u>k>k>I;w;|>L>Z>Q;],w;}'|R~,k>NRu>w;Q;;'bRn>9>.)0>1'X,x>C;U>h>|>{,w;h,;,D;n>1,x,P;p>g-;,|>>!+,$'k>j-=SMRO'yQf{S>^,NR9RZ's>g,Z',,BR+,~,v,]>),LR^>t;^>j-I;q>0'~,I;h>f{-S;SBR^,*'z;IRsRZ'$QMRf{>SIR_Qs>;S^>},}'='xQbRv,@R,S9R-'sRv,Z'O''S)S&Ph.I ........E X Qm+ + + + + + + ", +" + + + + + + + aKemz ........I (#!S~S{SI;Q;x>w;u>h>R;u>}'$,A,Q,]S'!e>|>k,#'l>b>f>c,y,e>H;n,#,Nb,C'N<6>p;|>e>n>`>U>|>w;2'},i,S;h>R;V>/,$'h>U>u,G>S;,,@>3,N>;,H>;;%;H;$'^>^S0'Q;F'lRf{/SLRC;N>(Sq><;g-9>X>.,$,S;h,+,p>G>G>G>6'D;y>1'}'|>|>}'k>S;h,j-s>z;CRxQw,='],g,z;2Q;'^,_S%'''h,/,',/,-'I;h>W;u>A;$'Q;,;-':S/,s>)RGRWR;'5Rz;;SZ'-'-'V>]R%'sRw,-'@>/,>'&,h>xQ>'>',SWR^,.S;'-S[Ss>}S(#I ........z Y k + + + + + + + , ", +" =.+ + + + + + + |.I '...........d$|S1S,'k>h,R;1't;KR3;n>n>%>/;b>L-@;/;D;B;@;@;H;|,;>Z>:>qPr;D;x>t;B,x,k,P;@;M$'F;P;!;$,n;/;u-%;s>$'t;;,h>;,7=z;BR2=OR@'u,(,CR=S=,2;BRcR:;j-,>-;u,sRWRO'-'L=-'.SNR1;;;~>h,}'G>J>S;c=eor;/;C;}-@;h>],k>h,;>1'R;Q;,,t,A;NR9Rz;MR2S,;-'9R2QWR`RP;B;g,Z'BR0>N;XRh,3;k>{>j-3S~,s>-'v,z;7=@>@>8SQ}I ........y z r + + + + + + + + ", +" &(+ + + + + + + 9S'...........Y [&OPOR0SCRv,,>k>j-)>7-=;>;;;w;{-(;5;|=r-x;->/;l>/;{;A'0,b=b>@;F;/;-;[=%;_><;-;c=J>(;~=v;b=;;g-c=,;b>g>d;6;O=b=q=@>c=L<,,7=_-Q;[=h>@>NR,>f=N;2;2;@>aS_-bS5;3;cS]>x;-;O;g-N;<;K;3;3;}-P-!;b'dS!-b=h>R;f=W;|>,>eS:;-'#>@>F']>,;NRI-S-JR.SLRfSgShSi=-'j-N*,;s-|-,><;d;,>e->',;h>,,6;5SWRiS:SQ;@>jSMRaSWRkS>'lSj-@>GR{RsRIR+SWRmSnS-'='_-oS_-pSsRO'CRMRz;qSl'Q'........I c/s + + + + + + + + ", +" + + + + + + + + 0 _.Y ..........(#rSsSV>2=GR2;v;L-i={-K;T-O;P;!-5-{-W>@;r,}-Q-M==;;;g-j=*StSd>f=!;v-3;~>{-4SB;5;V;v;f=4;K;s->;->g>g-A;uSg=j-2=j--;j=l-GRN;,>t;lSlS,;dSe-W;:SvSS-+S.ST;}-AB,Sv>P;R*)>u-6;W;1;1;-;o-O=5;O=<;%;v>Q;%;g>}-j-7=u*O;h>NRwSl-:SNRw,GRGR+SxSfSN*4SN;~,b',>@>1=}-w={-C;W;L;:;WRz;o>kS^-lSAS-SlSl-l-7=^>u;w,7RCRCR6SBSCSiSkSMRO'KR6SGR+SvS>'='DSy I ..........z e | + + + + + + + ", +" + + + + + + + + ES`%Y ........I L.FS]>]>IRW;|;g>;,v;}-W;F;-;K;/;i=F;!;l>D;d>+-%;A;h>F;rPGSB=j=v;+;w;~-c=B;:>u-P;~>>;{-,;Q-sP5;~-3;r-,>c=C;{-,>N;,;HSBR6;_-u;2=7=CR{-}-A;Q;7=-S.S{-F;iSNR^SGRvS%;B;,;2=>;D;/;j=w=b={;O=A;j-f=H-a=W>ISQ;r-2=j-,;d=j-6;-'O'JSz;,,L;z;_-_->':S@>,,MSlSiSCRkSIR+SJRNR7=u;NSlS2=JSmSOSS-S-mSiS_-IR$'PS0.,...........H v m + + + + + + + ^ ", +" . + + + + + + + 4 L D Y ........,.%$QSu-RSr-!;J>g-|-t;F;[*~-W;2;S*:-2;`=L=`=-;s-P-d>v-b=b=`=i=Z=v-2;q=p-]=SSA*[=p-M;6;s-b=2;g=]>u-N*z;z;p-}-jS5=v,L;;'TS-S SLRVRl-KR4S:;@>>'US;;v-f=%;N*OSO'u;b=VSd;L;:=v-->s;v;O=dS3;3=d=|=APt;1=l-KRS-N;jSk>q=;;2=GRvSvSWRgS2=^-O'-Sf=e-kSd=X=N*:;W&6;I-s-r-,;O=|-l->SGRCRIS>'|;V*iS+SGRWRu;S-WSvSA*N*NRu*7;+SO'XS]>jS_-@>jSO'WRLR:S]>YSZS|#,...........=-P.4>+ + + + + + + * ", +" : + + + + + + + uM@.E ..........9.`S To-O=a=s-f=v=c=[=q=P&~-W&a=Q*1-P&s-X*T*O=~=C=0=Z&.Tx=0=v-R*C*r=!-p*S*I&~-3=:-A*N*t;O*d=T*u=W*W;,;N*+T@TX&N*v*JS#T$T%T0=O&]-^-,;GR&TlS7=v-`=r*0=*T=TR=-Tn=r*K;|=g=|=c=a=T=k&y*9=I-T=!-L*r-l&H-I-k-;TX&^-R*l=>TjSI-nSgSMSI-,TlSI-6;fSu*A*(-jS/-6;N*(-K*L*7;'TP=S=(-)TJRk-MSS-!TnS5=fS~T_-I-MSKSr-MSjS =X&{TI-z*iS]Tn=CRfSiSR=JR1=^T/TZ f...........Z y k + + + + + + + + ", +" + + + + + + + + &.J '...........I L. TX*v;q=b=c=T*c=v>p=T-(Th=.=O-w=+*'=E*r*[=z=h=~=%*x=xP2;W=t=O=L*I*M*0=N*A*p=k=d=2=A*W&K*2=r=}*1=6=S-A*6;xSxS_T:TUS1=q-(-I-K*S-6;,;r-'=S*S*v=#>GR:-k=W&J&f=s-3=k&W&A*N;kS$TfSlS&TS=fS[T5=6=k-JRv*jSq=}TkSl-K*GRP=5=CRP=fSl-5=lS|T{TJR5=3=L*1TI-u*&T^-_-W&2TxS5=OSu;S=7;S-KS~TJS3TGRN;4Tz f........... . .P + + + + + + + + ", +" + + + + + + + + [.+.'...........9.h.5T(TE&p=S*g=T-d=q=6T0=+=C*I*S*Q*l*w=hPv=R*.=N- -x=M*h-H*f-(TB*D*A=A*S*0=I&7T8TR*T*k=6;I-I*T*(-I-3=n=!T9TGR0TxST*A*JSaTJRbT8TS*S*X*Z=6Tn=bTT*k&P=Q*|=)-2ScT3=c=(TdTT*q=k&3=6-s*L*3=S*A*^-c=b=v=L*2=:;nSu;mSnSeT/-*;{T%TjS_-fTxS'TaT/-lScTcTe-4=k=u*cTu*5=5=A*v*^-cTJRk-jSaTfSxSL*g=gT~Tk-I-6=(-0=~TjShTJS)TfSASnSS=fTJ-iT+SjTA f...........y y ^|+ + + + + + + + ", +" + + + + + + + + q x X%..........,.g.kTlTmTq=+*v=L*R*i=}*[=p=q=T&H*j=V*v=P&a=j=[*.=nT4-q=P*v=R*0=D*r*r*Z*<=N*l=W*o>/-k=k&N&d=fSbT4=,T,TMSnSoT%TK*^-pT.SiT^-GRqTv=Z*Z=jSP=oSgTrTE&d=I&Y*q=4SmS5=I-z*R*I&S*I&[=W*q=n=T*r-^-O*K*jSK*^-JRMSlSS-jS/-3=vSaTaT_-{T*;4SsTxSI-jSiTS-k=6;^-)T)TS=+TA*tTJR/-|-fSaT(-(-v*v*v*5={TT7T'&5=JTJ-jScT!T(-v*nS(-cTv*u=N*L*@T!T(-7T4S>T4=k-z*P=k-%TS=k-S=xSJR5=KTJ-/-/-S=S*H-aTLT'&l=cTv*fT~T_-(-MT*TFSL+,.........I ` E 2%+ + + + + + + + ^ ", +" ] + + + + + + + + 6.y z ............g.V.NTOT^$~&L%)&9%6*n&9$A$q*I&k&k&V*x%6=PTl=n&QTRTSTE&X&2*x%W&L%$%2*z*z*N%6*+*g&[TiTK*P='&TTUTnSV*rTVTWTXTpTvTMSA%aTXS2*+*[-@TK*JT%TKTYT%TH$)&R*g%3*ZTfTJT>TiTrTL%(-6*v$L%L*7&l&L*r*D%g%>T>TbT]-(-@TS=`TST U.Uv*+Uv*TT@U#US=XT'&7T$Ug&3*JT%U UP={T'&H%>TJTTT&U{T%UTTxSoSjSXT7TP=!TjS]%OS~T[T%TE$5=WT.UxS.UWT4*@T*UF@9...........` D O + + + + + + + + [ ", +" A]+ + + + + + + + =$0. ...........Y [&=U-U;Uv$>U>U,U3*p&Q%'&/-l%.*l&g%)-l&XS3*2T6&W*+U'UV&H&E&P=rTG&bT3*3*_*2*P=)Ug&2*]%fS!UsT%T'&D%XSy$6&%T=T[TLTaTg&rT2S4*~Uj%k%!T!TXTA%]%D%{U'T]US&rTQ$!TJT^U%Uj%%Uv*MS4*I*i&D%T*L*7&L*P$2*K*0%G%N%P=3**Tk-P=/UiTWTWT/U)%'TsTH%(Uk-P=P='%M%'&)Uj%3*5=_U}$XT{Tk-:U%Uv*fT%UMT7*K*=T6=Q$*ToS2S%UU2*OT`&2*XS/-JTz*!U@%T&_U!T_UK*}$S&mT%UmT_UiTS=3*D%($6U!TKT[UbTj%&U7Uf&G%iT[T[TfTiT(%>U]U8UTI-&UTQ$'&6UxScU]%:UUk$/$H$,UE$!%NU!%{$CUa%$%_%%&~$A%NUu$3*}$_U}$($9%%UJ%XT<$]%}UE$n$Q#5U%U^Ul$OUJ%5U]%GUnUPUQURU'T[TaU!Th%E$S#J$p#2$'%^UP$0%x%p$A$B%JT>T=TyUEU&USUTU=TWTHU&UwU[UxU~$CUXT)%F$UUWTCUOUVUJT&UuU^UQ$Q$aTWUDU[UuUl$XUXTVW+ + + + + + + + + B> ", +" + + + + + + + + + + B#P._.Y ..........Y f.9.,W'WPU WMV;#j+-WfVm@LVj@)WB%!WnVkV_m WVU_mnVVUDVSV!W*Wv+LV~Wj@MV_mjV{W;VVU)Wm@ WjVoVMV+W]WgVj@oV[92V-@^WjV/W(WOi_WSe.G ...............I h.~*yWzWAW873WdW]Wh5BW/W`kG ", +" ( + + + + + + + + + + [.u '.'...............I g.z X.X+X@XiAqV{jfW}WDV/WRW#X$XCV%X9|qVoW{WB}dWVU0|NA0W9|c|(W{WLWdW(W!WLVFCbs]W;oDVUV:W[99|IW$W$XRW$|fW.............. .X%Za/.+ + + + + + + + + + + ", +" H[+ + + + + + + + + + 3%2%+.z Z ..............9.h.tM[n;X>XMV%|}0qI[9]r$XfW{jA}Id,X[9c|b| W'XFC$duWA}OiuWB}|kg6TABWyOFC)X!XB}A}BysWrmE}~XIdUW#X{XCW]X^X/W)1a|;oor/X=X$|`kD}XW)X)1RW(X#W=Xbs,X/W_XsW&XUV/W]rtDtDA}LWb|IdsWFCbsqWFCFCAp/Wbs:Xh@{jHlwn(b@{jbsqXC}rX'Xrm'2sXFWtXuX~*%$9...................'.D v %.+ + + + + + + + + + + ] ", +" + + + + + + + + + + + + vXb/H '...................,.g.L+wXxXyXByZt$|rqzXrqApAXaXBX_XCXzX#|DXC}ApEXTiFXkn|kGX5XHXoX*|{j03D}03{XIXTAfWIXJXKX)19|*sGjLX|kwn/Wbs&|03MXNXjXOXTikn&|!1$|zXC}En*|#|kXOXzXPX}0TAQX_XGX]rTAAp03RXaXSXTXz#UXg.8...................y =-Q.;74 + + + + + + + + + + + ", +" |@+ + + + + + + + + + + Z%B#ZaS.` y ................I 2.tMu%VXWXXXiX&|YX*s|9ZX_X|k`Xkn)X YzX9|BX*s5X*s*|.Y+Y@Yrq#Y=|FX Y,X8X$Y%Y8X$YG}&YE}&YGj6vrq*Y=Y]r6v-Y=X+YoX;YBX>Y_Xwh)172,Y'Y$Y&Y9|AXTADX)Y}0!YoX|kG}-YGj_X~Y{Y]Yu%^Y(#,.................Y ` S.J h*Z-+ + + + + + + + + + + . ", +" . + + + + + + + + + + + *.;7v S.E .................I ,.0.X%Y ,W/Y(Y_XEXrA#Yvcwh_Y:YFX*sFXFX_XrqFXFCLn ", +" % + + + + + + + + + + + + | xYW Y A .........................,.f.S.yYzYAYBYCYDYEY&dvc'Y*s@YFX_YFY7d@YtjGjYX!1=YmYYX03GYHYIY)1)1JY>Y:Ykn)1By YoY!17YKY]r)1%Y}YYXGjLYHYMY;YCXNYOY%|PYQYRYSY$$.^L.,.I ......................Y D z L v!4 + + + + + + + + + + + + K, ", +" < + + + + + + + + + + + + + |.s P.H '.Y ......................I f.%$G@S+TYUYVYWYXYYYZYhC*s!1)1oY[YZX`YLY*Y]r ZoY.Z6Y*YG}ZY03zX_Y)1QX&Y@Y+ZWioY-Y]t@Z#ZKX1Y$Z}0%Z&Z!1rY*Z=Z-Z;Z>ZS+,ZM.f.9.......................Y y u%Y !.6.1 + + + + + + + + + + + + + ", +" < + + + + + + + + + + + + + >W8 L .. .Z .........................,.M.A#,*D T.'Z)Z!Z}X~Z{Z]Z`XYXwh Z Z#Y^Z Z/ZiX/Z(Z-Y`X_Z:Z= ", +" Uc+ + + + + + + + + + + + + + + 4 CZb.b/y 3.z .........................................................................................................................Y Z A z I=!.r n'+ + + + + + + + + + + + + + + + EF ", +" < + + + + + + + + + + + + + + + + 4 /)DZM P.z Z .Y Y Y ................................................................................................Y Y Y .` z Y #.V :^].+ + + + + + + + + + + + + + + + + ", +" @ [ + + + + + + + + + + + + + + + + 3%U q h*=m+...Y '.Z Z .y Y ........................................................................Y Y .` Z ` Y ..,.${EZP &.*.+ + + + + + + + + + + + + + + + ( [ ", +" ^ A]+ + + + + + + + + + + + + + + + + + 1 N.6 0Q %B]w x Y z D D z Z ` .Y ............................................Y y ` Z z E A z y x J K FZV O.c.3%+ + + + + + + + + + + + + + + + + + 1&' ", +" !G< + + + + + + + + + + + + + + + + + + + + + c.q ;7k|c u K w ,.S.D B C Q+A D E E z z z z z z z z E E D D A C G D X%I +.ZaL e lIGZO.AE+ + + + + + + + + + + + + + + + + + + + + + n ", +" gF|@+ + + + + + + + + + + + + + + + + + + + + + + + + HZl /)/.v!V Y%;.c M #.L ${B]v K K K v B]L #.=md b s r}0QB#q I)m | + + + + + + + + + + + + + + + + + + + + + + + + 3 cI ", +" @ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | 3%*.N.N.m c.c.c.m N.*.3%| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ ", +" e$0-: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( * * ", +" =.diUc&(+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e$= ] ", +" m'n D-< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _ iF>= ", +" cIdF.1. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e$IZsd6A ", +" e$+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |a ", +" @ : + + + + + + ( / ", +" "}; diff --git a/source/platform/unix/i_input.cpp b/source/platform/unix/i_input.cpp deleted file mode 100644 index 7b34c1b02..000000000 --- a/source/platform/unix/i_input.cpp +++ /dev/null @@ -1,723 +0,0 @@ -/* -** This code is partially original EDuke32 code, partially taken from ZDoom -** -** For the portions taken from ZDoom the following applies: -** -**--------------------------------------------------------------------------- -** Copyright 2005-2016 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include - -#include "m_argv.h" - -#include "c_buttons.h" -#include "d_event.h" -#include "d_gui.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "c_cvars.h" -#include "keydef.h" -#include "utf8.h" -#include "menu/menu.h" - - -char grabmouse_low(char a); -void WindowMoved(int x, int y); -extern SDL_Window* sdl_window; - - -static uint8_t keytranslation[SDL_NUM_SCANCODES]; - -void buildkeytranslationtable(void) -{ - memset(keytranslation, 0, sizeof(keytranslation)); - -#define MAP(x,y) keytranslation[x] = y - MAP(SDL_SCANCODE_BACKSPACE, 0xe); - MAP(SDL_SCANCODE_TAB, 0xf); - MAP(SDL_SCANCODE_RETURN, 0x1c); - MAP(SDL_SCANCODE_PAUSE, 0x59); // 0x1d + 0x45 + 0x9d + 0xc5 - MAP(SDL_SCANCODE_ESCAPE, 0x1); - MAP(SDL_SCANCODE_SPACE, 0x39); - MAP(SDL_SCANCODE_COMMA, 0x33); - MAP(SDL_SCANCODE_NONUSBACKSLASH, 0x56); - MAP(SDL_SCANCODE_MINUS, 0xc); - MAP(SDL_SCANCODE_PERIOD, 0x34); - MAP(SDL_SCANCODE_SLASH, 0x35); - MAP(SDL_SCANCODE_0, 0xb); - MAP(SDL_SCANCODE_1, 0x2); - MAP(SDL_SCANCODE_2, 0x3); - MAP(SDL_SCANCODE_3, 0x4); - MAP(SDL_SCANCODE_4, 0x5); - MAP(SDL_SCANCODE_5, 0x6); - MAP(SDL_SCANCODE_6, 0x7); - MAP(SDL_SCANCODE_7, 0x8); - MAP(SDL_SCANCODE_8, 0x9); - MAP(SDL_SCANCODE_9, 0xa); - MAP(SDL_SCANCODE_SEMICOLON, 0x27); - MAP(SDL_SCANCODE_APOSTROPHE, 0x28); - MAP(SDL_SCANCODE_EQUALS, 0xd); - MAP(SDL_SCANCODE_LEFTBRACKET, 0x1a); - MAP(SDL_SCANCODE_BACKSLASH, 0x2b); - MAP(SDL_SCANCODE_RIGHTBRACKET, 0x1b); - MAP(SDL_SCANCODE_A, 0x1e); - MAP(SDL_SCANCODE_B, 0x30); - MAP(SDL_SCANCODE_C, 0x2e); - MAP(SDL_SCANCODE_D, 0x20); - MAP(SDL_SCANCODE_E, 0x12); - MAP(SDL_SCANCODE_F, 0x21); - MAP(SDL_SCANCODE_G, 0x22); - MAP(SDL_SCANCODE_H, 0x23); - MAP(SDL_SCANCODE_I, 0x17); - MAP(SDL_SCANCODE_J, 0x24); - MAP(SDL_SCANCODE_K, 0x25); - MAP(SDL_SCANCODE_L, 0x26); - MAP(SDL_SCANCODE_M, 0x32); - MAP(SDL_SCANCODE_N, 0x31); - MAP(SDL_SCANCODE_O, 0x18); - MAP(SDL_SCANCODE_P, 0x19); - MAP(SDL_SCANCODE_Q, 0x10); - MAP(SDL_SCANCODE_R, 0x13); - MAP(SDL_SCANCODE_S, 0x1f); - MAP(SDL_SCANCODE_T, 0x14); - MAP(SDL_SCANCODE_U, 0x16); - MAP(SDL_SCANCODE_V, 0x2f); - MAP(SDL_SCANCODE_W, 0x11); - MAP(SDL_SCANCODE_X, 0x2d); - MAP(SDL_SCANCODE_Y, 0x15); - MAP(SDL_SCANCODE_Z, 0x2c); - MAP(SDL_SCANCODE_DELETE, 0xd3); - MAP(SDL_SCANCODE_KP_0, 0x52); - MAP(SDL_SCANCODE_KP_1, 0x4f); - MAP(SDL_SCANCODE_KP_2, 0x50); - MAP(SDL_SCANCODE_KP_3, 0x51); - MAP(SDL_SCANCODE_KP_4, 0x4b); - MAP(SDL_SCANCODE_KP_5, 0x4c); - MAP(SDL_SCANCODE_KP_CLEAR, 0x4c); - MAP(SDL_SCANCODE_CLEAR, 0x4c); - MAP(SDL_SCANCODE_KP_6, 0x4d); - MAP(SDL_SCANCODE_KP_7, 0x47); - MAP(SDL_SCANCODE_KP_8, 0x48); - MAP(SDL_SCANCODE_KP_9, 0x49); - MAP(SDL_SCANCODE_KP_PERIOD, 0x53); - MAP(SDL_SCANCODE_KP_DIVIDE, 0xb5); - MAP(SDL_SCANCODE_KP_MULTIPLY, 0x37); - MAP(SDL_SCANCODE_KP_MINUS, 0x4a); - MAP(SDL_SCANCODE_KP_PLUS, 0x4e); - MAP(SDL_SCANCODE_KP_ENTER, 0x9c); - //MAP(SDL_SCANCODE_KP_EQUALS, ); - MAP(SDL_SCANCODE_UP, 0xc8); - MAP(SDL_SCANCODE_DOWN, 0xd0); - MAP(SDL_SCANCODE_RIGHT, 0xcd); - MAP(SDL_SCANCODE_LEFT, 0xcb); - MAP(SDL_SCANCODE_INSERT, 0xd2); - MAP(SDL_SCANCODE_HOME, 0xc7); - MAP(SDL_SCANCODE_END, 0xcf); - MAP(SDL_SCANCODE_PAGEUP, 0xc9); - MAP(SDL_SCANCODE_PAGEDOWN, 0xd1); - MAP(SDL_SCANCODE_F1, 0x3b); - MAP(SDL_SCANCODE_F2, 0x3c); - MAP(SDL_SCANCODE_F3, 0x3d); - MAP(SDL_SCANCODE_F4, 0x3e); - MAP(SDL_SCANCODE_F5, 0x3f); - MAP(SDL_SCANCODE_F6, 0x40); - MAP(SDL_SCANCODE_F7, 0x41); - MAP(SDL_SCANCODE_F8, 0x42); - MAP(SDL_SCANCODE_F9, 0x43); - MAP(SDL_SCANCODE_F10, 0x44); - MAP(SDL_SCANCODE_F11, 0x57); - MAP(SDL_SCANCODE_F12, 0x58); - MAP(SDL_SCANCODE_NUMLOCKCLEAR, 0x45); - MAP(SDL_SCANCODE_CAPSLOCK, 0x3a); - MAP(SDL_SCANCODE_SCROLLLOCK, 0x46); - MAP(SDL_SCANCODE_RSHIFT, 0x36); - MAP(SDL_SCANCODE_LSHIFT, 0x2a); - MAP(SDL_SCANCODE_RCTRL, 0x9d); - MAP(SDL_SCANCODE_LCTRL, 0x1d); - MAP(SDL_SCANCODE_RALT, 0xb8); - MAP(SDL_SCANCODE_LALT, 0x38); - MAP(SDL_SCANCODE_LGUI, 0xdb); // win l - MAP(SDL_SCANCODE_RGUI, 0xdc); // win r -// MAP(SDL_SCANCODE_PRINTSCREEN, -2); // 0xaa + 0xb7 - MAP(SDL_SCANCODE_SYSREQ, 0x54); // alt+printscr -// MAP(SDL_SCANCODE_PAUSE, 0xb7); // ctrl+pause - MAP(SDL_SCANCODE_MENU, 0xdd); // win menu? - MAP(SDL_SCANCODE_GRAVE, 0x29); // tilde -#undef MAP -} - - - -// -// initmouse() -- init mouse input -// -void mouseInit(void) -{ - mouseGrabInput(g_mouseEnabled = g_mouseLockedToWindow); // FIXME - SA -} - -// -// uninitmouse() -- uninit mouse input -// -void mouseUninit(void) -{ - mouseGrabInput(0); - g_mouseEnabled = 0; -} - - -// -// grabmouse_low() -- show/hide mouse cursor, lower level (doesn't check state). -// furthermore return 0 if successful. -// - -char grabmouse_low(char a) -{ - /* FIXME: Maybe it's better to make sure that grabmouse_low - is called only when a window is ready? */ - if (sdl_window) - SDL_SetWindowGrab(sdl_window, a ? SDL_TRUE : SDL_FALSE); - return SDL_SetRelativeMouseMode(a ? SDL_TRUE : SDL_FALSE); -} - -// -// grabmouse() -- show/hide mouse cursor -// -void mouseGrabInput(bool grab) -{ - if (appactive && g_mouseEnabled) - { - if ((grab != g_mouseGrabbed) && !grabmouse_low(grab)) - g_mouseGrabbed = grab; - } - else - g_mouseGrabbed = grab; - - inputState.MouseSetPos(0, 0); - SDL_ShowCursor(!grab ? SDL_ENABLE : SDL_DISABLE); - if (grab) GUICapture &= ~1; - else GUICapture |= 1; -} - - -// So this is how the engine handles text input? -// Argh. This is just gross. -int scancodetoasciihack(SDL_Event& ev) -{ - int sc = ev.key.keysym.scancode; - SDL_Keycode keyvalue = ev.key.keysym.sym; - int code = keytranslation[sc]; - // Modifiers that have to be held down to be effective - // (excludes KMOD_NUM, for example). - static const int MODIFIERS = - KMOD_LSHIFT | KMOD_RSHIFT | KMOD_LCTRL | KMOD_RCTRL | - KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI; - - // XXX: see osd.c, OSD_HandleChar(), there are more... - if ( - (sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER || - sc == SDL_SCANCODE_ESCAPE || - sc == SDL_SCANCODE_BACKSPACE || - sc == SDL_SCANCODE_TAB || - (((ev.key.keysym.mod) & MODIFIERS) == KMOD_LCTRL && - (sc >= SDL_SCANCODE_A && sc <= SDL_SCANCODE_Z)))) - { - char keyvalue; - switch (sc) - { - case SDL_SCANCODE_RETURN: case SDL_SCANCODE_KP_ENTER: keyvalue = '\r'; break; - case SDL_SCANCODE_ESCAPE: keyvalue = 27; break; - case SDL_SCANCODE_BACKSPACE: keyvalue = '\b'; break; - case SDL_SCANCODE_TAB: keyvalue = '\t'; break; - default: keyvalue = sc - SDL_SCANCODE_A + 1; break; // Ctrl+A --> 1, etc. - } - } - else - { - /* - Necessary for Duke 3D's method of entering cheats to work without showing IMEs. - SDL_TEXTINPUT is preferable overall, but with bitmap fonts it has no advantage. - */ - // Note that this is not how text input is supposed to be handled! - - if ('a' <= keyvalue && keyvalue <= 'z') - { - if (!!(ev.key.keysym.mod & KMOD_SHIFT) ^ !!(ev.key.keysym.mod & KMOD_CAPS)) - keyvalue -= 'a' - 'A'; - } - else if (ev.key.keysym.mod & KMOD_SHIFT) - { - keyvalue = g_keyAsciiTableShift[code]; - } - else if (ev.key.keysym.mod & KMOD_NUM) // && !(ev.key.keysym.mod & KMOD_SHIFT) - { - switch (keyvalue) - { - case SDLK_KP_1: keyvalue = '1'; break; - case SDLK_KP_2: keyvalue = '2'; break; - case SDLK_KP_3: keyvalue = '3'; break; - case SDLK_KP_4: keyvalue = '4'; break; - case SDLK_KP_5: keyvalue = '5'; break; - case SDLK_KP_6: keyvalue = '6'; break; - case SDLK_KP_7: keyvalue = '7'; break; - case SDLK_KP_8: keyvalue = '8'; break; - case SDLK_KP_9: keyvalue = '9'; break; - case SDLK_KP_0: keyvalue = '0'; break; - case SDLK_KP_PERIOD: keyvalue = '.'; break; - case SDLK_KP_COMMA: keyvalue = ','; break; - } - } - - switch (keyvalue) - { - case SDLK_KP_DIVIDE: keyvalue = '/'; break; - case SDLK_KP_MULTIPLY: keyvalue = '*'; break; - case SDLK_KP_MINUS: keyvalue = '-'; break; - case SDLK_KP_PLUS: keyvalue = '+'; break; - } - } - if (keyvalue >= 0x80) keyvalue = 0; // Sadly ASCII only... - return keyvalue; -} - -static void PostMouseMove(int x, int y) -{ - static int lastx = 0, lasty = 0; - event_t ev = { 0,0,0,0,0,0,0 }; - - ev.x = x; - ev.y = y; - lastx = x; - lasty = y; - if (ev.x | ev.y) - { - ev.type = EV_Mouse; - D_PostEvent(&ev); - } -} - -CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) - -static void MouseRead() -{ - int x, y; - -#if 0 - if (NativeMouse) - { - return; - } -#endif - - SDL_GetRelativeMouseState(&x, &y); - if (!m_noprescale) - { - x *= 3; - y *= 2; - } - if (x | y) - { - PostMouseMove(x, -y); - } -} - -int32_t handleevents_pollsdl(void) -{ - int32_t code, rv = 0, j; - SDL_Event ev; - event_t evt; - - while (SDL_PollEvent(&ev)) - { - switch (ev.type) - { - case SDL_TEXTINPUT: - { - j = 0; - const uint8_t* text = (uint8_t*)ev.text.text; - while ((j = GetCharFromString(text))) - { - code = ev.text.text[j]; - // Fixme: Send an EV_GUI_Event instead and properly deal with Unicode. - if ((GUICapture & 1) && menuactive != MENU_WaitKey) - { - evt = { EV_GUI_Event, EV_GUI_Char, int16_t(j), !!(SDL_GetModState() & KMOD_ALT) }; - D_PostEvent(&evt); - } - } - break; - } - - case SDL_KEYDOWN: - case SDL_KEYUP: - { - if ((GUICapture & 1) && menuactive != MENU_WaitKey) - { - evt = {}; - evt.type = EV_GUI_Event; - evt.subtype = ev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp; - SDL_Keymod kmod = SDL_GetModState(); - evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((kmod & KMOD_ALT) ? GKM_ALT : 0); - - if (evt.subtype == EV_GUI_KeyDown && ev.key.repeat) - { - evt.subtype = EV_GUI_KeyRepeat; - } - - switch (ev.key.keysym.sym) - { - case SDLK_KP_ENTER: evt.data1 = GK_RETURN; break; - case SDLK_PAGEUP: evt.data1 = GK_PGUP; break; - case SDLK_PAGEDOWN: evt.data1 = GK_PGDN; break; - case SDLK_END: evt.data1 = GK_END; break; - case SDLK_HOME: evt.data1 = GK_HOME; break; - case SDLK_LEFT: evt.data1 = GK_LEFT; break; - case SDLK_RIGHT: evt.data1 = GK_RIGHT; break; - case SDLK_UP: evt.data1 = GK_UP; break; - case SDLK_DOWN: evt.data1 = GK_DOWN; break; - case SDLK_DELETE: evt.data1 = GK_DEL; break; - case SDLK_ESCAPE: evt.data1 = GK_ESCAPE; break; - case SDLK_F1: evt.data1 = GK_F1; break; - case SDLK_F2: evt.data1 = GK_F2; break; - case SDLK_F3: evt.data1 = GK_F3; break; - case SDLK_F4: evt.data1 = GK_F4; break; - case SDLK_F5: evt.data1 = GK_F5; break; - case SDLK_F6: evt.data1 = GK_F6; break; - case SDLK_F7: evt.data1 = GK_F7; break; - case SDLK_F8: evt.data1 = GK_F8; break; - case SDLK_F9: evt.data1 = GK_F9; break; - case SDLK_F10: evt.data1 = GK_F10; break; - case SDLK_F11: evt.data1 = GK_F11; break; - case SDLK_F12: evt.data1 = GK_F12; break; - default: - if (ev.key.keysym.sym < 256) - { - evt.data1 = ev.key.keysym.sym; - } - break; - } - if (evt.data1 < 128) - { - evt.data1 = toupper(evt.data1); - D_PostEvent(&evt); - } - } - else - { - auto const& sc = ev.key.keysym.scancode; - code = keytranslation[sc]; - - // The pause key generates a release event right after - // the pressing one. As a result, it gets unseen - // by the game most of the time. - if (code == 0x59 && ev.type == SDL_KEYUP) // pause - break; - - int keyvalue = ev.type == SDL_KEYDOWN ? scancodetoasciihack(ev) : 0; - event_t evt = { (uint8_t)(ev.type == SDL_KEYUP ? EV_KeyUp : EV_KeyDown), 0, (int16_t)code, (int16_t)keyvalue }; - D_PostEvent(&evt); - } - - break; - } - - case SDL_MOUSEWHEEL: - if (GUICapture) - { - evt.type = EV_GUI_Event; - - if (ev.wheel.y == 0) - evt.subtype = ev.wheel.x > 0 ? EV_GUI_WheelRight : EV_GUI_WheelLeft; - else - evt.subtype = ev.wheel.y > 0 ? EV_GUI_WheelUp : EV_GUI_WheelDown; - - SDL_Keymod kmod = SDL_GetModState(); - evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((kmod & KMOD_ALT) ? GKM_ALT : 0); - - D_PostEvent(&evt); - } - else - { - // This never sends keyup events. They must be delayed for this to work with game buttons. - if (ev.wheel.y > 0) - { - evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELUP }; - D_PostEvent(&evt); - } - if (ev.wheel.y < 0) - { - evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELDOWN }; - D_PostEvent(&evt); - } - if (ev.wheel.x > 0) - { - evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELRIGHT }; - D_PostEvent(&evt); - } - if (ev.wheel.y < 0) - { - evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELLEFT }; - D_PostEvent(&evt); - } - } - break; - - case SDL_WINDOWEVENT: - switch (ev.window.event) - { - case SDL_WINDOWEVENT_FOCUS_GAINED: - case SDL_WINDOWEVENT_FOCUS_LOST: - appactive = (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED); - if (g_mouseGrabbed && g_mouseEnabled) - grabmouse_low(appactive); - break; - - case SDL_WINDOWEVENT_MOVED: - { - WindowMoved(ev.window.data1, ev.window.data2); - break; - } - case SDL_WINDOWEVENT_ENTER: - g_mouseInsideWindow = 1; - break; - case SDL_WINDOWEVENT_LEAVE: - g_mouseInsideWindow = 0; - break; - } - - break; - - case SDL_MOUSEMOTION: - //case SDL_JOYBALLMOTION: - { - // The menus need this, even in non GUI-capture mode - event_t event; - event.data1 = ev.motion.x; - event.data2 = ev.motion.y; - - //screen->ScaleCoordsFromWindow(event.data1, event.data2); - - event.type = EV_GUI_Event; - event.subtype = EV_GUI_MouseMove; - - SDL_Keymod kmod = SDL_GetModState(); - event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((kmod & KMOD_ALT) ? GKM_ALT : 0); - - D_PostEvent(&event); - break; - } - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - - if (!GUICapture) - { - evt.type = ev.type == SDL_MOUSEBUTTONDOWN ? EV_KeyDown : EV_KeyUp; - - switch (ev.button.button) - { - case SDL_BUTTON_LEFT: evt.data1 = KEY_MOUSE1; break; - case SDL_BUTTON_MIDDLE: evt.data1 = KEY_MOUSE3; break; - case SDL_BUTTON_RIGHT: evt.data1 = KEY_MOUSE2; break; - case SDL_BUTTON_X1: evt.data1 = KEY_MOUSE4; break; - case SDL_BUTTON_X2: evt.data1 = KEY_MOUSE5; break; - case 6: evt.data1 = KEY_MOUSE6; break; - case 7: evt.data1 = KEY_MOUSE7; break; - case 8: evt.data1 = KEY_MOUSE8; break; - //default: printf("SDL mouse button %s %d\n", sev.type == SDL_MOUSEBUTTONDOWN ? "down" : "up", sev.button.button); break; - } - - if (evt.data1 != 0) - { - D_PostEvent(&evt); - } - } - else if ((ev.button.button >= SDL_BUTTON_LEFT && ev.button.button <= SDL_BUTTON_X2)) - { - int x, y; - SDL_GetMouseState(&x, &y); - - evt.type = EV_GUI_Event; - evt.data1 = x; - evt.data2 = y; - - //screen->ScaleCoordsFromWindow(event.data1, event.data2); - - if (ev.type == SDL_MOUSEBUTTONDOWN) - { - switch (ev.button.button) - { - case SDL_BUTTON_LEFT: evt.subtype = EV_GUI_LButtonDown; break; - case SDL_BUTTON_MIDDLE: evt.subtype = EV_GUI_MButtonDown; break; - case SDL_BUTTON_RIGHT: evt.subtype = EV_GUI_RButtonDown; break; - case SDL_BUTTON_X1: evt.subtype = EV_GUI_BackButtonDown; break; - case SDL_BUTTON_X2: evt.subtype = EV_GUI_FwdButtonDown; break; - default: assert(false); evt.subtype = EV_GUI_None; break; - } - } - else - { - switch (ev.button.button) - { - case SDL_BUTTON_LEFT: evt.subtype = EV_GUI_LButtonUp; break; - case SDL_BUTTON_MIDDLE: evt.subtype = EV_GUI_MButtonUp; break; - case SDL_BUTTON_RIGHT: evt.subtype = EV_GUI_RButtonUp; break; - case SDL_BUTTON_X1: evt.subtype = EV_GUI_BackButtonUp; break; - case SDL_BUTTON_X2: evt.subtype = EV_GUI_FwdButtonUp; break; - default: assert(false); evt.subtype = EV_GUI_None; break; - } - } - - SDL_Keymod kmod = SDL_GetModState(); - evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((kmod & KMOD_ALT) ? GKM_ALT : 0); - - D_PostEvent(&evt); - } - break; - - case SDL_JOYHATMOTION: - { - int32_t hatvals[16] = { - -1, // centre - 0, // up 1 - 9000, // right 2 - 4500, // up+right 3 - 18000, // down 4 - -1, // down+up!! 5 - 13500, // down+right 6 - -1, // down+right+up!! 7 - 27000, // left 8 - 27500, // left+up 9 - -1, // left+right!! 10 - -1, // left+right+up!! 11 - 22500, // left+down 12 - -1, // left+down+up!! 13 - -1, // left+down+right!! 14 - -1, // left+down+right+up!! 15 - }; - if (appactive && ev.jhat.hat < joystick.numHats) - joystick.pHat[ev.jhat.hat] = hatvals[ev.jhat.value & 15]; - break; - } - - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: -#if 0 // SDL_MAJOR_VERSION >= 2 - if (joystick.isGameController) - break; - fallthrough__; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: -#endif - if (!GUICapture) - { - evt.type = ev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp; - evt.data1 = KEY_FIRSTJOYBUTTON + ev.jbutton.button; - if (evt.data1 != 0) - D_PostEvent(&evt); - } - break; - - case SDL_QUIT: - throw ExitEvent(0); // completely bypass the hackery in the games to block Alt-F4. - return -1; - - default: - break; - } - } - MouseRead(); - - return rv; -} - -int32_t handleevents(void) -{ - int32_t rv; - - if (inputchecked && g_mouseEnabled) - { - // This is a horrible crutch - if (inputState.mouseReadButtons() & WHEELUP_MOUSE) - { - event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELUP }; - D_PostEvent(&ev); - } - if (inputState.mouseReadButtons() & WHEELDOWN_MOUSE) - { - event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELDOWN }; - D_PostEvent(&ev); - } - if (inputState.mouseReadButtons() & WHEELLEFT_MOUSE) - { - event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELLEFT }; - D_PostEvent(&ev); - } - if (inputState.mouseReadButtons() & WHEELRIGHT_MOUSE) - { - event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELRIGHT }; - D_PostEvent(&ev); - } - } - - rv = handleevents_pollsdl(); - - inputchecked = 0; - timerUpdateClock(); - //I_ProcessJoysticks(); - return rv; -} - - -int32_t handleevents_peekkeys(void) -{ - SDL_PumpEvents(); - - return SDL_PeepEvents(NULL, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN); -} - -void I_SetMouseCapture() -{ - // Clear out any mouse movement. - SDL_CaptureMouse(SDL_TRUE); -} - -void I_ReleaseMouseCapture() -{ - SDL_CaptureMouse(SDL_FALSE); -}

q c #0056C4", +",q c #004AA7", +"'q c #02469E", +")q c #143F8D", +"!q c #000C34", +"~q c #636F83", +"{q c #E2E1E0", +"]q c #BEBDBC", +"^q c #1C1D20", +"/q c #0E4090", +"(q c #0046A1", +"_q c #004FB4", +":q c #004399", +"r c #15387E", +",r c #0E2146", +"'r c #031333", +")r c #696A70", +"!r c #D6D4D2", +"~r c #D1CFCB", +"{r c #717885", +"]r c #0B1B3D", +"^r c #0D214C", +"/r c #15377C", +"(r c #0C3886", +"_r c #003483", +":r c #003C99", +"s c #D0CFCD", +",s c #D7D5D1", +"'s c #A0A2A6", +")s c #273753", +"!s c #123882", +"~s c #032D72", +"{s c #0045B4", +"]s c #022E85", +"^s c #0047BA", +"/s c #053F9F", +"(s c #788292", +"_s c #C8C7C6", +":s c #0E43A0", +"t c #0042A6", +",t c #0E3F95", +"'t c #243553", +")t c #9EA0A2", +"!t c #BBBAB9", +"~t c #45423D", +"{t c #000019", +"]t c #0A1833", +"^t c #6B6B6B", +"/t c #B6B3AE", +"(t c #A5A6A6", +"_t c #505969", +":t c #000E35", +"u c #013D87", +",u c #013E8A", +"'u c #01397F", +")u c #013A81", +"!u c #014193", +"~u c #014497", +"{u c #013E8B", +"]u c #013A82", +"^u c #01479E", +"/u c #0149A4", +"(u c #0150B3", +"_u c #0154BD", +":u c #013D89", +"v c #C6C5C4", +",v c #0E4196", +"'v c #004099", +")v c #0046A6", +"!v c #004AB1", +"~v c #004BB1", +"{v c #004BB5", +"]v c #004EBB", +"^v c #0E4197", +"/v c #02173E", +"(v c #20304F", +"_v c #838387", +":v c #969694", +"w c #031840", +",w c #1E2E4D", +"'w c #7A7B7D", +")w c #8D8C8B", +"!w c #051C49", +"~w c #0C1B38", +"{w c #626467", +"]w c #A09E99", +"^w c #999A9A", +"/w c #B8B8B6", +"(w c #B3B3B0", +"_w c #485060", +":w c #001037", +"x c #6A6D72", +",x c #102040", +"'x c #133472", +")x c #1A3E88", +"!x c #012F7C", +"~x c #002460", +"{x c #00286C", +"]x c #002665", +"^x c #002767", +"/x c #002769", +"(x c #0A1449", +"_x c #0B1347", +":x c #00328F", +"y c #0148AA", +",y c #013C8C", +"'y c #013175", +")y c #013377", +"!y c #01357D", +"~y c #01377E", +"{y c #01357E", +"]y c #013378", +"^y c #0249A9", +"/y c #024DB3", +"(y c #01439B", +"_y c #024BAD", +":y c #013C8D", +"z c #023A81", +",z c #024398", +"'z c #0350B3", +")z c #0351B7", +"!z c #024295", +"~z c #0348A2", +"{z c #0247A2", +"]z c #0248A2", +"^z c #024BA8", +"/z c #03469D", +"(z c #074499", +"_z c #717B8E", +":z c #EEEEED", +"A c #747372", +",A c #707070", +"'A c #989692", +")A c #4C515D", +"!A c #143B88", +"~A c #03317F", +"{A c #061C5C", +"]A c #091752", +"^A c #002C7D", +"/A c #0048C1", +"(A c #0F3D90", +"_A c #BEC1C6", +":A c #E4E3E1", +"B c #0F254F", +",B c #A1A0A1", +"'B c #A6A39E", +")B c #64676E", +"!B c #0F1F3D", +"~B c #143574", +"{B c #032F7A", +"]B c #00286E", +"^B c #002563", +"/B c #022977", +"(B c #3B3B3B", +"_B c #0E0E3C", +":B c #081D5C", +"C c #0550B0", +",C c #0456BC", +"'C c #0450AE", +")C c #044EAC", +"!C c #04499F", +"~C c #044FAD", +"{C c #024EAD", +"]C c #0B1D3D", +"^C c #44516D", +"/C c #E9E7E4", +"(C c #D0D0CF", +"_C c #BCBCBB", +":C c #B9BABB", +"D c #6E6F71", +",D c #727375", +"'D c #727374", +")D c #757575", +"!D c #969595", +"~D c #828487", +"{D c #8B8D90", +"]D c #898A8C", +"^D c #868789", +"/D c #848587", +"(D c #858688", +"_D c #828385", +":D c #818284", +"E c #989796", +",E c #959493", +"'E c #9E9D9C", +")E c #969695", +"!E c #989897", +"~E c #A09F9E", +"{E c #9E9E9D", +"]E c #9A9A99", +"^E c #959594", +"/E c #9B9B9A", +"(E c #9C9B9A", +"_E c #949392", +":E c #939392", +"F c #103986", +",F c #000B35", +"'F c #566278", +")F c #CBCAC7", +"!F c #C9C7C6", +"~F c #C8C8C9", +"{F c #BBBCBD", +"]F c #A8AAAB", +"^F c #ADAFB1", +"/F c #A7A8AA", +"(F c #A1A3A6", +"_F c #94999B", +":F c #919698", +"G c #424242", +",G c #505050", +"'G c #5E5E5E", +")G c #6A6A6A", +"!G c #383838", +"~G c #A2A19D", +"{G c #3E434D", +"]G c #032B73", +"^G c #002A77", +"/G c #002567", +"(G c #00276D", +"_G c #002C7B", +":G c #00286F", +"H c #0549A0", +",H c #0651B1", +"'H c #084FAE", +")H c #47546E", +"!H c #B6B6B8", +"~H c #B6B8B7", +"{H c #ACADAF", +"]H c #A3A5A6", +"^H c #9EA1A2", +"/H c #9EA2A4", +"(H c #9C9FA2", +"_H c #979B9E", +":H c #9A9EA1", +"I c #00092C", +",I c #032F73", +"'I c #002C6C", +")I c #003075", +"!I c #002E74", +"~I c #002E72", +"{I c #00327B", +"]I c #003177", +"^I c #002F76", +"/I c #002D71", +"(I c #002A68", +"_I c #002864", +":I c #002D6F", +"J c #032768", +",J c #002D7F", +"'J c #09174F", +")J c #0D0F3F", +"!J c #090F3B", +"~J c #0D1043", +"{J c #041C59", +"]J c #01338A", +"^J c #14377B", +"/J c #253555", +"(J c #A0A4AA", +"_J c #BEBEBC", +":J c #AFAFB0", +"K c #B4B5B3", +",K c #9BA0A2", +"'K c #93979A", +")K c #909598", +"!K c #909698", +"~K c #92979C", +"{K c #B2B5B6", +"]K c #2A2A2A", +"^K c #A3A29E", +"/K c #053374", +"(K c #023172", +"_K c #033272", +":K c #03377F", +"L c #8D9398", +",L c #A9ACAE", +"'L c #B8B9BB", +")L c #414650", +"!L c #143B83", +"~L c #073375", +"{L c #04357A", +"]L c #053274", +"^L c #043170", +"/L c #05306F", +"(L c #05367B", +"_L c #053377", +":L c #05377E", +"M c #7B7A78", +",M c #7E7D7B", +"'M c #7C7B7A", +")M c #7B7B79", +"!M c #7F7E7D", +"~M c #7E7D7C", +"{M c #838280", +"]M c #898886", +"^M c #8B8A89", +"/M c #949492", +"(M c #9F9F9D", +"_M c #9B9A98", +":M c #9F9E9C", +"N c #676D78", +",N c #656C76", +"'N c #646A75", +")N c #656A76", +"!N c #606671", +"~N c #606570", +"{N c #626772", +"]N c #5E636F", +"^N c #5E6470", +"/N c #5C626D", +"(N c #5B606C", +"_N c #5B616C", +":N c #595E69", +"O c #122549", +",O c #0E2247", +"'O c #143880", +")O c #032561", +"!O c #00205B", +"~O c #00215D", +"{O c #001E54", +"]O c #00205C", +"^O c #052066", +"/O c #01246B", +"(O c #002D80", +"_O c #002C80", +":O c #002E83", +"P c #0B469A", +",P c #0A4597", +"'P c #0B4291", +")P c #194190", +"!P c #16397E", +"~P c #041F46", +"{P c #052553", +"]P c #072B5D", +"^P c #0A2665", +"/P c #093078", +"(P c #094394", +"_P c #0A4394", +":P c #0B4496", +"