Packages
fnord
0.9.5
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/notifier.ex
defmodule Notifier do
@moduledoc """
A simple notification module that works on MacOS and Linux.
On macOS it uses AppleScript (`osascript`); on Linux it uses `notify-send` or
`dunstify`.
If neither is available, it falls back to printing a bell to STDERR.
"""
@type platform :: :mac | :linux | :other
@spec notify(String.t(), String.t(), keyword) :: :ok | {:error, term}
def notify(title, body, opts \\ []) do
case platform(opts) do
:mac -> mac_notify(title, body, opts)
:linux -> linux_notify(title, body, opts)
:other -> fallback_beep(title, body, opts)
end
end
@doc """
Attempts to dismiss/clear notifications for the given group.
Only works on systems that support notification dismissal.
"""
@spec dismiss(String.t(), keyword) :: :ok | {:error, term}
def dismiss(group \\ "fnord", opts \\ []) do
case platform(opts) do
:mac -> mac_dismiss(group)
:linux -> linux_dismiss(group)
:other -> :ok
end
end
# Decide platform (injectable for tests)
@spec platform(keyword) :: platform
defp platform(opts) do
case Keyword.get(opts, :platform) do
:mac ->
:mac
:linux ->
:linux
:other ->
:other
nil ->
case :os.type() do
{:unix, :darwin} -> :mac
{:unix, :linux} -> :linux
_ -> :other
end
end
end
# ----------------------------------------------------------------------------
# MacOS (always AppleScript)
# ----------------------------------------------------------------------------
defp mac_notify(title, body, opts) do
subtitle = Keyword.get(opts, :subtitle)
title = applestr(title)
body = applestr(body)
subtitle_script =
if subtitle do
"subtitle " <> applestr(subtitle)
else
""
end
run("osascript", [
"-e",
"display notification #{body} with title #{title} #{subtitle_script}"
])
end
# AppleScript notifications can't be programmatically dismissed.
defp mac_dismiss(_group), do: :ok
defp applestr(s), do: ~s(") <> String.replace(s, ~s("), ~s(\\")) <> ~s(")
# ----------------------------------------------------------------------------
# Linux
# ----------------------------------------------------------------------------
defp linux_notify(title, body, opts) do
if gui_env?() do
urgency = Keyword.get(opts, :urgency, "critical")
timeout_ms = Keyword.get(opts, :timeout_ms, 10000)
cond do
exe?("notify-send") ->
run("notify-send", ["--urgency=#{urgency}", "--expire-time=#{timeout_ms}", title, body])
exe?("dunstify") ->
run("dunstify", ["-u", urgency, "-t", Integer.to_string(timeout_ms), title, body])
true ->
fallback_beep(title, body, opts)
end
else
fallback_beep(title, body, opts)
end
end
defp gui_env?() do
(System.get_env("WAYLAND_DISPLAY") || "") != "" ||
(System.get_env("DISPLAY") || "") != ""
end
defp linux_dismiss(_group), do: :ok
# ----------------------------------------------------------------------------
# Fallback
# ----------------------------------------------------------------------------
defp fallback_beep(_title, _body, _opts) do
IO.puts(:stderr, "\a")
:ok
end
# ----------------------------------------------------------------------------
# Util
# ----------------------------------------------------------------------------
defp exe?(name), do: System.find_executable(name) != nil
defp run(cmd, args) do
case System.cmd(cmd, args, stderr_to_stdout: true) do
{_out, 0} -> :ok
{out, code} -> {:error, {cmd, code, out}}
end
end
end