diff options
author | Benjamin C. Pierce <bcpierce@cis.upenn.edu> | 2007-04-13 19:43:44 +0000 |
---|---|---|
committer | Benjamin C. Pierce <bcpierce@cis.upenn.edu> | 2007-04-13 19:43:44 +0000 |
commit | d9f3234ea6fe4eb44f14b9ccc455cde49037f9e0 (patch) | |
tree | 1aec43ed6afc4ee4cc316a3f5e6ec7909528bf99 | |
parent | 275ec30d81e5b4d2c57280f63f2f1e4e6cf69047 (diff) | |
download | unison-d9f3234ea6fe4eb44f14b9ccc455cde49037f9e0.zip unison-d9f3234ea6fe4eb44f14b9ccc455cde49037f9e0.tar.gz unison-d9f3234ea6fe4eb44f14b9ccc455cde49037f9e0.tar.bz2 |
* Copy small fix
25 files changed, 2146 insertions, 0 deletions
diff --git a/src/RECENTNEWS b/src/RECENTNEWS index d5a960d..f142222 100644 --- a/src/RECENTNEWS +++ b/src/RECENTNEWS @@ -1,3 +1,7 @@ +CHANGES FROM VERSION 2.27.19 + +* Copy small fix +------------------------------- CHANGES FROM VERSION 2.27.14 * Copying over changes from 2.27 branch to developer sources (checkpoint) diff --git a/src/mkProjectInfo.ml b/src/mkProjectInfo.ml index 098427c..74d30ae 100644 --- a/src/mkProjectInfo.ml +++ b/src/mkProjectInfo.ml @@ -51,3 +51,4 @@ Printf.printf "VERSION=%d.%d.%d\n" majorVersion minorVersion pointVersion;; Printf.printf "NAME=%s\n" projectName;; + diff --git a/src/uimac/English.lproj/InfoPlist.strings b/src/uimac/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 0000000..ce15fb6 --- /dev/null +++ b/src/uimac/English.lproj/InfoPlist.strings diff --git a/src/uimac/English.lproj/MainMenu.nib/classes.nib b/src/uimac/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 0000000..56d717d --- /dev/null +++ b/src/uimac/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,106 @@ +{ + IBClasses = ( + { + ACTIONS = { + copyLR = id; + copyRL = id; + forceNewer = id; + forceOlder = id; + ignoreExt = id; + ignoreName = id; + ignorePath = id; + leaveAlone = id; + merge = id; + revert = id; + selectConflicts = id; + }; + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; + }, + { + ACTIONS = { + cancelProfileButton = id; + createButton = id; + endPasswordWindow = id; + installCommandLineTool = id; + onlineHelp = id; + openButton = id; + raiseAboutWindow = id; + restartButton = id; + saveProfileButton = id; + syncButton = id; + }; + CLASS = MyController; + LANGUAGE = ObjC; + OUTLETS = { + ConnectingView = NSView; + aboutWindow = NSWindow; + chooseProfileView = NSView; + detailsTextView = NSTextView; + mainWindow = NSWindow; + passwordCancelButton = NSButton; + passwordText = NSTextField; + passwordWindow = NSWindow; + preferencesController = PreferencesController; + preferencesView = NSView; + profileController = ProfileController; + statusText = NSTextField; + tableView = ReconTableView; + updatesText = NSTextField; + updatesView = NSView; + versionText = NSTextField; + }; + SUPERCLASS = NSObject; + }, + { + ACTIONS = {anyEnter = id; localClick = id; remoteClick = id; }; + CLASS = PreferencesController; + LANGUAGE = ObjC; + OUTLETS = { + cancelButton = NSButton; + firstRootText = NSTextField; + localButtonCell = NSButtonCell; + profileNameText = NSTextField; + remoteButtonCell = NSButtonCell; + saveButton = NSButton; + secondRootHost = NSTextField; + secondRootText = NSTextField; + secondRootUser = NSTextField; + }; + SUPERCLASS = NSObject; + }, + { + CLASS = ProfileController; + LANGUAGE = ObjC; + OUTLETS = {tableView = NSTableView; }; + SUPERCLASS = NSObject; + }, + { + CLASS = ProfileTableView; + LANGUAGE = ObjC; + OUTLETS = {myController = MyController; }; + SUPERCLASS = NSTableView; + }, + {CLASS = ReconItem; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = { + copyLR = id; + copyRL = id; + forceNewer = id; + forceOlder = id; + ignoreExt = id; + ignoreName = id; + ignorePath = id; + leaveAlone = id; + merge = id; + revert = id; + selectConflicts = id; + }; + CLASS = ReconTableView; + LANGUAGE = ObjC; + SUPERCLASS = NSTableView; + } + ); + IBVersion = 1; +}
\ No newline at end of file diff --git a/src/uimac/English.lproj/MainMenu.nib/info.nib b/src/uimac/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 0000000..9b49b29 --- /dev/null +++ b/src/uimac/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBDocumentLocation</key> + <string>318 45 509 310 0 0 1280 832 </string> + <key>IBEditorPositions</key> + <dict> + <key>197</key> + <string>450 391 383 326 0 0 1280 832 </string> + <key>198</key> + <string>307 297 669 515 0 0 1280 832 </string> + <key>29</key> + <string>72 209 280 44 0 0 1280 832 </string> + <key>307</key> + <string>392 388 499 332 0 0 1280 832 </string> + <key>423</key> + <string>450 391 383 326 0 0 1280 832 </string> + </dict> + <key>IBFramework Version</key> + <string>364.0</string> + <key>IBOpenObjects</key> + <array> + <integer>423</integer> + <integer>198</integer> + <integer>29</integer> + <integer>402</integer> + <integer>234</integer> + <integer>21</integer> + <integer>307</integer> + <integer>197</integer> + </array> + <key>IBSystem Version</key> + <string>7U16</string> +</dict> +</plist> diff --git a/src/uimac/English.lproj/MainMenu.nib/objects.nib b/src/uimac/English.lproj/MainMenu.nib/objects.nib Binary files differnew file mode 100644 index 0000000..6d5d3c6 --- /dev/null +++ b/src/uimac/English.lproj/MainMenu.nib/objects.nib diff --git a/src/uimac/Info.plist b/src/uimac/Info.plist new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/uimac/Info.plist diff --git a/src/uimac/Info.plist.template b/src/uimac/Info.plist.template new file mode 100644 index 0000000..22a46af --- /dev/null +++ b/src/uimac/Info.plist.template @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleName</key> + <string>Unison</string> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>Unison</string> + <key>CFBundleIconFile</key> + <string>Unison.icns</string> + <key>CFBundleIdentifier</key> + <string>edu.upenn.cis.Unison</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>@@VERSION@@</string> + <key>CFBundleShortVersionString</key> + <string>@@VERSION@@</string> + <key>CFBundleGetInfoString</key> + <string>@@VERSION@@. ©1999-2007, licensed under GNU GPL.</string> + <key>NSHumanReadableCopyright</key> + <string>©1999-2006, licensed under GNU GPL.</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/src/uimac/MyController.h b/src/uimac/MyController.h new file mode 100644 index 0000000..82ba65f --- /dev/null +++ b/src/uimac/MyController.h @@ -0,0 +1,69 @@ +/* MyController */ +/* Copyright (c) 2003, see file COPYING for details. */ + +#import <Cocoa/Cocoa.h> +#define CAML_NAME_SPACE +#include <caml/mlvalues.h> +#import "ProfileController.h" +#import "PreferencesController.h" +#import "ReconTableView.h" + +@interface MyController : NSObject +{ + IBOutlet NSWindow *mainWindow; + + IBOutlet ProfileController *profileController; + IBOutlet NSView *chooseProfileView; + NSSize chooseProfileSize; + + IBOutlet PreferencesController *preferencesController; + IBOutlet NSView *preferencesView; + NSSize preferencesSize; + + IBOutlet NSView *updatesView; + NSSize updatesSize; + + IBOutlet NSView *ConnectingView; + NSSize ConnectingSize; + + IBOutlet ReconTableView *tableView; + IBOutlet NSTextField *updatesText; + + IBOutlet NSWindow *passwordWindow; + IBOutlet NSTextField *passwordText; + IBOutlet NSTextView *detailsTextView; + IBOutlet NSTextField *statusText; + + IBOutlet NSButton *passwordCancelButton; + + IBOutlet NSWindow *aboutWindow; + IBOutlet NSTextField *versionText; + + + NSView *blankView; + value caml_reconItems; + NSMutableArray *reconItems; + value preconn; + + NSString *pName; +} +- (IBAction)createButton:(id)sender; +- (IBAction)saveProfileButton:(id)sender; +- (IBAction)cancelProfileButton:(id)sender; +- (IBAction)openButton:(id)sender; +- (IBAction)restartButton:(id)sender; +- (IBAction)syncButton:(id)sender; +- (IBAction)onlineHelp:(id)sender; +- (int)numberOfRowsInTableView:(NSTableView *)aTableView; +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex; +- (void)raisePasswordWindow:(NSString *)prompt; +- (IBAction)raiseAboutWindow:(id)sender; +- (void)controlTextDidEndEditing:(NSNotification *)notification; +- (IBAction)endPasswordWindow:(id)sender; +- (NSMutableArray *)reconItems; +- (int)updateForIgnore:(int)i; +- (void)displayDetails:(int)i; +- (IBAction)installCommandLineTool:(id)sender; +@end diff --git a/src/uimac/MyController.m b/src/uimac/MyController.m new file mode 100644 index 0000000..eb5c011 --- /dev/null +++ b/src/uimac/MyController.m @@ -0,0 +1,497 @@ +/* Copyright (c) 2003, see file COPYING for details. */ + +#import "MyController.h" +#import "ReconItem.h" +#include <caml/callback.h> +#include <caml/alloc.h> +#include <caml/mlvalues.h> +#include <caml/memory.h> + +extern value Callback_checkexn(value,value); +extern value Callback2_checkexn(value,value,value); + +@implementation MyController + +static MyController *me; // needed by reloadTable and displayStatus, below + +- (void)resizeWindowToSize:(NSSize)newSize +{ + NSRect aFrame; + + float newHeight = newSize.height; + float newWidth = newSize.width; + + aFrame = [NSWindow contentRectForFrameRect:[mainWindow frame] + styleMask:[mainWindow styleMask]]; + + aFrame.origin.y += aFrame.size.height; + aFrame.origin.y -= newHeight; + aFrame.size.height = newHeight; + aFrame.size.width = newWidth; + + aFrame = [NSWindow frameRectForContentRect:aFrame + styleMask:[mainWindow styleMask]]; + + [mainWindow setFrame:aFrame display:YES animate:YES]; +} + +- (void)chooseProfiles +{ + [mainWindow setContentView:blankView]; + [self resizeWindowToSize:chooseProfileSize]; + [mainWindow setContentView:chooseProfileView]; + [mainWindow makeFirstResponder:[profileController tableView]]; // profiles get keyboard input +} + +- (IBAction)createButton:(id)sender +{ + [preferencesController reset]; + [mainWindow setContentView:blankView]; + [self resizeWindowToSize:preferencesSize]; + [mainWindow setContentView:preferencesView]; +} + +- (IBAction)saveProfileButton:(id)sender +{ + if ([preferencesController validatePrefs]) { + [profileController initProfiles]; // so the list contains the new profile + [self chooseProfiles]; + } +} + +- (IBAction)cancelProfileButton:(id)sender +{ + [self chooseProfiles]; +} + +- (void)updateReconItems +{ + [reconItems release]; + reconItems = [[NSMutableArray alloc] init]; + int j = 0; + int n = Wosize_val(caml_reconItems); + for (; j<n; j++) { + [reconItems insertObject:[ReconItem initWithRi:Field(caml_reconItems,j)] + atIndex:j]; + } +} + +- (void)displayDetails:(int)i +{ + if (i >= 0 && i < [reconItems count]) + [detailsTextView setString:[[reconItems objectAtIndex:i] details]]; +} +- (void)clearDetails +{ + [detailsTextView setString:@""]; +} + +- (void)doUpdateThread:(id)whatever +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + preconn = Val_unit; // so old preconn can be garbage collected + value *f = caml_named_value("unisonInit2"); + caml_reconItems = Callback_checkexn(*f, Val_unit); + [pool release]; +} + +- (void)afterUpdate:(NSNotification *)notification +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSThreadWillExitNotification + object:nil]; + [self updateReconItems]; + if ([reconItems count] > 0) + [tableView selectRow:0 byExtendingSelection:NO]; + + // label the left and right columns with the roots + NSTableHeaderCell *left = [[[tableView tableColumns] objectAtIndex:0] headerCell]; + value *f = caml_named_value("unisonFirstRootString"); + [left setObjectValue:[NSString stringWithCString:String_val(Callback_checkexn(*f, Val_unit))]]; + NSTableHeaderCell *right = [[[tableView tableColumns] objectAtIndex:2] headerCell]; + f = caml_named_value("unisonSecondRootString"); + [right setObjectValue:[NSString stringWithCString:String_val(Callback_checkexn(*f, Val_unit))]]; + + // cause scrollbar to display if necessary + [tableView reloadData]; + + // activate menu items + [tableView setEditable:YES]; +} + +- (void)afterOpen +{ + NSLog(@"Connected."); + // move to updates window after clearing it + [self clearDetails]; + [reconItems release]; + reconItems = nil; + [mainWindow setContentView:blankView]; + [self resizeWindowToSize:updatesSize]; + [mainWindow setContentView:updatesView]; + + // reconItems table gets keyboard input + [mainWindow makeFirstResponder:tableView]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(afterUpdate:) + name:NSThreadWillExitNotification object:nil]; + [NSThread detachNewThreadSelector:@selector(doUpdateThread:) + toTarget:self withObject:nil]; +} + +- (void)connect:(value)profileName +{ + // contact server, propagate prefs + NSLog(@"Connecting..."); + + // Switch to ConnectingView + [mainWindow setContentView:blankView]; + [self resizeWindowToSize:ConnectingSize]; + [mainWindow setContentView:ConnectingView]; + [ConnectingView setNeedsDisplay:YES]; // FIX: this doesn't seem to work fast enough + + // possibly slow -- need another thread? Print "contacting server" + value *f = NULL; + f = caml_named_value("unisonInit1"); + preconn = Callback_checkexn(*f, profileName); + if (preconn == Val_unit) { + [self afterOpen]; // no prompting required + return; + } + // prompting required + preconn = Field(preconn,0); // value of Some + f = caml_named_value("openConnectionPrompt"); + value prompt = Callback_checkexn(*f, preconn); + if (prompt == Val_unit) { + // turns out, no prompt needed, but must finish opening connection + f = caml_named_value("openConnectionEnd"); + Callback_checkexn(*f, preconn); + [self afterOpen]; + return; + } + [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]]; +} + +- (IBAction)openButton:(id)sender +{ + NSString *profile = [profileController selected]; + [updatesText setStringValue:[NSString stringWithFormat:@"Synchronizing profile '%@'", + profile]]; + const char *s = [profile cString]; + value caml_s = caml_copy_string(s); + [self connect:caml_s]; + return; +} + +- (IBAction)restartButton:(id)sender +{ + [tableView setEditable:NO]; + [self chooseProfiles]; +} + +- (void)doSyncThread:(id)whatever +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + value *f = caml_named_value("unisonSynchronize"); + Callback_checkexn(*f, Val_unit); + [pool release]; +} + +- (void)afterSync:(NSNotification *)notification +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSThreadWillExitNotification + object:nil]; + int i; + for (i = 0; i < [reconItems count]; i++) { + [[reconItems objectAtIndex:i] resetProgress]; + } + [tableView reloadData]; +} + +- (IBAction)syncButton:(id)sender +{ + [tableView setEditable:NO]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(afterSync:) + name:NSThreadWillExitNotification object:nil]; + [NSThread detachNewThreadSelector:@selector(doSyncThread:) + toTarget:self withObject:nil]; +} + +- (void)updateTableView:(int)i +{ + [[reconItems objectAtIndex:i] resetProgress]; + [tableView reloadData]; // FIX: can we redisplay just row i? +} + +// A function called from ocaml +CAMLprim value reloadTable(value row) +{ + int i = Int_val(row); + [me updateTableView:i]; // we need 'me' to access its instance variables + return Val_unit; +} + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + if (!reconItems) return 0; + else return [reconItems count]; +} + +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + if (!reconItems) { + return @"[internal error]"; + } + if (rowIndex >= 0 && rowIndex < [reconItems count]) { + NSString *identifier = [aTableColumn identifier]; + ReconItem *ri = [reconItems objectAtIndex:rowIndex]; + NSString *s = [ri valueForKey:identifier]; + return s; + } + else return @"[internal error!]"; +} +- (void)tableViewSelectionDidChange:(NSNotification *)note +{ + int n = [tableView numberOfSelectedRows]; + if (n == 1) [self displayDetails:[tableView selectedRow]]; + else [self clearDetails]; +} + +- (void)raisePasswordWindow:(NSString *)prompt +{ + // FIX: some prompts don't ask for password, need to look at it + NSLog(@"Got the prompt: '%@'",prompt); + value *f = caml_named_value("unisonPasswordMsg"); + value v = Callback_checkexn(*f, caml_copy_string([prompt cString])); + if (v == Val_true) { + [NSApp beginSheet:passwordWindow + modalForWindow:mainWindow + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + return; + } + f = caml_named_value("unisonAuthenticityMsg"); + v = Callback_checkexn(*f, caml_copy_string([prompt cString])); + if (v == Val_true) { + int i = NSRunAlertPanel(@"New host",prompt,@"Yes",@"No",nil); + if (i == NSAlertDefaultReturn) { + f = caml_named_value("openConnectionReply"); + Callback2_checkexn(*f, preconn, caml_copy_string("yes")); + f = caml_named_value("openConnectionPrompt"); + value prompt = Callback_checkexn(*f, preconn); + if (prompt == Val_unit) { + // all done with prompts, finish opening connection + f = caml_named_value("openConnectionEnd"); + Callback_checkexn(*f, preconn); + [self afterOpen]; + return; + } + else { + [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]]; + return; + } + } + if (i == NSAlertAlternateReturn) { + f = caml_named_value("openConnectionCancel"); + Callback_checkexn(*f, preconn); + return; + } + else { + NSLog(@"Unrecognized response '%d' from NSRunAlertPanel",i); + f = caml_named_value("openConnectionCancel"); + Callback_checkexn(*f, preconn); + return; + } + } + NSLog(@"Unrecognized message from ssh: %@",prompt); + f = caml_named_value("openConnectionCancel"); + Callback_checkexn(*f, preconn); +} +// The password window will invoke this when Enter occurs, b/c we +// are the delegate. +- (void)controlTextDidEndEditing:(NSNotification *)notification +{ + NSNumber *reason = [[notification userInfo] objectForKey:@"NSTextMovement"]; + int code = [reason intValue]; + if (code == NSReturnTextMovement) + [self endPasswordWindow:self]; +} +// Or, the Continue button will invoke this when clicked +- (IBAction)endPasswordWindow:(id)sender +{ + [passwordWindow orderOut:self]; + [NSApp endSheet:passwordWindow]; + if ([sender isEqualTo:passwordCancelButton]) { + value *f = caml_named_value("openConnectionCancel"); + Callback_checkexn(*f, preconn); + [self chooseProfiles]; + return; + } + NSString *password = [passwordText stringValue]; + value *f = NULL; + const char *s = [password cString]; + value caml_s = caml_copy_string(s); + f = caml_named_value("openConnectionReply"); + Callback2_checkexn(*f, preconn, caml_s); + f = caml_named_value("openConnectionPrompt"); + value prompt = Callback_checkexn(*f, preconn); + if (prompt == Val_unit) { + // all done with prompts, finish opening connection + f = caml_named_value("openConnectionEnd"); + Callback_checkexn(*f, preconn); + [self afterOpen]; + } + else [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]]; +} + +- (IBAction)raiseAboutWindow:(id)sender +{ + [aboutWindow makeKeyAndOrderFront:nil]; +} + +- (IBAction)onlineHelp:(id)sender +{ + [[NSWorkspace sharedWorkspace] + openURL:[NSURL URLWithString:@"http://www.cis.upenn.edu/~bcpierce/unison/docs.html"]]; +} + + +- (NSMutableArray *)reconItems // used in ReconTableView only +{ + return reconItems; +} + +- (int)updateForIgnore:(int)i +{ + value *f = caml_named_value("unisonUpdateForIgnore"); + int j = Int_val(Callback_checkexn(*f,Val_int(i))); + f = caml_named_value("unisonState"); + caml_reconItems = Callback_checkexn(*f, Val_unit); + [self updateReconItems]; + return j; +} + +- (void)statusTextSet:(NSString *)s { + [statusText setStringValue:s]; +} + +// A function called from ocaml +CAMLprim value displayStatus(value s) +{ + [me statusTextSet:[NSString stringWithCString:String_val(s)]]; +// NSLog(@"dS: %s",String_val(s)); + return Val_unit; +} + +- (void)awakeFromNib +{ + /**** Initialize locals ****/ + me = self; + chooseProfileSize = [chooseProfileView frame].size; + updatesSize = [updatesView frame].size; + preferencesSize = [preferencesView frame].size; + ConnectingSize = [ConnectingView frame].size; + blankView = [[NSView alloc] init]; + /* Double clicking in the profile list will open the profile */ + [[profileController tableView] setTarget:self]; + [[profileController tableView] setDoubleAction:@selector(openButton:)]; + /* Set up the version string in the about box. We use a custom + about box just because PRCS doesn't seem capable of getting the + version into the InfoPlist.strings file; otherwise we'd use the + standard about box. */ + value *f = NULL; + f = caml_named_value("unisonGetVersion"); + [versionText setStringValue: + [NSString stringWithCString: + String_val(Callback_checkexn(*f, Val_unit))]]; + + /* Ocaml initialization */ + // FIX: Does this occur before ProfileController awakeFromNib? + caml_reconItems = preconn = Val_int(0); + caml_register_global_root(&caml_reconItems); + caml_register_global_root(&preconn); + + /* Command-line processing */ + f = caml_named_value("unisonInit0"); + value clprofile = Callback_checkexn(*f, Val_unit); + + /* Set up the first window the user will see */ + if (Is_block(clprofile)) { + /* A profile name was given on the command line */ + value caml_profile = Field(clprofile,0); + NSString *profile = [NSString stringWithCString:String_val(caml_profile)]; + [updatesText setStringValue:[NSString stringWithFormat:@"Synchronizing profile '%@'", + profile]]; + /* If invoked from terminal we need to bring the app to the front */ + [NSApp activateIgnoringOtherApps:YES]; + + /* Start the connection */ + [self connect:caml_profile]; + } + else { + /* If invoked from terminal we need to bring the app to the front */ + [NSApp activateIgnoringOtherApps:YES]; + /* Bring up the dialog to choose a profile */ + [self chooseProfiles]; + } +} + +/* from http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/index.html */ +#include <Security/Authorization.h> +#include <Security/AuthorizationTags.h> +- (IBAction)installCommandLineTool:(id)sender +{ + /* Install the command-line tool in /usr/bin/unison. + Requires root privilege, so we ask for it and pass the task off to /bin/sh. */ + + OSStatus myStatus; + + AuthorizationFlags myFlags = kAuthorizationFlagDefaults; + AuthorizationRef myAuthorizationRef; + myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, + myFlags, &myAuthorizationRef); + if (myStatus != errAuthorizationSuccess) return; + + { + AuthorizationItem myItems = {kAuthorizationRightExecute, 0, + NULL, 0}; + AuthorizationRights myRights = {1, &myItems}; + myFlags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + myStatus = + AuthorizationCopyRights(myAuthorizationRef,&myRights,NULL,myFlags,NULL); + } + if (myStatus == errAuthorizationSuccess) { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *bundle_path = [bundle bundlePath]; + NSString *exec_path = + [bundle_path stringByAppendingString:@"/Contents/MacOS/cltool"]; + // Not sure why but this doesn't work: + // [bundle pathForResource:@"cltool" ofType:nil]; + + if (exec_path == nil) return; + char *args[] = { "-f", (char *)[exec_path cString], "/usr/bin/unison", NULL }; + + myFlags = kAuthorizationFlagDefaults; + myStatus = AuthorizationExecuteWithPrivileges + (myAuthorizationRef, "/bin/cp", myFlags, args, + NULL); + } + AuthorizationFree (myAuthorizationRef, kAuthorizationFlagDefaults); + + /* + if (myStatus == errAuthorizationCanceled) + NSLog(@"The attempt was canceled\n"); + else if (myStatus) NSLog(@"There was an authorization error: %ld\n", myStatus); + */ +} + +@end diff --git a/src/uimac/PreferencesController.h b/src/uimac/PreferencesController.h new file mode 100644 index 0000000..a867505 --- /dev/null +++ b/src/uimac/PreferencesController.h @@ -0,0 +1,22 @@ +/* PreferencesController */ + +#import <Cocoa/Cocoa.h> + +@interface PreferencesController : NSObject +{ + IBOutlet NSButton *cancelButton; + IBOutlet NSTextField *firstRootText; + IBOutlet NSButtonCell *localButtonCell; + IBOutlet NSTextField *profileNameText; + IBOutlet NSButtonCell *remoteButtonCell; + IBOutlet NSButton *saveButton; + IBOutlet NSTextField *secondRootHost; + IBOutlet NSTextField *secondRootText; + IBOutlet NSTextField *secondRootUser; +} +- (IBAction)anyEnter:(id)sender; +- (IBAction)localClick:(id)sender; +- (IBAction)remoteClick:(id)sender; +- (BOOL)validatePrefs; +- (void)reset; +@end diff --git a/src/uimac/PreferencesController.m b/src/uimac/PreferencesController.m new file mode 100644 index 0000000..e5479ac --- /dev/null +++ b/src/uimac/PreferencesController.m @@ -0,0 +1,96 @@ +#import "PreferencesController.h" +#define CAML_NAME_SPACE +#include <caml/alloc.h> +#include <caml/callback.h> + +extern value Callback3_checkexn(value,value,value,value); + +@implementation PreferencesController + +- (void)reset +{ + [profileNameText setStringValue:@""]; + [firstRootText setStringValue:@""]; + [secondRootUser setStringValue:@""]; + [secondRootHost setStringValue:@""]; + [secondRootText setStringValue:@""]; + [remoteButtonCell setState:NSOnState]; + [localButtonCell setState:NSOffState]; + [secondRootUser setSelectable:YES]; + [secondRootUser setEditable:YES]; + [secondRootHost setSelectable:YES]; + [secondRootHost setEditable:YES]; +} + +- (BOOL)validatePrefs +{ + NSString *profileName = [profileNameText stringValue]; + if (profileName == nil | [profileName isEqualTo:@""]) { + // FIX: should check for already existing names too + NSRunAlertPanel(@"Error",@"You must enter a profile name",@"OK",nil,nil); + return NO; + } + NSString *firstRoot = [firstRootText stringValue]; + if (firstRoot == nil | [firstRoot isEqualTo:@""]) { + NSRunAlertPanel(@"Error",@"You must enter a first root",@"OK",nil,nil); + return NO; + } + NSString *secondRoot; + if ([remoteButtonCell state] == NSOnState) { + NSString *user = [secondRootUser stringValue]; + if (user == nil | [user isEqualTo:@""]) { + NSRunAlertPanel(@"Error",@"You must enter a user",@"OK",nil,nil); + return NO; + } + NSString *host = [secondRootHost stringValue]; + if (host == nil | [host isEqualTo:@""]) { + NSRunAlertPanel(@"Error",@"You must enter a host",@"OK",nil,nil); + return NO; + } + NSString *file = [secondRootText stringValue]; + // OK for empty file, e.g., ssh://foo@bar/ + secondRoot = [NSString stringWithFormat:@"ssh://%@@%@/%@",user,host,file]; + } + else { + secondRoot = [secondRootText stringValue]; + if (secondRoot == nil | [secondRoot isEqualTo:@""]) { + NSRunAlertPanel(@"Error",@"You must enter a second root file",@"OK",nil,nil); + return NO; + } + } + value *f = caml_named_value("unisonProfileInit"); + Callback3_checkexn(*f, caml_copy_string([profileName cString]), + caml_copy_string([firstRoot cString]), + caml_copy_string([secondRoot cString])); + return YES; +} + +/* The target when enter is pressed in any of the text fields */ +// FIX: this is broken, it takes tab, mouse clicks, etc. +- (IBAction)anyEnter:(id)sender +{ + NSLog(@"enter"); + [self validatePrefs]; +} + +- (IBAction)localClick:(id)sender +{ + NSLog(@"local"); + [secondRootUser setStringValue:@""]; + [secondRootHost setStringValue:@""]; + [secondRootUser setSelectable:NO]; + [secondRootUser setEditable:NO]; + [secondRootHost setSelectable:NO]; + [secondRootHost setEditable:NO]; +} + +- (IBAction)remoteClick:(id)sender +{ + NSLog(@"remote"); + [secondRootUser setSelectable:YES]; + [secondRootUser setEditable:YES]; + [secondRootHost setSelectable:YES]; + [secondRootHost setEditable:YES]; +} + +@end diff --git a/src/uimac/ProfileController.h b/src/uimac/ProfileController.h new file mode 100644 index 0000000..20bbc97 --- /dev/null +++ b/src/uimac/ProfileController.h @@ -0,0 +1,19 @@ +/* ProfileController */ +/* Copyright (c) 2003, see file COPYING for details. */ + +#import <Cocoa/Cocoa.h> + +@interface ProfileController : NSObject +{ + IBOutlet NSTableView *tableView; + NSMutableArray *profiles; + int defaultIndex; // -1 if no default, else the index in profiles of @"default" +} +- (void)initProfiles; +- (int)numberOfRowsInTableView:(NSTableView *)aTableView; +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex; +- (NSString *)selected; +- (NSTableView *)tableView; // allows MyController to set up firstResponder +@end diff --git a/src/uimac/ProfileController.m b/src/uimac/ProfileController.m new file mode 100644 index 0000000..0515399 --- /dev/null +++ b/src/uimac/ProfileController.m @@ -0,0 +1,83 @@ +/* Copyright (c) 2003, see file COPYING for details. */ + +#import "ProfileController.h" +#define CAML_NAME_SPACE +#include <caml/mlvalues.h> +#include <caml/callback.h> + +extern value Callback_checkexn(value,value); + +@implementation ProfileController + +NSString *unisonDirectory() +{ + static value *f = NULL; + if (f == NULL) + f = caml_named_value("unisonDirectory"); + return [NSString stringWithCString:String_val(Callback_checkexn(*f, Val_unit))]; +} + +- (void)initProfiles +{ + NSString *directory = unisonDirectory(); + NSArray *files = [[NSFileManager defaultManager] directoryContentsAtPath:directory]; + unsigned int count = [files count]; + unsigned int i,j; + + [profiles release]; + profiles = [[NSMutableArray alloc] init]; + defaultIndex = -1; + + for (i = j = 0; i < count; i++) { + NSString *file = [files objectAtIndex:i]; + if ([[file pathExtension] isEqualTo:@"prf"]) { + NSString *withoutExtension = [file stringByDeletingPathExtension]; + [profiles insertObject:withoutExtension atIndex:j]; + if ([@"default" isEqualTo:withoutExtension]) defaultIndex = j; + j++; + } + } + if (j > 0) + [tableView selectRow:0 byExtendingSelection:NO]; +} + +- (void)awakeFromNib +{ + // start with the default profile selected + [self initProfiles]; + if (defaultIndex >= 0) + [tableView selectRow:defaultIndex byExtendingSelection:NO]; + // on awake the scroll bar is inactive, but after adding profiles we might need it; + // reloadData makes it happen. Q: is setNeedsDisplay more efficient? + [tableView reloadData]; +} + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + if (!profiles) return 0; + else return [profiles count]; +} + +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + if (rowIndex >= 0 && rowIndex < [profiles count]) + return [profiles objectAtIndex:rowIndex]; + else return @"[internal error!]"; +} + +- (NSString *)selected +{ + int rowIndex = [tableView selectedRow]; + if (rowIndex >= 0 && rowIndex < [profiles count]) + return [profiles objectAtIndex:rowIndex]; + else return @"[internal error!]"; +} + +- (NSTableView *)tableView +{ + return tableView; +} + +@end diff --git a/src/uimac/ProfileTableView.h b/src/uimac/ProfileTableView.h new file mode 100644 index 0000000..6a5cbc5 --- /dev/null +++ b/src/uimac/ProfileTableView.h @@ -0,0 +1,10 @@ +/* ProfileTableView */ + +#import <Cocoa/Cocoa.h> +#import "MyController.h" + +@interface ProfileTableView : NSTableView +{ + IBOutlet MyController *myController; +} +@end diff --git a/src/uimac/ProfileTableView.m b/src/uimac/ProfileTableView.m new file mode 100644 index 0000000..07c1df6 --- /dev/null +++ b/src/uimac/ProfileTableView.m @@ -0,0 +1,18 @@ +#import "ProfileTableView.h" + +@implementation ProfileTableView + +- (void)keyDown:(NSEvent *)event +{ + unichar c = [[event characters] characterAtIndex:0]; + switch (c) { + case '\r': + [myController openButton:self]; + break; + default: + [super keyDown:event]; + break; + } +} + +@end diff --git a/src/uimac/ReconItem.h b/src/uimac/ReconItem.h new file mode 100644 index 0000000..54a07cc --- /dev/null +++ b/src/uimac/ReconItem.h @@ -0,0 +1,30 @@ +/* ReconItem */ + +#import <Cocoa/Cocoa.h> +#define CAML_NAME_SPACE +#include <caml/mlvalues.h> + +@interface ReconItem : NSObject +{ + NSString *path; + NSString *left; + NSString *right; + NSString *direction; + NSString *progress; + NSString *details; + value ri; // an ocaml Common.reconItem +} ++ initWithRi:(value)ri; +- (NSString *) path; +- (NSString *) left; +- (NSString *) right; +- (NSString *) direction; +- (void) doAction:(unichar)action; +- (void) doIgnore:(unichar)action; +- (NSString *) progress; +- (void)resetProgress; +- (NSString *) details; +- (BOOL)isConflict; +- (void)revertDirection; + +@end diff --git a/src/uimac/ReconItem.m b/src/uimac/ReconItem.m new file mode 100644 index 0000000..7e0686c --- /dev/null +++ b/src/uimac/ReconItem.m @@ -0,0 +1,174 @@ +#import "ReconItem.h" +#include <caml/callback.h> +#include <caml/memory.h> + +extern value Callback_checkexn(value,value); + +@implementation ReconItem + +-(void)dealloc +{ + ri = Val_unit; + caml_remove_global_root(&ri); + [super dealloc]; +} + +- (void)setRi:(value)v +{ + caml_register_global_root(&ri); // needed in case of ocaml garbage collection + ri = v; +} + ++ (id)initWithRi:(value)v +{ + ReconItem *r = [[ReconItem alloc] init]; + [r setRi:v]; + return r; +} + +- (NSString *)path +{ + if (path) return path; + + value *f = caml_named_value("unisonRiToPath"); + [path release]; + path = [NSString stringWithCString:String_val(Callback_checkexn(*f, ri))]; + [path retain]; + return path; +} + +- (NSString *)left +{ + if (left) return left; + + value *f = caml_named_value("unisonRiToLeft"); + [left release]; + left = [NSString stringWithCString:String_val(Callback_checkexn(*f, ri))]; + [left retain]; + return left; +} + +- (NSString *)right +{ + if (right) return right; + + value *f = caml_named_value("unisonRiToRight"); + [right release]; + right = [NSString stringWithCString:String_val(Callback_checkexn(*f, ri))]; + [right retain]; + return right; +} + +- (NSString *)direction +{ + if (direction) return direction; + + value *f = caml_named_value("unisonRiToDirection"); + value v = Callback_checkexn(*f, ri); + char *s = String_val(v); + [direction release]; + direction = [NSString stringWithCString:s]; + [direction retain]; + return direction; +} + +- (void)setDirection:(char *)d +{ + [direction release]; + direction = nil; + value *f = caml_named_value(d); + Callback_checkexn(*f, ri); +} + +- (void)doAction:(unichar)action +{ + switch (action) { + case '>': + [self setDirection:"unisonRiSetRight"]; + break; + case '<': + [self setDirection:"unisonRiSetLeft"]; + break; + case '/': + [self setDirection:"unisonRiSetConflict"]; + break; + case '-': + [self setDirection:"unisonRiForceOlder"]; + break; + case '+': + [self setDirection:"unisonRiForceNewer"]; + break; + case 'm': + [self setDirection:"unisonRiSetMerge"]; + break; + default: + NSLog(@"ReconItem.doAction : unknown action"); + break; + } +} + +- (void)doIgnore:(unichar)action +{ + value *f; + switch (action) { + case 'I': + f = caml_named_value("unisonIgnorePath"); + Callback_checkexn(*f, ri); + break; + case 'E': + f = caml_named_value("unisonIgnoreExt"); + Callback_checkexn(*f, ri); + break; + case 'N': + f = caml_named_value("unisonIgnoreName"); + Callback_checkexn(*f, ri); + break; + default: + NSLog(@"ReconItem.doIgnore : unknown ignore"); + break; + } +} + +- (NSString *)progress +{ + if (progress) return progress; + + value *f = caml_named_value("unisonRiToProgress"); + progress = [NSString stringWithCString:String_val(Callback_checkexn(*f, ri))]; + [progress retain]; + return progress; +} + +- (void)resetProgress +{ + // Get rid of the memoized progress because we expect it to change + [progress release]; + progress = nil; +} + +- (NSString *)details +{ + if (details) return details; + + value *f = caml_named_value("unisonRiToDetails"); + details = [NSString stringWithCString:String_val(Callback_checkexn(*f, ri))]; + [details retain]; + return details; +} + +- (BOOL)isConflict +{ + value *f = caml_named_value("unisonRiIsConflict"); + if (Callback_checkexn(*f, ri) == Val_true) return YES; + else return NO; +} + +- (void)revertDirection +{ + value *f = caml_named_value("unisonRiRevert"); + Callback_checkexn(*f, ri); + [direction release]; + direction = nil; +} + +@end diff --git a/src/uimac/ReconTableView.h b/src/uimac/ReconTableView.h new file mode 100644 index 0000000..0416bb0 --- /dev/null +++ b/src/uimac/ReconTableView.h @@ -0,0 +1,31 @@ +// +// ReconTableView.h +// +// NSTableView extended to handle additional keyboard events for the reconcile window. +// The keyDown: method is redefined. +// +// Created by Trevor Jim on Wed Aug 27 2003. +// Copyright (c) 2003, licensed under GNU GPL. +// + +#import <AppKit/AppKit.h> + +@interface ReconTableView : NSTableView { + BOOL editable; +} +- (BOOL)editable; +- (void)setEditable:(BOOL)x; +- (IBAction)ignorePath:(id)sender; +- (IBAction)ignoreExt:(id)sender; +- (IBAction)ignoreName:(id)sender; +- (IBAction)copyLR:(id)sender; +- (IBAction)copyRL:(id)sender; +- (IBAction)leaveAlone:(id)sender; +- (IBAction)forceOlder:(id)sender; +- (IBAction)forceNewer:(id)sender; +- (IBAction)selectConflicts:(id)sender; +- (IBAction)revert:(id)sender; +- (IBAction)merge:(id)sender; +- (BOOL)validateMenuItem:(NSMenuItem *)item; + +@end diff --git a/src/uimac/ReconTableView.m b/src/uimac/ReconTableView.m new file mode 100644 index 0000000..fd17a2b --- /dev/null +++ b/src/uimac/ReconTableView.m @@ -0,0 +1,180 @@ +// +// ReconTableView.m +// Unison +// +// Created by Trevor Jim on Wed Aug 27 2003. +// Copyright (c) 2003. See file COPYING for details. +// + +#import "ReconTableView.h" +#import "ReconItem.h" +#import "MyController.h" + +@implementation ReconTableView + +- (BOOL)editable +{ + return editable; +} + +- (void)setEditable:(BOOL)x +{ + editable = x; +} + +- (void)awakeFromNib +{ + editable = NO; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + if ([menuItem action] == @selector(selectAll:) + || [menuItem action] == @selector(selectConflicts:) + || [menuItem action] == @selector(copyLR:) + || [menuItem action] == @selector(copyRL:) + || [menuItem action] == @selector(leaveAlone:) + || [menuItem action] == @selector(forceNewer:) + || [menuItem action] == @selector(forceOlder:) + || [menuItem action] == @selector(revert:) + || [menuItem action] == @selector(merge:) + || [menuItem action] == @selector(ignorePath:) + || [menuItem action] == @selector(ignoreExt:) + || [menuItem action] == @selector(ignoreName:)) + return editable; + else return YES; +} + +- (void)doIgnore:(unichar)c +{ + NSMutableArray *reconItems = [[self dataSource] reconItems]; + NSEnumerator *e = [self selectedRowEnumerator]; + NSNumber *n = [e nextObject]; + int i = -1; + for (; n != nil; n = [e nextObject]) { + i = [n intValue]; + [[reconItems objectAtIndex:i] doIgnore:c]; + } + if (i>=0) { // something was selected + i = [[self dataSource] updateForIgnore:i]; + [self selectRow:i byExtendingSelection:NO]; + [self reloadData]; + } +} + +- (IBAction)ignorePath:(id)sender +{ + [self doIgnore:'I']; +} + +- (IBAction)ignoreExt:(id)sender +{ + [self doIgnore:'E']; +} + +- (IBAction)ignoreName:(id)sender +{ + [self doIgnore:'N']; +} + +- (void)doAction:(unichar)c +{ + NSEnumerator *e = [self selectedRowEnumerator]; + NSNumber *n = [e nextObject]; + int numSelected = 0; + int i = -1; + for (; n != nil; n = [e nextObject]) { + numSelected++; + i = [n intValue]; + NSMutableArray *reconItems = [[self dataSource] reconItems]; + [[reconItems objectAtIndex:i] doAction:c]; + } + if (numSelected>0) { + if (numSelected == 1 && [self numberOfRows] > i+1) { + // Move to next row, unless already at last row, or if more than one row selected + [self selectRow:i+1 byExtendingSelection:NO]; + [self scrollRowToVisible:i+1]; + } + else [self reloadData]; + } +} + +- (IBAction)copyLR:(id)sender +{ + [self doAction:'>']; +} + +- (IBAction)copyRL:(id)sender +{ + [self doAction:'<']; +} + +- (IBAction)leaveAlone:(id)sender +{ + [self doAction:'/']; +} + +- (IBAction)forceOlder:(id)sender +{ + [self doAction:'-']; +} + +- (IBAction)forceNewer:(id)sender +{ + [self doAction:'+']; +} + +- (IBAction)selectConflicts:(id)sender +{ + [self deselectAll:self]; + NSMutableArray *reconItems = [[self dataSource] reconItems]; + int i = 0; + for (; i < [reconItems count]; i++) { + if ([[reconItems objectAtIndex:i] isConflict]) + [self selectRow:i byExtendingSelection:YES]; + } +} + +- (IBAction)revert:(id)sender +{ + NSMutableArray *reconItems = [[self dataSource] reconItems]; + NSEnumerator *e = [self selectedRowEnumerator]; + NSNumber *n = [e nextObject]; + int i; + for (; n != nil; n = [e nextObject]) { + i = [n intValue]; + [[reconItems objectAtIndex:i] revertDirection]; + } + [self reloadData]; +} + +- (IBAction)merge:(id)sender +{ + [self doAction:'m']; +} + +/* There are menu commands for these, but we add some shortcuts so you don't + have to press the Command key */ +- (void)keyDown:(NSEvent *)event +{ + unichar c = [[event characters] characterAtIndex:0]; + switch (c) { + case '>': + case NSRightArrowFunctionKey: + [self doAction:'>']; + break; + case '<': + case NSLeftArrowFunctionKey: + [self doAction:'<']; + break; + case '?': + case '/': + [self doAction:'/']; + break; + default: + [super keyDown:event]; + break; + } +} + +@end diff --git a/src/uimac/TrevorsUnison.icns b/src/uimac/TrevorsUnison.icns Binary files differnew file mode 100644 index 0000000..e1fa6f8 --- /dev/null +++ b/src/uimac/TrevorsUnison.icns diff --git a/src/uimac/Unison.icns b/src/uimac/Unison.icns Binary files differnew file mode 100644 index 0000000..34bf600 --- /dev/null +++ b/src/uimac/Unison.icns diff --git a/src/uimac/cltool.c b/src/uimac/cltool.c new file mode 100644 index 0000000..2726b19 --- /dev/null +++ b/src/uimac/cltool.c @@ -0,0 +1,67 @@ +/* cltool.c + + This is a command-line tool for Mac OS X that looks up the unison + application, where ever it has been installed, and runs it. This + is intended to be installed in a standard place (e.g., + /usr/bin/unison) to make it easy to invoke unison as a server, or + to use unison from the command line when it has been installed with + a GUI. + + */ + +#import <CoreServices/CoreServices.h> +#import <ApplicationServices/ApplicationServices.h> +#include <stdio.h> + +#define BUFSIZE 1024 +#define EXECPATH "/Contents/MacOS/Unison" + +int main(int argc, char **argv) { + + /* Look up the application by its bundle identifier, which is given + in the Info.plist file. This will continue to work even if the + user changes the name of the application, unlike + fullPathForApplication. */ + + FSRef fsref; + OSStatus status; + int len; + char buf[BUFSIZE]; + + status = LSFindApplicationForInfo(kLSUnknownCreator,CFSTR("edu.upenn.cis.Unison"),NULL,&fsref,NULL); + if (status) { + if (status == kLSApplicationNotFoundErr) { + fprintf(stderr,"Error: can't find the Unison application using the Launch Services database.\n"); + fprintf(stderr,"Try launching Unison from the Finder, and then try this again.\n",status); + } + else fprintf(stderr,"Error: can't find Unison application (%d).\n",status); + exit(1); + } + + status = FSRefMakePath(&fsref,buf,BUFSIZE); + if (status) { + fprintf(stderr,"Error: problem building path to Unison application (%d).\n",status); + exit(1); + } + + len = strlen(buf); + if (len + strlen(EXECPATH) + 1 > BUFSIZE) { + fprintf(stderr,"Error: path to Unison application exceeds internal buffer size (%d).\n",BUFSIZE); + exit(1); + } + strcat(buf,EXECPATH); + + /* It's important to pass the absolute path on to the GUI, + that's how it knows where to find the bundle, e.g., the + Info.plist file. */ + argv[0] = buf; + + // printf("The Unison executable is at %s\n",argv[0]); + // printf("Running...\n"); + + execv(argv[0],argv); + + /* If we get here the execv has failed; print an error message to stderr */ + perror("unison"); + exit(1); +} diff --git a/src/uimac/main.m b/src/uimac/main.m new file mode 100644 index 0000000..a2b4852 --- /dev/null +++ b/src/uimac/main.m @@ -0,0 +1,89 @@ +// +// main.m +// uimac +// +// Created by Trevor Jim on Sun Aug 17 2003. +// Copyright (c) 2003, see file COPYING for details. +// + +#import <Cocoa/Cocoa.h> + +#define CAML_NAME_SPACE +#include <caml/callback.h> + +void reportExn(value e) { + value *f = caml_named_value("unisonExnInfo"); + char *m = String_val(caml_callback(*f,Extract_exception(e))); + NSString *s = [NSString stringWithFormat:@"Uncaught exception: %s", m]; + NSLog(@"%@",s); + NSRunAlertPanel(@"Fatal error",s,@"Exit",nil,nil); +} + +value Callback_checkexn(value c,value v) { + value e = caml_callback_exn(c,v); + if (!Is_exception_result(e)) return e; + reportExn(e); + exit(1); +} + +value Callback2_checkexn(value c,value v1,value v2) { + value e = caml_callback2_exn(c,v1,v2); + if (!Is_exception_result(e)) return e; + reportExn(e); + exit(1); +} + +value Callback3_checkexn(value c,value v1,value v2,value v3) { + value e = caml_callback3_exn(c,v1,v2,v3); + if (!Is_exception_result(e)) return e; + reportExn(e); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int i; + + /* When you click-start or use the open command, the program is invoked with + a command-line arg of the form -psn_XXXXXXXXX. The XXXXXXXX is a "process + serial number" and it seems to be important for Carbon programs. We need + to get rid of it if it's there so the ocaml code won't exit. Note, the + extra arg is not added if the binary is invoked directly from the command + line without using the open command. */ + if (argc == 2 && strncmp(argv[1],"-psn_",5) == 0) { + argc--; + argv[1] = NULL; + } + + /* Initialize ocaml gc, etc. */ + caml_startup((char **)argv); // cast to avoid warning, caml_startup assumes non-const, + // NSApplicationMain assumes const + + /* Check for invocations that don't start up the gui */ + for (i=1; i<argc; i++) { + if (!strcmp(argv[i],"-doc") || + !strcmp(argv[i],"-help") || + !strcmp(argv[i],"-version") || + !strcmp(argv[i],"-server") || + !strcmp(argv[i],"-socket") || + !strcmp(argv[i],"-ui")) { + /* We install an autorelease pool here because there might be callbacks + from ocaml to objc code */ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + value *f = caml_named_value("unisonNonGuiStartup"); + value e = caml_callback_exn(*f,Val_unit); + if (Is_exception_result(e)) { + value *f = caml_named_value("unisonExnInfo"); + char *m = String_val(caml_callback(*f,Extract_exception(e))); + NSLog(@"Uncaught exception: %s", m); + exit(1); + } + [pool release]; + /* If we get here without exiting first, the non GUI startup detected a + -ui graphic or command-line profile, and we should in fact start the GUI. */ + } + } + + /* go! */ + return NSApplicationMain(argc, argv); +} diff --git a/src/uimac/uimac.pbproj/project.pbxproj b/src/uimac/uimac.pbproj/project.pbxproj new file mode 100644 index 0000000..5fd3d1d --- /dev/null +++ b/src/uimac/uimac.pbproj/project.pbxproj @@ -0,0 +1,580 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 39; + objects = { + 080E96DDFE201D6D7F000001 = { + children = ( + ); + isa = PBXGroup; + name = Classes; + refType = 4; + sourceTree = "<group>"; + }; + 089C165CFE840E0CC02AAC07 = { + children = ( + 089C165DFE840E0CC02AAC07, + ); + isa = PBXVariantGroup; + name = InfoPlist.strings; + refType = 4; + sourceTree = "<group>"; + }; + 089C165DFE840E0CC02AAC07 = { + fileEncoding = 10; + isa = PBXFileReference; + lastKnownFileType = text.plist.strings; + name = English; + path = English.lproj/InfoPlist.strings; + refType = 4; + sourceTree = "<group>"; + }; +//080 +//081 +//082 +//083 +//084 +//100 +//101 +//102 +//103 +//104 + 1058C7A0FEA54F0111CA2CBB = { + children = ( + 1058C7A1FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = "Linked Frameworks"; + refType = 4; + sourceTree = "<group>"; + }; + 1058C7A1FEA54F0111CA2CBB = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = Cocoa.framework; + path = /System/Library/Frameworks/Cocoa.framework; + refType = 0; + sourceTree = "<absolute>"; + }; + 1058C7A2FEA54F0111CA2CBB = { + children = ( + 29B97325FDCFA39411CA2CEA, + 29B97324FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Frameworks"; + refType = 4; + sourceTree = "<group>"; + }; +//100 +//101 +//102 +//103 +//104 +//190 +//191 +//192 +//193 +//194 + 19C28FACFE9D520D11CA2CBB = { + children = ( + 69C625F50664EC3300B3C46A, + ); + isa = PBXGroup; + name = Products; + refType = 4; + sourceTree = "<group>"; + }; +//190 +//191 +//192 +//193 +//194 +//290 +//291 +//292 +//293 +//294 + 29B97313FDCFA39411CA2CEA = { + buildSettings = { + }; + buildStyles = ( + 4A9504CCFFE6A4B311CA0CBA, + 4A9504CDFFE6A4B311CA0CBA, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 29B97314FDCFA39411CA2CEA; + projectDirPath = ""; + targets = ( + 69C625DD0664EC3300B3C46A, + ); + }; + 29B97314FDCFA39411CA2CEA = { + children = ( + 69E407B907EB95AA00D37AA1, + 080E96DDFE201D6D7F000001, + 29B97315FDCFA39411CA2CEA, + 29B97317FDCFA39411CA2CEA, + 29B97323FDCFA39411CA2CEA, + 19C28FACFE9D520D11CA2CBB, + 69C625F40664EC3300B3C46A, + ); + isa = PBXGroup; + name = uimac; + path = ""; + refType = 4; + sourceTree = "<group>"; + }; + 29B97315FDCFA39411CA2CEA = { + children = ( + 29B97316FDCFA39411CA2CEA, + 69660DC604F08CC100CF23A4, + 69660DC704F08CC100CF23A4, + 69BA7DA804FD695200CF23A4, + 69BA7DA904FD695200CF23A4, + 690F564404F11EC300CF23A4, + 690F564504F11EC300CF23A4, + 69D3C6FA04F1CC3700CF23A4, + 69D3C6F904F1CC3700CF23A4, + 697985CD050CFA2D00CF23A4, + 697985CE050CFA2D00CF23A4, + 691CE180051BB44A00CF23A4, + 691CE181051BB44A00CF23A4, + ); + isa = PBXGroup; + name = "Other Sources"; + path = ""; + refType = 4; + sourceTree = "<group>"; + }; + 29B97316FDCFA39411CA2CEA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = main.m; + refType = 4; + sourceTree = "<group>"; + }; + 29B97317FDCFA39411CA2CEA = { + children = ( + 29B97318FDCFA39411CA2CEA, + 089C165CFE840E0CC02AAC07, + 69C625CA0664E94E00B3C46A, + ); + isa = PBXGroup; + name = Resources; + path = ""; + refType = 4; + sourceTree = "<group>"; + }; + 29B97318FDCFA39411CA2CEA = { + children = ( + 29B97319FDCFA39411CA2CEA, + ); + isa = PBXVariantGroup; + name = MainMenu.nib; + path = ""; + refType = 4; + sourceTree = "<group>"; + }; + 29B97319FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.nib; + name = English; + path = English.lproj/MainMenu.nib; + refType = 4; + sourceTree = "<group>"; + }; + 29B97323FDCFA39411CA2CEA = { + children = ( + 1058C7A0FEA54F0111CA2CBB, + 1058C7A2FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = Frameworks; + path = ""; + refType = 4; + sourceTree = "<group>"; + }; + 29B97324FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + sourceTree = "<absolute>"; + }; + 29B97325FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + sourceTree = "<absolute>"; + }; +//290 +//291 +//292 +//293 +//294 +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 + 4A9504CCFFE6A4B311CA0CBA = { + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + NSZombieEnabled = YES; + OPTIMIZATION_CFLAGS = "-O0"; + ZERO_LINK = YES; + }; + isa = PBXBuildStyle; + name = Development; + }; + 4A9504CDFFE6A4B311CA0CBA = { + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; + ZERO_LINK = NO; + }; + isa = PBXBuildStyle; + name = Deployment; + }; +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 +//690 +//691 +//692 +//693 +//694 + 690F564404F11EC300CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = ProfileController.h; + refType = 4; + sourceTree = "<group>"; + }; + 690F564504F11EC300CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = ProfileController.m; + refType = 4; + sourceTree = "<group>"; + }; + 691CE180051BB44A00CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = ProfileTableView.h; + refType = 4; + sourceTree = "<group>"; + }; + 691CE181051BB44A00CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = ProfileTableView.m; + refType = 4; + sourceTree = "<group>"; + }; + 69660DC604F08CC100CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = MyController.h; + refType = 4; + sourceTree = "<group>"; + }; + 69660DC704F08CC100CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = MyController.m; + refType = 4; + sourceTree = "<group>"; + }; + 697985CD050CFA2D00CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = PreferencesController.h; + refType = 4; + sourceTree = "<group>"; + }; + 697985CE050CFA2D00CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = PreferencesController.m; + refType = 4; + sourceTree = "<group>"; + }; + 69BA7DA804FD695200CF23A4 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = ReconTableView.h; + refType = 4; + sourceTree = "<group>"; + }; + 69BA7DA904FD695200CF23A4 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = ReconTableView.m; + refType = 4; + sourceTree = "<group>"; + }; + 69C625CA0664E94E00B3C46A = { + isa = PBXFileReference; + lastKnownFileType = image.icns; + path = Unison.icns; + refType = 4; + sourceTree = "<group>"; + }; + 69C625DD0664EC3300B3C46A = { + buildPhases = ( + 69C625DE0664EC3300B3C46A, + 69C625E50664EC3300B3C46A, + 69C625E90664EC3300B3C46A, + 69C625F10664EC3300B3C46A, + ); + buildRules = ( + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + HEADER_SEARCH_PATHS = "$(OCAMLLIBDIR)"; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + LIBRARY_SEARCH_PATHS = ""; + OCAMLLIBDIR = /usr/local/lib/ocaml; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = "$(SRCROOT)/../unison-blob.o -L$(OCAMLLIBDIR) -lunix -lstr -lasmrun"; + PREBINDING = NO; + PRODUCT_NAME = Unison; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + isa = PBXNativeTarget; + name = uimac; + productInstallPath = "$(HOME)/Applications"; + productName = uimac; + productReference = 69C625F50664EC3300B3C46A; + productType = "com.apple.product-type.application"; + }; + 69C625DE0664EC3300B3C46A = { + buildActionMask = 2147483647; + files = ( + 69C625DF0664EC3300B3C46A, + 69C625E00664EC3300B3C46A, + 69C625E10664EC3300B3C46A, + 69C625E20664EC3300B3C46A, + 69C625E30664EC3300B3C46A, + 69C625E40664EC3300B3C46A, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 69C625DF0664EC3300B3C46A = { + fileRef = 69660DC604F08CC100CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E00664EC3300B3C46A = { + fileRef = 690F564404F11EC300CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E10664EC3300B3C46A = { + fileRef = 69D3C6FA04F1CC3700CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E20664EC3300B3C46A = { + fileRef = 69BA7DA804FD695200CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E30664EC3300B3C46A = { + fileRef = 697985CD050CFA2D00CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E40664EC3300B3C46A = { + fileRef = 691CE180051BB44A00CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E50664EC3300B3C46A = { + buildActionMask = 2147483647; + files = ( + 69C625E60664EC3300B3C46A, + 69C625E70664EC3300B3C46A, + 69C625E80664EC3300B3C46A, + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 69C625E60664EC3300B3C46A = { + fileRef = 29B97318FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E70664EC3300B3C46A = { + fileRef = 089C165CFE840E0CC02AAC07; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E80664EC3300B3C46A = { + fileRef = 69C625CA0664E94E00B3C46A; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625E90664EC3300B3C46A = { + buildActionMask = 2147483647; + files = ( + 69C625EA0664EC3300B3C46A, + 69C625EB0664EC3300B3C46A, + 69C625EC0664EC3300B3C46A, + 69C625ED0664EC3300B3C46A, + 69C625EE0664EC3300B3C46A, + 69C625EF0664EC3300B3C46A, + 69C625F00664EC3300B3C46A, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 69C625EA0664EC3300B3C46A = { + fileRef = 29B97316FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + ATTRIBUTES = ( + ); + }; + }; + 69C625EB0664EC3300B3C46A = { + fileRef = 69660DC704F08CC100CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625EC0664EC3300B3C46A = { + fileRef = 690F564504F11EC300CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625ED0664EC3300B3C46A = { + fileRef = 69D3C6F904F1CC3700CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625EE0664EC3300B3C46A = { + fileRef = 69BA7DA904FD695200CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625EF0664EC3300B3C46A = { + fileRef = 697985CE050CFA2D00CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625F00664EC3300B3C46A = { + fileRef = 691CE181051BB44A00CF23A4; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625F10664EC3300B3C46A = { + buildActionMask = 2147483647; + files = ( + 69C625F20664EC3300B3C46A, + 69E407BA07EB95AA00D37AA1, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 69C625F20664EC3300B3C46A = { + fileRef = 1058C7A1FEA54F0111CA2CBB; + isa = PBXBuildFile; + settings = { + }; + }; + 69C625F40664EC3300B3C46A = { + isa = PBXFileReference; + lastKnownFileType = text.xml; + path = Info.plist; + refType = 4; + sourceTree = "<group>"; + }; + 69C625F50664EC3300B3C46A = { + explicitFileType = wrapper.application; + includeInIndex = 0; + isa = PBXFileReference; + path = Unison.app; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 69D3C6F904F1CC3700CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = ReconItem.m; + refType = 4; + sourceTree = "<group>"; + }; + 69D3C6FA04F1CC3700CF23A4 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = ReconItem.h; + refType = 4; + sourceTree = "<group>"; + }; + 69E407B907EB95AA00D37AA1 = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = Security.framework; + path = /System/Library/Frameworks/Security.framework; + refType = 0; + sourceTree = "<absolute>"; + }; + 69E407BA07EB95AA00D37AA1 = { + fileRef = 69E407B907EB95AA00D37AA1; + isa = PBXBuildFile; + settings = { + }; + }; + }; + rootObject = 29B97313FDCFA39411CA2CEA; +} |