diff --git a/.github/scripts/app/bootstrap_mac.sh b/.github/scripts/app/bootstrap_mac.sh
index 90412e612..8995483c9 100755
--- a/.github/scripts/app/bootstrap_mac.sh
+++ b/.github/scripts/app/bootstrap_mac.sh
@@ -6,8 +6,8 @@ main() {
wxwidgets_repo="wxWidgets/wxWidgets"
wxwidgets_ref="v3.1.7"
- otp_repo="erlang/otp"
- otp_ref="OTP-25.0.2"
+ otp_repo="wojtekmach/otp"
+ otp_ref="wm-WX_MACOS_NON_GUI_APP"
elixir_vsn="1.13.4"
target=$(target)
@@ -78,7 +78,8 @@ build_wxwidgets() {
--with-libpng=builtin \
--with-liblzma=builtin \
--with-zlib=builtin \
- --with-expat=builtin
+ --with-expat=builtin \
+ --with-regex=builtin
make
make install
cd -
diff --git a/app_builder/examples/wx_demo/lib/wx_demo.ex b/app_builder/examples/wx_demo/lib/wx_demo.ex
index 3ab46eb2a..beab01d5e 100644
--- a/app_builder/examples/wx_demo/lib/wx_demo.ex
+++ b/app_builder/examples/wx_demo/lib/wx_demo.ex
@@ -16,38 +16,21 @@ end
defmodule WxDemo.Window do
@moduledoc false
+ use GenServer, restart: :transient
- @behaviour :wx_object
+ def start_link(arg) do
+ GenServer.start_link(__MODULE__, arg, name: __MODULE__)
+ end
# https://github.com/erlang/otp/blob/OTP-24.1.2/lib/wx/include/wx.hrl#L1314
@wx_id_exit 5006
@wx_id_osx_hide 5250
- def start_link(_) do
- {:wx_ref, _, _, pid} = :wx_object.start_link({:local, __MODULE__}, __MODULE__, [], [])
- {:ok, pid}
- end
-
- def child_spec(init_arg) do
- %{
- id: __MODULE__,
- start: {__MODULE__, :start_link, [init_arg]},
- restart: :transient
- }
- end
-
- def windows_connected(url) do
- url
- |> String.trim()
- |> String.trim_leading("\"")
- |> String.trim_trailing("\"")
- |> windows_to_wx()
- end
-
@impl true
def init(_) do
+ AppBuilder.init()
app_name = "WxDemo"
- os = os()
+ os = AppBuilder.os()
wx = :wx.new()
frame = :wxFrame.new(wx, -1, app_name, size: {400, 400})
@@ -59,33 +42,24 @@ defmodule WxDemo.Window do
:wxFrame.connect(frame, :command_menu_selected, skip: true)
:wxFrame.connect(frame, :close_window, skip: true)
- case os do
- :macos ->
- :wx.subscribe_events()
-
- :windows ->
- windows_to_wx(System.get_env("WXDEMO_URL") || "")
- end
-
- state = %{frame: frame}
- {frame, state}
+ {:ok, %{frame: frame}}
end
@impl true
- def handle_event({:wx, @wx_id_exit, _, _, _}, state) do
+ def handle_info({:wx, @wx_id_exit, _, _, _}, state) do
:init.stop()
{:stop, :shutdown, state}
end
@impl true
- def handle_event({:wx, _, _, _, {:wxClose, :close_window}}, state) do
+ def handle_info({:wx, _, _, _, {:wxClose, :close_window}}, state) do
:init.stop()
{:stop, :shutdown, state}
end
# ignore other menu events
@impl true
- def handle_event({:wx, _, _, _, {:wxCommand, :command_menu_selected, _, _, _}}, state) do
+ def handle_info({:wx, _, _, _, {:wxCommand, :command_menu_selected, _, _, _}}, state) do
{:noreply, state}
end
@@ -118,23 +92,4 @@ defmodule WxDemo.Window do
|> :wxMenu.findItem(@wx_id_exit)
|> :wxMenuItem.setItemLabel("Quit #{app_name}\tCtrl+Q")
end
-
- defp os() do
- case :os.type() do
- {:unix, :darwin} -> :macos
- {:win32, _} -> :windows
- end
- end
-
- defp windows_to_wx("") do
- send(__MODULE__, {:new_file, ''})
- end
-
- defp windows_to_wx("wxdemo://" <> _ = url) do
- send(__MODULE__, {:open_url, String.to_charlist(url)})
- end
-
- defp windows_to_wx(path) do
- send(__MODULE__, {:open_file, String.to_charlist(path)})
- end
end
diff --git a/app_builder/examples/wx_demo/mix.exs b/app_builder/examples/wx_demo/mix.exs
index d12373993..1647f3dcd 100644
--- a/app_builder/examples/wx_demo/mix.exs
+++ b/app_builder/examples/wx_demo/mix.exs
@@ -46,12 +46,12 @@ defmodule WxDemo.MixProject do
]
]
],
+ event_handler: WxDemo.Window,
macos: [
build_dmg: macos_notarization != nil,
notarization: macos_notarization
],
windows: [
- server: WxDemo,
build_installer: true
]
]
diff --git a/app_builder/lib/app_builder.ex b/app_builder/lib/app_builder.ex
index b4aa840f0..4a246e618 100644
--- a/app_builder/lib/app_builder.ex
+++ b/app_builder/lib/app_builder.ex
@@ -18,6 +18,36 @@ defmodule AppBuilder do
end
end
+ def init do
+ if input = System.get_env("APP_BUILDER_INPUT") do
+ __rpc__(self(), input)
+ end
+ end
+
+ def __rpc__(event_handler) do
+ input = IO.read(:line) |> String.trim()
+ __rpc__(event_handler, input)
+ end
+
+ def __rpc__(event_handler, "open_app") do
+ send(event_handler, :open_app)
+ end
+
+ def __rpc__(event_handler, "open_url:" <> url) do
+ send(event_handler, {:open_url, url})
+ end
+
+ def __rpc__(event_handler, "open_file:" <> path) do
+ path =
+ if os() == :windows do
+ String.replace(path, "\\", "/")
+ else
+ path
+ end
+
+ send(event_handler, {:open_file, path})
+ end
+
defp validate_options(options) do
os = os()
@@ -25,6 +55,7 @@ defmodule AppBuilder do
all: [
:name,
:icon_path,
+ :event_handler,
url_schemes: [],
document_types: [],
additional_paths: []
diff --git a/app_builder/lib/app_builder/windows.ex b/app_builder/lib/app_builder/windows.ex
index 34571faaa..c233a8630 100644
--- a/app_builder/lib/app_builder/windows.ex
+++ b/app_builder/lib/app_builder/windows.ex
@@ -53,27 +53,14 @@ defmodule AppBuilder.Windows do
release
end
- def __send_events__(server, input)
+ def handle_event(module, input)
- def __send_events__(server, "new_file") do
- send(server, {:new_file, ''})
+ def handle_event(module, "open_url:" <> url) do
+ module.open_url(url)
end
- def __send_events__(server, "reopen_app") do
- send(server, {:reopen_app, ''})
- end
-
- def __send_events__(server, "open_url:" <> url) do
- send(server, {:open_url, String.to_charlist(url)})
- end
-
- def __send_events__(server, "open_file:" <> path) do
- path =
- path
- |> String.replace("\\", "/")
- |> String.to_charlist()
-
- send(server, {:open_file, path})
+ def handle_event(module, "open_file:" <> path) do
+ module.open_file(String.replace(path, "\\", "/"))
end
defp ensure_vcredistx64 do
diff --git a/app_builder/lib/app_builder/wx.ex b/app_builder/lib/app_builder/wx.ex
deleted file mode 100644
index 32c451c53..000000000
--- a/app_builder/lib/app_builder/wx.ex
+++ /dev/null
@@ -1,39 +0,0 @@
-defmodule AppBuilder.Wx do
- require Logger
-
- @doc """
- Subscribe the given `server` to application events.
-
- The possible events are:
-
- * `{:new_file, []}`
- * `{:reopen_app, []}`
- * `{:open_file, path}`
- * `{:open_url, url}`
-
- On macOS, we simply call `:wx.subscribe_events()`. On Windows,
- we emulate it.
- """
- def subscribe_to_app_events(server) do
- unless Code.ensure_loaded?(:wx) do
- Logger.error("""
- wx is not available.
-
- Please add it to your extra applications:
-
- extra_applications: [:wx]
- """)
-
- raise "wx is not available"
- end
-
- case :os.type() do
- {:unix, :darwin} ->
- :wx.subscribe_events()
-
- {:win32, _} ->
- input = System.get_env("APP_BUILDER_INPUT", "new_file")
- AppBuilder.Windows.__send_events__(server, input)
- end
- end
-end
diff --git a/app_builder/lib/templates/macos/Entitlements.plist.eex b/app_builder/lib/templates/macos/Entitlements.plist.eex
index 9cbd47fe2..5e8e61cde 100644
--- a/app_builder/lib/templates/macos/Entitlements.plist.eex
+++ b/app_builder/lib/templates/macos/Entitlements.plist.eex
@@ -8,5 +8,7 @@
com.apple.security.cs.allow-dyld-environment-variables
+ com.apple.security.cs.disable-library-validation
+
diff --git a/app_builder/lib/templates/macos/Launcher.swift.eex b/app_builder/lib/templates/macos/Launcher.swift.eex
index f3e116d2b..97abc6d60 100644
--- a/app_builder/lib/templates/macos/Launcher.swift.eex
+++ b/app_builder/lib/templates/macos/Launcher.swift.eex
@@ -1,17 +1,121 @@
<%
-additional_paths = [
- "rel/erts-#{@release.erts_version}/bin"
-] ++ @app_options[:additional_paths]
+event_handler = @app_options |> Keyword.fetch!(:event_handler) |> inspect()
-additional_paths = Enum.map_join(additional_paths, ":", &"\\(resourcePath)/#{&1}")
+additional_paths =
+ ["rel/erts-#{@release.erts_version}/bin"] ++ @app_options[:additional_paths]
+ |> Enum.map_join(":", &"\\(resourcePath)/#{&1}")
%>
-import Foundation
import Cocoa
+class AppDelegate: NSObject, NSApplicationDelegate {
+ var releaseTask: Process!
+ var isRunning = false
+ var initialInput = "open_app"
+
+ func applicationDidFinishLaunching(_ aNotification: Notification) {
+ if !isRunning {
+ releaseTask = startRelease(initialInput)
+ isRunning = true
+ }
+ }
+
+ func applicationWillTerminate(_ n: Notification) {
+ if (releaseTask.isRunning == true) {
+ log("terminating release task")
+ releaseTask.terminate()
+ }
+ }
+
+ func application(_ app: NSApplication, open urls: [URL]) {
+ for url in urls {
+ var input : String
+
+ if url.isFileURL {
+ input = "open_file:\(url.path)"
+ } else {
+ input = "open_url:\(url)"
+ }
+
+ if isRunning {
+ rpc(input)
+ } else {
+ initialInput = input
+ }
+ }
+ }
+
+}
+
+func startRelease(_ input : String) -> Process {
+ let task = buildReleaseTask()
+ task.environment!["APP_BUILDER_INPUT"] = input
+ task.arguments = ["start"]
+
+ task.terminationHandler = {(t: Process) in
+ if t.terminationStatus == 0 {
+ log("release exited with: \(t.terminationStatus)")
+ } else {
+ runAlert(messageText: "\(appName) exited with error status \(t.terminationStatus).")
+ }
+
+ NSApp.terminate(nil)
+ }
+
+ try! task.run()
+ log("release pid: \(task.processIdentifier)")
+
+ DispatchQueue.global(qos: .userInteractive).async {
+ task.waitUntilExit()
+ }
+
+ return task
+}
+
+func rpc(_ event: String) {
+ let input = Pipe()
+ let task = buildReleaseTask()
+ task.standardInput = input
+ input.fileHandleForWriting.write("\(event)\n".data(using: .utf8)!)
+ task.arguments = ["rpc", "AppBuilder.__rpc__(<%= event_handler %>)"]
+ try! task.run()
+ task.waitUntilExit()
+
+ if task.terminationStatus != 0 {
+ runAlert(messageText: "Something went wrong")
+ }
+}
+
+func buildReleaseTask() -> Process {
+ let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/<%= @release.name %>", ofType: "")!
+ let resourcePath = Bundle.main.resourcePath ?? ""
+ let additionalPaths = "<%= additional_paths %>"
+
+ var environment = ProcessInfo.processInfo.environment
+ let path = environment["PATH"] ?? ""
+ environment["PATH"] = "\(additionalPaths):\(path)"
+
+ let task = Process()
+ task.environment = environment
+ task.launchPath = releaseScriptPath
+ task.standardOutput = logFile
+ task.standardError = logFile
+ return task
+}
+
+func runAlert(messageText: String) {
+ DispatchQueue.main.async {
+ let alert = NSAlert()
+ alert.alertStyle = .critical
+ alert.messageText = messageText
+ alert.informativeText = "Logs available at: \(logPath)"
+ alert.runModal()
+ }
+}
+
func log(_ line: String) {
- logFile.write("\(line)\n".data(using: .utf8)!)
+ logFile.write("[\(appName)Launcher] \(line)\n".data(using: .utf8)!)
}
let fm = FileManager.default
@@ -22,33 +126,7 @@ if !fm.fileExists(atPath: logPath) { fm.createFile(atPath: logPath, contents: Da
let logFile = FileHandle(forUpdatingAtPath: logPath)!
logFile.seekToEndOfFile()
-let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/<%= @release.name %>", ofType: "")!
-
-let resourcePath = Bundle.main.resourcePath ?? ""
-let additionalPaths = "<%= additional_paths %>"
-
-var environment = ProcessInfo.processInfo.environment
-let path = environment["PATH"] ?? ""
-environment["PATH"] = "\(additionalPaths):\(path)"
-
-let task = Process()
-task.environment = environment
-task.launchPath = releaseScriptPath
-task.arguments = ["start"]
-task.standardOutput = logFile
-task.standardError = logFile
-
-log("[\(appName)Launcher] starting release")
-try task.run()
-log("[\(appName)Launcher] pid: \(task.processIdentifier)")
-
-task.waitUntilExit()
-log("[\(appName)Launcher] release exited with \(task.terminationStatus)")
-
-if task.terminationStatus != 0 {
- let alert = NSAlert()
- alert.alertStyle = .critical
- alert.messageText = "\(appName) exited with error status \(task.terminationStatus)."
- alert.informativeText = "Logs available at: \(logPath)"
- alert.runModal()
-}
+let app = NSApplication.shared
+let delegate = AppDelegate()
+app.delegate = delegate
+app.run()
diff --git a/app_builder/lib/templates/windows/Launcher.vbs.eex b/app_builder/lib/templates/windows/Launcher.vbs.eex
index 0fc87bd6d..a11bb0def 100644
--- a/app_builder/lib/templates/windows/Launcher.vbs.eex
+++ b/app_builder/lib/templates/windows/Launcher.vbs.eex
@@ -1,48 +1,32 @@
-' This vbs script avoids a flashing cmd window when launching the release bat file
+<%
+
+event_handler = @app_options |> Keyword.fetch!(:event_handler) |> inspect()
+
+additional_paths =
+ ["rel/erts-#{@release.erts_version}/bin"] ++ @app_options[:additional_paths]
+ |> Enum.map(&("root & \"" <> String.replace(&1, "/", "\\") <> ";\" & "))
+
+%>
root = Left(Wscript.ScriptFullName, Len(Wscript.ScriptFullName) - Len(Wscript.ScriptName))
script = root & "rel\bin\<%= @release.name %>.bat"
-Set shell = CreateObject("WScript.Shell")
-
-' Below we run two commands:
-'
-' 1. bin/release rpc
-' 2. bin/release start
-'
-' The first one will only succeed when the app is already running. The second one when it is not.
-' It's ok for either to fail because we run them asynchronously.
-
-Set env = shell.Environment("Process")
-env("PATH") = "<%= Enum.map_join(@app_options[:additional_paths], ";", &String.replace(&1, "/", "\\")) %>" & env("PATH")
-
If WScript.Arguments.Count > 0 Then
input = WScript.Arguments(0)
Else
- input = "reopen_app"
+ input = "open_app"
End If
-' Below, we're basically doing:
-'
-' $ bin/release rpc 'AppBuilder.Windows.__send_events__(MyApp, input)'
-'
-' We send the input through IO, as opposed using the rpc expression, to avoid RCE.
-cmd = "echo " & input & " | """ & script & """ rpc ""AppBuilder.Windows.__send_events__(<%= inspect(@app_options[:server]) %>, String.trim(IO.read(:line)))"""
-code = shell.Run("cmd /c " & cmd, 0)
+Set shell = CreateObject("WScript.Shell")
+Set env = shell.Environment("Process")
+env("PATH") = <%= additional_paths %>env("PATH")
-' Below, we're basically doing:
-'
-' $ bin/release start
-'
-' We send the input through the environment variable as we can't easily access argv
-' when booting through the release script.
-
-If WScript.Arguments.Count > 0 Then
- env("APP_BUILDER_INPUT") = WScript.Arguments(0)
-Else
- env("APP_BUILDER_INPUT") = "new_file"
-End If
+' try release rpc, if release is down, this will fail but that's ok.
+cmd = "echo " & input & " | """ & script & """ rpc ""AppBuilder.__rpc__(<%= event_handler %>)"""
+status = shell.Run("cmd /c " & cmd, 0, true)
+' try release start, if release is up, this will fail but that's ok.
+env("APP_BUILDER_INPUT") = input
cmd = """" & script & """ start"
-code = shell.Run("cmd /c " & cmd & " >> " & root & "\Logs\<%= @app_options[:name] %>.log 2>&1", 0)
+status = shell.Run("cmd /c " & cmd, 0)
diff --git a/lib/livebook_app.ex b/lib/livebook_app.ex
index f5553b081..f65da6c35 100644
--- a/lib/livebook_app.ex
+++ b/lib/livebook_app.ex
@@ -1,12 +1,105 @@
if Mix.target() == :app do
+ defmodule LivebookApp do
+ @moduledoc false
+ @name __MODULE__
+ @wxID_OPEN 5000
+ @wxID_EXIT 5006
+ @wxBITMAP_TYPE_PNG 15
+
+ use GenServer
+
+ def start_link(arg) do
+ GenServer.start_link(__MODULE__, arg, name: @name)
+ end
+
+ taskbar_icon_path = "rel/app/icon.png"
+ @external_resource taskbar_icon_path
+ @taskbar_icon File.read!(taskbar_icon_path)
+
+ @impl true
+ def init(_) do
+ AppBuilder.init()
+ os = AppBuilder.os()
+ :wx.new()
+
+ # TODO: instead of embedding the icon and copying to tmp, copy the file known location.
+ # It's a bit tricky because it needs to support all the ways of running the app:
+ # 1. MIX_TARGET=app mix phx.server
+ # 2. mix app
+ # 3. mix release app
+ taskbar_icon_path = Path.join(System.tmp_dir!(), "icon.png")
+ File.write!(taskbar_icon_path, @taskbar_icon)
+ icon = :wxIcon.new(taskbar_icon_path, type: @wxBITMAP_TYPE_PNG)
+
+ menu_items = [
+ {"Open Browser", key: "ctrl+o", id: @wxID_OPEN},
+ {"Quit", key: "ctrl+q", id: @wxID_EXIT}
+ ]
+
+ taskbar = WxUtils.taskbar("Livebook", icon, menu_items)
+
+ if os == :windows do
+ :wxTaskBarIcon.connect(taskbar, :taskbar_left_down,
+ callback: fn _, _ ->
+ open_browser()
+ end
+ )
+ end
+
+ {:ok, nil}
+ end
+
+ @impl true
+ def handle_info(:open_app, state) do
+ open_browser()
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:open_file, path}, state) do
+ path
+ |> Livebook.Utils.notebook_open_url()
+ |> open_browser()
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:open_url, "livebook://" <> rest}, state) do
+ "https://#{rest}"
+ |> Livebook.Utils.notebook_import_url()
+ |> open_browser()
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:wx, @wxID_EXIT, _, _, _}, _state) do
+ System.stop(0)
+ end
+
+ @impl true
+ def handle_info({:wx, @wxID_OPEN, _, _, _}, state) do
+ open_browser()
+ {:noreply, state}
+ end
+
+ if Mix.env() == :dev do
+ @impl true
+ def handle_info(event, state) do
+ IO.inspect(event)
+ {:noreply, state}
+ end
+ end
+
+ defp open_browser(url \\ LivebookWeb.Endpoint.access_url()) do
+ Livebook.Utils.browser_open(url)
+ end
+ end
+
defmodule WxUtils do
@moduledoc false
-
- defmacro wxID_ANY, do: -1
- defmacro wxID_OPEN, do: 5000
- defmacro wxID_EXIT, do: 5006
- defmacro wxID_OSX_HIDE, do: 5250
- defmacro wxBITMAP_TYPE_PNG, do: 15
+ @wxID_ANY -1
def taskbar(title, icon, menu_items) do
pid = self()
@@ -66,7 +159,7 @@ if Mix.target() == :app do
Enum.each(items, fn
{title, options} ->
- id = Keyword.get(options, :id, wxID_ANY())
+ id = Keyword.get(options, :id, @wxID_ANY)
title =
case Keyword.fetch(options, :key) do
@@ -82,149 +175,5 @@ if Mix.target() == :app do
menu
end
-
- def menubar(app_name, menus) do
- menubar = :wxMenuBar.new()
-
- if AppBuilder.os() == :macos, do: fixup_macos_menubar(menubar, app_name)
-
- for {title, menu_items} <- menus do
- true = :wxMenuBar.append(menubar, menu(menu_items), title)
- end
-
- menubar
- end
-
- defp fixup_macos_menubar(menubar, app_name) do
- menu = :wxMenuBar.oSXGetAppleMenu(menubar)
-
- menu
- |> :wxMenu.findItem(wxID_OSX_HIDE())
- |> :wxMenuItem.setItemLabel("Hide #{app_name}\tCtrl+H")
-
- menu
- |> :wxMenu.findItem(wxID_EXIT())
- |> :wxMenuItem.setItemLabel("Quit #{app_name}\tCtrl+Q")
- end
- end
-
- defmodule LivebookApp do
- @moduledoc false
- @name __MODULE__
-
- use GenServer
- import WxUtils
-
- def start_link(arg) do
- GenServer.start_link(__MODULE__, arg, name: @name)
- end
-
- taskbar_icon_path = "rel/app/icon.png"
- @external_resource taskbar_icon_path
- @taskbar_icon File.read!(taskbar_icon_path)
-
- @impl true
- def init(_) do
- os = AppBuilder.os()
- wx = :wx.new()
- AppBuilder.Wx.subscribe_to_app_events(@name)
-
- menu_items = [
- {"Open Browser", key: "ctrl+o", id: wxID_OPEN()},
- {"Quit", key: "ctrl+q", id: wxID_EXIT()}
- ]
-
- if os == :macos do
- :wxMenuBar.setAutoWindowMenu(false)
-
- menubar =
- menubar("Livebook", [
- {"File", menu_items}
- ])
-
- :ok = :wxMenuBar.connect(menubar, :command_menu_selected, skip: true)
-
- # TODO: use :wxMenuBar.macSetCommonMenuBar/1 when OTP 25 is out
- frame = :wxFrame.new(wx, -1, "", size: {0, 0})
- :wxFrame.show(frame)
- :wxFrame.setMenuBar(frame, menubar)
- end
-
- # TODO: instead of embedding the icon and copying to tmp, copy the file known location.
- # It's a bit tricky because it needs to support all the ways of running the app:
- # 1. MIX_TARGET=app mix phx.server
- # 2. mix app
- # 3. mix release app
- taskbar_icon_path = Path.join(System.tmp_dir!(), "icon.png")
- File.write!(taskbar_icon_path, @taskbar_icon)
- icon = :wxIcon.new(taskbar_icon_path, type: wxBITMAP_TYPE_PNG())
-
- taskbar = taskbar("Livebook", icon, menu_items)
-
- if os == :windows do
- :wxTaskBarIcon.connect(taskbar, :taskbar_left_down,
- callback: fn _, _ ->
- open_browser()
- end
- )
- end
-
- {:ok, nil}
- end
-
- @impl true
- def handle_info({:wx, wxID_EXIT(), _, _, _}, _state) do
- System.stop(0)
- end
-
- @impl true
- def handle_info({:wx, wxID_OPEN(), _, _, _}, state) do
- open_browser()
- {:noreply, state}
- end
-
- # This event is triggered when the application is opened for the first time
- @impl true
- def handle_info({:new_file, ''}, state) do
- open_browser()
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:reopen_app, _}, state) do
- open_browser()
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:open_file, path}, state) do
- path
- |> List.to_string()
- |> Livebook.Utils.notebook_open_url()
- |> open_browser()
-
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:open_url, 'livebook://' ++ rest}, state) do
- "https://#{rest}"
- |> Livebook.Utils.notebook_import_url()
- |> open_browser()
-
- {:noreply, state}
- end
-
- if Mix.env() == :dev do
- @impl true
- def handle_info(event, state) do
- IO.inspect(event)
- {:noreply, state}
- end
- end
-
- defp open_browser(url \\ LivebookWeb.Endpoint.access_url()) do
- Livebook.Utils.browser_open(url)
- end
end
end
diff --git a/mix.exs b/mix.exs
index 792de8615..474958596 100644
--- a/mix.exs
+++ b/mix.exs
@@ -160,8 +160,8 @@ defmodule Livebook.MixProject do
]
]
],
+ event_handler: LivebookApp,
additional_paths: [
- "rel/erts-#{:erlang.system_info(:version)}/bin",
"rel/vendor/elixir/bin"
],
macos: [
@@ -171,7 +171,6 @@ defmodule Livebook.MixProject do
notarization: macos_notarization
],
windows: [
- server: LivebookApp,
icon_path: "rel/app/icon.ico",
build_installer: true
]
diff --git a/rel/app/env.bat.eex b/rel/app/env.bat.eex
index 0e63d90c1..420220840 100644
--- a/rel/app/env.bat.eex
+++ b/rel/app/env.bat.eex
@@ -1,3 +1,4 @@
+set RELEASE_NODE=livebook
set RELEASE_MODE=interactive
set LIVEBOOK_SHUTDOWN_ENABLED=true
diff --git a/rel/app/env.sh.eex b/rel/app/env.sh.eex
index f75c1188c..c199a48d0 100644
--- a/rel/app/env.sh.eex
+++ b/rel/app/env.sh.eex
@@ -1,5 +1,7 @@
+export RELEASE_NODE=livebook
export RELEASE_MODE=interactive
export LIVEBOOK_SHUTDOWN_ENABLED=true
+export WX_MACOS_NON_GUI_APP=1
cookie_path="${RELEASE_ROOT}/releases/COOKIE"
if [ ! -f $cookie_path ]; then