Skip to content

Commit eee0168

Browse files
fix(macos): associate wx notifications with main frame
- Call wxNotificationMessage:setParent/2 on macOS so Notification Center ties the toast to the app window (wx default main window can be wrong for BEAM). - Log when show/2 returns false with pointers to issue #38 and settings. - Add ELIXIR_DESKTOP_OS=macos for testable macOS branches; add OS.macos?/0. - Guard notification_new/show when NO_WX yields nil notification refs. Refs #38 Co-authored-by: Dominic Letz <dominicletz@users.noreply.github.com>
1 parent 1fbfde4 commit eee0168

5 files changed

Lines changed: 97 additions & 9 deletions

File tree

lib/desktop/fallback.ex

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ defmodule Desktop.Fallback do
22
require Logger
33
alias Desktop.{Wx, OS}
44

5+
@notification_show_failed """
6+
wxWidgets failed to show a desktop notification (wxNotificationMessage:show/2 returned false). \
7+
On macOS, ensure notifications are enabled for the app in System Settings, use a packaged .app \
8+
with matching bundle id / Info.plist for the Erlang VM, and see \
9+
https://github.com/elixir-desktop/desktop/issues/38 \
10+
"""
11+
512
@moduledoc """
613
Fallback handles version differences in the :wx modules needed for showing the
714
WebView and Desktop notifications and it uses the highest available
@@ -170,7 +177,7 @@ defmodule Desktop.Fallback do
170177
webview
171178
end
172179

173-
def notification_new(title, type) do
180+
def notification_new(title, type, parent \\ nil) do
174181
if module?(:wxNotificationMessage) do
175182
flag =
176183
case type do
@@ -180,8 +187,9 @@ defmodule Desktop.Fallback do
180187
end
181188

182189
notification = call(:wxNotificationMessage, :new, [title, [flags: flag]])
190+
notification_set_parent(notification, parent)
183191

184-
if notification_events_available?() do
192+
if notification != nil and notification_events_available?() do
185193
for event <- [
186194
:notification_message_click,
187195
:notification_message_dismissed,
@@ -190,9 +198,11 @@ defmodule Desktop.Fallback do
190198
call(:wxNotificationMessage, :connect, [notification, event])
191199
end
192200
else
193-
Logger.warning(
194-
"Missing support for wxNotificationMessage Events - upgrade to wxWidgets 3.1 - messages won't be clickable"
195-
)
201+
if notification != nil do
202+
Logger.warning(
203+
"Missing support for wxNotificationMessage Events - upgrade to wxWidgets 3.1 - messages won't be clickable"
204+
)
205+
end
196206
end
197207

198208
notification
@@ -204,13 +214,20 @@ defmodule Desktop.Fallback do
204214
end
205215

206216
def notification_show(notification, message, timeout, title \\ nil) do
207-
if module?(:wxNotificationMessage) do
217+
if module?(:wxNotificationMessage) and notification != nil do
208218
if title != nil do
209219
call(:wxNotificationMessage, :setTitle, [notification, to_charlist(title)])
210220
end
211221

212222
call(:wxNotificationMessage, :setMessage, [notification, to_charlist(message)])
213-
call(:wxNotificationMessage, :show, [notification, [timeout: timeout]])
223+
224+
case call(:wxNotificationMessage, :show, [notification, [timeout: timeout]]) do
225+
false ->
226+
Logger.warning(@notification_show_failed)
227+
228+
_ ->
229+
:ok
230+
end
214231
else
215232
Logger.notice("NOTIFICATION: #{title}: #{message}")
216233
end
@@ -244,6 +261,14 @@ defmodule Desktop.Fallback do
244261
end
245262
end
246263

264+
defp notification_set_parent(notification, parent) do
265+
if notification != nil and parent != nil and OS.macos?() and
266+
module?(:wxNotificationMessage) and
267+
Kernel.function_exported?(:wxNotificationMessage, :setParent, 2) do
268+
call(:wxNotificationMessage, :setParent, [notification, parent])
269+
end
270+
end
271+
247272
defp call(module, method, args \\ []) do
248273
if System.get_env("NO_WX") == nil and Code.ensure_loaded?(module) and
249274
Kernel.function_exported?(module, method, length(args)) do

lib/desktop/os.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ defmodule Desktop.OS do
1111
- Windows
1212
- Linux
1313
14+
`ELIXIR_DESKTOP_OS` can be set to `android`, `ios`, or `macos` to force the
15+
corresponding type (useful in tests). On real devices, omit it and the OS is
16+
detected from `:os.type()`.
17+
1418
"""
1519

1620
@doc """
@@ -45,6 +49,11 @@ defmodule Desktop.OS do
4549
"ios" ->
4650
IOS
4751

52+
# Lets CI and Linux developers run macOS-specific branches (e.g. notification wiring)
53+
# without a Darwin host. Not used in production builds.
54+
"macos" ->
55+
MacOS
56+
4857
_ ->
4958
case :os.type() do
5059
{:unix, :darwin} -> MacOS
@@ -93,6 +102,9 @@ defmodule Desktop.OS do
93102
end
94103
end
95104

105+
@doc false
106+
def macos?(), do: type() == MacOS
107+
96108
defp kill_heart() do
97109
heart = Process.whereis(:heart)
98110

lib/desktop/window.ex

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ defmodule Desktop.Window do
196196
wx_menubar
197197
end
198198

199-
if OS.type() == MacOS do
199+
if OS.macos?() do
200200
update_apple_menu(window_title, frame, wx_menubar || :wxMenuBar.new())
201201
end
202202

@@ -468,6 +468,12 @@ defmodule Desktop.Window do
468468
* `:callback` - A function to be executed when the user clicks on the
469469
notification.
470470
471+
On macOS, notifications are associated with the main `wxFrame` via
472+
`wxNotificationMessage:setParent/2` so they follow the same app identity as
473+
the visible window. If nothing appears, check System Settings → Notifications
474+
and (for distributed apps) bundle id / Info.plist alignment for the VM
475+
(see GitHub issue #38).
476+
471477
## Examples
472478
473479
iex> Desktop.Window.show_notification(pid, "Hello, world!")
@@ -658,7 +664,7 @@ defmodule Desktop.Window do
658664
{n, _} =
659665
note =
660666
case Map.get(noties, id, nil) do
661-
nil -> {Fallback.notification_new(title || window_title, type), callback}
667+
nil -> {Fallback.notification_new(title || window_title, type, frame), callback}
662668
{note, _} -> {note, callback}
663669
end
664670

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule Desktop.FallbackNotificationTest do
2+
use ExUnit.Case
3+
4+
describe "notification_new/3" do
5+
test "parent option does not crash when wx is disabled (NO_WX)" do
6+
old_no_wx = System.get_env("NO_WX")
7+
old_os = System.get_env("ELIXIR_DESKTOP_OS")
8+
9+
on_exit(fn ->
10+
restore_env("NO_WX", old_no_wx)
11+
restore_env("ELIXIR_DESKTOP_OS", old_os)
12+
end)
13+
14+
System.put_env("NO_WX", "1")
15+
System.put_env("ELIXIR_DESKTOP_OS", "macos")
16+
17+
assert Desktop.OS.macos?()
18+
# Parent is ignored when notification object cannot be created; must not raise.
19+
assert Desktop.Fallback.notification_new("Title", :info, :not_a_wx_window) == nil
20+
end
21+
end
22+
23+
defp restore_env(key, nil), do: System.delete_env(key)
24+
defp restore_env(key, value), do: System.put_env(key, value)
25+
end

test/os_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule Desktop.OSTest do
2+
use ExUnit.Case
3+
4+
describe "type/0 and ELIXIR_DESKTOP_OS" do
5+
test "macos override forces MacOS for tests and CI" do
6+
old = System.get_env("ELIXIR_DESKTOP_OS")
7+
8+
try do
9+
System.put_env("ELIXIR_DESKTOP_OS", "macos")
10+
assert Desktop.OS.type() == MacOS
11+
after
12+
if old do
13+
System.put_env("ELIXIR_DESKTOP_OS", old)
14+
else
15+
System.delete_env("ELIXIR_DESKTOP_OS")
16+
end
17+
end
18+
end
19+
end
20+
end

0 commit comments

Comments
 (0)