Packages
livebook
0.6.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_app.ex
if Mix.target() == :app do
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
def os do
case :os.type() do
{:unix, :darwin} -> :macos
{:win32, _} -> :windows
end
end
def taskbar(title, icon, menu_items) do
pid = self()
options = if os() == :macos, do: [iconType: 1], else: []
# skip keyboard shortcuts
menu_items =
for item <- menu_items do
{title, options} = item
options = Keyword.delete(options, :key)
{title, options}
end
taskbar =
:wxTaskBarIcon.new(
[
createPopupMenu: fn ->
menu = menu(menu_items)
# For some reason, on macOS the menu event must be handled in another process
# but on Windows it must be either the same process OR we use the callback.
case os() do
:macos ->
env = :wx.get_env()
Task.start_link(fn ->
:wx.set_env(env)
:wxMenu.connect(menu, :command_menu_selected)
receive do
message ->
send(pid, message)
end
end)
:windows ->
:ok =
:wxMenu.connect(menu, :command_menu_selected,
callback: fn wx, _ ->
send(pid, wx)
end
)
end
menu
end
] ++ options
)
:wxTaskBarIcon.setIcon(taskbar, icon, tooltip: title)
taskbar
end
def menu(items) do
menu = :wxMenu.new()
Enum.each(items, fn
{title, options} ->
id = Keyword.get(options, :id, wxID_ANY())
title =
case Keyword.fetch(options, :key) do
{:ok, key} ->
title <> "\t" <> key
:error ->
title
end
:wxMenu.append(menu, id, title)
end)
menu
end
def menubar(app_name, menus) do
menubar = :wxMenuBar.new()
if 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/taskbar_icon.png"
@external_resource taskbar_icon_path
@taskbar_icon File.read!(taskbar_icon_path)
@impl true
def init(_) do
os = 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!(), "taskbar_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