lib/desktop/os.ex

defmodule Desktop.OS do
  @moduledoc """
    The OS module provides shortcuts and helper functions
    to access OS specific information.

    Most significant one should use OS.type() to differentiate
    between the currently supported environments:
    - Android
    - IOS
    - MacOS
    - Windows
    - Linux

  """

  @doc """
    Returns the users home directory
  """
  @spec home :: binary
  def home() do
    {:ok, [[home]]} = :init.get_argument(:home)
    List.to_string(home)
  end

  @doc false
  def raise_frame(frame) do
    if type() == MacOS do
      name = System.get_env("EMU", "beam.smp")

      fn -> System.cmd("open", ["-a", name], stderr_to_stdout: true) end
      |> spawn_link()
    else
      # Calling  this on wxDirDialog segfaults on macos..
      :wxTopLevelWindow.setFocus(frame)
      :wxWindow.raise(frame)
    end
  end

  @spec type :: Linux | MacOS | Windows | Android | IOS
  def type() do
    case System.get_env("ELIXIR_DESKTOP_OS", nil) do
      "android" ->
        Android

      "ios" ->
        IOS

      _ ->
        case :os.type() do
          {:unix, :darwin} -> MacOS
          {:unix, :linux} -> Linux
          {:win32, _} -> Windows
        end
    end
  end

  def shutdown() do
    spawn(fn ->
      Process.sleep(300)

      if windows?() do
        System.stop(0)
      else
        kill_heart()
        System.halt(0)
      end
    end)
  end

  def restart() do
    if windows?() or mobile?() do
      # on windows reinitializing wx-widgets does work sometimes...
      :init.restart()
    else
      # relying on heart
      kill_beam()
    end
  end

  def windows?() do
    type() == Windows
  end

  def mobile?() do
    case type() do
      Android -> true
      IOS -> true
      _other -> false
    end
  end

  defp kill_heart() do
    heart = Process.whereis(:heart)

    if heart != nil do
      {:links, links} = Process.info(Process.whereis(:heart), :links)
      port = Enum.find(links, fn id -> is_port(id) end)

      if port != nil do
        {:os_pid, heart_pid} = Port.info(port, :os_pid)
        System.cmd("kill", ["-9", "#{heart_pid}"], stderr_to_stdout: true)
        # kill thyself
        kill_beam()
      end
    end
  end

  defp kill_beam() do
    System.cmd("kill", ["-9", "#{:os.getpid()}"], stderr_to_stdout: true)
  end

  @doc """
  This is a Path.expand variant that normalizes the drive letter
  on windows
  """
  @spec path_expand(binary) :: binary
  def path_expand(path) do
    if windows?() do
      path = Path.expand(path)
      drv = String.first(path)
      String.replace_prefix(path, drv <> ":", String.upcase(drv) <> ":")
    else
      Path.expand(path)
    end
  end
end