Skip to content

Commit

Permalink
Added support for macOS AppKit apps
Browse files Browse the repository at this point in the history
Restructured project

Added an AppKit demo project
  • Loading branch information
LeoNatan committed Aug 2, 2023
1 parent c3e635c commit 809538f
Show file tree
Hide file tree
Showing 24 changed files with 2,867 additions and 831 deletions.
2 changes: 1 addition & 1 deletion LNViewHierarchyDumper/LNViewHierarchyDumper/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.2.1</string>
<string>1.2</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// LNViewHierarchyDumper+LibraryDebug.h
//
//
// Created by Leo Natan on 02/08/2023.
//

#import "LNViewHierarchyDumper-Private.h"

NS_ASSUME_NONNULL_BEGIN

@interface LNViewHierarchyDumper (LibraryDebug)

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// LNViewHierarchyDumper+LibraryDebug.m
//
//
// Created by Leo Natan on 02/08/2023.
//

#import "LNViewHierarchyDumper+LibraryDebug.h"

//#if DEBUG
//@import ObjectiveC;
//@import Darwin;
//@import MachO.dyld;
//#endif

@implementation LNViewHierarchyDumper (LibraryDebug)

//#if DEBUG
//static void func(const struct mach_header* mh, intptr_t vmaddr_slide)
//{
// Dl_info DlInfo;
// dladdr(mh, &DlInfo);
// const char* image_name = DlInfo.dli_fname;
//
// NSLog(@"%s", image_name);
//}
//
//
//static NSUInteger requestCounter = 0;
//static NSUInteger responseCounter = 0;
//
//__attribute__((constructor))
//static void zzz(void)
//{
// //We are looking for:
// // DebugHierarchyFoundation.framework
// // libViewDebuggerSupport.dylib
// _dyld_register_func_for_add_image(func);
//
// //The following code will dump requests made by Xcode View Hierarchy Inspector (and this framework) to ~/Desktop/Request_x.json
// {
// Class cls = NSClassFromString(@"DebugHierarchyRequest");
// SEL sel = NSSelectorFromString(@"requestWithBase64Data:error:");
// Method m = class_getClassMethod(cls, sel);
// id(*orig)(id, SEL, NSString*, NSError**) = (void*)method_getImplementation(m);
//
// method_setImplementation(m, imp_implementationWithBlock(^(id _self, NSString* request, NSError** error) {
// NSData* b64Data = [[NSData alloc] initWithBase64EncodedString:request options:0];
// NSString* homePath = NSHomeDirectory();
//#if TARGET_OS_SIMULATOR
// homePath = [homePath substringToIndex:[homePath rangeOfString:@"/Library"].location];
//#endif
// NSLog(@"%@", homePath);
// NSURL* outputURL = [[NSURL fileURLWithPath:homePath] URLByAppendingPathComponent:[NSString stringWithFormat:@"Desktop/Request_%@.json", @(requestCounter++)]];
// [b64Data writeToURL:outputURL atomically:YES];
//
// return orig(_self, sel, request, error);
// }));
// }
//
// {
// Class cls = NSClassFromString(@"DebugHierarchyTargetHub");
// SEL sel = NSSelectorFromString(@"performRequest:error:");
// Method m = class_getInstanceMethod(cls, sel);
// NSData*(*orig)(id, SEL, id, NSError**) = (void*)method_getImplementation(m);
// method_setImplementation(m, imp_implementationWithBlock(^(id _self, id request, NSError** error) {
// NSData* rv = orig(_self, sel, request, error);
//
// if(rv != nil)
// {
// NSString* homePath = NSHomeDirectory();
//#if TARGET_OS_SIMULATOR
// homePath = [homePath substringToIndex:[homePath rangeOfString:@"/Library"].location];
//#endif
// NSURL* outputURL = [[NSURL fileURLWithPath:homePath] URLByAppendingPathComponent:[NSString stringWithFormat:@"Desktop/Response_%@.json", @(responseCounter++)]];
// [rv writeToURL:outputURL atomically:YES];
// }
//
// return rv;
// }));
// }
//}
//#endif

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// LNViewHierarchyDumper+LibrarySupport.h
// ViewHierarchyDumpTester
//
// Created by Leo Natan on 02/08/2023.
//

#import "LNViewHierarchyDumper.h"

NS_ASSUME_NONNULL_BEGIN

#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST

@interface NSTask : NSObject @end

@interface NSTask ()

@property (nullable, copy) NSURL* executableURL;
@property (nullable, copy) NSArray<NSString*>* arguments;
@property (nullable, copy) NSDictionary<NSString*, NSString*>* environment;
@property (nullable, retain) id standardOutput;
@property (nullable, retain) id standardError;

@property(readonly) int terminationStatus;

@property(copy, nonnull) void (^terminationHandler)(NSTask* _Nonnull task);

- (BOOL)launch;

@end

#endif

@interface NSObject ()

//DBGTargetHub
+ (id)sharedHub;
- (NSData*)performRequestWithRequestInBase64:(NSString*)arg1;

//DebugHierarchyTargetHub
- (nullable id)performRequest:(id /*DBGTargetHub*/)arg1 error:(NSError**)error;

//DebugHierarchyRequest
+ (nullable id)requestWithBase64Data:(NSString*)arg1 error:(NSError**)arg2;

//NSTask
- (BOOL)launchAndReturnError:(out NSError **_Nullable)error;

@end

@interface LNViewHierarchyDumper (LibrarySupport)

+ (NSURL*)_xcodeURLOrError:(out NSError* __strong * _Nullable)outError;
+ (nullable NSURL*)_debugHierarchyFoundationFrameworkURL:(NSURL*)runtimeURL error:(out NSError** _Nullable)error;
+ (nullable NSURL*)_libViewDebuggerSupportURL:(NSURL*)runtimeURL error:(out NSError** _Nullable)error;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//
// LNViewHierarchyDumper+LibrarySupport.m
// ViewHierarchyDumpTester
//
// Created by Leo Natan on 02/08/2023.
//

#import "LNViewHierarchyDumper+LibrarySupport.h"

@implementation LNViewHierarchyDumper (LibrarySupport)

+ (BOOL)_isMacSandboxed
{
return NSProcessInfo.processInfo.environment[@"APP_SANDBOX_CONTAINER_ID"].length > 0;
}

+ (NSURL*)_xcodeURLOrError:(out NSError* __strong * _Nullable)outError
{
static NSURL* rv = nil;
static NSError* error = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(self._isMacSandboxed)
{
rv = [NSURL fileURLWithPath:@"/Applications/Xcode.app/Contents"];
if([rv checkResourceIsReachableAndReturnError:NULL] == NO)
{
rv = [NSURL fileURLWithPath:@"/Applications/Xcode-beta.app/Contents"];
if([rv checkResourceIsReachableAndReturnError:NULL] == NO)
{
rv = NULL;
error = [NSError errorWithDomain:@"LNViewHierarchyDumperDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: @"App is sandboxed and cannot find Xcode in\n/Applications/Xcode.app\nor\n/Applications/Xcode-beta.app"}];
return;
}
}
}
else
{
NSTask* whichXcodeTask = [NSTask new];
[whichXcodeTask setValue:[NSURL fileURLWithPath:@"/usr/bin/xcode-select"] forKey:@"executableURL"];
whichXcodeTask.arguments = @[@"-p"];
whichXcodeTask.environment = @{};

NSPipe* outPipe = [NSPipe pipe];
NSMutableData* outData = [NSMutableData new];
whichXcodeTask.standardOutput = outPipe;
outPipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle * _Nonnull fileHandle) {
[outData appendData:fileHandle.availableData];
};

NSPipe* errPipe = [NSPipe pipe];
NSMutableData* errData = [NSMutableData new];
whichXcodeTask.standardError = errPipe;
errPipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle * _Nonnull fileHandle) {
[errData appendData:fileHandle.availableData];
};

dispatch_semaphore_t waitForTermination = dispatch_semaphore_create(0);

[whichXcodeTask setValue:^(NSTask* _Nonnull task) {
outPipe.fileHandleForReading.readabilityHandler = nil;
errPipe.fileHandleForReading.readabilityHandler = nil;

dispatch_semaphore_signal(waitForTermination);
} forKey:@"terminationHandler"];

NSError* taskError;
[(id)whichXcodeTask launchAndReturnError:&taskError];

if(taskError == nil)
{
dispatch_semaphore_wait(waitForTermination, DISPATCH_TIME_FOREVER);
}
else
{
error = taskError;
return;
}

if(whichXcodeTask.terminationStatus != 0)
{
NSString* errString = [[NSString alloc] initWithData:errData encoding:NSUTF8StringEncoding];
error = [NSError errorWithDomain:@"LNViewHierarchyDumperDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: errString}];
return;
}

NSString* xcodePath = [[[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];

if(xcodePath.length == 0)
{
error = [NSError errorWithDomain:@"LNViewHierarchyDumperDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Unable to find the current active developer directory"}];

return;
}

rv = [[[NSURL fileURLWithPath:xcodePath] URLByAppendingPathComponent:@".."] URLByStandardizingPath];
}
});
if(outError != NULL)
{
*outError = error;
}
return rv;
}

+ (NSURL*)_debugHierarchyFoundationFrameworkURL_iOS:(NSURL*)runtimeURL
{
NSString* rv = nil;
if(@available(iOS 17, *))
{
rv = @"System/Library/PrivateFrameworks/DebugHierarchyFoundation.framework";
}
else
{
rv = @"Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework";
}
#if TARGET_OS_SIMULATOR
return [runtimeURL URLByAppendingPathComponent:rv];
#else
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"/%@", rv]];
#endif
}

+ (NSURL*)_debugHierarchyFoundationFrameworkURL_macOS:(NSURL*)runtimeURL isCatalyst:(BOOL)isCatalyst
{
return [runtimeURL URLByAppendingPathComponent:@"SharedFrameworks/DebugHierarchyFoundation.framework"];
}

+ (nullable NSURL*)_debugHierarchyFoundationFrameworkURL:(NSURL*)runtimeURL error:(out NSError** _Nullable)error
{
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
if (@available(iOS 14.0, *))
{
if(NSProcessInfo.processInfo.isiOSAppOnMac)
{
NSError* xcodeError = nil;
NSURL* xcodeURL = [self _xcodeURLOrError:&xcodeError];
if(xcodeError)
{
if(error) { *error = xcodeError; }
return nil;
}

return [self _debugHierarchyFoundationFrameworkURL_macOS:xcodeURL isCatalyst:YES];
}
}
return [self _debugHierarchyFoundationFrameworkURL_iOS:runtimeURL];
#else
#if TARGET_OS_MACCATALYST
BOOL isCatalyst = YES;
#else
BOOL isCatalyst = NO;
#endif
return [self _debugHierarchyFoundationFrameworkURL_macOS:runtimeURL isCatalyst:isCatalyst];
#endif
}

+ (NSURL*)_libViewDebuggerSupportURL_iOS:(NSURL*)runtimeURL
{
NSString* rv = nil;
if(@available(iOS 17, *))
{
rv = @"usr/lib/libViewDebuggerSupport.dylib";
}
else
{
rv = @"Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib";
}
#if TARGET_OS_SIMULATOR
return [runtimeURL URLByAppendingPathComponent:rv];
#else
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"/%@", rv]];
#endif
}

+ (NSURL*)_libViewDebuggerSupportURL_macOS:(NSURL*)runtimeURL isCatalyst:(BOOL)isCatalyst
{
NSString* libName = isCatalyst ? @"libViewDebuggerSupport_macCatalyst.dylib" : @"libViewDebuggerSupport.dylib";
return [runtimeURL URLByAppendingPathComponent:[NSString stringWithFormat:@"Developer/Platforms/MacOSX.platform/Developer/Library/Debugger/%@", libName]];
}

+ (nullable NSURL*)_libViewDebuggerSupportURL:(NSURL*)runtimeURL error:(out NSError** _Nullable)error
{
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
if (@available(iOS 14.0, *))
{
if(NSProcessInfo.processInfo.isiOSAppOnMac)
{
NSError* xcodeError = nil;
NSURL* xcodeURL = [self _xcodeURLOrError:&xcodeError];
if(xcodeError)
{
if(error) { *error = xcodeError; }
return nil;
}

return [self _libViewDebuggerSupportURL_macOS:xcodeURL isCatalyst:YES];
}
}
return [self _libViewDebuggerSupportURL_iOS:runtimeURL];
#else
#if TARGET_OS_MACCATALYST
BOOL isCatalyst = YES;
#else
BOOL isCatalyst = NO;
#endif
return [self _libViewDebuggerSupportURL_macOS:runtimeURL isCatalyst:isCatalyst];
#endif
}


@end
Loading

0 comments on commit 809538f

Please sign in to comment.