source: launchers/macosx/obj-cpp/main.mm @ c8490a3

Last change on this file since c8490a3 was c8490a3, checked in by meeh <meeh@…>, 2 years ago

Starting to get stable and usable. Basically everything is based on callbacks.

  • Property mode set to 100644
File size: 19.6 KB
Line 
1#include <functional>
2#include <memory>
3#include <iostream>
4#include <algorithm>
5#include <string>
6#include <list>
7#include <sys/stat.h>
8#include <stdlib.h>
9#include <future>
10#include <vector>
11
12#import <Foundation/Foundation.h>
13
14#include <CoreFoundation/CoreFoundation.h>
15#include <CoreFoundation/CFStream.h>
16#include <CoreFoundation/CFPropertyList.h>
17#include <CoreFoundation/CFDictionary.h>
18#include <CoreFoundation/CFArray.h>
19#include <CoreFoundation/CFString.h>
20#include <CoreFoundation/CFPreferences.h>
21
22#import <objc/Object.h>
23#import <Cocoa/Cocoa.h>
24#import <AppKit/AppKit.h>
25#import <AppKit/NSApplication.h>
26
27#include "AppDelegate.h"
28#include "StatusItemButton.h"
29#include "RouterTask.h"
30#include "JavaHelper.h"
31#include "fn.h"
32#include "optional.hpp"
33
34#define debug(format, ...) CFShow([NSString stringWithFormat:format, ## __VA_ARGS__]);
35
36JvmListSharedPtr gRawJvmList = nullptr;
37
38
39@interface MenuBarCtrl () <StatusItemButtonDelegate, NSMenuDelegate>
40@end
41
42@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
43@end
44
45
46maybeAnRouterRunner getGlobalRouterObject()
47{
48    std::lock_guard<std::mutex> lock(globalRouterStatusMutex);
49    return globalRouterStatus;
50}
51
52void setGlobalRouterObject(RouterTask* newRouter)
53{
54    std::lock_guard<std::mutex> lock(globalRouterStatusMutex);
55    globalRouterStatus.emplace(newRouter);
56}
57
58std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir) {
59/*
60  NSLog(@"Arguments: %@", [NSString stringWithUTF8String:arguments.c_str()]);
61  auto launchLambda = [](JavaRunner *javaRun) {
62    javaRun->javaProcess->start_process();
63    auto pid = javaRun->javaProcess->pid();
64    std::cout << "I2P Router process id = " << pid << std::endl;
65
66    // Blocking
67    javaRun->javaProcess->wait();
68  };
69  auto callbackAfterExit = [](){
70    printf("Callback after exit\n");
71  };
72  NSLog(@"Still fine!");
73
74  setGlobalRouterObject(new JavaRunner{ javaBin, arguments, i2pBaseDir, std::move(launchLambda), std::move(callbackAfterExit) });
75
76  NSLog(@"Still fine!");
77  return std::async(std::launch::async, [&]{
78      getGlobalRouterObject().value()->execute();
79      return 0;
80    });
81*/
82    //CFShow(arguments);
83
84    @try {
85        RTaskOptions* options = [RTaskOptions alloc];
86        options.binPath = javaBin;
87        options.arguments = arguments;
88        options.i2pBaseDir = i2pBaseDir;
89        auto instance = [[[RouterTask alloc] initWithOptions: options] autorelease];
90        setGlobalRouterObject(instance);
91        //NSThread *thr = [[NSThread alloc] initWithTarget:instance selector:@selector(execute) object:nil];
92        [instance execute];
93        sendUserNotification(APP_IDSTR, @"The I2P router is starting up.");
94        auto pid = [instance getPID];
95        return std::async(std::launch::async, [&pid]{
96          return pid;
97        });
98    }
99    @catch (NSException *e)
100        {
101        auto errStr = [NSString stringWithFormat:@"Expection occurred %@",[e reason]];
102                NSLog(@"%@", errStr);
103        sendUserNotification(APP_IDSTR, errStr);
104        return std::async(std::launch::async, [&]{
105          return 0;
106        });
107        }
108}
109
110void openUrl(NSString* url)
111{
112    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: url]];
113}
114
115
116@implementation MenuBarCtrl
117
118- (void) statusItemButtonLeftClick: (StatusItemButton *) button
119{
120  CFShow(CFSTR("Left button clicked!"));
121  NSEvent *event = [NSApp currentEvent];
122}
123
124- (void) statusItemButtonRightClick: (StatusItemButton *) button
125{
126  CFShow(CFSTR("Right button clicked!"));
127  NSEvent *event = [NSApp currentEvent];
128  [self.statusItem popUpStatusItemMenu: self.menu];
129}
130
131- (void)statusBarImageBtnClicked
132{
133  [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(btnPressedAction) userInfo:nil repeats:NO];
134}
135
136- (void)btnPressedAction:(id)sender
137{
138  NSLog(@"Button presseeeeeeed");
139  NSEvent *event = [NSApp currentEvent];
140}
141
142- (void) openRouterConsoleBtnHandler: (NSMenuItem *) menuItem
143{
144  NSLog(@"Clicked openRouterConsoleBtnHandler");
145  openUrl(@"http://127.0.0.1:7657");
146}
147
148- (void) startJavaRouterBtnHandler: (NSMenuItem *) menuItem
149{
150  NSLog(@"Clicked startJavaRouterBtnHandler");
151}
152
153- (void) restartJavaRouterBtnHandler: (NSMenuItem *) menuItem
154{
155  NSLog(@"Clicked restartJavaRouterBtnHandler");
156  if (getGlobalRouterObject().has_value())
157  {
158      sendUserNotification(APP_IDSTR, @"Requesting the I2P router to restart.");
159      [getGlobalRouterObject().value() requestRestart];
160      NSLog(@"Requested restart");
161  }
162}
163
164- (void) stopJavaRouterBtnHandler: (NSMenuItem *) menuItem
165{
166  NSLog(@"Clicked stopJavaRouterBtnHandler");
167  if (getGlobalRouterObject().has_value())
168  {
169      sendUserNotification(APP_IDSTR, @"Requesting the I2P router to shutdown.");
170      [getGlobalRouterObject().value() requestShutdown];
171      NSLog(@"Requested shutdown");
172  }
173}
174
175- (void) quitWrapperBtnHandler: (NSMenuItem *) menuItem
176{
177  NSLog(@"quitWrapper event handler called!");
178  [[NSApplication sharedApplication] terminate:self];
179}
180
181- (MenuBarCtrl *) init
182{
183  self.menu = [self createStatusBarMenu];
184  self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
185
186  self.image = [NSImage imageNamed:@"ItoopieTransparent.png"];
187  [self.image setTemplate:YES];
188  self.statusItem.image = self.image;
189
190  self.statusItem.highlightMode = NO;
191  self.statusItem.toolTip = @"I2P Router Controller";
192
193  self.statusBarButton = [[StatusItemButton alloc] initWithImage:self.image];
194  self.statusBarButton.menu = self.menu;
195
196  // Selecting action
197  //[self.statusBarButton setAction:@selector(statusBarImageBtnClicked)];
198  //[self.statusBarButton setTarget:self];
199  self.statusBarButton.delegate = self;
200  [self.statusItem popUpStatusItemMenu: self.menu];
201
202  [self.statusItem setView: self.statusBarButton];
203  NSLog(@"Initialized statusbar and such");
204  return self;
205}
206
207-(void) dealloc
208{
209  [self.image release];
210  [self.menu release];
211}
212
213- (NSMenu *)createStatusBarMenu
214{
215  NSMenu *menu = [[NSMenu alloc] init];
216  [menu setAutoenablesItems:NO];
217
218  NSMenuItem *openConsoleI2Pbtn =
219    [[NSMenuItem alloc] initWithTitle:@"Open Console"
220                        action:@selector(openRouterConsoleBtnHandler:)
221                        keyEquivalent:@""];
222  [openConsoleI2Pbtn setTarget:self];
223  [openConsoleI2Pbtn setEnabled:YES];
224
225  NSMenuItem *startI2Pbtn =
226    [[NSMenuItem alloc] initWithTitle:@"Start I2P"
227                        action:@selector(startJavaRouterBtnHandler:)
228                        keyEquivalent:@""];
229  [startI2Pbtn setTarget:self];
230  if ([self.userPreferences boolForKey:@"autoStartRouter"])
231  {
232    [startI2Pbtn setEnabled:NO];
233  } else {
234    [startI2Pbtn setEnabled:YES];
235  }
236
237  NSMenuItem *restartI2Pbtn =
238    [[NSMenuItem alloc] initWithTitle:@"Restart I2P"
239                        action:@selector(restartJavaRouterBtnHandler:)
240                        keyEquivalent:@""];
241  [restartI2Pbtn setTarget:self];
242  [restartI2Pbtn setEnabled:YES];
243
244  NSMenuItem *stopI2Pbtn =
245    [[NSMenuItem alloc] initWithTitle:@"Stop I2P"
246                        action:@selector(stopJavaRouterBtnHandler:)
247                        keyEquivalent:@""];
248  [stopI2Pbtn setTarget:self];
249  [stopI2Pbtn setEnabled:YES];
250
251  NSMenuItem *quitWrapperBtn =
252    [[NSMenuItem alloc] initWithTitle:@"Quit I2P Wrapper"
253                        action:@selector(quitWrapperBtnHandler:)
254                        keyEquivalent:@""];
255  [quitWrapperBtn setTarget:self];
256  [quitWrapperBtn setEnabled:YES];
257
258
259  [menu addItem:openConsoleI2Pbtn];
260  [menu addItem:startI2Pbtn];
261  [menu addItem:stopI2Pbtn];
262  [menu addItem:restartI2Pbtn];
263  [menu addItem:quitWrapperBtn];
264  return menu;
265}
266
267@end
268
269@implementation ExtractMetaInfo
270@end
271
272@implementation AppDelegate
273
274- (void)extractI2PBaseDir:(ExtractMetaInfo *)metaInfo completion:(void(^)(BOOL success, NSError *error))completion
275{
276  std::string basePath([metaInfo.i2pBase UTF8String]);
277  NSParameterAssert(metaInfo.i2pBase);
278  NSError *error = NULL;
279  BOOL success;
280  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
281
282    // Get paths
283    NSBundle *launcherBundle = [NSBundle mainBundle];
284
285    std::string basearg("-Di2p.dir.base=");
286    basearg += basePath;
287
288    std::string zippath("-Di2p.base.zip=");
289    zippath += [metaInfo.zipFile UTF8String];
290
291    std::string jarfile("-cp ");
292    jarfile += [metaInfo.jarFile UTF8String];
293
294    // Create directory
295    mkdir(basePath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
296
297    auto cli = JavaRunner::defaultFlagsForExtractorJob;
298    setenv("I2PBASE", basePath.c_str(), true);
299    setenv("ZIPPATH", zippath.c_str(), true);
300    //setenv("DYLD_LIBRARY_PATH",".:/usr/lib:/lib:/usr/local/lib", true);
301
302    cli.push_back(basearg);
303    cli.push_back(zippath);
304    cli.push_back(jarfile);
305    cli.push_back("net.i2p.launchers.BaseExtractor");
306
307    //auto charCli = map(cli, [](std::string str){ return str.c_str(); });
308    std::string execStr = [metaInfo.javaBinary UTF8String];
309    for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; });
310
311    NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]);
312    try {
313        sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!", self.contentImage);
314        int extractStatus = Popen(execStr.c_str(), environment{{
315            {"ZIPPATH", zippath.c_str()},
316            {"I2PBASE", basePath.c_str()}
317        }}).wait();
318        NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]);
319        if (extractStatus == 0)
320        {
321            //success = YES;
322        }
323    } catch (subprocess::OSError &err) {
324        auto errMsg = [NSString stringWithUTF8String:err.what()];
325        //success = NO;
326        NSLog(@"Exception: %@", errMsg);
327        sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg], self.contentImage);
328    }
329
330    // All done. Assume success and error are already set.
331    dispatch_async(dispatch_get_main_queue(), ^{
332      sendUserNotification(APP_IDSTR, @"Extraction complete!", self.contentImage);
333      if (completion) {
334        completion(success, error);
335      }
336    });
337  });
338}
339
340- (void)startupI2PRouter:(ExtractMetaInfo *)metaInfo
341{
342  std::string basePath([metaInfo.i2pBase UTF8String]);
343  auto buildClassPath = [](std::string basePath) -> std::vector<std::string> {
344      return globVector(basePath+std::string("/lib/*.jar"));
345  };
346    // Expect base to be extracted by now.
347
348    // Get paths
349    NSBundle *launcherBundle = [NSBundle mainBundle];
350  auto jarList = buildClassPath(basePath);
351  std::string classpathStrHead = "-classpath";
352  std::string classpathStr = "";
353  classpathStr += [[launcherBundle pathForResource:@"launcher" ofType:@"jar"] UTF8String];
354  std::string prefix(basePath);
355  prefix += "/lib/";
356  for_each(jarList, [&classpathStr](std::string str){ classpathStr += std::string(":") + str; });
357  //if (self.enableVerboseLogging) NSLog(@"Classpath: %@\n",[NSString stringWithUTF8String:classpathStr.c_str()]);
358
359  try {
360    auto argList = JavaRunner::defaultStartupFlags;
361
362    std::string baseDirArg("-Di2p.dir.base=");
363    baseDirArg += basePath;
364    std::string javaLibArg("-Djava.library.path=");
365    javaLibArg += basePath;
366    // TODO: pass this to JVM
367    auto java_opts = getenv("JAVA_OPTS");
368
369    argList.push_back([NSString stringWithUTF8String:baseDirArg.c_str()]);
370    argList.push_back([NSString stringWithUTF8String:javaLibArg.c_str()]);
371    argList.push_back([NSString stringWithUTF8String:classpathStrHead.c_str()]);
372    argList.push_back([NSString stringWithUTF8String:classpathStr.c_str()]);
373    argList.push_back(@"net.i2p.router.Router");
374    auto javaBin = std::string([metaInfo.javaBinary UTF8String]);
375
376
377    sendUserNotification(APP_IDSTR, @"I2P Router is starting up!", self.contentImage);
378    auto nsJavaBin = metaInfo.javaBinary;
379    auto nsBasePath = metaInfo.i2pBase;
380    NSArray* arrArguments = [NSArray arrayWithObjects:&argList[0] count:argList.size()];
381    startupRouter(nsJavaBin, arrArguments, nsBasePath);
382    //if (self.enableVerboseLogging) NSLog(@"Defaults: %@", [pref dictionaryRepresentation]);
383  } catch (std::exception &err) {
384    auto errMsg = [NSString stringWithUTF8String:err.what()];
385    NSLog(@"Exception: %@", errMsg);
386    sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg], self.contentImage);
387  }
388}
389
390- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
391                               shouldPresentNotification:(NSUserNotification *)notification {
392    return YES;
393}
394
395- (NSString *)userSelectJavaHome:(JvmListPtr)rawJvmList
396{
397  NSString *appleScriptString = @"set jvmlist to {\"Newest\"";
398  for (auto item : *rawJvmList) {
399    auto str = strprintf(",\"%s\"", item->JVMName.c_str()).c_str();
400    NSString* tmp = [NSString stringWithUTF8String:str];
401    appleScriptString = [appleScriptString stringByAppendingString:tmp];
402  }
403  appleScriptString = [appleScriptString stringByAppendingString:@"}\nchoose from list jvmlist\n"];
404  NSAppleScript *theScript = [[NSAppleScript alloc] initWithSource:appleScriptString];
405  NSDictionary *theError = nil;
406  NSString* userResult = [[theScript executeAndReturnError: &theError] stringValue];
407  NSLog(@"User choosed %@.\n", userResult);
408  if (theError != nil)
409  {
410    NSLog(@"Error: %@.\n", theError);
411  }
412  return userResult;
413}
414
415
416- (void)userChooseJavaHome {
417  listAllJavaInstallsAvailable();
418  std::shared_ptr<JvmHomeContext> appContext = std::shared_ptr<JvmHomeContext>( new JvmHomeContext() );
419  for (auto item : *appContext->getJvmList()) {
420    printf("JVM %s (Version: %s, Directory: %s)\n", item->JVMName.c_str(), item->JVMPlatformVersion.c_str(), item->JVMHomePath.c_str());
421  }
422  JvmListPtr rawJvmList = appContext->getJvmList();
423  NSString * userJavaHome = [self userSelectJavaHome: rawJvmList];
424  // TODO: Add logic so user can set preferred JVM
425}
426
427- (void)setApplicationDefaultPreferences {
428  auto defaultJVMHome = check_output({"/usr/libexec/java_home","-v",DEF_MIN_JVM_VER});
429  auto tmpStdStr = std::string(defaultJVMHome.buf.data());
430  trim(tmpStdStr);
431  auto cfDefaultHome  = CFStringCreateWithCString(NULL, const_cast<const char *>(tmpStdStr.c_str()), kCFStringEncodingUTF8);
432  [self.userPreferences registerDefaults:@{
433    @"javaHome" : (NSString *)cfDefaultHome,
434    @"lastI2PVersion" : (NSString *)CFSTR(DEF_I2P_VERSION),
435    @"enableLogging": @true,
436    @"enableVerboseLogging": @true,
437    @"autoStartRouter": @true
438  }];
439  if (self.enableVerboseLogging) NSLog(@"Default JVM home preference set to: %@", (NSString *)cfDefaultHome);
440
441  auto dict = [self.userPreferences dictionaryRepresentation];
442  [self.userPreferences setPersistentDomain:dict forName:NSAPPDOMAIN];
443
444  CFPreferencesSetMultiple((CFDictionaryRef)dict, NULL, CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
445  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
446  //CFPreferencesSetAppValue(@"javaHome", (CFPropertyListRef)cfDefaultHome, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
447
448  if (self.enableVerboseLogging) NSLog(@"Default preferences stored!");
449}
450
451
452- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
453  // Init application here
454  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
455  // Start with user preferences
456  self.userPreferences = [NSUserDefaults standardUserDefaults];
457  [self setApplicationDefaultPreferences];
458  self.enableLogging = [self.userPreferences boolForKey:@"enableLogging"];
459  self.enableVerboseLogging = [self.userPreferences boolForKey:@"enableVerboseLogging"];
460
461
462  // Get paths
463  NSBundle *launcherBundle = [NSBundle mainBundle];
464  auto iconImage = [launcherBundle pathForResource:@"ItoopieTransparent" ofType:@"png"];
465  self.contentImage = [NSImage imageNamed:iconImage];
466
467  gRawJvmList = std::make_shared<std::list<JvmVersionPtr> >(std::list<JvmVersionPtr>());
468  // In case we are unbundled, make us a proper UI application
469  [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
470  [NSApp activateIgnoringOtherApps:YES];
471  //auto prefArray = CFPreferencesCopyKeyList(CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
472  //CFShow(prefArray);
473  auto javaHomePref = [self.userPreferences stringForKey:@"javaHome"];
474  if (self.enableVerboseLogging) NSLog(@"Java home from preferences: %@", javaHomePref);
475
476
477  // This is the only GUI the user experience on a regular basis.
478  self.menuBarCtrl = [[MenuBarCtrl alloc] init];
479
480  NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
481  if (self.enableVerboseLogging) NSLog(@"Appdomain is: %@", appDomain);
482
483  NSLog(@"We should have started the statusbar object by now...");
484
485  // Figure out base directory
486  const char* pathFromHome = "/Users/%s/Library/I2P";
487  auto username = getenv("USER");
488  char buffer[strlen(pathFromHome)+strlen(username)];
489  sprintf(buffer, pathFromHome, username);
490  std::string i2pBaseDir(buffer);
491  if (self.enableVerboseLogging) printf("Home directory is: %s\n", buffer);
492
493
494  //[statusBarButton setAction:@selector(itemClicked:)];
495  //dispatch_async(dispatch_get_main_queue(), ^{
496  //});
497  auto pref = self.userPreferences;
498  self.menuBarCtrl.userPreferences = self.userPreferences;
499  self.menuBarCtrl.enableLogging = self.enableLogging;
500  self.menuBarCtrl.enableVerboseLogging = self.enableVerboseLogging;
501
502  if (self.enableVerboseLogging) NSLog(@"processinfo %@", [[NSProcessInfo processInfo] arguments]);
503
504  auto getJavaHomeLambda = [&pref,&self]() -> std::string {
505      NSString* val = @"";
506      val = [pref stringForKey:@"javaHome"];
507      if (val == NULL) val = @"";
508      if (self.enableVerboseLogging) NSLog(@"Javahome: %@", val);
509      return std::string([val UTF8String]);;
510  };
511
512  auto getJavaBin = [&getJavaHomeLambda]() -> std::string {
513      // Get Java home
514    auto javaHome = getJavaHomeLambda();
515    trim(javaHome); // Trim to remove endline
516    auto javaBin = std::string(javaHome);
517    javaBin += "/bin/java"; // Append java binary to path.
518    return javaBin;
519  };
520
521  auto buildClassPath = [](std::string basePath) -> std::vector<std::string> {
522      return globVector(basePath+std::string("/lib/*.jar"));
523  };
524
525
526  auto metaInfo = [ExtractMetaInfo alloc];
527  metaInfo.i2pBase = [NSString stringWithUTF8String:buffer];
528  metaInfo.javaBinary = [NSString stringWithUTF8String:getJavaBin().c_str()];
529  metaInfo.jarFile = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
530  metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
531
532  std::string basearg("-Di2p.dir.base=");
533  basearg += i2pBaseDir;
534
535  std::string jarfile("-cp ");
536  jarfile += [metaInfo.zipFile UTF8String];
537
538  struct stat sb;
539  if ( !(stat(buffer, &sb) == 0 && S_ISDIR(sb.st_mode)) )
540  {
541    // I2P is not extracted.
542    if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!");
543
544    [self extractI2PBaseDir: metaInfo completion:^(BOOL success, NSError *error) {
545        //__typeof__(self) strongSelf = weakSelf;
546        //if (strongSelf == nil) return;
547        [self startupI2PRouter:metaInfo];
548    }];
549
550  } else {
551      if (self.enableVerboseLogging) NSLog(@"I2P directory found!");
552      [self startupI2PRouter:metaInfo];
553  }
554
555}
556
557
558
559/**
560 *
561 * Exit sequence
562 *
563 **/
564- (void)applicationWillTerminate:(NSNotification *)aNotification {
565  // Tear down here
566  NSString *string = @"applicationWillTerminate executed";
567  NSLog(@"%@", string);
568  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil];
569}
570
571
572/* wrapper for main */
573- (AppDelegate *)initWithArgc:(int)argc argv:(const char **)argv {
574  return self;
575}
576@end
577
578
579
580int main(int argc, const char **argv)
581{
582  NSApplication *app = [NSApplication sharedApplication];
583  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
584
585
586
587  app.delegate = [[AppDelegate alloc] initWithArgc:argc argv:argv];
588  [NSBundle loadNibNamed:@"I2Launcher" owner:NSApp];
589
590  [NSApp run];
591  // Handle any errors
592  //CFRelease(javaHomes);
593  //CFRelease(err);
594  [pool drain];
595  return 0;
596}
597
598
599
Note: See TracBrowser for help on using the repository browser.