defmodule Desktop.Wx.TaskBarIcon do @moduledoc """ TaskBarIcon module for Desktop, based on :wxTaskBarIcon """ require Logger alias Desktop.OS defstruct fn_create_popup: nil, skip_popup_menu?: true, wx_taskbar_icon: nil @type wx_menu() :: any() @type wx_taskbar_icon() :: any() @type create_popup() :: (() -> wx_menu()) @type t() :: %__MODULE__{ fn_create_popup: nil | create_popup(), skip_popup_menu?: boolean(), wx_taskbar_icon: wx_taskbar_icon() } @spec create(fn_create_popup :: create_popup()) :: {:ok, t()} | {:error, any()} def create(fn_create_popup) when is_function(fn_create_popup) do taskbar_icon = %__MODULE__{ fn_create_popup: fn_create_popup, skip_popup_menu?: true } with {:ok, taskbar_icon = %{wx_taskbar_icon: wx_taskbar_icon}} <- create(taskbar_icon, OS.type()) do OnCrash.call(fn -> :wx.set_env(Desktop.Env.wx_env()) :wxTaskBarIcon.removeIcon(wx_taskbar_icon) :wxTaskBarIcon.destroy(wx_taskbar_icon) end) {:ok, taskbar_icon} end end @spec create(t(), os_type :: atom()) :: {:ok, t()} | {:error, any()} def create(taskbar_icon = %__MODULE__{fn_create_popup: fn_create_popup}, MacOS) when is_function(fn_create_popup) do with {:ok, wx_taskbar_icon} <- create_taskbar_icon_osx(fn_create_popup) do {:ok, %{ taskbar_icon | wx_taskbar_icon: wx_taskbar_icon, # On MacOS we always skip using popup_menu() method skip_popup_menu?: true }} end end def create(taskbar_icon = %__MODULE__{fn_create_popup: fn_create_popup}, _) do case create_taskbar_icon(createPopupMenu: fn_create_popup) do {:ok, wx_taskbar_icon} -> {:ok, %{taskbar_icon | wx_taskbar_icon: wx_taskbar_icon}} _ -> # Failed to create with :wxTaskBarIcon.new/1 # Fallback to :wxTaskBarIcon.new/0 case create_taskbar_icon_fallback() do {:ok, wx_taskbar_icon} -> # When using the fallback, enable popup_menu() # by setting skip_popup_menu? to false {:ok, %{taskbar_icon | wx_taskbar_icon: wx_taskbar_icon, skip_popup_menu?: false}} error -> error end end end @spec popup_menu(t()) :: boolean() @spec popup_menu(t(), atom()) :: boolean() def popup_menu(taskbar_icon, event \\ :taskbar_left_down) def popup_menu( %__MODULE__{ wx_taskbar_icon: wx_taskbar_icon, fn_create_popup: fn_create_popup, skip_popup_menu?: true }, :taskbar_left_down ) when is_function(fn_create_popup) do menu = fn_create_popup.() :wxTaskBarIcon.popupMenu(wx_taskbar_icon, menu) end def popup_menu( %__MODULE__{ wx_taskbar_icon: wx_taskbar_icon, fn_create_popup: fn_create_popup, skip_popup_menu?: false }, _ ) when is_function(fn_create_popup) do menu = fn_create_popup.() :wxTaskBarIcon.popupMenu(wx_taskbar_icon, menu) end def popup_menu( %__MODULE__{ fn_create_popup: fn_create_popup }, _ ) when is_function(fn_create_popup) do true end def popup_menu(_, _) do false end def set_icon(%__MODULE__{wx_taskbar_icon: wx_taskbar_icon}, icon) do :wxTaskBarIcon.setIcon(wx_taskbar_icon, icon) end def set_icon(%__MODULE__{wx_taskbar_icon: wx_taskbar_icon}, icon, options) do :wxTaskBarIcon.setIcon(wx_taskbar_icon, icon, options) end def remove_icon(%__MODULE__{wx_taskbar_icon: wx_taskbar_icon}) do :wxTaskBarIcon.removeIcon(wx_taskbar_icon) end def connect(%__MODULE__{wx_taskbar_icon: wx_taskbar_icon}) do :wxTaskBarIcon.connect(wx_taskbar_icon, :taskbar_left_down, skip: true) :wxTaskBarIcon.connect(wx_taskbar_icon, :taskbar_right_down, skip: true) end defp create_taskbar_icon_osx(fn_create_popup) do if supports_method?(:new, 1) do # Supports :wxTaskBarIcon.new(opts) if is_module?(:wxWebView) do # Proper OTP24 release create_taskbar_icon(createPopupMenu: fn_create_popup) else # Pre-OTP24 custom version for backwards compat. try do :wxTaskBarIcon.new(fn_create_popup) catch :error, :function_clause -> Logger.error("No MacOS compatible :wxTaskBarIcon found! Please use at least OTP24") create_taskbar_icon_fallback() else wx_taskbar_icon -> {:ok, wx_taskbar_icon} end end else # Does not support constructor with 1 argument create_taskbar_icon_fallback() end end defp create_taskbar_icon(opts) do if supports_method?(:new, 1) do try do :wxTaskBarIcon.new(opts) rescue e -> {:error, e} else wx_taskbar_icon -> {:ok, wx_taskbar_icon} end else {:error, ":wxTaskBarIcon.new/1 not available"} end end defp create_taskbar_icon_fallback() do if supports_method?(:new, 0) do try do :wxTaskBarIcon.new() rescue e -> {:error, e} else wx_taskbar_icon -> {:ok, wx_taskbar_icon} end else {:error, ":wxTaskBarIcon.new/0 not available"} end end defp supports_method?(method, arity) do if is_module?(:wxTaskBarIcon) do Kernel.function_exported?(:wxTaskBarIcon, method, arity) else false end end defp is_module?(module) do Code.ensure_loaded?(module) end end