From e36a3b318a4cdf4e8dde00d1449d404fb6e2101a Mon Sep 17 00:00:00 2001
From: meeh <meeh@mail.i2p>
Date: Sun, 10 Mar 2019 11:16:56 +0000
Subject: [PATCH] Mac OSX Launcher: * Fixed startup option so the launcher can
 start at OSX login/bootup. * Added I2P Browser to the list of "firefox"
 browsers to detect. * Changed hardcoded path lookup to native "registry"
 lookup for firefox application. * Made the advanced preferences table
 editable by the user. * Cleanup of old and/or unused code. * Bugfixes.

---
 launchers/macosx/AppDelegate.h                |   1 -
 launchers/macosx/Changes.md                   |  17 ++
 .../I2PLauncher/Base.lproj/UserInterfaces.xib |  52 +++++-
 .../macosx/I2PLauncher/Preferences.storyboard |  17 +-
 .../I2PLauncher/SwiftMainDelegate.swift       |  26 +++
 .../I2PLauncher/Utils/Preferences.swift       |   2 +-
 .../macosx/I2PLauncher/Utils/Startup.swift    | 156 +++++++++++-------
 .../Utils/browser/FirefoxManager.swift        |  29 +++-
 .../RouterProcessStatus+ObjectiveC.swift      |  10 --
 .../userinterface/RouterStatusView.swift      |   3 -
 .../userinterface/StatusBarController.swift   |   3 +
 .../PreferencesViewController+TableView.swift |  18 +-
 .../PreferencesViewController.swift           |  40 ++++-
 launchers/macosx/SBridge.h                    |   4 -
 launchers/macosx/SBridge.mm                   | 148 -----------------
 launchers/macosx/main.mm                      |  26 ---
 16 files changed, 270 insertions(+), 282 deletions(-)
 create mode 100644 launchers/macosx/Changes.md

diff --git a/launchers/macosx/AppDelegate.h b/launchers/macosx/AppDelegate.h
index 171c6b9d31..4088f0bf58 100644
--- a/launchers/macosx/AppDelegate.h
+++ b/launchers/macosx/AppDelegate.h
@@ -100,7 +100,6 @@ inline void sendUserNotification(NSString* title, NSString* informativeText, boo
 - (void) awakeFromNib;
 - (void) applicationDidFinishLaunching:(NSNotification *)aNotification;
 - (void) applicationWillTerminate:(NSNotification *)aNotification;
-- (void) setApplicationDefaultPreferences;
 - (AppDelegate *) initWithArgc:(int)argc argv:(const char **)argv;
 - (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center
                                shouldPresentNotification:(NSUserNotification *)notification;
diff --git a/launchers/macosx/Changes.md b/launchers/macosx/Changes.md
new file mode 100644
index 0000000000..5e7c8f4bc7
--- /dev/null
+++ b/launchers/macosx/Changes.md
@@ -0,0 +1,17 @@
+# Change Log
+
+## 0.9.38
+
+* Initial alpha/beta ish.
+* Preferences dialog (unfinished).
+* Firefox detection.
+
+## 0.9.39
+
+* Fixed startup option so the launcher can start at OSX login/bootup.
+* Added I2P Browser to the list of "firefox" browsers to detect.
+* Changed hardcoded path lookup to native "registry" lookup for firefox application.
+* Made the advanced preferences table editable by the user.
+* Cleanup of old and/or unused code.
+* Bugfixes.
+
diff --git a/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
index 2233374394..d5d200a06e 100644
--- a/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
+++ b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -37,5 +37,53 @@
             </items>
             <point key="canvasLocation" x="17" y="167"/>
         </menu>
+        <menuItem title="Application" allowsKeyEquivalentWhenHidden="YES" id="84R-pF-Wt1">
+            <modifierMask key="keyEquivalentModifierMask"/>
+            <menu key="submenu" title="Application" id="Trv-j7-WYu">
+                <items>
+                    <menuItem title="About Application" id="XDp-94-iig">
+                        <modifierMask key="keyEquivalentModifierMask"/>
+                        <connections>
+                            <action selector="orderFrontStandardAboutPanel:" target="-1" id="gJe-90-jzd"/>
+                        </connections>
+                    </menuItem>
+                    <menuItem isSeparatorItem="YES" id="xT4-H9-l6g"/>
+                    <menuItem title="Preferences…" keyEquivalent="," id="2Kn-gO-qBP">
+                        <connections>
+                            <action selector="handleNativePreferencesClicked:" target="-1" id="wIn-2L-u1k"/>
+                        </connections>
+                    </menuItem>
+                    <menuItem isSeparatorItem="YES" id="45f-s8-MiT"/>
+                    <menuItem title="Services" id="x2v-WG-Nuy">
+                        <modifierMask key="keyEquivalentModifierMask"/>
+                        <menu key="submenu" title="Services" systemMenu="services" id="DkL-DH-gJf"/>
+                    </menuItem>
+                    <menuItem isSeparatorItem="YES" id="T1H-h7-Sat"/>
+                    <menuItem title="Hide Application" keyEquivalent="h" id="uAA-NV-src">
+                        <connections>
+                            <action selector="hide:" target="-1" id="wFN-Nz-FpI"/>
+                        </connections>
+                    </menuItem>
+                    <menuItem title="Hide Others" keyEquivalent="h" id="ext-76-4lm">
+                        <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                        <connections>
+                            <action selector="hideOtherApplications:" target="-1" id="KyT-0x-vod"/>
+                        </connections>
+                    </menuItem>
+                    <menuItem title="Show All" id="HmI-6K-eUt">
+                        <modifierMask key="keyEquivalentModifierMask"/>
+                        <connections>
+                            <action selector="unhideAllApplications:" target="-1" id="mXg-9c-azx"/>
+                        </connections>
+                    </menuItem>
+                    <menuItem isSeparatorItem="YES" id="xOX-eV-fcA"/>
+                    <menuItem title="Quit Application" keyEquivalent="q" id="Fap-30-HH0">
+                        <connections>
+                            <action selector="terminate:" target="-1" id="IM7-XC-JlF"/>
+                        </connections>
+                    </menuItem>
+                </items>
+            </menu>
+        </menuItem>
     </objects>
 </document>
diff --git a/launchers/macosx/I2PLauncher/Preferences.storyboard b/launchers/macosx/I2PLauncher/Preferences.storyboard
index 836b9144cb..49de4db2e2 100644
--- a/launchers/macosx/I2PLauncher/Preferences.storyboard
+++ b/launchers/macosx/I2PLauncher/Preferences.storyboard
@@ -67,7 +67,7 @@
                             <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aAH-4e-tuf">
                                 <rect key="frame" x="18" y="198" width="325" height="26"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                                <buttonCell key="cell" type="check" title="Start the I2P Launcher at User login (Mac startup)" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="EOY-1C-aqa">
+                                <buttonCell key="cell" type="check" title="Start the I2P Launcher at User login (Mac startup)" bezelStyle="regularSquare" imagePosition="left" inset="2" id="EOY-1C-aqa">
                                     <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                     <font key="font" metaFont="system"/>
                                 </buttonCell>
@@ -132,7 +132,7 @@
                             <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XPH-xk-vMg">
                                 <rect key="frame" x="18" y="173" width="313" height="23"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                                <buttonCell key="cell" type="check" title="Start Firefox with a I2P enabled profile at launch" bezelStyle="regularSquare" imagePosition="left" enabled="NO" state="on" inset="2" id="i7v-mQ-d1Y">
+                                <buttonCell key="cell" type="check" title="Start Firefox with a I2P enabled profile at launch" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="i7v-mQ-d1Y">
                                     <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                     <font key="font" metaFont="system"/>
                                 </buttonCell>
@@ -218,7 +218,7 @@
                             <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SSS-Fz-fYY">
                                 <rect key="frame" x="18" y="345" width="257" height="28"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                                <buttonCell key="cell" type="check" title="Yes, I want to edit advanced settings" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="UzU-4G-MLw">
+                                <buttonCell key="cell" type="check" title="Yes, I want to edit advanced settings" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UzU-4G-MLw">
                                     <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                     <font key="font" metaFont="system"/>
                                 </buttonCell>
@@ -243,14 +243,14 @@
                                     <rect key="frame" x="1" y="0.0" width="559" height="310"/>
                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                     <subviews>
-                                        <tableView identifier="AdvancedView" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="M6Y-Yi-YWr" viewBased="YES" id="lzO-OC-oiQ" customClass="AdvancedTableView" customModule="I2PLauncher" customModuleProvider="target">
+                                        <tableView identifier="AdvancedView" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="M6Y-Yi-YWr" viewBased="YES" id="lzO-OC-oiQ" customClass="AdvancedTableView" customModule="I2PLauncher" customModuleProvider="target">
                                             <rect key="frame" x="0.0" y="0.0" width="645" height="285"/>
                                             <autoresizingMask key="autoresizingMask"/>
                                             <size key="intercellSpacing" width="3" height="2"/>
                                             <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
                                             <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
                                             <tableColumns>
-                                                <tableColumn identifier="KeyColumnID" width="116" minWidth="40" maxWidth="1000" id="3Hj-6J-5ww">
+                                                <tableColumn identifier="KeyColumnID" editable="NO" width="116" minWidth="40" maxWidth="1000" id="3Hj-6J-5ww">
                                                     <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Key">
                                                         <font key="font" metaFont="smallSystem"/>
                                                         <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@@ -283,7 +283,7 @@
                                                         </tableCellView>
                                                     </prototypeCellViews>
                                                 </tableColumn>
-                                                <tableColumn identifier="DefaultColumnID" width="120" minWidth="40" maxWidth="1000" id="xna-T0-L5h">
+                                                <tableColumn identifier="DefaultColumnID" editable="NO" width="120" minWidth="40" maxWidth="1000" id="xna-T0-L5h">
                                                     <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Default Value">
                                                         <font key="font" metaFont="smallSystem"/>
                                                         <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@@ -336,11 +336,14 @@
                                                                 <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2ro-Gm-4DU">
                                                                     <rect key="frame" x="0.0" y="0.0" width="400" height="17"/>
                                                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                                                                    <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Sj7-Se-KMC">
+                                                                    <textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Table View Cell" usesSingleLineMode="YES" id="Sj7-Se-KMC">
                                                                         <font key="font" metaFont="system"/>
                                                                         <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                                                                         <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                                                     </textFieldCell>
+                                                                    <connections>
+                                                                        <action selector="onEnterInTextField:" target="mVJ-sm-WjL" id="4y0-HJ-teb"/>
+                                                                    </connections>
                                                                 </textField>
                                                             </subviews>
                                                             <connections>
diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
index 26f18bc38f..7eee3d1da0 100644
--- a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
+++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
@@ -32,8 +32,10 @@ class Logger {
   let statusBarController = StatusBarController()
   let sharedRouterMgmr = RouterManager.shared()
   
+  // Constructor, think of it like an early entrypoint.
   override init() {
     super.init()
+    
     if (!DetectJava.shared().isJavaFound()) {
     DetectJava.shared().findIt()
       if (!DetectJava.shared().isJavaFound()) {
@@ -55,6 +57,7 @@ class Logger {
     }
   } // End of init()
   
+  // A function which detects the current installed I2P router version
   @objc func findInstalledI2PVersion() {
     var i2pPath = Preferences.shared().i2pBaseDirectory
     let jExecPath:String = Preferences.shared().javaCommandPath
@@ -84,6 +87,8 @@ class Logger {
     }
   }
   
+  
+  // Helper functions for the optional dock icon
   func triggerDockIconShowHide(showIcon state: Bool) -> Bool {
     var result: Bool
     if state {
@@ -94,6 +99,7 @@ class Logger {
     return result
   }
   
+  // Helper functions for the optional dock icon
   func getDockIconStateIsShowing() -> Bool {
     if NSApp.activationPolicy() == NSApplicationActivationPolicy.regular {
       return true
@@ -102,6 +108,11 @@ class Logger {
     }
   }
   
+  /**
+   *
+   * This is the swift "entrypoint". In C it would been "main(argc,argv)"
+   *
+   */
   @objc func applicationDidFinishLaunching() {
     switch Preferences.shared().showAsIconMode {
     case .bothIcon, .dockIcon:
@@ -121,6 +132,15 @@ class Logger {
     if isRunning {
       DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
     }
+    
+    if (Preferences.shared().alsoStartFirefoxOnLaunch)
+    {
+      // TODO: For some reason it does not seem to obay the two minutes delay.
+      // If set, execute i2p browser / firefox after two minutes.
+      DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
+        FirefoxManager.shared().executeFirefox()
+      }
+    }
   }
   
   @objc func listenForEvent(eventName: String, callbackActionFn: @escaping ((Any?)->()) ) {
@@ -136,6 +156,12 @@ class Logger {
     NSWorkspace.shared().open(NSURL(string: url)! as URL)
   }
   
+  /**
+   *
+   * This function will execute when the launcher shuts down for some reason.
+   * Could be either OS or user triggered.
+   *
+   */
   @objc func applicationWillTerminate() {
     // Shutdown stuff
     if (Preferences.shared().stopRouterOnLauncherShutdown) {
diff --git a/launchers/macosx/I2PLauncher/Utils/Preferences.swift b/launchers/macosx/I2PLauncher/Utils/Preferences.swift
index 2fd174d62c..1122e830cf 100644
--- a/launchers/macosx/I2PLauncher/Utils/Preferences.swift
+++ b/launchers/macosx/I2PLauncher/Utils/Preferences.swift
@@ -116,7 +116,7 @@ class Preferences  : NSObject {
     defaults["I2Pref_letRouterLiveEvenLauncherDied"] = false
     defaults["I2Pref_allowAdvancedPreferences"] = false
     defaults["I2Pref_alsoStartFirefoxOnLaunch"] = true
-    defaults["I2Pref_firefoxBundlePath"] = "/Applications/Firefox.app"
+    defaults["I2Pref_useServiceManagementAsStartupTool"] = false
     defaults["I2Pref_firefoxProfilePath"] = NSString(format: "%@/Library/Application Support/i2p/profile", home)
     defaults["I2Pref_consolePortCheckNum"] = 7657
     defaults["I2Pref_i2pBaseDirectory"] = NSString(format: "%@/Library/I2P", home)
diff --git a/launchers/macosx/I2PLauncher/Utils/Startup.swift b/launchers/macosx/I2PLauncher/Utils/Startup.swift
index 2a28bfedf2..b563537bb6 100644
--- a/launchers/macosx/I2PLauncher/Utils/Startup.swift
+++ b/launchers/macosx/I2PLauncher/Utils/Startup.swift
@@ -8,81 +8,111 @@
 
 import Foundation
 
-class Startup {
+class Startup : NSObject {
   
+  let loginItemsList : LSSharedFileList = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue();
   
-  /*
-  func applicationIsInStartUpItems() -> Bool {
-    return itemReferencesInLoginItems().existingReference != nil
+  
+  
+  func addLoginItem(_ path: CFURL) -> Bool {
+    
+    if(getLoginItem(path) != nil) {
+      print("Login Item has already been added to the list.");
+      return true;
+    }
+    
+    var path : CFURL = CFURLCreateWithString(nil, Bundle.main.bundleURL.absoluteString as CFString, nil);
+    print("Path adding to Login Item list is: ", path);
+    
+    // add new Login Item at the end of Login Items list
+    if let loginItem = LSSharedFileListInsertItemURL(loginItemsList,
+                                                     getLastLoginItemInList(),
+                                                     nil, nil,
+                                                     path,
+                                                     nil, nil) {
+      print("Added login item is: ", loginItem);
+      return true;
+    }
+    
+    return false;
   }
   
-  func toggleLaunchAtStartup() {
-    let itemReferences = itemReferencesInLoginItems()
-    let shouldBeToggled = (itemReferences.existingReference == nil)
-    let loginItemsRef = LSSharedFileListCreate(
-      nil,
-      kLSSharedFileListSessionLoginItems.takeRetainedValue(),
-      nil
-      ).takeRetainedValue() as LSSharedFileList?
-    
-    if loginItemsRef != nil {
-      if shouldBeToggled {
-        if let appUrl: CFURL = NSURL.fileURLWithPath(Bundle.mainBundle().bundlePath) {
-          LSSharedFileListInsertItemURL(loginItemsRef, itemReferences.lastReference, nil, nil, appUrl, nil, nil)
-          print("Application was added to login items")
-        }
-      } else {
-        if let itemRef = itemReferences.existingReference {
-          LSSharedFileListItemRemove(loginItemsRef,itemRef);
-          print("Application was removed from login items")
-        }
+  
+  func removeLoginItem(_ path: CFURL) -> Bool {
+    
+    // remove Login Item from the Login Items list
+    if let oldLoginItem = getLoginItem(path) {
+      print("Old login item is: ", oldLoginItem);
+      if(LSSharedFileListItemRemove(loginItemsList, oldLoginItem) == noErr) {
+        return true;
       }
+      return false;
     }
+    print("Login Item for given path not found in the list.");
+    return true;
   }
   
-  func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItem?, lastReference: LSSharedFileListItem?) {
-    var itemUrl = UnsafeMutablePointer<Unmanaged<CFURL>?>.allocate(capacity: 1)
-    
-    let appUrl = NSURL.fileURL(withPath: Bundle.main.bundlePath)
-    if !appUrl.absoluteString.isEmpty {
-      let loginItemsRef = LSSharedFileListCreate(
-        nil,
-        kLSSharedFileListSessionLoginItems.takeRetainedValue(),
-        nil
-        ).takeRetainedValue() as LSSharedFileList?
+  
+  func getLoginItem(_ path : CFURL) -> LSSharedFileListItem! {
+    
+    var path : CFURL = CFURLCreateWithString(nil, Bundle.main.bundleURL.absoluteString as CFString, nil);
+    
+    
+    // Copy all login items in the list
+    let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue();
+    
+    var foundLoginItem : LSSharedFileListItem?;
+    var nextItemUrl : Unmanaged<CFURL>?;
+    
+    // Iterate through login items to find one for given path
+    print("App URL: ", path);
+    for var i in (0..<loginItems.count)  // CFArrayGetCount(loginItems)
+    {
+      
+      var nextLoginItem : LSSharedFileListItem = loginItems.object(at: i) as! LSSharedFileListItem; // CFArrayGetValueAtIndex(loginItems, i).;
       
-      if loginItemsRef != nil {
-        let loginItems = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray
-        print("There are \(loginItems.count) login items")
+      
+      if(LSSharedFileListItemResolve(nextLoginItem, 0, &nextItemUrl, nil) == noErr) {
+        
+        
         
-        if(loginItems.count > 0) {
-          let lastItemRef = loginItems.lastObject as! LSSharedFileListItem
-          
-          for var currentItem in loginItems {
-            let currentItemRef = currentItem as! LSSharedFileListItem
-            
-            let urlRef: CFURL = LSSharedFileListItemCopyResolvedURL(currentItemRef, 0, nil) as! CFURL
-            let url = urlRef.takeUnretainedValue()
-            if !urlRef?.isEmpty {
-              print("URL Ref: \(urlRef.lastPathComponent)")
-              if urlRef.isEqual(appUrl) {
-                return (currentItemRef, lastItemRef)
-              }
-            }
-            else {
-              print("Unknown login application")
-            }
-          }
-          // The application was not found in the startup list
-          return (nil, lastItemRef)
-          
-        } else  {
-          let addatstart: LSSharedFileListItem = kLSSharedFileListItemBeforeFirst.takeRetainedValue()
-          return(nil,addatstart)
+        print("Next login item URL: ", nextItemUrl!.takeUnretainedValue());
+        // compare searched item URL passed in argument with next item URL
+        if(nextItemUrl!.takeRetainedValue() == path) {
+          foundLoginItem = nextLoginItem;
         }
       }
     }
     
-    return (nil, nil)
-  }*/
+    return foundLoginItem;
+  }
+  
+  func getLastLoginItemInList() -> LSSharedFileListItem! {
+    
+    // Copy all login items in the list
+    let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue() as NSArray;
+    if(loginItems.count > 0) {
+      let lastLoginItem = loginItems.lastObject as! LSSharedFileListItem;
+      
+      print("Last login item is: ", lastLoginItem);
+      return lastLoginItem
+    }
+    
+    return kLSSharedFileListItemBeforeFirst.takeRetainedValue();
+  }
+  
+  func isLoginItemInList(_ path : CFURL) -> Bool {
+    
+    if(getLoginItem(path) != nil) {
+      return true;
+    }
+    
+    return false;
+  }
+  
+  static func appPath() -> CFURL {
+    
+    return NSURL.fileURL(withPath: Bundle.main.bundlePath) as CFURL;
+  }
+  
 }
diff --git a/launchers/macosx/I2PLauncher/Utils/browser/FirefoxManager.swift b/launchers/macosx/I2PLauncher/Utils/browser/FirefoxManager.swift
index d30c35691a..12a842d2e0 100644
--- a/launchers/macosx/I2PLauncher/Utils/browser/FirefoxManager.swift
+++ b/launchers/macosx/I2PLauncher/Utils/browser/FirefoxManager.swift
@@ -29,6 +29,7 @@ class FirefoxManager {
     return self.isFirefoxProfileExtracted
   }
   
+  // Since we execute in the "unix/POSIX way", we need the full path of the binary.
   func bundleExecutableSuffixPath() -> String {
     return "/Contents/MacOS/firefox"
   }
@@ -42,15 +43,37 @@ class FirefoxManager {
     return true
   }
 
+  /**
+   *
+   * First, try find I2P Browser, if  it fails, then try Firefox or Firefox Developer.
+   *
+   * Instead of using hardcoded paths, or file search we use OS X's internal "registry" API
+   * and detects I2P Browser or Firefox by bundle name. (or id, but name is more readable)
+   *
+   */
   func tryAutoDetect() -> Bool {
-    let expectedPath = Preferences.shared()["I2Pref_firefoxBundlePath"] as! String
+    var browserPath = NSWorkspace.shared().fullPath(forApplication: "I2P Browser")
+    if (browserPath == nil)
+    {
+      browserPath = NSWorkspace.shared().fullPath(forApplication: "Firefox")
+      if (browserPath == nil)
+      {
+        browserPath = NSWorkspace.shared().fullPath(forApplication: "Firefox Developer")
+      }
+    }
    
     self.isFirefoxProfileExtracted = directoryExistsAtPath(Preferences.shared()["I2Pref_firefoxProfilePath"] as! String)
     
-    let result = directoryExistsAtPath(expectedPath)
+    // If browserPath is still nil, then nothing above was found and we can return early.
+    if (browserPath == nil)
+    {
+      return false
+    }
+    
+    let result = directoryExistsAtPath(browserPath!)
     self.isFirefoxFound = result
     if (result) {
-      self.firefoxAppPath = expectedPath
+      self.firefoxAppPath = browserPath!
       return true
     }
     return false
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift
index a4f2b4868f..8810883d5b 100644
--- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift
@@ -10,14 +10,4 @@ import Foundation
 
 extension RouterProcessStatus {
   
-  static func createNewRouterProcess(i2pPath: String) {
-    let timeWhenStarted = Date()
-    RouterProcessStatus.routerStartedAt = timeWhenStarted
-    SBridge.sharedInstance().startupI2PRouter(i2pPath)
-    RouterManager.shared().updateState()
-  }
-  static func shutdownRouterChildProcess() {
-    RouterManager.shared().getRouterTask()?.requestShutdown()
-    RouterManager.shared().updateState()
-  }
 }
diff --git a/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift
index a26e4aef14..b8a53c54d2 100644
--- a/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift
+++ b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift
@@ -39,9 +39,6 @@ import Cocoa
   
   @objc func actionBtnStartRouter(_ sender: Any?) {
     NSLog("Router start clicked")
-    /*if (RouterManager.shared().getRouterTask() == nil) {
-      SBridge.sharedInstance().startupI2PRouter(RouterProcessStatus.i2pDirectoryPath)
-    }*/
     (sender as! NSButton).isTransparent = true
     let routerStatus = RouterRunner.launchAgent?.status()
     DispatchQueue(label: "background_start").async {
diff --git a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift
index 35529e001c..af15c41466 100644
--- a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift
+++ b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift
@@ -21,6 +21,9 @@ import Cocoa
 
   @IBOutlet var routerStatusTabView: RouterStatusView?
   
+  @IBAction func handleNativePreferencesClicked(_ sender: Any) {
+    StatusBarController.launchPreferences(sender)
+  }
   //var updateObjectRef : SUUpdater?
   
   @objc func handleOpenConsole(_ sender: Any?) {
diff --git a/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController+TableView.swift b/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController+TableView.swift
index 1b009ddd63..2d6363c5fe 100644
--- a/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController+TableView.swift
+++ b/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController+TableView.swift
@@ -25,13 +25,15 @@ extension PreferencesViewController: NSTableViewDelegate {
   }
   
   func tableViewDoubleClick(_ sender:AnyObject) {
-    
+    print("Double click")
     // 1
-    /*guard tableView.selectedRow >= 0,
-     let item = Preferences.shared()[tableView.selectedRow] else {
-     return
-     }
-     
+    print(self.advPrefTableView.selectedRow)
+    guard self.advPrefTableView.selectedRow >= 0,
+      let item = Preferences.shared()[self.advPrefTableView.selectedRow] else {
+        return
+    }
+    print(item.name)
+     /*
      if item.isFolder {
      // 2
      self.representedObject = item.url as Any
@@ -45,7 +47,7 @@ extension PreferencesViewController: NSTableViewDelegate {
   
   func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
     // 1
-    guard let sortDescriptor = tableView.sortDescriptors.first else {
+    guard let sortDescriptor = self.advPrefTableView.sortDescriptors.first else {
       return
     }
     /*if let order = Directory.FileOrder(rawValue: sortDescriptor.key!) {
@@ -74,7 +76,7 @@ extension PreferencesViewController: NSTableViewDelegate {
       text = item.name!
       cellIdentifier = CellIdentifiers.NameCell
     } else if tableColumn == tableView.tableColumns[1] {
-      text = "\(item.defaultValue!)"
+      text = "\(item.defaultValue ?? "")"
       cellIdentifier = CellIdentifiers.DefaultCell
     } else if tableColumn == tableView.tableColumns[2] {
       let thing = (item.selectedValue ?? "none")
diff --git a/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController.swift b/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController.swift
index 799813486f..9670b97cc6 100644
--- a/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController.swift
+++ b/launchers/macosx/I2PLauncher/userinterface/preferences/PreferencesViewController.swift
@@ -38,6 +38,15 @@ class PreferencesViewController: NSViewController {
   @IBOutlet var checkboxStopWithLauncher: NSButton?
   @IBOutlet var buttonResetRouterConfig: NSButton?
   
+  @IBAction func onEnterInTextField(_ sender: NSTextField) {
+    let selectedRowNumber = advPrefTableView.selectedRow
+    print("Trying to store preferences")
+    let currentItem = Preferences.shared()[selectedRowNumber]
+    currentItem?.selectedValue = sender.stringValue
+    Preferences.shared()[selectedRowNumber] = currentItem
+    UserDefaults.standard.set(sender.stringValue, forKey: (currentItem?.name)!)
+    Preferences.shared().syncPref()
+  }
   
   override func viewDidLoad() {
     super.viewDidLoad()
@@ -60,6 +69,8 @@ class PreferencesViewController: NSViewController {
       advPrefTableView.tableColumns[0].sortDescriptorPrototype = NSSortDescriptor(key: "name", ascending: true)
       advPrefTableView.tableColumns[1].sortDescriptorPrototype = NSSortDescriptor(key: "defaultValue", ascending: true)
       advPrefTableView.tableColumns[2].sortDescriptorPrototype = NSSortDescriptor(key: "selectedValue", ascending: true)
+      
+      self.advPrefTableView.isEnabled = Preferences.shared().allowAdvancedPreferenceEdit
     }
     
     // Update radio buttons to reflect runtime/stored preferences
@@ -124,17 +135,30 @@ class PreferencesViewController: NSViewController {
   
   @IBAction func checkboxStartLauncherOnOSXStartupClicked(_ sender: NSButton) {
     let launcherAppId = "net.i2p.bootstrap.macosx.StartupItemApp"
+    let startupMgr = Startup()
     switch sender.state {
     case NSOnState:
       print("on")
       Preferences.shared()["I2Pref_startLauncherAtLogin"] = true
-      let success = SMLoginItemSetEnabled(launcherAppId as CFString, true)
-      print("SMLoginItemSetEnabled returned \(success)....")
+      if (Preferences.shared()["I2Pref_useServiceManagementAsStartupTool"] as! Bool)
+      {
+        let success = SMLoginItemSetEnabled(launcherAppId as CFString, true)
+        print("SMLoginItemSetEnabled returned \(success)....")
+      } else {
+        startupMgr.addLoginItem(Startup.appPath())
+        print("Shared file for auto-startup added. (viewable via OSX Preferences -> Users -> Login Items)")
+      }
     case NSOffState:
       print("off")
       Preferences.shared()["I2Pref_startLauncherAtLogin"] = false
-      let success = SMLoginItemSetEnabled(launcherAppId as CFString, false)
-      print("SMLoginItemSetEnabled returned \(success)....")
+      if (Preferences.shared()["I2Pref_useServiceManagementAsStartupTool"] as! Bool)
+      {
+        let success = SMLoginItemSetEnabled(launcherAppId as CFString, false)
+        print("SMLoginItemSetEnabled returned \(success)....")
+      } else {
+        startupMgr.removeLoginItem(Startup.appPath())
+        print("Shared file for auto-startup removed (if any). (viewable via OSX Preferences -> Users -> Login Items)")
+      }
     case NSMixedState:
       print("mixed")
     default: break
@@ -143,9 +167,11 @@ class PreferencesViewController: NSViewController {
   @IBAction func checkboxStartFirefoxAlsoAtLaunchClicked(_ sender: NSButton) {
     switch sender.state {
     case NSOnState:
-      print("on")
+      print("launch firefox: on")
+      Preferences.shared().alsoStartFirefoxOnLaunch = true
     case NSOffState:
-      print("off")
+      print("launch firefox: off")
+      Preferences.shared().alsoStartFirefoxOnLaunch = false
     case NSMixedState:
       print("mixed")
     default: break
@@ -261,9 +287,11 @@ class PreferencesViewController: NSViewController {
     case NSOnState:
       print("on")
       Preferences.shared().allowAdvancedPreferenceEdit = true
+      self.advPrefTableView.isEnabled = true
     case NSOffState:
       print("off")
       Preferences.shared().allowAdvancedPreferenceEdit = false
+      self.advPrefTableView.isEnabled = false
     case NSMixedState:
       print("mixed")
     default: break
diff --git a/launchers/macosx/SBridge.h b/launchers/macosx/SBridge.h
index 7e6abff08c..a0cd50b4a0 100644
--- a/launchers/macosx/SBridge.h
+++ b/launchers/macosx/SBridge.h
@@ -18,8 +18,6 @@
 #include <string>
 #include <vector>
 #include "include/fn.h"
-//std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus = nil);
-
 
 namespace osx {
   inline void openUrl(NSString* url)
@@ -58,8 +56,6 @@ inline std::string buildClassPathForObjC(std::string basePath)
 
 @interface SBridge : NSObject
 @property (nonatomic, assign) I2PRouterTask* currentRouterInstance;
-- (NSString*) buildClassPath:(NSString*)i2pPath;
-- (void) startupI2PRouter:(NSString*)i2pRootPath;
 - (void) openUrl:(NSString*)url;
 + (void) logProxy:(int)level formattedMsg:(NSString*)formattedMsg;
 + (void) sendUserNotification:(NSString*)title formattedMsg:(NSString*)formattedMsg;
diff --git a/launchers/macosx/SBridge.mm b/launchers/macosx/SBridge.mm
index a56e6e46a3..b573b01329 100644
--- a/launchers/macosx/SBridge.mm
+++ b/launchers/macosx/SBridge.mm
@@ -28,84 +28,6 @@
 #include "include/fn.h"
 
 
-
-std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus) {
-  @try {
-    
-    /**
-     *
-     * The following code will do a test, where it lists all known processes in the OS (visibility depending on user rights)
-     * and scan for any command/arguments matching the substring "i2p.jar" - and in which case it won't start I2P itself.
-     *
-     **/
-    IIProcessInfo* processInfoObj = [[IIProcessInfo alloc] init];
-    [processInfoObj obtainFreshProcessList];
-    auto anyRouterLookingProcs = [processInfoObj findProcessWithStringInNameOrArguments:@"i2p.jar"];
-    if (anyRouterLookingProcs) {
-      /**
-       * The router was found running
-       */
-      auto errMessage = @"Seems i2p is already running - I've detected another process with i2p.jar in it's arguments.";
-      MLog(4, @"%@", errMessage);
-      sendUserNotification(APP_IDSTR, errMessage);
-      [routerStatus triggerEventWithEn:@"router_already_running" details:@"won't start - another router is running"];
-      return std::async(std::launch::async, []{
-        return -1;
-      });
-    } else {
-      /**
-       * No router was detected running
-       **/
-      RTaskOptions* options = [RTaskOptions alloc];
-      options.binPath = javaBin;
-      options.arguments = arguments;
-      options.i2pBaseDir = i2pBaseDir;
-      auto instance = [[I2PRouterTask alloc] initWithOptions: options];
-      
-      [[SBridge sharedInstance] setCurrentRouterInstance:instance];
-      [instance execute];
-      sendUserNotification(APP_IDSTR, @"The I2P router is starting up.");
-      auto pid = [instance getPID];
-      MLog(2, @"Got pid: %d", pid);
-      if (routerStatus != nil) {
-        // TODO: Merge events router_start and router_pid ?
-        [routerStatus triggerEventWithEn:@"router_start" details:@"normal start"];
-        [routerStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]];
-      }
-      NSString *applicationSupportDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
-      auto pidFile = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/i2p/router.pid", applicationSupportDirectory]];
-      NSError *err;
-      
-      if (![[NSString stringWithFormat:@"%d", pid] writeToURL:pidFile atomically:YES encoding:NSUTF8StringEncoding error:&err]) {
-        MLog(4, @"Error; %@", err);
-      } else {
-        MLog(3, @"Wrote pid file to %@", pidFile);
-      }
-      
-      return std::async(std::launch::async, [&pid]{
-        return pid;
-      });
-    }
-  }
-  @catch (NSException *e)
-  {
-    auto errStr = [NSString stringWithFormat:@"Expection occurred %@",[e reason]];
-    MLog(4, @"%@", errStr);
-    sendUserNotification(APP_IDSTR, errStr);
-    [[SBridge sharedInstance] setCurrentRouterInstance:nil];
-    
-    if (routerStatus != nil) {
-      [routerStatus triggerEventWithEn:@"router_exception" details:errStr];
-    }
-    
-    return std::async(std::launch::async, [&]{
-      return 0;
-    });
-  }
-}
-
-
-
 @implementation SBridge
 
 // this makes it a singleton
@@ -129,81 +51,11 @@ std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments,
   osx::openUrl(url);
 }
 
-- (NSString*) buildClassPath:(NSString*)i2pPath
-{
-  const char * basePath = [i2pPath UTF8String];
-  auto jarList = buildClassPathForObjC(basePath);
-  const char * classpath = jarList.c_str();
-  MLog(0, @"Classpath from ObjC = %s", classpath);
-  return [[NSString alloc] initWithUTF8String:classpath];
-}
-
 + (void) logProxy:(int)level formattedMsg:(NSString*)formattedMsg
 {
   MLog(level, formattedMsg);
 }
 
-
-- (void)startupI2PRouter:(NSString*)i2pRootPath
-{
-  std::string basePath([i2pRootPath UTF8String]);
-  
-  auto classPathStr = buildClassPathForObjC(basePath);
-  
-  RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init];
-  
-  NSString *confDir = [NSString stringWithFormat:@"%@/Library/Application\\ Support/i2p", NSHomeDirectory()];
-  
-  try {
-    std::vector<NSString*> argList = {
-      @"-v",
-      @"1.7+",
-      @"--exec",
-      @"java",
-      @"-Xmx512M",
-      @"-Xms128m",
-      @"-Djava.awt.headless=true",
-      [NSString stringWithFormat:@"-Dwrapper.logfile=%@/router.log", [NSString stringWithUTF8String:getDefaultLogDir().c_str()]],
-      @"-Dwrapper.logfile.loglevel=DEBUG",
-      [NSString stringWithFormat:@"-Dwrapper.java.pidfile=%@/router.pid", confDir],
-      @"-Dwrapper.console.loglevel=DEBUG"
-    };
-    
-    std::string baseDirArg("-Di2p.dir.base=");
-    baseDirArg += basePath;
-    std::string javaLibArg("-Djava.library.path=");
-    javaLibArg += basePath;
-    // TODO: pass this to JVM
-    //auto java_opts = getenv("JAVA_OPTS");
-    
-    std::string cpString = std::string("-cp");
-    
-    argList.push_back([NSString stringWithUTF8String:baseDirArg.c_str()]);
-    argList.push_back([NSString stringWithUTF8String:javaLibArg.c_str()]);
-    argList.push_back([NSString stringWithUTF8String:cpString.c_str()]);
-    argList.push_back([NSString stringWithUTF8String:classPathStr.c_str()]);
-    argList.push_back(@"net.i2p.router.Router");
-    auto javaBin = std::string("/usr/libexec/java_home");
-    
-    
-    sendUserNotification(APP_IDSTR, @"I2P Router is starting up!");
-    auto nsJavaBin = [NSString stringWithUTF8String:javaBin.c_str()];
-    auto nsBasePath = i2pRootPath;
-    NSArray* arrArguments = [NSArray arrayWithObjects:&argList[0] count:argList.size()];
-    
-    MLog(0, @"Trying to run command: %@", nsJavaBin);
-    MLog(0, @"With I2P Base dir: %@", i2pRootPath);
-    MLog(0, @"And Arguments: %@", arrArguments);
-    startupRouter(nsJavaBin, arrArguments, nsBasePath, routerStatus);
-  } catch (std::exception &err) {
-    auto errMsg = [NSString stringWithUTF8String:err.what()];
-    MLog(4, @"Exception: %@", errMsg);
-    sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]);
-    [routerStatus setRouterStatus: false];
-    [routerStatus setRouterRanByUs: false];
-    [routerStatus triggerEventWithEn:@"router_exception" details:[NSString stringWithFormat:@"Error: %@", errMsg]];
-  }
-}
 @end
 
 
diff --git a/launchers/macosx/main.mm b/launchers/macosx/main.mm
index 16ccaa78c3..5230e53c36 100644
--- a/launchers/macosx/main.mm
+++ b/launchers/macosx/main.mm
@@ -69,29 +69,6 @@ using namespace subprocess;
   [self.deployer extractI2PBaseDir:completion];
 }
 
-- (void)setApplicationDefaultPreferences {
-  [self.userPreferences registerDefaults:@{
-    @"enableLogging": @YES,
-    @"enableVerboseLogging": @YES,
-    @"autoStartRouterAtBoot": @NO,
-    @"startLauncherAtLogin": @NO,
-    @"startRouterAtStartup": @YES,
-    @"stopRouterAtShutdown": @YES,
-    @"letRouterLiveEvenLauncherDied": @NO,
-    @"consolePortCheckNum": @7657,
-    @"i2pBaseDirectory": (NSString *)CFStringCreateWithCString(NULL, const_cast<const char *>(getDefaultBaseDir().c_str()), kCFStringEncodingUTF8)
-  }];
-
-  auto dict = [self.userPreferences dictionaryRepresentation];
-  [self.userPreferences setPersistentDomain:dict forName:NSAPPDOMAIN];
-
-  CFPreferencesSetMultiple((CFDictionaryRef)dict, NULL, CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
-  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
-
-  NSLog(@"Default preferences stored!");
-}
-
-
 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
   // Init application here
   
@@ -103,9 +80,6 @@ using namespace subprocess;
   
   // Start with user preferences
   self.userPreferences = [NSUserDefaults standardUserDefaults];
-  [self setApplicationDefaultPreferences];
-  self.enableLogging = [self.userPreferences boolForKey:@"enableLogging"];
-  self.enableVerboseLogging = [self.userPreferences boolForKey:@"enableVerboseLogging"];
   // In case we are unbundled, make us a proper UI application
   [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
   [NSApp activateIgnoringOtherApps:YES];
-- 
GitLab