defmodule WebengineKiosk do use Supervisor def child_spec({opts, genserver_opts}) do id = genserver_opts[:id] || __MODULE__ %{ id: id, start: {__MODULE__, :start_link, [opts, genserver_opts]} } end def child_spec(opts) do child_spec({opts, []}) end @moduledoc """ Control a fullscreen web browser using Elixir for use in a kiosk """ @doc """ Start the kiosk. The kiosk starts fullscreen and goes to a default local web page. To change this, set one or more options: * `background_color: color` - specify a background color as #RRGGBB or by name * `blank_image: path` - specify a path to an image for when the screen is blanked * `data_dir: path` - specify a writable path for data files * `debug_keys: boolean` - enable key combinations useful for debugging * `fullscreen: boolean` - show fullscreen * `gid: gid` - run the browser with this group id * `homepage: url` - load this page first. For local files, specify `file:///path/to/index.html` * `monitor: index` - select the monitor for the web browser (0, 1, etc.) * `opengl: "gl" | "gles" | "software" | "auto"` - specify the OpenGL backend. This is only a hint. * `progress: boolean` - show a progress bar when loading pages * `run_as_root: boolean` - set to true if you really want to run Chromium as root * `sounds: boolean` - play sounds on clicks * `uid: uid` - run the browser as this user Untested: * `clear_cache: boolean` * `proxy_enable: boolean` - enable/disable proxy support * `proxy_system: ` - * `proxy_host: hostname` - the host to connect to for using a proxy * `proxy_port: port` - the port to connect to on the proxy * `proxy_username: username` - a username for the proxy * `proxy_password: password` - a password for the proxy * `stay_on_top: boolean` - * `window_clicked_sound: url` - a sound to play when the window is clicked * `link_clicked_sound: url` - a sound to play when a link is clicked * `hide_cursor: boolean` - show or hide the mouse pointer * `javascript: boolean` - enable or disable Javascript support * `javascript_can_open_windows: boolean` - allow Javascript to open windows * `width: pixels` - when not fullscreen, the window is this width * `height: pixels` - when not fullscreen, the window is this height """ @spec start_link(Keyword.t(), GenServer.options()) :: {:ok, pid} | {:error, term} def start_link(args, genserver_opts \\ []) do Supervisor.start_link(__MODULE__, args, genserver_opts) end @doc """ Stop the kiosk """ @spec stop(Supervisor.supervisor()) :: :ok def stop(server) do Supervisor.stop(server) end @impl true def init(init_arg) do registry_name = "webengine_kiosk_registry_#{System.unique_integer([:positive])}" |> String.to_atom() children = [ Supervisor.child_spec({Registry, keys: :duplicate, name: registry_name}, id: :registry), Supervisor.child_spec({WebengineKiosk.Kiosk, %{args: init_arg, parent: self()}}, id: :kiosk) ] Supervisor.init(children, strategy: :one_for_one) end @doc """ Blank the screen The web browser will be replaced by a screen with the `blank_image`. If someone clicks or taps on the screen then a wakeup message will be sent. While the screen is in the blank state, it can still accept requests to go to other URLs. """ @spec blank(Supervisor.supervisor()) :: :ok def blank(server), do: call_kiosk(server, {:blank, true}) @doc """ Unblank the screen Show the web browser again. """ @spec unblank(Supervisor.supervisor()) :: :ok def unblank(server), do: call_kiosk(server, {:blank, false}) @doc """ Request that the browser go to the homepage. """ @spec go_home(Supervisor.supervisor()) :: :ok | {:error, term} def go_home(server), do: call_kiosk(server, :go_home) @doc """ Request that the browser go to the specified URL. """ @spec go_to_url(Supervisor.supervisor(), String.t()) :: :ok | {:error, term} def go_to_url(server, url), do: call_kiosk(server, {:go_to_url, url}) @doc """ Run Javascript in the browser. """ @spec run_javascript(Supervisor.supervisor(), String.t()) :: :ok | {:error, term} def run_javascript(server, code), do: call_kiosk(server, {:run_javascript, code}) @doc """ Reload the current page. """ @spec reload(Supervisor.supervisor()) :: :ok | {:error, term()} def reload(server), do: call_kiosk(server, :reload) @doc """ Go to the previously visited page. """ @spec back(Supervisor.supervisor()) :: :ok | {:error, term()} def back(server), do: call_kiosk(server, :back) @doc """ Go forward in history. """ @spec forward(Supervisor.supervisor()) :: :ok | {:error, term()} def forward(server), do: call_kiosk(server, :forward) @doc """ Stop loading the current page. """ @spec stop_loading(Supervisor.supervisor()) :: :ok | {:error, term()} def stop_loading(server), do: call_kiosk(server, :stop_loading) @doc """ Set the zoom factor for displaying the page. """ @spec set_zoom(Supervisor.supervisor(), number()) :: :ok | {:error, term()} def set_zoom(server, factor) when is_number(factor) and factor > 0 do call_kiosk(server, {:set_zoom, factor}) end @doc """ Register calling process to receive events """ @spec register(Supervisor.supervisor()) :: {:ok, pid} | {:error, term()} def register(server) do Registry.register(registry_name(server), "events", []) end @doc """ Unregister calling process from receiving events """ @spec unregister(Supervisor.supervisor()) :: :ok def unregister(server) do Registry.unregister(registry_name(server), "events") end @spec dispatch_event(Supervisor.supervisor(), any) :: :ok def dispatch_event(server, event) do Registry.dispatch(registry_name(server), "events", fn entries -> for {pid, _} <- entries, do: send(pid, event) end) end @spec registry_name(Supervisor.supervisor()) :: atom() defp registry_name(server) do {:ok, pid} = find_child(server, :registry) {:registered_name, reg_name} = Process.info(pid, :registered_name) reg_name end @spec call_kiosk(Supervisor.supervisor(), term) :: term defp call_kiosk(server, msg) do {:ok, pid} = find_child(server, :kiosk) GenServer.call(pid, msg) end @spec find_child(Supervisor.supervisor(), atom()) :: {:ok, pid} | :error defp find_child(supervisor, id) do ret = Supervisor.which_children(supervisor) |> Enum.find(fn {ch_id, _, _, _} -> ch_id == id end) if ret != nil do {_, pid, _, _} = ret {:ok, pid} else :error end end end