macOS support fixes
* Adding macOS FileSystemWatcher, this should allow files to be monitored and update like the other OSs * Reworked how macOS launches processes to use NSTask which just deals with escaped and unescaped paths better * Made a change to the ScriptsBuilder::RunBuildTool, this was adding the escaped values to the path, in reality it should be up to the underlying OS to make sure things are properly escaped, so removed those as they just end up causing issues. Also instead of appending the args to the fileName we just properly use the Arguments variable on the CreateProcessSettings * No longer use open in order to show files in the finder, we use the proper method selectFile * made a slight cleanup change to the MacPlatform Tick function * Added ToNSString functions just to make that easier * Added a ParseArguments function that will take a string and turn it into an array for NSTask
This commit is contained in:
@@ -70,6 +70,46 @@ CFStringRef AppleUtils::ToString(const StringView& str)
|
||||
return CFStringCreateWithBytes(nullptr, (const UInt8*)str.GetText(), str.Length() * sizeof(Char), kCFStringEncodingUTF16LE, false);
|
||||
}
|
||||
|
||||
NSString* AppleUtils::ToNSString(const StringView& str)
|
||||
{
|
||||
NSString* ret = !str.IsEmpty() ? [[NSString alloc] initWithBytes: (const UInt8*)str.Get() length: str.Length() * sizeof(Char) encoding: NSUTF16LittleEndianStringEncoding] : nil;
|
||||
return ret ? ret : @"";
|
||||
}
|
||||
|
||||
NSString* AppleUtils::ToNSString(const char* string)
|
||||
{
|
||||
NSString* ret = string ? [NSString stringWithUTF8String: string] : nil;
|
||||
return ret ? ret : @"";
|
||||
}
|
||||
|
||||
|
||||
NSArray* AppleUtils::ParseArguments(NSString* argsString) {
|
||||
NSMutableArray *argsArray = [NSMutableArray array];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:argsString];
|
||||
NSString *currentArg = nil;
|
||||
BOOL insideQuotes = NO;
|
||||
|
||||
while (![scanner isAtEnd]) {
|
||||
if (insideQuotes) {
|
||||
[scanner scanUpToString:@"\"" intoString:¤tArg];
|
||||
[scanner scanString:@"\"" intoString:NULL];
|
||||
insideQuotes = NO;
|
||||
} else {
|
||||
[scanner scanUpToString:@" " intoString:¤tArg];
|
||||
[scanner scanString:@" " intoString:NULL];
|
||||
}
|
||||
|
||||
if ([currentArg isEqualToString:@"\""]) {
|
||||
insideQuotes = YES;
|
||||
} else if (currentArg) {
|
||||
[argsArray addObject:currentArg];
|
||||
}
|
||||
}
|
||||
|
||||
return [argsArray copy];
|
||||
}
|
||||
|
||||
|
||||
typedef uint16_t offset_t;
|
||||
#define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ class AppleUtils
|
||||
public:
|
||||
static String ToString(CFStringRef str);
|
||||
static CFStringRef ToString(const StringView& str);
|
||||
static NSString* ToNSString(const StringView& str);
|
||||
static NSString* ToNSString(const char* string);
|
||||
static NSArray* ParseArguments(NSString* argsString);
|
||||
#if PLATFORM_MAC
|
||||
static Float2 PosToCoca(const Float2& pos);
|
||||
static Float2 CocaToPos(const Float2& pos);
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Windows/WindowsFileSystemWatcher.h"
|
||||
#elif PLATFORM_LINUX
|
||||
#include "Linux/LinuxFileSystemWatcher.h"
|
||||
#elif PLATFORM_MAC
|
||||
#include "Mac/MacFileSystemWatcher.h"
|
||||
#else
|
||||
#include "Base/FileSystemWatcherBase.h"
|
||||
#endif
|
||||
|
||||
@@ -132,7 +132,7 @@ bool MacFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringVie
|
||||
|
||||
bool MacFileSystem::ShowFileExplorer(const StringView& path)
|
||||
{
|
||||
return Platform::StartProcess(TEXT("open"), String::Format(TEXT("\"{0}\""), path), StringView::Empty) != 0;
|
||||
return [[NSWorkspace sharedWorkspace] selectFile: AppleUtils::ToNSString(FileSystem::ConvertRelativePathToAbsolute(path)) inFileViewerRootedAtPath: @""];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
100
Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp
Normal file
100
Source/Engine/Platform/Mac/MacFileSystemWatcher.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if PLATFORM_MAC
|
||||
#include "MacFileSystemWatcher.h"
|
||||
#include "Engine/Platform/Apple/AppleUtils.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Platform/Thread.h"
|
||||
#include "Engine/Threading/ThreadSpawner.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
|
||||
void DirectoryWatchCallback( ConstFSEventStreamRef StreamRef, void* FileWatcherPtr, size_t EventCount, void* EventPaths, const FSEventStreamEventFlags EventFlags[], const FSEventStreamEventId EventIDs[] )
|
||||
{
|
||||
MacFileSystemWatcher* macFileSystemWatcher = (MacFileSystemWatcher*)FileWatcherPtr;
|
||||
if (macFileSystemWatcher)
|
||||
{
|
||||
CFArrayRef EventPathArray = (CFArrayRef)EventPaths;
|
||||
for( size_t EventIndex = 0; EventIndex < EventCount; ++EventIndex )
|
||||
{
|
||||
const FSEventStreamEventFlags Flags = EventFlags[EventIndex];
|
||||
if( !(Flags & kFSEventStreamEventFlagItemIsFile) && !(Flags & kFSEventStreamEventFlagItemIsDir) )
|
||||
{
|
||||
// events about symlinks don't concern us
|
||||
continue;
|
||||
}
|
||||
|
||||
auto action = FileSystemAction::Unknown;
|
||||
|
||||
const bool added = ( Flags & kFSEventStreamEventFlagItemCreated );
|
||||
const bool renamed = ( Flags & kFSEventStreamEventFlagItemRenamed );
|
||||
const bool modified = ( Flags & kFSEventStreamEventFlagItemModified );
|
||||
const bool removed = ( Flags & kFSEventStreamEventFlagItemRemoved );
|
||||
|
||||
if (added)
|
||||
{
|
||||
action = FileSystemAction::Create;
|
||||
}
|
||||
|
||||
if (renamed || modified)
|
||||
{
|
||||
action = FileSystemAction::Delete;
|
||||
}
|
||||
|
||||
if (removed)
|
||||
{
|
||||
action = FileSystemAction::Modify;
|
||||
}
|
||||
|
||||
const String resolvedPath = AppleUtils::ToString((CFStringRef)CFArrayGetValueAtIndex(EventPathArray,EventIndex));
|
||||
|
||||
macFileSystemWatcher->OnEvent(resolvedPath, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MacFileSystemWatcher::MacFileSystemWatcher(const String& directory, bool withSubDirs)
|
||||
: FileSystemWatcherBase(directory, withSubDirs)
|
||||
{
|
||||
|
||||
CFStringRef FullPathMac = AppleUtils::ToString(StringView(directory));
|
||||
CFArrayRef PathsToWatch = CFArrayCreate(NULL, (const void**)&FullPathMac, 1, NULL);
|
||||
|
||||
CFAbsoluteTime Latency = 0.2;
|
||||
|
||||
FSEventStreamContext Context;
|
||||
Context.version = 0;
|
||||
Context.info = this;
|
||||
Context.retain = NULL;
|
||||
Context.release = NULL;
|
||||
Context.copyDescription = NULL;
|
||||
|
||||
EventStream = FSEventStreamCreate( NULL,
|
||||
&DirectoryWatchCallback,
|
||||
&Context,
|
||||
PathsToWatch,
|
||||
kFSEventStreamEventIdSinceNow,
|
||||
Latency,
|
||||
kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents
|
||||
);
|
||||
|
||||
CFRelease(PathsToWatch);
|
||||
CFRelease(FullPathMac);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop( EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
|
||||
FSEventStreamStart( EventStream );
|
||||
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
MacFileSystemWatcher::~MacFileSystemWatcher()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
FSEventStreamStop(EventStream);
|
||||
FSEventStreamUnscheduleFromRunLoop(EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
FSEventStreamInvalidate(EventStream);
|
||||
FSEventStreamRelease(EventStream);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
39
Source/Engine/Platform/Mac/MacFileSystemWatcher.h
Normal file
39
Source/Engine/Platform/Mac/MacFileSystemWatcher.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_MAC
|
||||
#include "Engine/Platform/Base/FileSystemWatcherBase.h"
|
||||
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mac platform implementation of the file system watching object.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API MacFileSystemWatcher : public FileSystemWatcherBase
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MacFileSystemWatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to watch.</param>
|
||||
/// <param name="withSubDirs">True if monitor the directory tree rooted at the specified directory or just a given directory.</param>
|
||||
MacFileSystemWatcher(const String& directory, bool withSubDirs);
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="MacFileSystemWatcher"/> class.
|
||||
/// </summary>
|
||||
~MacFileSystemWatcher();
|
||||
|
||||
public:
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
FSEventStreamRef EventStream;
|
||||
bool IsRunning;
|
||||
};
|
||||
#endif
|
||||
@@ -324,13 +324,16 @@ void MacPlatform::BeforeRun()
|
||||
void MacPlatform::Tick()
|
||||
{
|
||||
// Process system events
|
||||
while (true)
|
||||
NSEvent* event = nil;
|
||||
do
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
if (event == nil)
|
||||
break;
|
||||
[NSApp sendEvent:event];
|
||||
}
|
||||
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES];
|
||||
if (event)
|
||||
{
|
||||
[NSApp sendEvent:event];
|
||||
}
|
||||
|
||||
} while(event);
|
||||
|
||||
ApplePlatform::Tick();
|
||||
}
|
||||
@@ -461,50 +464,88 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanatize the string if the exePath has spaces with properly espcaped spaces for popen
|
||||
exePath.Replace(TEXT(" "), TEXT("\\ "));
|
||||
|
||||
const String cmdLine = exePath + TEXT(" ") + settings.Arguments;
|
||||
const StringAsANSI<> cmdLineAnsi(*cmdLine, cmdLine.Length());
|
||||
FILE* pipe = popen(cmdLineAnsi.Get(), "r");
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = AppleUtils::ToNSString(exePath);
|
||||
task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments));
|
||||
|
||||
if (cwd.Length() != 0)
|
||||
{
|
||||
Platform::SetWorkingDirectory(cwd);
|
||||
}
|
||||
if (!pipe)
|
||||
{
|
||||
LOG(Warning, "Failed to start process, errno={}", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: environment
|
||||
|
||||
task.currentDirectoryPath = AppleUtils::ToNSString(cwd);
|
||||
|
||||
|
||||
int32 returnCode = 0;
|
||||
|
||||
if (settings.WaitForEnd)
|
||||
{
|
||||
id<NSObject> outputObserver = nil;
|
||||
|
||||
if (captureStdOut)
|
||||
{
|
||||
char lineBuffer[1024];
|
||||
while (fgets(lineBuffer, sizeof(lineBuffer), pipe) != NULL)
|
||||
NSPipe *stdoutPipe = [NSPipe pipe];
|
||||
[task setStandardOutput:stdoutPipe];
|
||||
|
||||
outputObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName: NSFileHandleDataAvailableNotification
|
||||
object: [stdoutPipe fileHandleForReading]
|
||||
queue: nil
|
||||
usingBlock:^(NSNotification* notification)
|
||||
{
|
||||
char* p = lineBuffer + strlen(lineBuffer) - 1;
|
||||
if (*p == '\n') *p = 0;
|
||||
String line(lineBuffer);
|
||||
if (settings.SaveOutput)
|
||||
settings.Output.Add(line.Get(), line.Length());
|
||||
if (settings.LogOutput)
|
||||
Log::Logger::Write(LogType::Info, line);
|
||||
NSData* data = [stdoutPipe fileHandleForReading].availableData;
|
||||
if (data.length)
|
||||
{
|
||||
String line((char*)data.bytes);
|
||||
if (settings.SaveOutput)
|
||||
settings.Output.Add(line.Get(), line.Length());
|
||||
if (settings.LogOutput)
|
||||
Log::Logger::Write(LogType::Info, line);
|
||||
[[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
[[stdoutPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
|
||||
}
|
||||
else
|
||||
|
||||
String exception;
|
||||
|
||||
@try
|
||||
{
|
||||
while (!feof(pipe))
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
}
|
||||
@catch (NSException* e)
|
||||
{
|
||||
exception = e.reason.UTF8String;
|
||||
}
|
||||
|
||||
if (!exception.IsEmpty())
|
||||
{
|
||||
LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception);
|
||||
return -1;
|
||||
}
|
||||
|
||||
returnCode = [task terminationStatus];
|
||||
}
|
||||
else {
|
||||
|
||||
String exception;
|
||||
|
||||
@try
|
||||
{
|
||||
[task launch];
|
||||
}
|
||||
@catch (NSException* e)
|
||||
{
|
||||
exception = e.reason.UTF8String;
|
||||
}
|
||||
|
||||
if (!exception.IsEmpty())
|
||||
{
|
||||
LOG(Error, "Failed to run command {0} {1} with error {2}", settings.FileName, settings.Arguments, exception);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
@@ -237,8 +237,8 @@ class UnixConditionVariable;
|
||||
typedef UnixConditionVariable ConditionVariable;
|
||||
class MacFileSystem;
|
||||
typedef MacFileSystem FileSystem;
|
||||
class FileSystemWatcherBase;
|
||||
typedef FileSystemWatcherBase FileSystemWatcher;
|
||||
class MacFileSystemWatcher;
|
||||
typedef MacFileSystemWatcher FileSystemWatcher;
|
||||
class UnixFile;
|
||||
typedef UnixFile File;
|
||||
class MacPlatform;
|
||||
|
||||
Reference in New Issue
Block a user