diff --git a/c++/palanteer.h b/c++/palanteer.h index 2028f56..7b06a80 100644 --- a/c++/palanteer.h +++ b/c++/palanteer.h @@ -152,8 +152,12 @@ // Exit function when a crash occurs, called after logging the crash in Palanteer, flushing the recording and restoring signals // Default is a call to abort(). #ifndef PL_IMPL_CRASH_EXIT_FUNC +#ifdef __APPLE__ +#define PL_IMPL_CRASH_EXIT_FUNC() abort() +#else #define PL_IMPL_CRASH_EXIT_FUNC() quick_exit(1) #endif +#endif // Print error function (assertions, signals). // On Windows, shall probably be redirected on a MessageBox diff --git a/server/base/bsGl.h b/server/base/bsGl.h index 5c02e51..2c5bf99 100644 --- a/server/base/bsGl.h +++ b/server/base/bsGl.h @@ -31,6 +31,9 @@ #ifdef __linux__ #include "bsOsGlLnx.h" #endif +#ifdef __APPLE__ +#include "bsOsGlMac.h" +#endif #ifdef _WIN32 #include "bsOsGlWin.h" #endif diff --git a/server/base/bsNetwork.h b/server/base/bsNetwork.h index 2df8123..a3cd0c5 100644 --- a/server/base/bsNetwork.h +++ b/server/base/bsNetwork.h @@ -36,6 +36,17 @@ typedef int bsSocket_t; #endif +// MacOS +#ifdef __APPLE__ +#include +#include +#include +#include +#define bsIsSocketValid(s) ((s)>=0) +#define bsSocketError -1 +typedef int bsSocket_t; +#endif + // Windows #ifdef _WIN32 #include diff --git a/server/base/bsOs.h b/server/base/bsOs.h index 8becff6..7cfcead 100644 --- a/server/base/bsOs.h +++ b/server/base/bsOs.h @@ -52,10 +52,11 @@ class bsOsHandler { virtual void eventChar(char16_t codepoint) = 0; virtual void eventKeyPressed (bsKeycode keycode, bsKeyModState kms) = 0; virtual void eventKeyReleased(bsKeycode keycode, bsKeyModState kms) = 0; - virtual void eventButtonPressed (int buttonId, int x, int y, bsKeyModState kms) = 0; // 0=left, 1=middle, 2=right + virtual void eventButtonPressed (int buttonId, int x, int y, bsKeyModState kms) = 0; // 1=left, 2=middle, 3=right virtual void eventButtonReleased(int buttonId, int x, int y, bsKeyModState kms) = 0; virtual void eventMouseMotion (int x, int y) = 0; virtual void eventWheelScrolled (int x, int y, int steps, bsKeyModState kms) = 0; + virtual void eventModifiersChanged (bsKeyModState kms) = 0; // Others virtual bool isVisible(void) const = 0; virtual void quit(void) = 0; @@ -130,7 +131,7 @@ const char* strcasestr(const char* s, const char* sToFind); #define bsOsFseek _fseeki64 #define bsOsFtell _ftelli64 -#else // Linux case +#else // Linux/Mac case #define PL_DIR_SEP "/" #define PL_DIR_SEP_CHAR '/' diff --git a/server/base/bsOsGlMac.h b/server/base/bsOsGlMac.h new file mode 100644 index 0000000..2527cb6 --- /dev/null +++ b/server/base/bsOsGlMac.h @@ -0,0 +1,27 @@ +// The MIT License (MIT) +// +// Copyright(c) 2021, Damien Feneyrou +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#define GL_SILENCE_DEPRECATION +#define GL_GLEXT_PROTOTYPES 1 +#include diff --git a/server/base/bsOsMac.mm b/server/base/bsOsMac.mm new file mode 100644 index 0000000..d7592be --- /dev/null +++ b/server/base/bsOsMac.mm @@ -0,0 +1,1047 @@ +// The MIT License (MIT) +// +// Copyright(c) 2021, Damien Feneyrou +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifdef __APPLE__ + +#ifndef BS_NO_GRAPHIC +#define GL_SILENCE_DEPRECATION +#import +#endif + +// System +#include +#include +#include +#include +#include +#include +#include + +// Internal +#include "bs.h" +#include "bsOs.h" + +#ifndef BS_NO_GRAPHIC +#include "bsOsGlMac.h" +#include "bsKeycode.h" +#endif // ifndef BS_NO_GRAPHIC + +#include "bsString.h" +#include "bsTime.h" + + +#ifndef BS_NO_GRAPHIC + +// Originally from Carbon's Event.h. Copied from https://github.com/itfrombit/osx_handmade/blob/85e9828512d0e87dfe762494e30e4e800a930d47/code/osx_handmade_events.h + +/* + * Summary: + * Virtual keycodes + * + * Discussion: + * These constants are the virtual keycodes defined originally in + * Inside Mac Volume V, pg. V-191. They identify physical keys on a + * keyboard. Those constants with "ANSI" in the name are labeled + * according to the key position on an ANSI-standard US keyboard. + * For example, kVK_ANSI_A indicates the virtual keycode for the key + * with the letter 'A' in the US keyboard layout. Other keyboard + * layouts may have the 'A' key label on a different physical key; + * in this case, pressing 'A' will generate a different virtual + * keycode. + */ +enum { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; + +/* keycodes for keys that are independent of keyboard layout*/ +enum { + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Alternate = 0x3A, + kVK_Control = 0x3B, + kVK_RightCommand = 0x36, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightAlternate = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +// Global graphical context for Cocoa +// ================================== + +static struct { + // Render context + NSOpenGLContext* openGLContext = 0; + NSWindow* windowHandle; + + int windowWidth = -1; + int windowHeight = -1; + + bsKeycode kc; + bsKeyModState kms; + + NSMutableAttributedString* markedText; + + // Others + bsString appPath; + // Application + bsOsHandler* osHandler = 0; +} gGlob; + +static struct { + ClipboardType ownedType = ClipboardType::NONE; + bsString ownedData; + bool isReqReceived = false; + bsStringUtf16 reqData; +} gClip; + + +// Constant for empty ranges in NSTextInputClient +static const NSRange kEmptyRange = { NSNotFound, 0 }; + +@interface OpenGLInputView : NSOpenGLView +@end + +@implementation OpenGLInputView + +-(BOOL)acceptsFirstResponder{ + return TRUE; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +- (void)keyDown:(NSEvent*)event { + // We need to do this or we don't get insertText + [self interpretKeyEvents:@[event]]; +} + +- (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange { + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point { + return 0; +} + +- (void)doCommandBySelector:(nonnull SEL)selector { +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange { + return NSMakeRect(0.0, 0.0, 0.0, 0.0); +} + +- (BOOL)hasMarkedText { + return [gGlob.markedText length] > 0; +} + +- (void)insertText:(nonnull id)string replacementRange:(NSRange)replacementRange { + NSString* characters; + + if ([string isKindOfClass:[NSAttributedString class]]) { + characters = [string string]; + } else { + characters = (NSString*)string; + } + + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) { + uint16_t codepoint = 0; + + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF16StringEncoding + options:0 + range:range + remainingRange:&range]) { + if (codepoint!=0 && bsIsUnicodeDisplayable(codepoint)) { gGlob.osHandler->eventChar(codepoint); } + } + } +} + +- (NSRange)markedRange { + if ([gGlob.markedText length] > 0) { + return NSMakeRange(0, [gGlob.markedText length] - 1); + } else { + return kEmptyRange; + } +} + +- (NSRange)selectedRange { + return kEmptyRange; +} + +- (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + [gGlob.markedText release]; + if ([string isKindOfClass:[NSAttributedString class]]) { + gGlob.markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; + } else { + gGlob.markedText = [[NSMutableAttributedString alloc] initWithString:string]; + } +} + +- (void)unmarkText { + [[gGlob.markedText mutableString] setString:@""]; +} + +- (nonnull NSArray *)validAttributesForMarkedText { + // No attributes + return [NSArray array]; +} +@end + + +@interface WindowDelegate : NSObject +@end + +@implementation WindowDelegate +- (void)applicationDidFinishLaunching:(NSNotification *)notification { +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender{ + return YES; +} + +- (void)applicationWillTerminate:(NSNotification *)notification { +} + + +-(void)windowDidResize:(NSNotification *)notification { + // Unlike willResize or willZoom, this gets called after any resize for any reason. + NSRect windowRect = [gGlob.windowHandle frame]; + NSRect contentRect = [gGlob.windowHandle contentRectForFrameRect:windowRect]; + + gGlob.windowWidth = (int)contentRect.size.width; + gGlob.windowHeight = (int)contentRect.size.height; + + if (gGlob.osHandler) { + gGlob.osHandler->notifyWindowSize(gGlob.windowWidth * 2, gGlob.windowHeight * 2); + } +} + +-(void)windowDidBecomeKey:(NSNotification *)notification { + if (!gGlob.osHandler) { + return; + } + if(!gGlob.osHandler->isVisible()) { + gGlob.osHandler->notifyMapped(); + } + gGlob.osHandler->notifyEnter(gGlob.kms); +} + +-(void)windowDidResignKey:(NSNotification *)notification { + if (!gGlob.osHandler) { + return; + } + if(gGlob.osHandler->isVisible()) { + osHideWindow(); + gGlob.osHandler->notifyUnmapped(); + } + gGlob.osHandler->notifyLeave(gGlob.kms); +} + +- (void)windowWillClose:(NSNotification *)notification { + if (!gGlob.osHandler) { + return; + } + + gGlob.osHandler->quit(); +} +@end + + +// Window creation recipe (collection of pieces from internet) +// =========================================================== + +void +osCreateWindow(const char* windowTitle, const char* configName, float ratioLeft, float ratioTop, float ratioRight, float ratioBottom, bool overrideWindowManager) +{ + plAssert(ratioLeft>=0. && ratioLeft<=1.); + plAssert(ratioTop>=0. && ratioTop<=1.); + plAssert(ratioRight>=0. && ratioRight<=1.); + plAssert(ratioBottom>=0. && ratioBottom<=1.); + plAssert(ratioLefteventKeyPressed(kc, kms); + gGlob.kms = kms; + break; + case NSEventTypeKeyUp: + kms = modifierFlagsToModState([event modifierFlags]); + kc = keysymToKeycode([event keyCode]); + gGlob.osHandler->eventKeyReleased(kc, kms); + gGlob.kms = kms; + break; + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + loc = [event locationInWindow]; + gGlob.osHandler->eventMouseMotion((int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2); + break; + case NSEventTypeLeftMouseDown: + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonPressed(1, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + break; + case NSEventTypeRightMouseDown: + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonPressed(3, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + break; + case NSEventTypeOtherMouseDown: { + if ([event buttonNumber] == 2) { + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonPressed(2, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + } + break; + } + case NSEventTypeLeftMouseUp: + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonReleased(1, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + break; + case NSEventTypeRightMouseUp: + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonReleased(3, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + break; + case NSEventTypeOtherMouseUp: { + if ([event buttonNumber] == 2) { + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventButtonReleased(2, (int)loc.x*2, (gGlob.windowHeight - (int)loc.y)*2, kms); + gGlob.kms = kms; + } + break; + } + case NSEventTypeScrollWheel: + kms = modifierFlagsToModState([event modifierFlags]); + loc = [event locationInWindow]; + gGlob.osHandler->eventWheelScrolled((int)loc.x, (int)loc.y, ([event scrollingDeltaY]<0)? +1:-1, kms); + gGlob.kms = kms; + break; + case NSEventTypeFlagsChanged: + kms = modifierFlagsToModState([event modifierFlags]); + gGlob.osHandler->eventModifiersChanged(kms); + default: + break; + } + [NSApp sendEvent:event]; + } while (event != nil); +} + + +// Mac entry point +int +main(int argc, char* argv[]) +{ + return bsBootstrap(argc, argv); +} + +#endif // ifndef BS_NO_GRAPHIC + + + +// Misc OS +// ======= + +bsDate +osGetDate(void) +{ + time_t currentTime; + time(¤tTime); + struct tm* t = localtime(¤tTime); + plAssert(t); + return bsDate{1900+t->tm_year, 1+t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec}; +} + + + +// File system +// =========== + +bsString +osGetCurrentPath(void) +{ + char cwd[PATH_MAX]; + if(getcwd(cwd, sizeof(cwd)) != NULL) return bsString(&cwd[0]); + return ""; +} + + +bool +osFileExists(const bsString& path) +{ + struct stat st; + return stat(path.toChar(),&st)==0 && S_ISREG(st.st_mode); +} + + +bool +osDirectoryExists(const bsString& path) +{ + struct stat st; + return stat(path.toChar(),&st)==0 && S_ISDIR(st.st_mode); +} + + +bsString +osGetDirname(const bsString& path) +{ + int idx = path.rfindChar('/'); + return (idx<0)? path : path.subString(0, idx); +} + + +bsString +osGetBasename(const bsString& path) +{ + int idx = path.rfindChar('/'); + return (idx<0)? path : path.subString(idx+1, path.size()); +} + + +u32 +osGetDriveBitmap(void) +{ + // No concept of "drive" in MacOS + return 0; +} + + +FILE* +osFileOpen(const bsString& path, const char* mode) +{ + return fopen(path.toChar(), mode); // Utf-8 does not change the fopen prototype on Mac! +} + + +bool +osLoadFileContent(const bsString& path, bsVec& buffer, int maxSize) +{ + // Get file size + buffer.clear(); + size_t fileSize = osGetSize(path); + if(fileSize==0) return false; + if(maxSize>0 && fileSize>(size_t)maxSize) fileSize = maxSize; + // Open it + FILE* fh = osFileOpen(path, "rb"); + if(!fh) return false; + // Read it in buffer + buffer.resize(fileSize); + if(fread(&buffer[0], 1, fileSize, fh)!=fileSize) { + buffer.clear(); + fclose(fh); + return false; + } + // Close and return + fclose(fh); + return true; +} + +bsDirStatusCode +osMakeDir(const bsString& path) +{ + bsString tmp(path); + if(!tmp.empty() && tmp.back()=='/') tmp.pop_back(); + if(tmp.empty()) return bsDirStatusCode::FAILURE; + for(u8* p=&tmp[1]; *p; ++p) { + if(*p=='/') { + *p = 0; + int status = mkdir(tmp.toChar(), S_IRWXU); + if(status!=0 && errno!=EEXIST) { + if(errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + return bsDirStatusCode::FAILURE; + } + *p = '/'; + } + } + int status = mkdir(tmp.toChar(), S_IRWXU); + if(status==0) return bsDirStatusCode::OK; + else if(errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + else if(errno==EEXIST) return bsDirStatusCode::ALREADY_EXISTS; + return bsDirStatusCode::FAILURE; +} + + +bsDirStatusCode +osGetDirContent(const bsString& path, bsVec& entries) +{ + // Open directory + entries.clear(); + DIR* dir = opendir(path.toChar()); + if(!dir) { + if (errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + else if(errno==ENOTDIR) return bsDirStatusCode::NOT_A_DIRECTORY; + else if(errno==ENOENT) return bsDirStatusCode::DOES_NOT_EXIST; + return bsDirStatusCode::FAILURE; + } + + struct dirent* fe; + while((fe=readdir(dir))) { + if(fe->d_type==DT_REG || (fe->d_type==DT_DIR && strcmp(fe->d_name, ".")!=0 && strcmp(fe->d_name, "..")!=0)) { + entries.push_back(bsDirEntry{fe->d_name, fe->d_type==DT_DIR}); + } + } + + closedir(dir); + return bsDirStatusCode::OK; +} + + +size_t +osGetSize(const bsString& path) +{ + // Get infos on this path + struct stat statbuf; + if(stat(path.toChar(), &statbuf)==-1) { + return 0; // Return 0 in case of problem + } + + // File case + if(statbuf.st_mode&S_IFREG) { + return statbuf.st_size; // Return the size of the file + } + if(!(statbuf.st_mode&S_IFDIR)) { + return 0; // Not a file nor a directory + } + + // Directory case (recursive) + size_t dirSize = 0; + DIR* dir = opendir(path.toChar()); + if(!dir) return 0; + struct dirent* fe; + while((fe=readdir(dir))) { + if(fe->d_type==DT_REG || (fe->d_type==DT_DIR && strcmp(fe->d_name, ".")!=0 && strcmp(fe->d_name, "..")!=0)) { + dirSize += osGetSize((path+"/")+bsString(fe->d_name)); + } + } + closedir(dir); + return dirSize; +} + + +bsDate +osGetCreationDate(const bsString& path) +{ + struct stat s; + if(stat(path.toChar(), &s)==-1) return bsDate(); // If error, all fields are zero + struct tm* t = localtime(&s.st_mtime); + plAssert(t); + return bsDate{1900+t->tm_year, 1+t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec}; +} + + +bsDirStatusCode +osRemoveFile(const bsString& path) +{ + if(unlink(path.toChar())==0) return bsDirStatusCode::OK; + else if (errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + else if(errno==ENOENT) return bsDirStatusCode::DOES_NOT_EXIST; + return bsDirStatusCode::FAILURE; +} + + +bsDirStatusCode +osRemoveDir(const bsString& path, bool onlyIfEmpty) +{ + // Open directory + DIR* dir = opendir(path.toChar()); + if(!dir) { + if (errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + else if(errno==ENOTDIR) return bsDirStatusCode::NOT_A_DIRECTORY; + else if(errno==ENOENT) return bsDirStatusCode::DOES_NOT_EXIST; + return bsDirStatusCode::FAILURE; + } + // Empty it (non recursive) + if(!onlyIfEmpty) { + struct dirent* fe; + while((fe=readdir(dir))) { + if(fe->d_type==DT_REG || (fe->d_type==DT_DIR && strcmp(fe->d_name, ".")!=0 && strcmp(fe->d_name, "..")!=0)) { + unlink(((path+"/")+fe->d_name).toChar()); + } + } + } + closedir(dir); + + // Remove it + if(rmdir(path.toChar())==0) return bsDirStatusCode::OK; + else if(errno==EACCES) return bsDirStatusCode::PERMISSION_DENIED; + else if(errno==ENOTDIR) return bsDirStatusCode::NOT_A_DIRECTORY; + else if(errno==ENOENT) return bsDirStatusCode::DOES_NOT_EXIST; + return bsDirStatusCode::FAILURE; +} + + +#endif // __APPLE__ diff --git a/server/viewer/CMakeLists.txt b/server/viewer/CMakeLists.txt index 9384a02..481d0d9 100644 --- a/server/viewer/CMakeLists.txt +++ b/server/viewer/CMakeLists.txt @@ -17,6 +17,10 @@ if(WIN32) message("Palanteer feature 'stacktrace' enabled for viewer") add_definitions(-DPL_IMPL_STACKTRACE=1) endif() +elseif(APPLE) + # Apple: openGL is required + find_package(OpenGL REQUIRED) + set(PALANTEER_LIBS OpenGL::GL) else(WIN32) # Linux: openGL and X11 are required find_package(OpenGL REQUIRED) @@ -65,7 +69,13 @@ if(MSVC) file(GLOB ICON_RC CONFIGURE_DEPENDS *.rc) else() add_compile_options(-Wall -Wextra) - set(cxx_flags -fno-rtti -fno-exceptions -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-function) + + if(APPLE) + set(cxx_flags -fno-rtti -fno-exceptions -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-function -x objective-c++) + else() + set(cxx_flags -fno-rtti -fno-exceptions -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-function) + endif() + add_compile_options("$<$:${cxx_flags}>") # We have some C files too (in 3rd party zstd) endif() @@ -77,7 +87,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CUSTOM_FLAGS}") # ============== file(GLOB_RECURSE ZSTD_SRC CONFIGURE_DEPENDS ../external/zstd/*.c ../external/zstd/*.h) file(GLOB IMGUI_SRC CONFIGURE_DEPENDS ../external/imgui/*.cpp ../external/imgui/*.h) -file(GLOB BASE_SRC CONFIGURE_DEPENDS ../base/*.cpp ../base/*.h) +file(GLOB BASE_SRC CONFIGURE_DEPENDS ../base/*.cpp ../base/*.h ../base/*.mm) file(GLOB COMMON_SRC CONFIGURE_DEPENDS ../common/*.cpp ../common/*.h) file(GLOB VIEWER_SRC CONFIGURE_DEPENDS ../external/*.h *.cpp *.h) set(VIEWER_SRC ${VIEWER_SRC} ${COMMON_SRC} ${BASE_SRC} ${ZSTD_SRC} ${IMGUI_SRC} ${ICON_RC}) @@ -85,7 +95,11 @@ set(VIEWER_SRC ${VIEWER_SRC} ${COMMON_SRC} ${BASE_SRC} ${ZSTD_SRC} ${IMGUI_SRC} # Viewer executable # ================= add_executable("palanteer" ${WIN_MAIN} ${VIEWER_SRC}) +if(APPLE) +target_link_libraries("palanteer" ${PALANTEER_LIBS} Threads::Threads libpalanteer "-framework Foundation" "-framework Cocoa -framework OpenGL" objc) +else() target_link_libraries("palanteer" ${PALANTEER_LIBS} Threads::Threads libpalanteer) +endif() target_include_directories("palanteer" PRIVATE ../external/zstd ../external/zstd/common ../external/zstd/compress ../external/zstd/decompress ../external/imgui ../external ../../c++ ../base ../common) diff --git a/server/viewer/vwPlatform.cpp b/server/viewer/vwPlatform.cpp index ae98c3a..bf01288 100644 --- a/server/viewer/vwPlatform.cpp +++ b/server/viewer/vwPlatform.cpp @@ -70,7 +70,11 @@ crashErrorLogger(const char* msg) // ============================================================================================== static const GLchar* guiVertexShaderSrc = +#ifdef __APPLE__ + "#version 410 core\n" +#else "#version 300 es\n" +#endif "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 UV;\n" @@ -85,7 +89,11 @@ static const GLchar* guiVertexShaderSrc = "}\n"; static const GLchar* guiFragmentShaderSrc = +#ifdef __APPLE__ + "#version 410 core\n" +#else "#version 300 es\n" +#endif "precision mediump float;\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" @@ -684,6 +692,19 @@ vwPlatform::eventWheelScrolled (int x, int y, int steps, bsKeyModState kms) notifyDrawDirty(); } +void +vwPlatform::eventModifiersChanged (bsKeyModState kms) +{ + ImGuiIO& io = ImGui::GetIO(); + + io.KeyCtrl = kms.ctrl; + io.KeyShift = kms.shift; + io.KeyAlt = kms.alt; + io.KeySuper = kms.sys; + + notifyDrawDirty(); +} + void vwPlatform::eventChar(char16_t codepoint) diff --git a/server/viewer/vwPlatform.h b/server/viewer/vwPlatform.h index 9ae3ea6..168effc 100644 --- a/server/viewer/vwPlatform.h +++ b/server/viewer/vwPlatform.h @@ -70,6 +70,7 @@ class vwPlatform : public bsOsHandler { void eventButtonReleased(int buttonId, int x, int y, bsKeyModState kms); void eventMouseMotion (int x, int y); void eventWheelScrolled (int x, int y, int steps, bsKeyModState kms); + void eventModifiersChanged (bsKeyModState kms); private: vwPlatform(const vwPlatform& other); // To please static analyzers