From e5092e4c992e184500c6b88028377a7458571687 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Thu, 21 Apr 2022 09:51:48 +0200 Subject: [PATCH] Add taskbar icon to desktop app (#1119) --- app_builder/lib/app_builder/macos.ex | 6 +- app_builder/lib/app_builder/windows.ex | 92 ++++++-- app_builder/lib/app_builder/wx.ex | 39 ++++ app_builder/mix.exs | 9 +- lib/livebook_app.ex | 285 +++++++++++++++---------- mix.exs | 9 +- rel/app/standalone.exs | 5 +- rel/app/taskbar_icon.png | Bin 0 -> 21528 bytes 8 files changed, 310 insertions(+), 135 deletions(-) create mode 100644 app_builder/lib/app_builder/wx.ex create mode 100644 rel/app/taskbar_icon.png diff --git a/app_builder/lib/app_builder/macos.ex b/app_builder/lib/app_builder/macos.ex index 2407df910..2c4976887 100644 --- a/app_builder/lib/app_builder/macos.ex +++ b/app_builder/lib/app_builder/macos.ex @@ -111,7 +111,7 @@ defmodule AppBuilder.MacOS do File.mkdir_p!("tmp") launcher_src_path = "tmp/Launcher.swift" - File.write!(launcher_src_path, launcher(additional_paths)) + File.write!(launcher_src_path, launcher(release, additional_paths)) launcher_path = Path.join([app_bundle_path, "Contents", "MacOS", app_name <> "Launcher"]) File.mkdir_p!(Path.dirname(launcher_path)) @@ -133,7 +133,7 @@ defmodule AppBuilder.MacOS do release end - defp launcher(additional_paths) do + defp launcher(release, additional_paths) do additional_paths = Enum.map_join(additional_paths, ":", &"\\(resourcePath)#{&1}") """ @@ -149,7 +149,7 @@ defmodule AppBuilder.MacOS do let logFile = FileHandle(forUpdatingAtPath: logPath) logFile?.seekToEndOfFile() - let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/mac_app", ofType: "")! + let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/#{release.name}", ofType: "")! let resourcePath = Bundle.main.resourcePath ?? "" let additionalPaths = "#{additional_paths}" diff --git a/app_builder/lib/app_builder/windows.ex b/app_builder/lib/app_builder/windows.ex index 4a93195f7..29c5e79ba 100644 --- a/app_builder/lib/app_builder/windows.ex +++ b/app_builder/lib/app_builder/windows.ex @@ -4,6 +4,29 @@ defmodule AppBuilder.Windows do import AppBuilder.Utils require EEx + def __send_events__(server, input) + + def __send_events__(server, "new_file") do + send(server, {:new_file, ''}) + 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}) + end + @doc """ Creates a Windows installer. """ @@ -36,6 +59,9 @@ defmodule AppBuilder.Windows do erl_exe = Path.join([tmp_dir, "rel", "erts-#{release.erts_version}", "bin", "erl.exe"]) rcedit_path = ensure_rcedit() cmd!(rcedit_path, ["--set-icon", app_icon_path, erl_exe]) + manifest_path = Path.join(tmp_dir, "manifest.xml") + File.write!(manifest_path, manifest()) + cmd!(rcedit_path, ["--application-manifest", manifest_path, erl_exe]) File.write!(Path.join(tmp_dir, "#{app_name}.vbs"), launcher_vbs(release, options)) nsi_path = Path.join(tmp_dir, "#{app_name}.nsi") @@ -51,6 +77,21 @@ defmodule AppBuilder.Windows do release end + # https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process + defp manifest do + """ + + + + + true + PerMonitorV2 + + + + """ + end + code = """ <% app_name = Keyword.fetch!(options, :name) @@ -112,7 +153,7 @@ defmodule AppBuilder.Windows do <% end %> WriteRegStr HKCR "<%= app_name %>.<%= type.name %>" "" "<%= type.name %>" WriteRegStr HKCR "<%= app_name %>.<%= type.name %>\\DefaultIcon" "" "$INSTDIR\\app_icon.ico" - WriteRegStr HKCR "<%= app_name %>.<%= type.name %>\\shell\\open\\command" "" '$WINDIR\\system32\\wscript.exe "$INSTDIR\\<%= app_name %>.vbs" "%1"' + WriteRegStr HKCR "<%= app_name %>.<%= type.name %>\\shell\\open\\command" "" '$WINDIR\\system32\\wscript.exe "$INSTDIR\\<%= app_name %>.vbs" "open_file:%1"' <% end %> <%= for url_scheme <- url_schemes do %> @@ -122,7 +163,7 @@ defmodule AppBuilder.Windows do WriteRegStr HKCR "<%= url_scheme %>" "URL Protocol" "" WriteRegStr HKCR "<%= url_scheme %>\\shell" "" "" WriteRegStr HKCR "<%= url_scheme %>\\shell\\open" "" "" - WriteRegStr HKCR "<%= url_scheme %>\\shell\\open\\command" "" '$WINDIR\\system32\\wscript.exe "$INSTDIR\\<%= app_name %>.vbs" "%1"' + WriteRegStr HKCR "<%= url_scheme %>\\shell\\open\\command" "" '$WINDIR\\system32\\wscript.exe "$INSTDIR\\<%= app_name %>.vbs" "open_url:%1"' <% end %> SectionEnd @@ -143,16 +184,10 @@ defmodule AppBuilder.Windows do <% app_name = Keyword.fetch!(options, :name) module = Keyword.fetch!(options, :module) - %> - ' This vbs script avoids a flashing cmd window when launching the release bat file + %>' This vbs script avoids a flashing cmd window when launching the release bat file - path = Left(Wscript.ScriptFullName, Len(Wscript.ScriptFullName) - Len(Wscript.ScriptName)) & "rel\bin\<%= release.name %>.bat" - - If WScript.Arguments.Count > 0 Then - url = WScript.Arguments(0) - Else - url = "" - End If + root = Left(Wscript.ScriptFullName, Len(Wscript.ScriptFullName) - Len(Wscript.ScriptName)) + script = root & "rel\bin\<%= release.name %>.bat" Set shell = CreateObject("WScript.Shell") @@ -167,16 +202,35 @@ defmodule AppBuilder.Windows do Set env = shell.Environment("Process") env("PATH") = ".\rel\vendor\elixir\bin;.\rel\erts-<%= release.erts_version %>\bin;" & env("PATH") - ' > bin/release rpc "mod.windows_connected(url)" + If WScript.Arguments.Count > 0 Then + input = WScript.Arguments(0) + Else + input = "reopen_app" + End If + + ' Below, we're basically doing: ' - ' We send the URL through IO, as opposed through the rpc expression, to avoid RCE. - cmd = "echo """ & url & """ | """ & path & """ rpc <%= inspect(module) %>.windows_connected(IO.read(:line))" + ' $ 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(module) %>, String.trim(IO.read(:line)))\""" code = shell.Run("cmd /c " & cmd, 0) - ' > bin/release start - cmd = """" & path & """ start" - env("<%= String.upcase(app_name) <> "_URL" %>") = url - code = shell.Run("cmd /c " & cmd & " >> .\Logs\<%= app_name %>.log 2>&1", 0) + ' 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 + + cmd = \"""" & script & \""" start" + code = shell.Run("cmd /c " & cmd & " >> " & root & "\Logs\<%= app_name %>.log 2>&1", 0) """ EEx.function_from_string(:defp, :launcher_vbs, code, [:release, :options], trim: true) @@ -203,7 +257,7 @@ defmodule AppBuilder.Windows do url = "https://download.imagemagick.org/ImageMagick/download/binaries/ImageMagick-7.1.0-portable-Q16-x64.zip" - sha256 = "d7b82e95d2860042c241d9913e14832cf1491f39c4da91286bace39582916dc8" + sha256 = "b61a726cea1e3bf395b9aeb323fca062f574fbf8f11f4067f88a0e6b984a1391" AppBuilder.Utils.ensure_executable(url, sha256, "magick.exe") end diff --git a/app_builder/lib/app_builder/wx.ex b/app_builder/lib/app_builder/wx.ex new file mode 100644 index 000000000..32c451c53 --- /dev/null +++ b/app_builder/lib/app_builder/wx.ex @@ -0,0 +1,39 @@ +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/mix.exs b/app_builder/mix.exs index 3ac21eefa..4e6ab82b9 100644 --- a/app_builder/mix.exs +++ b/app_builder/mix.exs @@ -7,7 +7,14 @@ defmodule AppBuilder.MixProject do version: "0.1.0", elixir: "~> 1.13", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + + # Suppress warnings + xref: [ + exclude: [ + :wx + ] + ] ] end diff --git a/lib/livebook_app.ex b/lib/livebook_app.ex index 707b9e472..70dc84f44 100644 --- a/lib/livebook_app.ex +++ b/lib/livebook_app.ex @@ -1,96 +1,204 @@ if Mix.target() == :app do - defmodule LivebookApp do + defmodule WxUtils do @moduledoc false - @behaviour :wx_object + 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 - # 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(__MODULE__, [], []) - {:ok, pid} + def os do + case :os.type() do + {:unix, :darwin} -> :macos + {:win32, _} -> :windows + end end - def child_spec(init_arg) do - %{ - id: __MODULE__, - start: {__MODULE__, :start_link, [init_arg]}, - restart: :transient - } + 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 windows_connected(url) do - url - |> String.trim() - |> String.trim_leading("\"") - |> String.trim_trailing("\"") - |> windows_to_wx() + 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 - app_name = "Livebook" - - true = Process.register(self(), __MODULE__) os = os() - - # TODO: on all platforms either add a basic window with some buttons OR a wxwebview - size = if os == :macos, do: {0, 0}, else: {100, 100} - wx = :wx.new() - frame = :wxFrame.new(wx, -1, app_name, size: size) - :wxFrame.show(frame) + 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 - fixup_macos_menubar(frame, app_name) + :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 - :wxFrame.connect(frame, :command_menu_selected, skip: true) - :wxFrame.connect(frame, :close_window, skip: true) + # 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()) - case os do - :macos -> - :wx.subscribe_events() + taskbar = taskbar("Livebook", icon, menu_items) - :windows -> - windows_to_wx(System.get_env("LIVEBOOK_URL") || "") + if os == :windows do + :wxTaskBarIcon.connect(taskbar, :taskbar_left_down, + callback: fn _, _ -> + open_browser() + end + ) end - state = %{frame: frame} - {frame, state} + {:ok, nil} end @impl true - def handle_event({:wx, @wx_id_exit, _, _, _}, state) do - :init.stop() - {:stop, :shutdown, state} + def handle_info({:wx, wxID_EXIT(), _, _, _}, _state) do + System.stop(0) end @impl true - def handle_event({:wx, _, _, _, {:wxClose, :close_window}}, state) do - :init.stop() - {:stop, :shutdown, state} + 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 - Livebook.Utils.browser_open(LivebookWeb.Endpoint.access_url()) + open_browser() {:noreply, state} end - # TODO: investigate "Universal Links" [1], that is, instead of livebook://foo, we have - # https://livebook.dev/foo, which means the link works with and without Livebook.app. - # - # [1] https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content @impl true - def handle_info({:open_url, 'livebook://' ++ rest}, state) do - "https://#{rest}" - |> Livebook.Utils.notebook_import_url() - |> Livebook.Utils.browser_open() - + def handle_info({:reopen_app, _}, state) do + open_browser() {:noreply, state} end @@ -99,69 +207,30 @@ if Mix.target() == :app do path |> List.to_string() |> Livebook.Utils.notebook_open_url() - |> Livebook.Utils.browser_open() + |> open_browser() {:noreply, state} end @impl true - def handle_info({:reopen_app, _}, state) do - Livebook.Utils.browser_open(LivebookWeb.Endpoint.access_url()) + def handle_info({:open_url, 'livebook://' ++ rest}, state) do + "https://#{rest}" + |> Livebook.Utils.notebook_import_url() + |> open_browser() + {:noreply, state} end - # ignore other events - @impl true - def handle_info(_event, state) do - {:noreply, state} - end - - # 1. WxeApp attaches event handler to "Quit" menu item that does nothing (to not accidentally bring - # down the VM). Let's create a fresh menu bar without that caveat. - # 2. Fix app name - defp fixup_macos_menubar(frame, app_name) do - menubar = :wxMenuBar.new() - :wxFrame.setMenuBar(frame, menubar) - - menu = :wxMenuBar.oSXGetAppleMenu(menubar) - - # without this, for some reason setting the title later will make it non-bold - :wxMenu.getTitle(menu) - - # this is useful in dev, not needed when bundled in .app - :wxMenu.setTitle(menu, app_name) - - menu - |> :wxMenu.findItem(@wx_id_osx_hide) - |> :wxMenuItem.setItemLabel("Hide #{app_name}\tCtrl+H") - - menu - |> :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 + if Mix.env() == :dev do + @impl true + def handle_info(event, state) do + IO.inspect(event) + {:noreply, state} end end - defp windows_to_wx("") do - send(__MODULE__, {:new_file, ''}) - end - - defp windows_to_wx("livebook://" <> _ = url) do - send(__MODULE__, {:open_url, String.to_charlist(url)}) - end - - defp windows_to_wx(path) do - path = - path - |> String.replace("\\", "/") - |> String.to_charlist() - - send(__MODULE__, {:open_file, path}) + 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 c15986a43..972c5f880 100644 --- a/mix.exs +++ b/mix.exs @@ -5,8 +5,8 @@ defmodule Livebook.MixProject do @version "0.5.2" @description "Interactive and collaborative code notebooks - made with Phoenix LiveView" - @app_elixir_version "1.13.2" - @app_otp_version "24.2" + @app_elixir_version "1.13.4" + @app_otp_version "24.3" def project do [ @@ -176,7 +176,10 @@ defmodule Livebook.MixProject do name: "Livebook", version: @version, logo_path: "rel/app/mac-icon.png", - additional_paths: ["/rel/vendor/bin", "/rel/vendor/elixir/bin"], + additional_paths: [ + "/rel/erts-#{:erlang.system_info(:version)}/bin", + "/rel/vendor/elixir/bin" + ], url_schemes: ["livebook"], document_types: [ %{ diff --git a/rel/app/standalone.exs b/rel/app/standalone.exs index e164dc46d..8c1bb26d2 100644 --- a/rel/app/standalone.exs +++ b/rel/app/standalone.exs @@ -7,7 +7,10 @@ defmodule Standalone do """ @spec copy_otp(Mix.Release.t(), otp_version :: String.t()) :: Mix.Release.t() def copy_otp(release, otp_version) do - ensure_otp_version(otp_version) + if Mix.env() != :dev do + ensure_otp_version(otp_version) + end + {erts_source, otp_bin_dir, otp_lib_dir} = otp_dirs() # 1. copy erts/bin diff --git a/rel/app/taskbar_icon.png b/rel/app/taskbar_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..321b214d8609b0aea0bcfa6f35a5440bee81404c GIT binary patch literal 21528 zcmZ^L1zc3$)+h{v4Ba6iLw6%3F*MTMNOyN54bmteC7=@0-Q6i5-O>%xNcs-`@BQw5 z-|szs19Q&aYwwkN?X}KXn|I2J( }C~$Ca=&xlYRN>&@F9G1v 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