mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-03-30 17:43:26 +08:00
1517 lines
43 KiB
Objective-C
1517 lines
43 KiB
Objective-C
#import <Foundation/Foundation.h>
|
|
#import <AppKit/AppKit.h>
|
|
#import <ApplicationServices/ApplicationServices.h>
|
|
|
|
#import <objc/message.h>
|
|
#import <sys/param.h>
|
|
#import <signal.h>
|
|
|
|
#import <readline/readline.h>
|
|
#import <readline/history.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include "builtin_debugger_cmds.h"
|
|
|
|
#define DEVICE_FAMILY_IPHONE 1
|
|
#define DEVICE_FAMILY_IPAD 2
|
|
|
|
#define DEVICE_RETINA_FALSE 0
|
|
#define DEVICE_RETINA_TRUE 1
|
|
#define DEVICE_RETINA_3_5 2
|
|
#define DEVICE_RETINA_4 4
|
|
|
|
@interface Delegate : NSObject
|
|
- (NSString *)replEval:(NSString *)expression;
|
|
@end
|
|
|
|
#include "rmtask.m"
|
|
|
|
static bool spec_mode = false;
|
|
static bool debug_sim_window_offsets = false;
|
|
static int debug_mode = -1;
|
|
#define DEBUG_GDB 1
|
|
#define DEBUG_REPL 2
|
|
#define DEBUG_NOTHING 0
|
|
|
|
static Delegate *delegate = nil;
|
|
static NSArray *app_windows_ids = nil;
|
|
#if defined(SIMULATOR_IOS)
|
|
static RMTask *gdb_task = nil;
|
|
static id current_session = nil;
|
|
static BOOL debugger_killed_session = NO;
|
|
static NSString *xcode_path = nil;
|
|
static int simulator_retina_type = DEVICE_RETINA_FALSE;
|
|
static int simulator_device_family = DEVICE_FAMILY_IPHONE;
|
|
#endif
|
|
static NSString *sdk_version = nil;
|
|
static NSString *replSocketPath = nil;
|
|
|
|
static int repl_fd = -1;
|
|
static NSLock *repl_fd_lock = nil;
|
|
|
|
#define HISTORY_FILE @".repl_history"
|
|
|
|
static void
|
|
save_repl_history(void)
|
|
{
|
|
NSMutableArray *lines = [NSMutableArray array];
|
|
for (int i = 0; i < history_length; i++) {
|
|
HIST_ENTRY *entry = history_get(history_base + i);
|
|
if (entry == NULL) {
|
|
break;
|
|
}
|
|
[lines addObject:[NSString stringWithUTF8String:entry->line]];
|
|
}
|
|
NSString *data = [lines componentsJoinedByString:@"\n"];
|
|
NSError *error = nil;
|
|
if (![data writeToFile:HISTORY_FILE atomically:YES
|
|
encoding:NSUTF8StringEncoding error:&error]) {
|
|
fprintf(stderr, "*** Cannot save REPL history file to `%s': %s\n",
|
|
[HISTORY_FILE UTF8String], [[error description] UTF8String]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
load_repl_history(void)
|
|
{
|
|
NSString *data = [NSString stringWithContentsOfFile:HISTORY_FILE
|
|
encoding:NSUTF8StringEncoding error:nil];
|
|
if (data != nil) {
|
|
NSArray *lines = [data componentsSeparatedByString:@"\n"];
|
|
for (NSString *line in lines) {
|
|
line = [line stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
add_history([line UTF8String]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
terminate_session(void)
|
|
{
|
|
static bool terminated = false;
|
|
if (!terminated) {
|
|
#if defined(SIMULATOR_IOS)
|
|
// requestEndWithTimeout: must be called only once.
|
|
assert(current_session != nil);
|
|
((void (*)(id, SEL, double))objc_msgSend)(current_session,
|
|
@selector(requestEndWithTimeout:), 0.0);
|
|
#else
|
|
save_repl_history();
|
|
#endif
|
|
terminated = true;
|
|
}
|
|
}
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
static void
|
|
sigterminate(int sig)
|
|
{
|
|
terminate_session();
|
|
exit(0);
|
|
}
|
|
|
|
static void
|
|
sigforwarder(int sig)
|
|
{
|
|
if (gdb_task != nil) {
|
|
kill([gdb_task processIdentifier], sig);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
sigcleanup(int sig)
|
|
{
|
|
if (debug_mode == DEBUG_REPL) {
|
|
save_repl_history();
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
#if defined(SIMULATOR_OSX)
|
|
|
|
static NSTask *osx_task = nil;
|
|
|
|
static void
|
|
sigint_osx(int sig)
|
|
{
|
|
if (osx_task != nil) {
|
|
kill([osx_task processIdentifier], sig);
|
|
}
|
|
if (debug_mode == DEBUG_REPL) {
|
|
save_repl_history();
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
#endif
|
|
|
|
@implementation Delegate
|
|
|
|
static int expr_level = 0;
|
|
|
|
static NSString *
|
|
current_repl_prompt(NSString *top_level)
|
|
{
|
|
char question = '?';
|
|
if (top_level == nil) {
|
|
static bool first_time = true;
|
|
if (first_time) {
|
|
top_level = @"main";
|
|
first_time = false;
|
|
}
|
|
else {
|
|
top_level = [delegate replEval:@"self"];
|
|
}
|
|
question = '>';
|
|
}
|
|
|
|
if ([top_level length] > 30) {
|
|
top_level = [[top_level substringToIndex:30]
|
|
stringByAppendingString:@"..."];
|
|
}
|
|
|
|
NSString *prompt = [NSString stringWithFormat:@"(%@)%c ",
|
|
top_level, question];
|
|
|
|
for (int i = 0; i < expr_level; i++) {
|
|
prompt = [prompt stringByAppendingString:@" "];
|
|
}
|
|
return prompt;
|
|
}
|
|
|
|
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
|
|
// This readline function is not implemented in Snow Leopard.
|
|
// Code copied from http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libedit/readline.c?only_with_tag=MAIN
|
|
#if !defined(RL_PROMPT_START_IGNORE)
|
|
# define RL_PROMPT_START_IGNORE '\1'
|
|
#endif
|
|
#if !defined(RL_PROMPT_END_IGNORE)
|
|
# define RL_PROMPT_END_IGNORE '\2'
|
|
#endif
|
|
int
|
|
rl_set_prompt(const char *prompt)
|
|
{
|
|
char *p;
|
|
|
|
if (!prompt)
|
|
prompt = "";
|
|
if (rl_prompt != NULL && strcmp(rl_prompt, prompt) == 0)
|
|
return 0;
|
|
if (rl_prompt)
|
|
/*el_*/free(rl_prompt);
|
|
rl_prompt = strdup(prompt);
|
|
if (rl_prompt == NULL)
|
|
return -1;
|
|
|
|
while ((p = strchr(rl_prompt, RL_PROMPT_END_IGNORE)) != NULL)
|
|
*p = RL_PROMPT_START_IGNORE;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
refresh_repl_prompt(NSString *top_level, bool clear)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
static int previous_prompt_length = 0;
|
|
rl_set_prompt([current_repl_prompt(top_level) UTF8String]);
|
|
if (clear) {
|
|
putchar('\r');
|
|
for (int i = 0; i < previous_prompt_length; i++) {
|
|
putchar(' ');
|
|
}
|
|
putchar('\r');
|
|
//printf("\n\033[F\033[J"); // Clear.
|
|
rl_forced_update_display();
|
|
}
|
|
previous_prompt_length = strlen(rl_prompt);
|
|
});
|
|
}
|
|
|
|
static void
|
|
locate_app_windows_ids(void)
|
|
{
|
|
if (app_windows_ids != nil) {
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *ids = [[NSMutableArray alloc] init];
|
|
|
|
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll,
|
|
kCGNullWindowID);
|
|
bool bounds_ok = false;
|
|
|
|
for (NSDictionary *dict in (NSArray *)windows) {
|
|
#define validate(obj, klass) \
|
|
if (obj == nil || ![obj isKindOfClass:[klass class]]) { \
|
|
continue; \
|
|
}
|
|
id name = [dict objectForKey:@"kCGWindowName"];
|
|
validate(name, NSString);
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
static NSArray *patterns = nil;
|
|
if (patterns == nil) {
|
|
patterns = [[NSArray alloc] initWithObjects:
|
|
[NSString stringWithFormat:@"iPhone - iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPad - iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPad / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone (Retina 3.5-inch) - iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone (Retina 3.5-inch) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone Retina (3.5-inch) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone (Retina 4-inch) - iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone (Retina 4-inch) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone Retina (4-inch) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPhone Retina (4-inch 64-bit) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPad (Retina) - iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPad (Retina) / iOS %@", sdk_version],
|
|
[NSString stringWithFormat:@"iPad Retina / iOS %@", sdk_version],
|
|
nil];
|
|
}
|
|
|
|
bool found = false;
|
|
for (NSString *pattern in patterns) {
|
|
if ([name rangeOfString:pattern].location != NSNotFound) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
continue;
|
|
}
|
|
if ([name rangeOfString:@"Retina"].location != NSNotFound) {
|
|
simulator_retina_type = DEVICE_RETINA_TRUE;
|
|
if ([name rangeOfString:@"3.5-inch"].location != NSNotFound) {
|
|
simulator_retina_type = DEVICE_RETINA_3_5;
|
|
}
|
|
else if ([name rangeOfString:@"4-inch"].location != NSNotFound) {
|
|
simulator_retina_type = DEVICE_RETINA_4;
|
|
}
|
|
}
|
|
if ([name rangeOfString:@"iPad"].location != NSNotFound) {
|
|
simulator_device_family = DEVICE_FAMILY_IPAD;
|
|
}
|
|
#else // !SIMULATOR_IOS
|
|
int window_pid = [[dict objectForKey:@"kCGWindowOwnerPID"] intValue];
|
|
if (window_pid != [osx_task processIdentifier]) {
|
|
continue;
|
|
}
|
|
if ([[dict objectForKey:@"kCGWindowName"]
|
|
isEqualToString:@"__HIGHLIGHT_OVERLAY__"]) {
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#undef validate
|
|
[ids addObject:[dict objectForKey:@"kCGWindowNumber"]];
|
|
bounds_ok = true;
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
// On iOS there is only one app window (the simulator).
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (ids.count > 0) {
|
|
app_windows_ids = (NSArray *)ids;
|
|
} else {
|
|
[ids release];
|
|
}
|
|
CFRelease(windows);
|
|
|
|
if (!bounds_ok) {
|
|
#if defined(SIMULATOR_IOS)
|
|
static bool error_printed = false;
|
|
if (!error_printed) {
|
|
fprintf(stderr,
|
|
"*** Cannot locate the Simulator app, mouse-over disabled\n");
|
|
error_printed = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
static NSArray *
|
|
get_app_windows_bounds(void)
|
|
{
|
|
if (app_windows_ids == nil) {
|
|
locate_app_windows_ids();
|
|
if (app_windows_ids == nil) return nil;
|
|
}
|
|
|
|
NSMutableArray *app_windows_bounds = [NSMutableArray arrayWithCapacity:app_windows_ids.count];
|
|
|
|
int count = app_windows_ids.count;
|
|
uint32_t ids[count];
|
|
for (int i = 0; i < count; i++) {
|
|
ids[i] = [[app_windows_ids objectAtIndex:i] intValue];
|
|
}
|
|
CFArrayRef windowArray = CFArrayCreate(NULL, (const void **)ids, count, NULL);
|
|
NSArray *windows = (NSArray *)CGWindowListCreateDescriptionFromArray(windowArray);
|
|
CFRelease(windowArray);
|
|
|
|
// The window(s) are gone. Need to find them again.
|
|
//
|
|
// This happens, for instance, when the iOS Simulator changes the devices
|
|
// scale.
|
|
if (windows.count != app_windows_ids.count) {
|
|
app_windows_ids = nil;
|
|
locate_app_windows_ids();
|
|
if (app_windows_ids == nil) return nil;
|
|
}
|
|
|
|
NSRect bounds = NSZeroRect;
|
|
|
|
for (NSDictionary *dict in windows) {
|
|
// TODO is validation here really needed?
|
|
//#define validate(obj, klass) \
|
|
//if (obj == nil || ![obj isKindOfClass:[klass class]]) { \
|
|
//continue; \
|
|
//}
|
|
|
|
id bounds_dict = [dict objectForKey:@"kCGWindowBounds"];
|
|
// validate(bounds_dict, NSDictionary);
|
|
|
|
id x = [bounds_dict objectForKey:@"X"];
|
|
id y = [bounds_dict objectForKey:@"Y"];
|
|
id width = [bounds_dict objectForKey:@"Width"];
|
|
id height = [bounds_dict objectForKey:@"Height"];
|
|
|
|
//validate(x, NSNumber);
|
|
//validate(y, NSNumber);
|
|
//validate(width, NSNumber);
|
|
//validate(height, NSNumber);
|
|
|
|
bounds.origin.x = [x intValue];
|
|
bounds.origin.y = [y intValue];
|
|
bounds.size.width = [width intValue];
|
|
bounds.size.height = [height intValue];
|
|
|
|
// #undef validate
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
// Inset the main view frame.
|
|
//
|
|
// Devices that (at a scale of 100%) have thick borders and their
|
|
// respective frames.
|
|
//
|
|
// On Retina Mac, the Retina iOS devices are shown at their native size
|
|
// while on a Non-Retina Mac they are twice as large.
|
|
//
|
|
|
|
// Non-Retina Mac
|
|
// ===========================================================
|
|
// DEVICE | PORTRAIT FRAME
|
|
// ===========================================================
|
|
// Original iPhone | 368x716
|
|
// -----------------------------------------------------------
|
|
// iPhone Retina 3.5" | 724x1044
|
|
// -----------------------------------------------------------
|
|
// iPhone Retina 4" | 724x1220
|
|
// -----------------------------------------------------------
|
|
// Original iPad | 852x1108
|
|
// -----------------------------------------------------------
|
|
// iPad Retina | 1704x2216 (TODO This is the screenshot!)
|
|
// -----------------------------------------------------------
|
|
|
|
// Retina Mac
|
|
//
|
|
// ===========================================================
|
|
// DEVICE | PORTRAIT FRAME
|
|
// ===========================================================
|
|
// Original iPhone | 368x716
|
|
// -----------------------------------------------------------
|
|
// iPhone Retina 3.5" | 368x716
|
|
// -----------------------------------------------------------
|
|
// iPhone Retina 4" | 386x806
|
|
// -----------------------------------------------------------
|
|
// Original iPad | 852x1108
|
|
// -----------------------------------------------------------
|
|
// iPad Retina | 852x1108
|
|
// -----------------------------------------------------------
|
|
|
|
int w = (int)CGRectGetWidth(bounds);
|
|
int h = (int)CGRectGetHeight(bounds);
|
|
// NSLog(@"W: %d H: %d", w, h);
|
|
bool portrait = w < h;
|
|
bool retina_mac = (int)[[NSScreen mainScreen] backingScaleFactor] > 1;
|
|
bool found_thick_border = false;
|
|
|
|
#define MATCHES_FRAME(ww, hh) (portrait ? (w == ww && h == hh) : (w == hh && h == ww))
|
|
#define LOG_MATCH(desc) \
|
|
if (debug_sim_window_offsets) { \
|
|
fprintf(stderr, "*** Recognized current Simulator window as %s.\n", desc); \
|
|
}
|
|
// Inset to account for the border and offset for the window drop shadow.
|
|
#define BORDER_INSET(x, y) \
|
|
bounds = CGRectOffset(portrait ? CGRectInset(bounds, x, y) : CGRectInset(bounds, y, x), 1, 2); \
|
|
found_thick_border = true;
|
|
|
|
if (simulator_device_family == DEVICE_FAMILY_IPHONE) {
|
|
if (simulator_retina_type == DEVICE_RETINA_FALSE) {
|
|
if (MATCHES_FRAME(368, 716)) {
|
|
LOG_MATCH("a Non-Retina iPhone with thick iPhone border");
|
|
// Same size on *Non* Retina and Retina Mac *with* iPhone border.
|
|
// State: Portrait & Landscape perfect!
|
|
BORDER_INSET(24, 118);
|
|
}
|
|
}
|
|
else if (simulator_retina_type == DEVICE_RETINA_3_5) {
|
|
if (retina_mac) {
|
|
if (MATCHES_FRAME(368, 716)) {
|
|
LOG_MATCH("a Retina iPhone 3.5\" with thick iPhone border " \
|
|
"running on a Retina Mac");
|
|
// Retina Mac with enough space for the full iPhone border.
|
|
// State: Portrait & Landscape perfect!
|
|
BORDER_INSET(24, 118);
|
|
}
|
|
else if (MATCHES_FRAME(366, 526)) {
|
|
LOG_MATCH("a Retina iPhone 3.5\" with thick iPad border " \
|
|
"running on a Retina Mac");
|
|
// Retina Mac with not enough space for the full iPhone border,
|
|
// so gets an iPad border.
|
|
// State: Untested! Does it even exist?
|
|
BORDER_INSET(23, 23);
|
|
}
|
|
}
|
|
else {
|
|
if (MATCHES_FRAME(368, 716)) {
|
|
LOG_MATCH("a Retina iPhone 3.5\" with thick iPhone border " \
|
|
"running on a Non-Retina Mac");
|
|
// Non-Retina Mac with enough space for the full iPhone border.
|
|
// State: Untested!
|
|
BORDER_INSET(24, 118);
|
|
}
|
|
else if (MATCHES_FRAME(724, 1044)) {
|
|
LOG_MATCH("a Retina iPhone 3.5\" with thick iPad border " \
|
|
"running on a Non-Retina Mac");
|
|
// Non-Retina Mac with not enough space for the full iPhone
|
|
// border, so gets an iPad border.
|
|
// State: Portrait & Landscape perfect!
|
|
BORDER_INSET(42, 42);
|
|
}
|
|
}
|
|
}
|
|
else if (simulator_retina_type == DEVICE_RETINA_4) {
|
|
if (retina_mac) {
|
|
if (MATCHES_FRAME(386, 806)) {
|
|
LOG_MATCH("a Retina iPhone 4\" with thick iPhone border " \
|
|
"running on a Retina Mac");
|
|
// Retina Mac with enough space for the full iPhone border.
|
|
// State: Untested!
|
|
BORDER_INSET(33, 119);
|
|
}
|
|
else if (MATCHES_FRAME(366, 614)) {
|
|
LOG_MATCH("a Retina iPhone 4\" with thick iPad border " \
|
|
"running on a Retina Mac");
|
|
// Retina Mac with not enough space for the full iPhone border,
|
|
// so gets an iPad border.
|
|
// State: Untested! Does it even exist?
|
|
BORDER_INSET(23, 23);
|
|
}
|
|
}
|
|
else {
|
|
if (MATCHES_FRAME(724, 1220)) {
|
|
LOG_MATCH("a Retina iPhone 4\" with thick iPad border " \
|
|
"running on a Non-Retina Mac");
|
|
// Non-Retina Mac with not enough space for the full iPhone
|
|
// border, so gets an iPad border.
|
|
// State: Portrait & Landscape perfect!
|
|
BORDER_INSET(42, 42);
|
|
}
|
|
else if (MATCHES_FRAME(706, 1374)) {
|
|
LOG_MATCH("a Retina iPhone 4\" with thick iPhone border " \
|
|
"running on a Retina Mac");
|
|
// Non-Retina Mac with enough space for the full iPhone border.
|
|
// State: Untested! Does it even exist?
|
|
BORDER_INSET(33, 119);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// iPad
|
|
else {
|
|
if (MATCHES_FRAME(852, 1108)) {
|
|
LOG_MATCH("a Non-Retina iPad with thick iPad border");
|
|
// Non-Retina iPad is the same size on Non-Retina and Retina Mac,
|
|
// as is the Retina iPad on a Retina Mac.
|
|
// State: Only Non-Retina iPad + Non-Retina Mac: Portrait & Landscape perfect!
|
|
BORDER_INSET(42, 42);
|
|
}
|
|
else if (simulator_retina_type == DEVICE_RETINA_TRUE &&
|
|
!retina_mac && MATCHES_FRAME(1582, 2216)) {
|
|
LOG_MATCH("a Retina iPad with thick border on a Non-Retina Mac");
|
|
// Retina iPad on a Non-Retina Mac is roughly twice as large.
|
|
// State: Untested! Does it even exist?
|
|
BORDER_INSET(42, 42);
|
|
}
|
|
}
|
|
if (!found_thick_border) {
|
|
if (debug_sim_window_offsets) {
|
|
fprintf(stderr, "*** Current Simulator window NOT recognized as " \
|
|
"having thick borders (%dx%d).\n", w, h);
|
|
}
|
|
bounds.origin.y += 24;
|
|
bounds.size.height -= 24;
|
|
}
|
|
|
|
#undef MATCHES_FRAME
|
|
#undef LOG_MATCH
|
|
#undef BORDER_INSET
|
|
#endif
|
|
|
|
[app_windows_bounds addObject:[NSValue valueWithRect:bounds]];
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
// On iOS there is only one app window (the simulator).
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
[windows release];
|
|
return (NSArray *)app_windows_bounds;
|
|
}
|
|
|
|
#define CONCURRENT_BEGIN dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
#define CONCURRENT_END });
|
|
|
|
static CGEventRef
|
|
event_tap_cb(CGEventTapProxy proxy, CGEventType type, CGEventRef event,
|
|
void *ctx)
|
|
{
|
|
static bool previousHighlight = false;
|
|
|
|
if (!(CGEventGetFlags(event) & kCGEventFlagMaskCommand)) {
|
|
CONCURRENT_BEGIN
|
|
if (previousHighlight) {
|
|
[delegate replEval:[NSString stringWithFormat:
|
|
@"<<MotionReplCaptureView %f,%f,%d", 0.0, 0.0, 0]];
|
|
previousHighlight = false;
|
|
}
|
|
refresh_repl_prompt(nil, true);
|
|
CONCURRENT_END
|
|
if (type == kCGEventLeftMouseDown) {
|
|
// Reset the simulator app bounds as it may have moved.
|
|
app_windows_ids = nil;
|
|
}
|
|
return event;
|
|
}
|
|
|
|
__block CGPoint mouseLocation = CGEventGetLocation(event);
|
|
const bool capture = type == kCGEventLeftMouseDown;
|
|
|
|
CONCURRENT_BEGIN
|
|
NSArray *app_windows_bounds = get_app_windows_bounds();
|
|
NSString *res = @"nil";
|
|
|
|
bool mouseInBounds = false;
|
|
NSRect windowBounds = { {0, 0}, {0, 0} };
|
|
if (app_windows_bounds != nil) {
|
|
for (NSValue *val in app_windows_bounds) {
|
|
windowBounds = [val rectValue];
|
|
if (NSPointInRect(mouseLocation, windowBounds)) {
|
|
mouseInBounds = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mouseInBounds) {
|
|
// We are over the Simulator.app main view.
|
|
// Inset the mouse location.
|
|
mouseLocation.x -= windowBounds.origin.x;
|
|
mouseLocation.y -= windowBounds.origin.y;
|
|
|
|
// Convert to relative point, taking scale out of the equation.
|
|
// (x=1, y=1) means (x=full-width, y=full-height)
|
|
mouseLocation.x /= windowBounds.size.width;
|
|
mouseLocation.y /= windowBounds.size.height;
|
|
|
|
// Send coordinate to the repl.
|
|
previousHighlight = true;
|
|
res = [delegate replEval:[NSString stringWithFormat:
|
|
@"<<MotionReplCaptureView %f,%f,%d", mouseLocation.x,
|
|
mouseLocation.y, capture ? 2 : 1]];
|
|
}
|
|
else {
|
|
if (previousHighlight) {
|
|
res = [delegate replEval:[NSString stringWithFormat:
|
|
@"<<MotionReplCaptureView %f,%f,%d", 0.0, 0.0, 0]];
|
|
previousHighlight = false;
|
|
}
|
|
}
|
|
|
|
if (capture) {
|
|
refresh_repl_prompt(nil, true);
|
|
}
|
|
else {
|
|
refresh_repl_prompt(res, true);
|
|
}
|
|
CONCURRENT_END
|
|
|
|
return event;
|
|
}
|
|
|
|
static void
|
|
start_capture(id delegate)
|
|
{
|
|
// We only want one kind of event at the moment: The mouse has moved
|
|
CGEventMask emask = CGEventMaskBit(kCGEventMouseMoved)
|
|
| CGEventMaskBit(kCGEventLeftMouseDown);
|
|
|
|
// Create the Tap
|
|
CFMachPortRef myEventTap = CGEventTapCreate(kCGSessionEventTap,
|
|
kCGTailAppendEventTap, kCGEventTapOptionListenOnly, emask,
|
|
&event_tap_cb, NULL);
|
|
|
|
// Create a RunLoop Source for it
|
|
CFRunLoopSourceRef eventTapRLSrc = CFMachPortCreateRunLoopSource(
|
|
kCFAllocatorDefault, myEventTap, 0);
|
|
|
|
// Add the source to the current RunLoop
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventTapRLSrc,
|
|
kCFRunLoopDefaultMode);
|
|
}
|
|
|
|
static bool
|
|
send_string(NSString *string)
|
|
{
|
|
const char *line = [string UTF8String];
|
|
const size_t line_len = strlen(line);
|
|
|
|
if (send(repl_fd, line, line_len, 0) != line_len) {
|
|
if (errno == EPIPE) {
|
|
terminate_session();
|
|
}
|
|
else {
|
|
perror("error when sending data to repl socket");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static NSString *
|
|
receive_string(void)
|
|
{
|
|
NSMutableString *res = [NSMutableString new];
|
|
bool received_something = false;
|
|
while (true) {
|
|
char buf[1024 + 1];
|
|
ssize_t len = recv(repl_fd, buf, sizeof buf, 0);
|
|
if (len == -1) {
|
|
if (errno == EAGAIN) {
|
|
if (!received_something) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (errno == EPIPE) {
|
|
terminate_session();
|
|
}
|
|
else {
|
|
perror("error when receiving data from repl socket");
|
|
}
|
|
[res release];
|
|
return nil;
|
|
}
|
|
if (len > 0) {
|
|
buf[len] = '\0';
|
|
[res appendString:[NSString stringWithUTF8String:buf]];
|
|
}
|
|
else {
|
|
if ([res length] == 0) {
|
|
[res release];
|
|
return nil;
|
|
}
|
|
}
|
|
received_something = true;
|
|
}
|
|
return [res autorelease];
|
|
}
|
|
|
|
- (NSString *)replEval:(NSString *)expression
|
|
{
|
|
if (repl_fd <= 0) {
|
|
return nil;
|
|
}
|
|
|
|
if (repl_fd_lock == nil) {
|
|
repl_fd_lock = [NSLock new];
|
|
}
|
|
|
|
[repl_fd_lock lock];
|
|
NSString *res = nil;
|
|
if (send_string(expression)) {
|
|
res = receive_string();
|
|
}
|
|
[repl_fd_lock unlock];
|
|
|
|
return res;
|
|
}
|
|
|
|
static NSArray *
|
|
repl_complete_data(const char *text)
|
|
{
|
|
// Determine if we want to complete a method or not.
|
|
size_t len = strlen(text);
|
|
if (len == 0) {
|
|
return NULL;
|
|
}
|
|
bool method = false;
|
|
int i;
|
|
for (i = len - 1; i >= 1; i--) {
|
|
if (text[i] == ' ' || text[i] == '\t') {
|
|
break;
|
|
}
|
|
else if (text[i] == '.') {
|
|
method = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Prepare the REPL expression to evaluate.
|
|
char buf[1024];
|
|
strlcpy(buf, "<<MotionReplPreserveLastExpr ", sizeof buf);
|
|
if (method) {
|
|
if (i >= sizeof(buf) - strlen(buf)) {
|
|
return NULL;
|
|
}
|
|
strncat(buf, text, i);
|
|
strlcat(buf, ".methods", sizeof buf);
|
|
}
|
|
else {
|
|
if (isupper(text[0])) {
|
|
strlcat(buf, "Object.constants", sizeof buf);
|
|
}
|
|
else if (text[0] == '@') {
|
|
strlcat(buf, "instance_variables", sizeof buf);
|
|
}
|
|
else {
|
|
strlcat(buf, "local_variables", sizeof buf);
|
|
}
|
|
}
|
|
|
|
// Evaluate the expression.
|
|
NSString *list = [delegate replEval:
|
|
[NSString stringWithUTF8String:buf]];
|
|
if ([list characterAtIndex:0] != '[') {
|
|
// Not an array, likely an exception.
|
|
return NULL;
|
|
}
|
|
// Ignore trailing '[' and ']'.
|
|
list = [list substringWithRange:NSMakeRange(1, [list length] - 2)];
|
|
|
|
// Split tokens.
|
|
NSMutableArray *data = [[NSMutableArray alloc] init];
|
|
NSArray *all = [list componentsSeparatedByString:@", "];
|
|
|
|
// Prepare first part of completion.
|
|
const char *p = &text[i];
|
|
if (method) {
|
|
p++;
|
|
}
|
|
NSString *last = [NSString stringWithUTF8String:p];
|
|
const size_t last_length = [last length];
|
|
|
|
// Filter all tokens based on the first part of completion.
|
|
for (NSString *res in all) {
|
|
size_t res_length = [res length];
|
|
int skip_beg = 1; // Results are symbols, so we skip ':'.
|
|
int skip_end = 0;
|
|
if (res_length < last_length + 1) {
|
|
continue;
|
|
}
|
|
if ([res characterAtIndex:skip_beg] == '"') {
|
|
skip_beg++; // Special symbol, :"foo:bar:".
|
|
skip_end++;
|
|
}
|
|
if (res_length < last_length + skip_beg + skip_end) {
|
|
continue;
|
|
}
|
|
if (last_length == 0) {
|
|
if (method && [res characterAtIndex:skip_beg] == '_') {
|
|
// Skip 'private' methods if we are searching for all
|
|
// methods.
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
NSString *first = [res substringWithRange:NSMakeRange(skip_beg,
|
|
last_length)];
|
|
if (![first isEqualToString:last]) {
|
|
continue;
|
|
}
|
|
}
|
|
res = [res substringWithRange:NSMakeRange(skip_beg,
|
|
[res length] - skip_beg - skip_end)];
|
|
[data addObject:res];
|
|
}
|
|
|
|
// Now prepare the suggested completion result.
|
|
int data_count = [data count];
|
|
if (data_count >= 1) {
|
|
NSString *suggested = nil;
|
|
if (method) {
|
|
suggested = [NSString stringWithUTF8String:text];
|
|
}
|
|
else if (data_count == 1) {
|
|
suggested = [data objectAtIndex:0];
|
|
}
|
|
else {
|
|
int i = 0, low = 100000;
|
|
while (i < data_count) {
|
|
int si = 0;
|
|
while (true) {
|
|
if (i + 1 >= data_count) {
|
|
break;
|
|
}
|
|
NSString *s1 = [data objectAtIndex:i];
|
|
NSString *s2 = [data objectAtIndex:i + 1];
|
|
if (si >= [s1 length] || si >= [s2 length]) {
|
|
break;
|
|
}
|
|
if ([s1 characterAtIndex:si] != [s2 characterAtIndex:si]) {
|
|
break;
|
|
}
|
|
si++;
|
|
}
|
|
if (low > si) {
|
|
low = si;
|
|
}
|
|
i++;
|
|
}
|
|
suggested = [[data objectAtIndex:0] substringToIndex:low];
|
|
}
|
|
[data insertObject:suggested atIndex:0];
|
|
}
|
|
|
|
return [data autorelease];
|
|
}
|
|
|
|
static char **
|
|
repl_complete(const char *text, int start, int end)
|
|
{
|
|
NSArray *data = repl_complete_data(text);
|
|
if (data == nil) {
|
|
return NULL;
|
|
}
|
|
int data_count = [data count];
|
|
if (data_count == 0) {
|
|
return NULL;
|
|
}
|
|
char **res = (char **)malloc(sizeof(char *) * (data_count + 1));
|
|
for (int i = 0; i < data_count; i++) {
|
|
res[i + 0] = strdup([[data objectAtIndex:i] UTF8String]);
|
|
}
|
|
res[[data count]] = NULL;
|
|
return res;
|
|
}
|
|
|
|
- (void)readEvalPrintLoop
|
|
{
|
|
[[NSAutoreleasePool alloc] init];
|
|
|
|
// Wait until the socket file is created.
|
|
while (true) {
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:replSocketPath]) {
|
|
break;
|
|
}
|
|
usleep(500000);
|
|
}
|
|
|
|
// Create the socket.
|
|
const int fd = socket(PF_LOCAL, SOCK_STREAM, 0);
|
|
if (fd == -1) {
|
|
perror("socket()");
|
|
terminate_session();
|
|
return;
|
|
}
|
|
fcntl(fd, F_SETFL, O_NONBLOCK);
|
|
|
|
// Prepare the name.
|
|
struct sockaddr_un name;
|
|
name.sun_family = PF_LOCAL;
|
|
strncpy(name.sun_path, [replSocketPath fileSystemRepresentation],
|
|
sizeof(name.sun_path));
|
|
|
|
// Connect.
|
|
if (connect(fd, (struct sockaddr *)&name, SUN_LEN(&name)) == -1) {
|
|
perror("connect()");
|
|
terminate_session();
|
|
return;
|
|
}
|
|
|
|
repl_fd = fd;
|
|
|
|
rl_readline_name = (char *)"RubyMotionRepl";
|
|
using_history();
|
|
load_repl_history();
|
|
rl_attempted_completion_function = repl_complete;
|
|
rl_basic_word_break_characters = strdup(" \t\n`<;|&(");
|
|
|
|
NSString *expr = nil;
|
|
while (true) {
|
|
// Read expression from stdin.
|
|
NSString *prompt = current_repl_prompt(nil);
|
|
char *line_cstr = readline([prompt UTF8String]);
|
|
if (line_cstr == NULL) {
|
|
terminate_session();
|
|
break;
|
|
}
|
|
NSString *line = [NSString stringWithUTF8String:line_cstr];
|
|
free(line_cstr);
|
|
line_cstr = NULL;
|
|
if ([line length] == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Parse the expression to see if it's complete.
|
|
static NSDictionary *begin_tokens = nil;
|
|
if (begin_tokens == nil) {
|
|
begin_tokens = [[NSDictionary alloc] initWithObjectsAndKeys:
|
|
@"1", @"class",
|
|
@"1", @"module",
|
|
@"1", @"def",
|
|
@"1", @"begin",
|
|
@"1", @"if",
|
|
@"1", @"unless",
|
|
@"1", @"case",
|
|
@"1", @"while",
|
|
@"1", @"for",
|
|
@"1", @"do",
|
|
nil];
|
|
}
|
|
NSMutableString *parse_data = [line mutableCopy];
|
|
while (true) {
|
|
NSUInteger i, count;
|
|
again:
|
|
i = 0;
|
|
count = [parse_data length];
|
|
for (i = 0; i < count; i++) {
|
|
UniChar c = [parse_data characterAtIndex:i];
|
|
switch (c) {
|
|
case '\'':
|
|
case '"':
|
|
case '/':
|
|
case '`':
|
|
for (NSUInteger k = i + 1; k < count; k++) {
|
|
UniChar c2 = [parse_data characterAtIndex:k];
|
|
if (c2 == '\\') {
|
|
k++;
|
|
}
|
|
else if (c2 == c) {
|
|
NSRange range = { i, k - i };
|
|
[parse_data deleteCharactersInRange:range];
|
|
goto again;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
NSArray *tokens = [parse_data componentsSeparatedByCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
[parse_data release];
|
|
int old_expr_level = expr_level;
|
|
for (NSString *token in tokens) {
|
|
if ([begin_tokens objectForKey:token] != nil) {
|
|
expr_level++;
|
|
}
|
|
else if ([token isEqualToString:@"end"]) {
|
|
expr_level--;
|
|
}
|
|
}
|
|
|
|
if (expr == nil) {
|
|
expr = line;
|
|
}
|
|
else {
|
|
expr = [expr stringByAppendingString:@"\n"];
|
|
expr = [expr stringByAppendingString:line];
|
|
}
|
|
|
|
if (old_expr_level - 1 == expr_level) {
|
|
printf("\e[1A\r\e[0K%s%s\n",
|
|
[[prompt substringToIndex:[prompt length] - 2] UTF8String],
|
|
[line UTF8String]);
|
|
}
|
|
|
|
// The expression is not complete yet.
|
|
if (expr_level > 0) {
|
|
continue;
|
|
}
|
|
|
|
// The expression is complete, add to history, eval it and print
|
|
// the result.
|
|
add_history([expr UTF8String]);
|
|
NSString *res = [self replEval:expr];
|
|
if (res == nil) {
|
|
if ([line compare:@"quit"] == NSOrderedSame
|
|
|| [line compare:@"exit"] == NSOrderedSame) {
|
|
terminate_session();
|
|
}
|
|
break;
|
|
}
|
|
printf("=> %s\n", [res UTF8String]);
|
|
|
|
expr = nil;
|
|
expr_level = 0;
|
|
}
|
|
}
|
|
|
|
static NSString *
|
|
save_debugger_command(NSString *cmds)
|
|
{
|
|
#if defined(SIMULATOR_IOS)
|
|
# define SIMGDBCMDS_BASE @"_simgdbcmds_ios"
|
|
#else
|
|
# define SIMGDBCMDS_BASE @"_simgdbcmds_osx"
|
|
#endif
|
|
NSString *cmds_path = [NSString pathWithComponents:
|
|
[NSArray arrayWithObjects:NSTemporaryDirectory(), SIMGDBCMDS_BASE,
|
|
nil]];
|
|
|
|
NSError *error = nil;
|
|
if (![cmds writeToFile:cmds_path atomically:YES
|
|
encoding:NSASCIIStringEncoding error:&error]) {
|
|
fprintf(stderr,
|
|
"*** Cannot write gdb commands file into path %s: %s\n",
|
|
[cmds_path UTF8String],
|
|
[[error description] UTF8String]);
|
|
exit(1);
|
|
}
|
|
return cmds_path;
|
|
}
|
|
|
|
static NSString *
|
|
gdb_commands_file(void)
|
|
{
|
|
NSString *cmds = @""\
|
|
"set breakpoint pending on\n"\
|
|
"break rb_exc_raise\n"\
|
|
"break malloc_error_break\n";
|
|
cmds = [cmds stringByAppendingFormat:@"%s\n", BUILTIN_DEBUGGER_CMDS];
|
|
NSString *user_cmds = [NSString stringWithContentsOfFile:
|
|
@"debugger_cmds" encoding:NSUTF8StringEncoding error:nil];
|
|
if (user_cmds != nil) {
|
|
cmds = [cmds stringByAppendingString:user_cmds];
|
|
cmds = [cmds stringByAppendingString:@"\n"];
|
|
}
|
|
if (getenv("no_continue") == NULL) {
|
|
cmds = [cmds stringByAppendingString:
|
|
#if defined(SIMULATOR_IOS)
|
|
@"continue\n"
|
|
#else
|
|
@"run\n"
|
|
#endif
|
|
];
|
|
}
|
|
|
|
return save_debugger_command(cmds);
|
|
}
|
|
|
|
static NSString *
|
|
lldb_commands_file(int pid, NSString *app_path)
|
|
{
|
|
NSString *cmds = @"";
|
|
if (pid >= 0) {
|
|
cmds = [cmds stringByAppendingFormat:@"process attach -p %d\n", pid];
|
|
}
|
|
else if (app_path != nil) {
|
|
cmds = [cmds stringByAppendingFormat:@"target create \"%@\"\n",
|
|
app_path];
|
|
}
|
|
else {
|
|
abort();
|
|
}
|
|
cmds = [cmds stringByAppendingString:@""\
|
|
"command script import /Library/RubyMotion/lldb/lldb.py\n"\
|
|
"breakpoint set --name rb_exc_raise\n"\
|
|
"breakpoint set --name malloc_error_break\n"];
|
|
NSString *user_cmds = [NSString stringWithContentsOfFile:
|
|
@"debugger_cmds" encoding:NSUTF8StringEncoding error:nil];
|
|
if (user_cmds != nil) {
|
|
cmds = [cmds stringByAppendingString:user_cmds];
|
|
cmds = [cmds stringByAppendingString:@"\n"];
|
|
}
|
|
if (getenv("no_continue") == NULL) {
|
|
cmds = [cmds stringByAppendingString:
|
|
#if defined(SIMULATOR_IOS)
|
|
@"continue\n"
|
|
#else
|
|
@"run\n"
|
|
#endif
|
|
];
|
|
}
|
|
|
|
return save_debugger_command(cmds);
|
|
}
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
- (void)session:(id)session didEndWithError:(NSError *)error
|
|
{
|
|
if (gdb_task != nil) {
|
|
[gdb_task terminate];
|
|
[gdb_task waitUntilExit];
|
|
}
|
|
|
|
if (debug_mode == DEBUG_REPL) {
|
|
save_repl_history();
|
|
}
|
|
|
|
if (spec_mode || error == nil || debugger_killed_session) {
|
|
int status = 0;
|
|
NSNumber *pidNumber = ((id (*)(id, SEL))objc_msgSend)(session,
|
|
@selector(simulatedApplicationPID));
|
|
if (pidNumber != nil && [pidNumber isKindOfClass:[NSNumber class]]) {
|
|
NSString *path = [NSString stringWithFormat:
|
|
@"/tmp/.rubymotion_process_exited.%@",
|
|
[pidNumber description]];
|
|
NSString *res = [NSString stringWithContentsOfFile:path
|
|
encoding:NSASCIIStringEncoding error:nil];
|
|
if (res != nil) {
|
|
status = [res intValue];
|
|
}
|
|
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
|
}
|
|
exit(status);
|
|
}
|
|
else {
|
|
fprintf(stderr, "*** Simulator session ended with error: %s\n",
|
|
[[error description] UTF8String]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
- (void)session:(id)session didStart:(BOOL)flag withError:(NSError *)error
|
|
{
|
|
if (!flag || error != nil) {
|
|
fprintf(stderr, "*** Simulator session started with error: %s\n",
|
|
[[error description] UTF8String]);
|
|
exit(1);
|
|
}
|
|
|
|
// Open simulator to the foreground.
|
|
if (!spec_mode) {
|
|
NSArray *ary = [NSRunningApplication runningApplicationsWithBundleIdentifier:
|
|
@"com.apple.iphonesimulator"];
|
|
if (ary != nil && [ary count] == 1) {
|
|
[[ary objectAtIndex:0] activateWithOptions:
|
|
NSApplicationActivateIgnoringOtherApps];
|
|
}
|
|
}
|
|
|
|
if (debug_mode == DEBUG_GDB) {
|
|
NSNumber *pidNumber = ((id (*)(id, SEL))objc_msgSend)(session,
|
|
@selector(simulatedApplicationPID));
|
|
if (pidNumber == nil || ![pidNumber isKindOfClass:[NSNumber class]]) {
|
|
fprintf(stderr, "*** Cannot get simulated application PID\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Forward ^C to gdb.
|
|
signal(SIGINT, sigforwarder);
|
|
|
|
// Run the debugger process.
|
|
NSString *gdb_path = [xcode_path stringByAppendingPathComponent:@"Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin"];
|
|
NSString *lldb_path = [xcode_path stringByAppendingPathComponent:@"Platforms/iPhoneSimulator.platform/Developer/usr/bin/lldb"];
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:gdb_path]) {
|
|
gdb_task = [[RMTask launchedTaskWithLaunchPath:gdb_path
|
|
arguments:[NSArray arrayWithObjects:@"--arch", @"i386", @"-q",
|
|
@"--pid", [pidNumber description], @"-x", gdb_commands_file(), nil]] retain];
|
|
}
|
|
else if ([[NSFileManager defaultManager] fileExistsAtPath:lldb_path]) {
|
|
gdb_task = [[RMTask launchedTaskWithLaunchPath:lldb_path
|
|
arguments:[NSArray arrayWithObjects:@"-a", @"i386",
|
|
@"-s", lldb_commands_file([pidNumber intValue], nil), nil]]
|
|
retain];
|
|
}
|
|
else {
|
|
fprintf(stderr,
|
|
"*** Cannot locate a debugger (either gdb `%s' or lldb `%s')\n",
|
|
[gdb_path UTF8String], [lldb_path UTF8String]);
|
|
exit(1);
|
|
}
|
|
[gdb_task waitUntilExit];
|
|
gdb_task = nil;
|
|
|
|
debugger_killed_session = YES;
|
|
((void (*)(id, SEL, NSTimeInterval))objc_msgSend)(session,
|
|
@selector(requestEndWithTimeout:), 0);
|
|
}
|
|
else if (debug_mode == DEBUG_REPL) {
|
|
[NSThread detachNewThreadSelector:@selector(readEvalPrintLoop)
|
|
toTarget:self withObject:nil];
|
|
start_capture(self);
|
|
}
|
|
|
|
//fprintf(stderr, "*** simulator session started\n");
|
|
}
|
|
#endif
|
|
|
|
@end
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
system("open http://www.youtube.com/watch?v=1orMXD_Ijbs&feature=fvst");
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
[[NSAutoreleasePool alloc] init];
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
# define MIN_ARGS 6
|
|
#else
|
|
# define MIN_ARGS 4
|
|
#endif
|
|
|
|
if (argc < MIN_ARGS) {
|
|
usage();
|
|
}
|
|
|
|
spec_mode = getenv("SIM_SPEC_MODE") != NULL;
|
|
debug_sim_window_offsets = getenv("DEBUG_SIM_WINDOW_OFFSETS") != NULL;
|
|
|
|
int argv_n = 1;
|
|
debug_mode = atoi(argv[argv_n++]);
|
|
#if defined(SIMULATOR_IOS)
|
|
NSNumber *device_family = [NSNumber numberWithInt:atoi(argv[argv_n++])];
|
|
#endif
|
|
sdk_version = [[NSString stringWithUTF8String:argv[argv_n++]] retain];
|
|
#if defined(SIMULATOR_IOS)
|
|
xcode_path = [[NSString stringWithUTF8String:argv[argv_n++]] retain];
|
|
#endif
|
|
NSString *app_path =
|
|
[NSString stringWithUTF8String:realpath(argv[argv_n++], NULL)];
|
|
|
|
NSMutableArray *app_args = [NSMutableArray new];
|
|
for (unsigned i = MIN_ARGS; i < argc; i++) {
|
|
[app_args addObject:[NSString stringWithUTF8String:argv[i]]];
|
|
}
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
// Load the framework.
|
|
[[NSBundle bundleWithPath:[xcode_path stringByAppendingPathComponent:@"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/iPhoneSimulatorRemoteClient.framework"]] load];
|
|
|
|
Class AppSpecifier =
|
|
NSClassFromString(@"DTiPhoneSimulatorApplicationSpecifier");
|
|
assert(AppSpecifier != nil);
|
|
|
|
Class SystemRoot = NSClassFromString(@"DTiPhoneSimulatorSystemRoot");
|
|
assert(SystemRoot != nil);
|
|
|
|
Class SessionConfig = NSClassFromString(@"DTiPhoneSimulatorSessionConfig");
|
|
assert(SessionConfig != nil);
|
|
|
|
Class Session = NSClassFromString(@"DTiPhoneSimulatorSession");
|
|
assert(Session != nil);
|
|
#endif
|
|
|
|
// Prepare app environment.
|
|
NSMutableDictionary *appEnvironment = [[[NSProcessInfo processInfo]
|
|
environment] mutableCopy];
|
|
if (debug_mode != DEBUG_NOTHING) {
|
|
// Prepare repl socket path.
|
|
NSString *tmpdir = NSTemporaryDirectory();
|
|
assert(tmpdir != nil);
|
|
char path[PATH_MAX];
|
|
snprintf(path, sizeof path, "%s/rubymotion-repl-XXXXXX",
|
|
[tmpdir fileSystemRepresentation]);
|
|
assert(mktemp(path) != NULL);
|
|
replSocketPath = [[[NSFileManager defaultManager]
|
|
stringWithFileSystemRepresentation:path length:strlen(path)]
|
|
retain];
|
|
[appEnvironment setObject:replSocketPath forKey:@"REPL_SOCKET_PATH"];
|
|
|
|
// Make sure the unix socket path does not exist.
|
|
[[NSFileManager defaultManager] removeItemAtPath:replSocketPath
|
|
error:nil];
|
|
|
|
// Prepare repl dylib path.
|
|
NSString *replPath = nil;
|
|
replPath = [[NSFileManager defaultManager]
|
|
stringWithFileSystemRepresentation:argv[0] length:strlen(argv[0])];
|
|
replPath = [replPath stringByDeletingLastPathComponent];
|
|
replPath = [replPath stringByDeletingLastPathComponent];
|
|
replPath = [replPath stringByDeletingLastPathComponent];
|
|
replPath = [replPath stringByAppendingPathComponent:@"data"];
|
|
#if defined(SIMULATOR_IOS)
|
|
replPath = [replPath stringByAppendingPathComponent:@"ios"];
|
|
#else
|
|
replPath = [replPath stringByAppendingPathComponent:@"osx"];
|
|
#endif
|
|
replPath = [replPath stringByAppendingPathComponent:@"libmacruby-repl.dylib"];
|
|
[appEnvironment setObject:replPath forKey:@"REPL_DYLIB_PATH"];
|
|
}
|
|
|
|
char *malloc_debug_level = NULL;
|
|
if ((malloc_debug_level = getenv("malloc_debug")) != NULL) {
|
|
int level = atoi(malloc_debug_level);
|
|
if (level >= 1) {
|
|
[appEnvironment setObject:@"1" forKey:@"MallocStackLoggingNoCompact"];
|
|
}
|
|
if (level >= 2) {
|
|
[appEnvironment setObject:@"/usr/lib/libgmalloc.dylib"
|
|
forKey:@"DYLD_INSERT_LIBRARIES"];
|
|
}
|
|
}
|
|
|
|
#if defined(SIMULATOR_IOS)
|
|
// Create application specifier.
|
|
id app_spec = ((id (*)(id, SEL, id))objc_msgSend)(AppSpecifier,
|
|
@selector(specifierWithApplicationPath:), app_path);
|
|
assert(app_spec != nil);
|
|
|
|
// Create system root.
|
|
id system_root = ((id (*)(id, SEL, id))objc_msgSend)(SystemRoot,
|
|
@selector(rootWithSDKVersion:), sdk_version);
|
|
if (system_root == nil) {
|
|
fprintf(stderr, "*** iOS simulator for %s SDK not found.\n\n",
|
|
[sdk_version UTF8String]);
|
|
exit(1);
|
|
}
|
|
|
|
// Create session config.
|
|
id config = [[SessionConfig alloc] init];
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setApplicationToSimulateOnStart:), app_spec);
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedApplicationLaunchArgs:), app_args);
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedApplicationLaunchEnvironment:),
|
|
appEnvironment);
|
|
((void (*)(id, SEL, BOOL))objc_msgSend)(config,
|
|
@selector(setSimulatedApplicationShouldWaitForDebugger:),
|
|
(debug_mode == DEBUG_GDB || getenv("SIM_WAIT_FOR_DEBUGGER") != NULL));
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedDeviceFamily:), device_family);
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedSystemRoot:), system_root);
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setLocalizedClientName:), @"NYANCAT");
|
|
|
|
char path[MAXPATHLEN] = {'\0'};
|
|
const char *stdout_path = getenv("SIM_STDOUT_PATH");
|
|
if (stdout_path == NULL) {
|
|
if (fcntl(STDOUT_FILENO, F_GETPATH, &path) == -1) {
|
|
printf("*** stdout unavailable, output disabled\n");
|
|
}
|
|
else {
|
|
stdout_path = path;
|
|
}
|
|
}
|
|
if (stdout_path != NULL) {
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedApplicationStdOutPath:),
|
|
[NSString stringWithUTF8String:stdout_path]);
|
|
}
|
|
|
|
const char *stderr_path = getenv("SIM_STDERR_PATH");
|
|
if (stderr_path == NULL) {
|
|
if (fcntl(STDERR_FILENO, F_GETPATH, &path) == -1) {
|
|
printf("*** stderr unavailable, output disabled\n");
|
|
}
|
|
else {
|
|
stderr_path = path;
|
|
}
|
|
}
|
|
if (stderr_path != NULL) {
|
|
((void (*)(id, SEL, id))objc_msgSend)(config,
|
|
@selector(setSimulatedApplicationStdErrPath:),
|
|
[NSString stringWithUTF8String:stderr_path]);
|
|
}
|
|
|
|
// Create session.
|
|
id session = [[Session alloc] init];
|
|
delegate = [[Delegate alloc] init];
|
|
((void (*)(id, SEL, id))objc_msgSend)(session, @selector(setDelegate:),
|
|
delegate);
|
|
|
|
// Start session.
|
|
NSError *error = nil;
|
|
if (!((BOOL (*)(id, SEL, id, double, id *))objc_msgSend)(session,
|
|
@selector(requestStartWithConfig:timeout:error:), config, 0.0,
|
|
&error)) {
|
|
fprintf(stderr, "*** Cannot start simulator: %s\n",
|
|
[[error description] UTF8String]);
|
|
exit(1);
|
|
}
|
|
|
|
if (debug_mode != DEBUG_GDB) {
|
|
// ^C should terminate the request.
|
|
current_session = session;
|
|
signal(SIGINT, sigterminate);
|
|
signal(SIGPIPE, sigcleanup);
|
|
}
|
|
|
|
[[NSRunLoop mainRunLoop] run];
|
|
|
|
#else // !SIMULATOR_IOS
|
|
|
|
if (debug_mode != DEBUG_GDB) {
|
|
signal(SIGINT, sigint_osx);
|
|
signal(SIGPIPE, sigcleanup);
|
|
|
|
delegate = [[Delegate alloc] init];
|
|
[NSThread detachNewThreadSelector:@selector(readEvalPrintLoop)
|
|
toTarget:delegate withObject:nil];
|
|
|
|
osx_task = [[NSTask alloc] init];
|
|
[osx_task setEnvironment:appEnvironment];
|
|
[osx_task setLaunchPath:app_path];
|
|
[osx_task setArguments:app_args];
|
|
[osx_task launch];
|
|
|
|
// move to the foreground.
|
|
usleep(0.1 * 1000000);
|
|
ProcessSerialNumber psn;
|
|
GetProcessForPID([osx_task processIdentifier], &psn);
|
|
SetFrontProcess(&psn);
|
|
|
|
start_capture(delegate);
|
|
[osx_task waitUntilExit];
|
|
int status = [osx_task terminationStatus];
|
|
exit(status);
|
|
}
|
|
else {
|
|
// Run the gdb process.
|
|
// XXX using system(3) as NSTask isn't working well (termios issue).
|
|
NSString *gdb_path = @"/usr/bin/gdb";
|
|
NSString *lldb_path = @"/usr/bin/lldb";
|
|
NSString *line = nil;
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:gdb_path]) {
|
|
line = [NSString stringWithFormat:@"%@ -x \"%@\" \"%@\"",
|
|
gdb_path, gdb_commands_file(), app_path];
|
|
}
|
|
else if ([[NSFileManager defaultManager] fileExistsAtPath:lldb_path]) {
|
|
line = [NSString stringWithFormat:@"%@ -s \"%@\"",
|
|
lldb_path, lldb_commands_file(-1, app_path)];
|
|
}
|
|
else {
|
|
fprintf(stderr,
|
|
"*** Cannot locate a debugger (either gdb `%s' or lldb `%s')\n",
|
|
[gdb_path UTF8String], [lldb_path UTF8String]);
|
|
exit(1);
|
|
}
|
|
system([line UTF8String]);
|
|
}
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
}
|