mirror of
				https://github.com/livebook-dev/livebook.git
				synced 2025-10-31 15:56:05 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			167 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
| import AppKit
 | |
| import ElixirKit
 | |
| 
 | |
| @main
 | |
| public struct Livebook {
 | |
|     public static func main() {
 | |
|         let app = NSApplication.shared
 | |
|         let delegate = AppDelegate()
 | |
|         app.delegate = delegate
 | |
|         app.run()
 | |
|     }
 | |
| }
 | |
| 
 | |
| class AppDelegate: NSObject, NSApplicationDelegate {
 | |
|     private var statusItem: NSStatusItem!
 | |
|     private var logPath: String!
 | |
|     private var launchedByOpenURL = false
 | |
|     private var initialURLs: [URL] = []
 | |
|     private var url: String?
 | |
|     private var bootScriptURL: URL!
 | |
| 
 | |
|     func applicationDidFinishLaunching(_ aNotification: Notification) {
 | |
|         logPath = "\(NSHomeDirectory())/Library/Logs/Livebook.log"
 | |
|         bootScriptURL = NSURL.fileURL(withPath: "\(NSHomeDirectory())/.livebookdesktop.sh")
 | |
| 
 | |
|         ElixirKit.API.start(
 | |
|             name: "app",
 | |
|             logPath: logPath,
 | |
|             readyHandler: {
 | |
|                 if (self.initialURLs == []) {
 | |
|                     ElixirKit.API.publish("open", "")
 | |
|                 }
 | |
|                 else {
 | |
|                     for url in self.initialURLs {
 | |
|                         ElixirKit.API.publish("open", url.absoluteString)
 | |
|                     }
 | |
|                 }
 | |
|             },
 | |
|             terminationHandler: { process in
 | |
|                 if process.terminationStatus != 0 {
 | |
|                     DispatchQueue.main.sync {
 | |
|                         let alert = NSAlert()
 | |
|                         alert.alertStyle = .critical
 | |
|                         alert.messageText = "Livebook exited with error status \(process.terminationStatus)"
 | |
|                         alert.addButton(withTitle: "Dismiss")
 | |
|                         alert.addButton(withTitle: "View Logs")
 | |
| 
 | |
|                         switch alert.runModal() {
 | |
|                         case .alertSecondButtonReturn:
 | |
|                             self.viewLogs()
 | |
|                         default:
 | |
|                             ()
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 NSApp.terminate(nil)
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
 | |
|         let button = statusItem.button!
 | |
|         let icon = NSImage(named: "Icon")!
 | |
|         let resizedIcon = NSImage(size: NSSize(width: 18, height: 18), flipped: false) { (dstRect) -> Bool in
 | |
|             icon.draw(in: dstRect)
 | |
|             return true
 | |
|         }
 | |
|         button.image = resizedIcon
 | |
|         let menu = NSMenu()
 | |
| 
 | |
|         let copyURLItem = NSMenuItem(title: "Copy URL", action: nil, keyEquivalent: "c")
 | |
| 
 | |
|         menu.items = [
 | |
|             NSMenuItem(title: "Open", action: #selector(open), keyEquivalent: "o"),
 | |
|             NSMenuItem(title: "New Notebook", action: #selector(openNewNotebook), keyEquivalent: "n"),
 | |
|             .separator(),
 | |
|             copyURLItem,
 | |
|             NSMenuItem(title: "View Logs", action: #selector(viewLogs), keyEquivalent: "l"),
 | |
|             NSMenuItem(title: "Open .livebookdesktop.sh", action: #selector(openBootScript), keyEquivalent: ""),
 | |
|             .separator(),
 | |
|             NSMenuItem(title: "Settings", action: #selector(openSettings), keyEquivalent: ","),
 | |
|             NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
 | |
|         ]
 | |
|         statusItem.menu = menu
 | |
| 
 | |
|         ElixirKit.API.addObserver(queue: .main) { (name, data) in
 | |
|             switch name {
 | |
|             case "url":
 | |
|                 copyURLItem.action = #selector(self.copyURL)
 | |
|                 self.url = data
 | |
|             default:
 | |
|                 fatalError("unknown event \(name)")
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     func applicationWillTerminate(_ aNotification: Notification) {
 | |
|         ElixirKit.API.stop()
 | |
|     }
 | |
| 
 | |
|     func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
 | |
|         ElixirKit.API.publish("open", "")
 | |
|         return true
 | |
|     }
 | |
| 
 | |
|     func application(_ app: NSApplication, open urls: [URL]) {
 | |
|         if !ElixirKit.API.isRunning {
 | |
|             initialURLs = urls
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         for url in urls {
 | |
|             ElixirKit.API.publish("open", url.absoluteString)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func open() {
 | |
|         ElixirKit.API.publish("open", "")
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func openNewNotebook() {
 | |
|         ElixirKit.API.publish("open", "/new")
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func copyURL() {
 | |
|         let pasteboard = NSPasteboard.general
 | |
|         pasteboard.clearContents()
 | |
|         pasteboard.setData(url!.data(using: .utf8), forType: NSPasteboard.PasteboardType.URL)
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func viewLogs() {
 | |
|         NSWorkspace.shared.open(NSURL.fileURL(withPath: logPath))
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func openSettings() {
 | |
|         ElixirKit.API.publish("open", "/settings")
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func openBootScript() {
 | |
|         let fm = FileManager.default
 | |
| 
 | |
|         if !fm.fileExists(atPath: bootScriptURL.path) {
 | |
|             let data = """
 | |
|             # This file is used to configure Livebook before booting.
 | |
|             # If you change this file, you must restart Livebook for your changes to take place.
 | |
|             # See https://hexdocs.pm/livebook/readme.html#environment-variables for all available environment variables.
 | |
| 
 | |
|             # # Allow Livebook to connect to remote machines over IPv6
 | |
|             # export LIVEBOOK_DISTRIBUTION=name
 | |
|             # export ERL_AFLAGS="-proto_dist inet6_tcp"
 | |
| 
 | |
|             # # Add Homebrew to PATH
 | |
|             # export PATH=/opt/homebrew/bin:$PATH
 | |
|             """.data(using: .utf8)
 | |
| 
 | |
|             fm.createFile(atPath: bootScriptURL.path, contents: data)
 | |
|         }
 | |
| 
 | |
|         NSWorkspace.shared.open(bootScriptURL)
 | |
|     }
 | |
| }
 |