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

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

Fixed a load of issues related to my "sync" approach, and Mac OSX's "async" API.

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