forked from I2P_Developers/i2p.i2p
Mac OS X Launcher - reborn - ALPHA!
TLDR; Howto? ant osxLauncher Privacy Notes? If you don't got SBT, a bash script will trigger download of SBT for you with task osxLauncher. Results? open ./launchers/output "Binary" App Bundle name: I2P.app Runtime base directory? ~/Library/I2P Runtime config directory? untouched. After talk on IRC with zzz, I rewrote the logic since we could start with a simple deploy, for a faster alpha version ready :) SBT will build a zip file from the content of pkg-temp, which CompleteDeployment.scala will again unzip in runtime. Right now it's quite basic, but the plan is to add version detection, so it's capable of upgrading a already deployed I2P base directory. OSXDeployment.scala is renamed to PartialDeployment.scala for usage in the browser bundle launcher, since it's going to be a subset of the files found in pkg-temp. A Info.plist is added to the launchers/macosx which is added to the application bundle under building. Note that this differ from the one in Start i2p router.app that's been here for years now.
This commit is contained in:
@ -59,6 +59,7 @@ launchers/target
|
||||
launchers/project/target
|
||||
launchers/common/target
|
||||
launchers/output
|
||||
launchers/project/project
|
||||
|
||||
# Reporting
|
||||
sloccount.sc
|
||||
|
16
build.xml
16
build.xml
@ -301,22 +301,22 @@
|
||||
|
||||
<target name="bbLauncher" depends="build">
|
||||
<sequential>
|
||||
<exec executable="sbt" dir="launcher" failonerror="true">
|
||||
<exec executable="sbt" dir="launchers" failonerror="true">
|
||||
<arg value="browserbundle:clean" />
|
||||
</exec>
|
||||
<exec executable="sbt" dir="launcher" failonerror="true">
|
||||
<exec executable="sbt" dir="launchers" failonerror="true">
|
||||
<arg value="browserbundle:assembly" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</target>
|
||||
|
||||
<target name="osxLauncher" depends="build">
|
||||
<target name="osxLauncher" depends="build,preppkg-osx">
|
||||
<sequential>
|
||||
<exec executable="sbt" dir="launcher" failonerror="true">
|
||||
<arg value="macosx:clean" />
|
||||
<exec executable="sbt" dir="launchers" failonerror="true">
|
||||
<arg value="macosx:cleanAllTask" />
|
||||
</exec>
|
||||
<exec executable="sbt" dir="launcher" failonerror="true">
|
||||
<arg value="macosx:assembly" />
|
||||
<exec executable="sbt" dir="launchers" failonerror="true">
|
||||
<arg value="macosx:buildAppBundleTask" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</target>
|
||||
@ -907,7 +907,7 @@
|
||||
<classpath>
|
||||
<pathelement location="build/i2p.jar" />
|
||||
<pathelement location="build/router.jar" />
|
||||
<pathelement location="${junit.home}/junit4.jar" />
|
||||
<pathelement location="${junit.home}/ant-junit4.jar" />
|
||||
<pathelement location="${hamcrest.home}/hamcrest-all.jar" />
|
||||
</classpath>
|
||||
<!--
|
||||
|
11
history.txt
11
history.txt
@ -1,3 +1,14 @@
|
||||
2018-05-06 meeh
|
||||
* launchers:
|
||||
- rewritten some logic
|
||||
- made CompleteDeployment which extracts i2pbase.zip
|
||||
- made a SBT task that creates i2pbase.zip
|
||||
- after the hassle I had even getting an JRE7, short: OSX assumes JRE8+
|
||||
- started on a class SystemTrayManager which also holds router state info for menu item filter
|
||||
- "ant osxLauncher" produces now a valid app bundle under launchers/output
|
||||
* Updated ant and made osxLauncher work again (first time triggers SBT dl if not already installed.)
|
||||
* monotone ignore file update
|
||||
|
||||
2018-05-05 zzz
|
||||
* i2ptunnel:
|
||||
- Link to SSL wizard
|
||||
|
@ -0,0 +1,48 @@
|
||||
package net.i2p.launchers
|
||||
|
||||
import java.io.{File, FileInputStream, FileOutputStream, InputStream}
|
||||
import java.nio.file.Path
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
/**
|
||||
*
|
||||
* CompleteDeployment - In use to deploy base path for the Mac OS X Bundle release.
|
||||
*
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class CompleteDeployment(val zipFile: File, val i2pBaseDir: File) {
|
||||
|
||||
if (!i2pBaseDir.exists()) {
|
||||
i2pBaseDir.mkdirs()
|
||||
} else {
|
||||
// TODO: Check what version etc..
|
||||
}
|
||||
|
||||
def unzip(zipFile: InputStream, destination: Path): Unit = {
|
||||
val zis = new ZipInputStream(zipFile)
|
||||
|
||||
Stream.continually(zis.getNextEntry).takeWhile(_ != null).foreach { file =>
|
||||
if (!file.isDirectory) {
|
||||
val outPath = destination.resolve(file.getName)
|
||||
val outPathParent = outPath.getParent
|
||||
if (!outPathParent.toFile.exists()) {
|
||||
outPathParent.toFile.mkdirs()
|
||||
}
|
||||
|
||||
val outFile = outPath.toFile
|
||||
val out = new FileOutputStream(outFile)
|
||||
val buffer = new Array[Byte](4096)
|
||||
Stream.continually(zis.read(buffer)).takeWhile(_ != -1).foreach(out.write(buffer, 0, _))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def makeDeployment : Future[Unit] = Future {
|
||||
unzip(new FileInputStream(zipFile), i2pBaseDir.toPath)
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,9 @@ import collection.JavaConverters._
|
||||
|
||||
/**
|
||||
*
|
||||
* OSXDeployment
|
||||
* NOTE: Work in progress: Originally written for OSX launcher - but will be used in BB launcher.
|
||||
*
|
||||
* PartialXDeployment
|
||||
*
|
||||
* This class can be a bit new for java developers. In Scala, when inherit other classes,
|
||||
* you would need to define their arguments if the super class only has constructors taking arguments.
|
||||
@ -76,7 +78,7 @@ import collection.JavaConverters._
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class OSXDeployment extends
|
||||
class PartialDeployment extends
|
||||
DeployProfile(
|
||||
OSXDefaults.getOSXConfigDirectory.getAbsolutePath,
|
||||
OSXDefaults.getOSXBaseDirectory.getAbsolutePath
|
@ -2,6 +2,9 @@ package net.i2p.launchers
|
||||
|
||||
import java.io.File
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.sys.process.Process
|
||||
|
||||
|
||||
/**
|
||||
* A abstract class is kind of like an java interface.
|
||||
@ -10,7 +13,7 @@ import java.io.File
|
||||
* @since 0.9.35
|
||||
*/
|
||||
abstract class RouterLauncher {
|
||||
def runRouter(basePath: File, args: Array[String]): Unit
|
||||
def runRouter(basePath: File, args: Array[String]): Future[Process]
|
||||
|
||||
def runRouter(args: Array[String]): Unit
|
||||
def runRouter(args: Array[String]): Future[Process]
|
||||
}
|
||||
|
47
launchers/macosx/Info.plist
Normal file
47
launchers/macosx/Info.plist
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>I2P</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Public Domain</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>0.9.35-experimental</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>i2p</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.i2p</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>I2P</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>I2P</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.1</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
<key>CGDisableCoalescedUpdates</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.5</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>I2P</string>
|
||||
<key>LSMinimumSystemVersionByArchitecture</key>
|
||||
<dict>
|
||||
<key>i386</key>
|
||||
<string>10.5.0</string>
|
||||
<key>x86_64</key>
|
||||
<string>10.6.0</string>
|
||||
</dict>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
@ -1,14 +1,12 @@
|
||||
import sbtassembly.AssemblyPlugin.defaultShellScript
|
||||
import sbt._
|
||||
import Keys._
|
||||
import sbt.io.IO
|
||||
import java.io.File
|
||||
import java.io.{File, FileNotFoundException, FileOutputStream}
|
||||
import java.util.zip._
|
||||
|
||||
lazy val i2pVersion = "0.9.34"
|
||||
|
||||
lazy val cleanAllTask = taskKey[Unit]("Clean up and remove the OSX bundle")
|
||||
lazy val buildAppBundleTask = taskKey[Unit](s"Build an Mac OS X bundle for I2P ${i2pVersion}.")
|
||||
lazy val bundleBuildPath = file("./output")
|
||||
lazy val buildDeployZipTask = taskKey[String](s"Build an zipfile with base directory for I2P ${i2pVersion}.")
|
||||
lazy val bundleBuildPath = new File("./output")
|
||||
|
||||
lazy val staticFiles = List(
|
||||
"blocklist.txt",
|
||||
@ -32,7 +30,7 @@ def defaultOSXLauncherShellScript(javaOpts: Seq[String] = Seq.empty): Seq[String
|
||||
Seq(
|
||||
"#!/usr/bin/env sh",
|
||||
s"""
|
||||
|echo "Yo"
|
||||
|echo "I2P - Mac OS X Launcher starting up"
|
||||
|export I2P=$$HOME/Library/I2P
|
||||
|for jar in `ls $${I2P}/lib/*.jar`; do
|
||||
| if [ ! -z $$CP ]; then
|
||||
@ -66,7 +64,6 @@ assemblyOption in assembly := (assemblyOption in assembly).value.copy(
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Di2p.dir.base=$I2P",
|
||||
"-Djava.library.path=$I2P"
|
||||
)))
|
||||
@ -92,36 +89,66 @@ cleanAllTask := {
|
||||
IO.delete(bundleBuildPath)
|
||||
}
|
||||
|
||||
buildDeployZipTask := {
|
||||
println(s"Starting the zip file build process. This might take a while..")
|
||||
if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
|
||||
val sourceDir = i2pBuildDir
|
||||
def recursiveListFiles(f: File): Array[File] = {
|
||||
val these = f.listFiles
|
||||
these ++ these.filter { f => f.isDirectory }.flatMap(recursiveListFiles).filter(!_.isDirectory)
|
||||
}
|
||||
def zip(out: String, files: Iterable[String]) = {
|
||||
import java.io.{ BufferedInputStream, FileInputStream, FileOutputStream }
|
||||
import java.util.zip.{ ZipEntry, ZipOutputStream }
|
||||
|
||||
val zip = new ZipOutputStream(new FileOutputStream(out))
|
||||
|
||||
files.foreach { name =>
|
||||
val fname = sourceDir.toURI.relativize(new File(name).toURI).toString
|
||||
//println(s"Zipping ${fname}")
|
||||
if (!new File(name).isDirectory) {
|
||||
zip.putNextEntry(new ZipEntry(fname))
|
||||
val in = new BufferedInputStream(new FileInputStream(name))
|
||||
var b = in.read()
|
||||
while (b > -1) {
|
||||
zip.write(b)
|
||||
b = in.read()
|
||||
}
|
||||
in.close()
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
zip.close()
|
||||
}
|
||||
val fileList = recursiveListFiles(sourceDir.getCanonicalFile).toList
|
||||
val zipFileName = new File(bundleBuildPath, "i2pbase.zip").getCanonicalPath
|
||||
zip(zipFileName, fileList.map { f => f.toString }.toIterable)
|
||||
zipFileName.toString
|
||||
}
|
||||
|
||||
buildAppBundleTask := {
|
||||
println(s"Building Mac OS X bundle for I2P version ${i2pVersion}.")
|
||||
bundleBuildPath.mkdir()
|
||||
if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
|
||||
val paths = Map[String,File](
|
||||
"execBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/MacOS"),
|
||||
"resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources"),
|
||||
"i2pbaseBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase"),
|
||||
"i2pJarsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/lib"),
|
||||
"webappsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/webapps")
|
||||
"resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources")
|
||||
)
|
||||
paths.map { case (s,p) => p.mkdirs() }
|
||||
val dirsToCopy = List("certificates","locale","man")
|
||||
|
||||
val launcherBinary = Some(assembly.value)
|
||||
launcherBinary.map { l => IO.copyFile( new File(l.toString), new File(paths.get("execBundlePath").get, "I2P") ) }
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* First of, if "map" is unknown for you - shame on you :p
|
||||
*
|
||||
* It's a loop basically where it loops through a list/array
|
||||
* with the current indexed item as subject.
|
||||
*
|
||||
* The code bellow takes the different lists and
|
||||
* copy all the directories or files from the i2p.i2p build dir,
|
||||
* and into the bundle so the launcher will know where to find i2p.
|
||||
*
|
||||
*/
|
||||
dirsToCopy.map { d => IO.copyDirectory( new File(resDir, d), new File(paths.get("i2pbaseBunldePath").get, d) ) }
|
||||
warsForCopy.map { w => IO.copyFile( new File(new File(i2pBuildDir, "webapps"), w), new File(paths.get("webappsBunldePath").get, w) ) }
|
||||
jarsForCopy.map { j => IO.copyFile( new File(new File(i2pBuildDir, "lib"), j), new File(paths.get("i2pJarsBunldePath").get, j) ) }
|
||||
val plistFile = new File("./macosx/Info.plist")
|
||||
if (plistFile.exists()) {
|
||||
println(s"Adding Info.plist...")
|
||||
IO.copyFile(plistFile, new File(bundleBuildPath, "I2P.app/Contents/Info.plist"))
|
||||
}
|
||||
|
||||
val zipFilePath = Some(buildDeployZipTask.value)
|
||||
|
||||
val zipFileOrigin = new File(zipFilePath.get)
|
||||
IO.copyFile(zipFileOrigin, new File(paths.get("resBundlePath").get, "i2pbase.zip"))
|
||||
println(s"Zip placed into bundle :)")
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,15 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
import java.awt.SystemTray
|
||||
import java.io.File
|
||||
|
||||
import net.i2p.launchers.{DeployProfile, OSXDefaults, OSXDeployment}
|
||||
import net.i2p.launchers.{CompleteDeployment, OSXDefaults}
|
||||
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
import scala.sys.process.Process
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -36,14 +43,53 @@ object LauncherAppMain extends App {
|
||||
|
||||
val i2pBaseDir = OSXDefaults.getOSXBaseDirectory
|
||||
|
||||
new OSXDeployment()
|
||||
val selfDirPath = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath).getParentFile
|
||||
|
||||
// Tricky to get around, but feels hard to use a "var" which means mutable..
|
||||
// It's like cursing in the church... Worse.
|
||||
var sysTray: Option[SystemTrayManager] = None
|
||||
|
||||
val deployment = new CompleteDeployment(new File(selfDirPath.getPath, "../Resources/i2pbase.zip"), i2pBaseDir)
|
||||
|
||||
val depProc = deployment.makeDeployment
|
||||
// Change directory to base dir
|
||||
System.setProperty("user.dir", i2pBaseDir.getAbsolutePath)
|
||||
|
||||
// System shutdown hook
|
||||
sys.ShutdownHookThread {
|
||||
println("exiting launcher process")
|
||||
}
|
||||
|
||||
Await.ready(depProc, 60000 millis)
|
||||
|
||||
println("I2P Base Directory Extracted.")
|
||||
|
||||
try {
|
||||
MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
|
||||
val routerProcess: Future[Process] = MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
|
||||
|
||||
if (SystemTray.isSupported) {
|
||||
sysTray = Some(new SystemTrayManager)
|
||||
}
|
||||
|
||||
routerProcess onComplete {
|
||||
case Success(forkResult) => {
|
||||
println(s"Router started successfully!")
|
||||
try {
|
||||
val routerPID = MacOSXRouterLauncher.pid(forkResult)
|
||||
println(s"PID is ${routerPID}")
|
||||
} catch {
|
||||
case ex:java.lang.RuntimeException => println(s"Minor error: ${ex.getMessage}")
|
||||
}
|
||||
if (!sysTray.isEmpty) sysTray.get.setRunning(true)
|
||||
}
|
||||
case Failure(fail) => {
|
||||
println(s"Router failed to start, error is: ${fail.toString}")
|
||||
}
|
||||
}
|
||||
|
||||
//Await.result(routerProcess, 5000 millis)
|
||||
|
||||
} finally {
|
||||
System.out.println("Exit.")
|
||||
System.out.println("Exit?")
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
import java.io.File
|
||||
import java.lang.reflect.Field
|
||||
|
||||
import scala.sys.process.Process
|
||||
import net.i2p.launchers.RouterLauncher
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
@ -13,25 +17,73 @@ import net.i2p.launchers.RouterLauncher
|
||||
*/
|
||||
object MacOSXRouterLauncher extends RouterLauncher {
|
||||
|
||||
override def runRouter(args: Array[String]): Unit = {}
|
||||
def pid(p: Process): Long = {
|
||||
val procField = p.getClass.getDeclaredField("p")
|
||||
procField.synchronized {
|
||||
procField.setAccessible(true)
|
||||
val proc = procField.get(p)
|
||||
try {
|
||||
proc match {
|
||||
case unixProc
|
||||
if unixProc.getClass.getName == "java.lang.UNIXProcess" => {
|
||||
val pidField = unixProc.getClass.getDeclaredField("pid")
|
||||
pidField.synchronized {
|
||||
pidField.setAccessible(true)
|
||||
try {
|
||||
pidField.getLong(unixProc)
|
||||
} finally {
|
||||
pidField.setAccessible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
case procImpl:java.lang.Process => {
|
||||
val f: Field = p.getClass().getDeclaredField("p")
|
||||
val f2: Field = f.get(p).getClass.getDeclaredField("pid")
|
||||
try {
|
||||
f.setAccessible(true)
|
||||
f2.setAccessible(true)
|
||||
val pid = f2.getLong(p)
|
||||
pid
|
||||
} finally {
|
||||
f2.setAccessible(false)
|
||||
f.setAccessible(false)
|
||||
}
|
||||
}
|
||||
// If someone wants to add support for Windows processes,
|
||||
// this would be the right place to do it:
|
||||
case _ => throw new RuntimeException(
|
||||
"Cannot get PID of a " + proc.getClass.getName)
|
||||
}
|
||||
} finally {
|
||||
procField.setAccessible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def runRouter(basePath: File, args: Array[String]): Unit = {
|
||||
lazy val javaOpts = Seq(
|
||||
"-Xmx512M",
|
||||
"-Xms128m",
|
||||
"-Dwrapper.logfile=/tmp/router.log",
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
s"-Di2p.dir.base=${basePath}",
|
||||
s"-Djava.library.path=${basePath}"
|
||||
)
|
||||
val javaOptsString = javaOpts.map(_ + " ").mkString
|
||||
val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
|
||||
println(s"CLI => ${cli}")
|
||||
val pb = Process(cli)
|
||||
// Use "run" to let it fork in behind
|
||||
val exitCode = pb.!
|
||||
|
||||
// ??? equals "throw not implemented" IIRC - it compiles at least :)
|
||||
override def runRouter(args: Array[String]): Future[Process] = ???
|
||||
|
||||
def runRouter(basePath: File, args: Array[String]): Future[Process] = {
|
||||
Future {
|
||||
lazy val javaOpts = Seq(
|
||||
"-Xmx512M",
|
||||
"-Xms128m",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Dwrapper.logfile=/tmp/router.log",
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
s"-Di2p.dir.base=${basePath}",
|
||||
s"-Djava.library.path=${basePath}"
|
||||
)
|
||||
val javaOptsString = javaOpts.map(_ + " ").mkString
|
||||
val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
|
||||
println(s"CLI => ${cli}")
|
||||
val pb = Process(cli)
|
||||
// Use "run" to let it fork in behind
|
||||
pb.run
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class SystemTrayManager {
|
||||
|
||||
object RouterState {
|
||||
var isRunning: Boolean = false
|
||||
var startupTime: Long = 0L
|
||||
}
|
||||
|
||||
def isRunning = RouterState.isRunning
|
||||
|
||||
def setRunning(runs: Boolean): Unit = {
|
||||
if (runs) setStartupTime()
|
||||
RouterState.isRunning = runs
|
||||
}
|
||||
|
||||
def setStartupTime() = {
|
||||
RouterState.startupTime = System.currentTimeMillis / 1000
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user