Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Menu adapters: `Desktop.Menu.Adapter.Json` and `Desktop.Menu.Adapter.Browser`
- Public `Desktop.*` APIs unchanged (`Desktop.Window`, `Desktop.Env`, `Desktop.Menu`, etc.)
- `Desktop.Platform.System.os_description/0` — backend-safe replacement for `:wx_misc.getOsDescription/0` (Wx, Json bridge, Browser)
- `Desktop.Platform.System.custom_event/2` — mobile bridge custom events (replaces Hex `Bridge` `[:custom_event, …]` calls)
- `Desktop.Platform.Content.reload/1` and `Desktop.Window.reload/1` — backend-safe webview reload (replaces `:wxWebView.reload/1`)
- Test suite: `mix test.fast`, `xvfb-run mix test.wx`, `mix test.guard` — see `docs/TEST_PLAN.md`
- Compile without OTP `:wx`: conditional `erl_src_paths` and `Desktop.Wx` fallbacks (no `wx.hrl` required)

Expand Down
10 changes: 10 additions & 0 deletions guides/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Desktop.Platform.backend() # e.g. Desktop.Backend.Wx
Desktop.Platform.capabilities() # %{window: true, content: :webview, ...}
Desktop.Platform.System.locale()
Desktop.Platform.System.os_description() # replaces :wx_misc.getOsDescription/0
Desktop.Platform.System.custom_event(:share, [path]) # mobile bridge custom events
Desktop.Window.reload(pid) # replaces :wxWebView.reload/1
```

| Backend | `window` | `content` | `menu` |
Expand All @@ -64,6 +66,14 @@ On Linux with DBus SNI available, Wx may use `:dbus` for the taskbar menu instea

On mobile targets, `desktop` uses `Desktop.Backend.Json` instead of OTP `:wx`. The Elixir side speaks the legacy JSON protocol over TCP to your native host app. Set `BRIDGE_PORT` to the port your host app listens on. Transport is built in as `Desktop.Bridge.Transport` — the separate `bridge` hex package is no longer required.

App-level native events (share, save, restart, etc.) use:

```elixir
Desktop.Platform.System.custom_event(:share, [path, label])
```

This sends `[:custom_event, event, args]` over the same wire format the Hex `Bridge` GenServer used previously.

Override explicitly if needed:

```elixir
Expand Down
6 changes: 6 additions & 0 deletions lib/desktop/backend/browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ defmodule Desktop.Backend.Browser do
@impl true
def os_description, do: nil

@impl true
def custom_event(_event, _args), do: :ok

@impl true
def activate_event_active?(_event), do: true

Expand Down Expand Up @@ -121,6 +124,9 @@ defmodule Desktop.Backend.Browser do
@impl true
def rebuild(_frame, _url), do: nil

@impl true
def reload(_content), do: :ok

@impl true
def put_webview_backend(name) do
Desktop.Env.put(:webview_backend, name)
Expand Down
17 changes: 17 additions & 0 deletions lib/desktop/backend/json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ defmodule Desktop.Backend.Json do
Protocol.call(:wx_misc, :getOsDescription, [])
end

@impl true
def custom_event(event, args) do
if Process.whereis(Transport) do
Protocol.call(:custom_event, event, args)
end

:ok
end

@impl true
def activate_event_active?(_event), do: true

Expand Down Expand Up @@ -218,6 +227,14 @@ defmodule Desktop.Backend.Json do
:ok
end

@impl true
def reload(nil), do: :ok

def reload(webview) do
Protocol.call(:wxWebView, :reload, [webview])
:ok
end

@impl true
def current_url(nil, last_url), do: last_url

Expand Down
11 changes: 11 additions & 0 deletions lib/desktop/backend/wx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ defmodule Desktop.Backend.Wx do
Null.wx_call(:wx_misc, :getOsDescription, [])
end

@impl true
def custom_event(_event, _args), do: :ok

@impl true
def activate_event_active?(event) do
if function_exported?(:wxActivateEvent, :getActive, 1) do
Expand Down Expand Up @@ -267,6 +270,14 @@ defmodule Desktop.Backend.Wx do
:ok
end

@impl true
def reload(nil), do: :ok

def reload(webview) do
Null.wx_call(:wxWebView, :reload, [webview])
:ok
end

@impl true
def current_url(nil, last_url), do: last_url

Expand Down
2 changes: 2 additions & 0 deletions lib/desktop/bridge/mock.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ defmodule Desktop.Bridge.Mock do
def handle_method([:wxLocale | _]), do: ~c"en"
def handle_method([:wx_misc, :launchDefaultBrowser | _]), do: :ok
def handle_method([:wx_misc, :getOsDescription | _]), do: ~c"Mock OS"
def handle_method([:custom_event | _]), do: :ok
def handle_method([:wxWebView, :reload | _]), do: :ok

def handle_method([type, :new | args]),
do: [id: System.unique_integer([:positive]), type: type, args: args]
Expand Down
10 changes: 10 additions & 0 deletions lib/desktop/platform/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Desktop.Platform.Content do
only_open :: boolean()
) :: :ok
@callback rebuild(frame :: term() | nil, last_url :: String.t() | nil) :: term() | nil
@callback reload(content :: term() | nil) :: :ok
@callback put_webview_backend(name :: String.t()) :: :ok

def attach(frame), do: Helpers.with_wx_env(fn -> impl().attach(frame) end)
Expand All @@ -30,6 +31,15 @@ defmodule Desktop.Platform.Content do
def rebuild(frame, last_url),
do: Helpers.with_wx_env(fn -> impl().rebuild(frame, last_url) end)

@doc """
Reloads the webview / native content handle.

Replaces direct `:wxWebView.reload/1` so the call works on Wx and Json
(mobile bridge) backends. No-op when content is `nil` or on Browser.
"""
def reload(content),
do: Helpers.with_wx_env(fn -> impl().reload(content) end)

def put_webview_backend(name),
do: Helpers.with_wx_env(fn -> impl().put_webview_backend(name) end)

Expand Down
12 changes: 12 additions & 0 deletions lib/desktop/platform/system.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Desktop.Platform.System do
@callback wx_available?() :: boolean()
@callback open_external_url(String.t()) :: :ok
@callback os_description() :: String.t() | charlist() | nil
@callback custom_event(event :: atom(), args :: list()) :: :ok
@callback activate_event_active?(event :: term()) :: boolean()

def init_env, do: impl().init_env()
Expand Down Expand Up @@ -57,6 +58,17 @@ defmodule Desktop.Platform.System do
end
end

@doc """
Sends a native-host custom event over the mobile bridge.

Replaces direct `GenServer.call(Bridge, {:bridge_call, …})` with
`[:custom_event, event, args]` JSON. No-op (`:ok`) on Wx/Browser backends
or when the bridge transport is not running.
"""
def custom_event(event, args \\ []) when is_atom(event) and is_list(args) do
Helpers.with_wx_env(fn -> impl().custom_event(event, args) end)
end

def activate_event_active?(event),
do: Helpers.with_wx_env(fn -> impl().activate_event_active?(event) end)

Expand Down
20 changes: 20 additions & 0 deletions lib/desktop/window.ex
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ defmodule Desktop.Window do
GenServer.cast(pid, {:load_url, url})
end

@doc """
Reload the Window webview / native content.

Replaces direct `:wxWebView.reload/1` on the handle from `webview/1`.

## Examples

iex> Desktop.Window.reload(pid)
:ok

"""
def reload(pid) do
GenServer.cast(pid, :reload)
end

@doc """
Show the Window if not visible with the given url.

Expand Down Expand Up @@ -634,6 +649,11 @@ defmodule Desktop.Window do
{:noreply, %Window{ui | webview: Fallback.webview_rebuild(ui)}}
end

def handle_cast(:reload, ui = %Window{webview: webview}) do
Platform.Content.reload(webview)
{:noreply, ui}
end

def handle_cast(
{:show_notification, message, id, type, title, callback, timeout},
ui = %Window{notifications: noties, title: window_title}
Expand Down
5 changes: 5 additions & 0 deletions test/desktop/backend/browser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ defmodule Desktop.Backend.BrowserTest do
assert Browser.get_env() == nil
assert Browser.locale() == nil
assert Browser.os_description() == nil
assert :ok = Browser.custom_event(:share, [])
refute Browser.wx_available?()
end

test "T-BRW: content reload is no-op" do
assert :ok = Browser.reload(nil)
end
end
14 changes: 14 additions & 0 deletions test/desktop/backend/json_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ defmodule Desktop.Backend.JsonTest do
assert Desktop.Platform.System.os_description() == "Mock OS"
end

test "T-JSN: custom_event via bridge RPC" do
assert :ok = Json.custom_event(:share, ["/tmp/file"])
assert :ok = Desktop.Platform.System.custom_event(:restart, [])
end

test "T-JSN: content reload via bridge RPC" do
wx = Transport.ensure_started()
{:ok, _frame, webview} = Json.open(wx: wx, title: "t", size: {200, 200}, icon: nil)

assert :ok = Json.reload(webview)
assert :ok = Desktop.Platform.Content.reload(webview)
assert :ok = Desktop.Platform.Content.reload(nil)
end

test "T-JSN: new frame handle shape" do
wx = Transport.ensure_started()

Expand Down
14 changes: 14 additions & 0 deletions test/desktop/backend/wx_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,18 @@ defmodule Desktop.Backend.WxTest do
result = Desktop.Platform.System.os_description()
assert result == nil or is_binary(result)
end

test "T-WX: custom_event is no-op" do
assert :ok = Desktop.Platform.System.custom_event(:share, [])
end

@tag timeout: 10_000
test "T-WX: content reload" do
wx = Desktop.Env.wx()
{:ok, frame, webview} = Wx.open(wx: wx, title: ~c"reload", size: {200, 200}, icon: nil)

assert :ok = Desktop.Platform.Content.reload(webview)
assert :ok = Desktop.Platform.Content.reload(nil)
Wx.destroy_frame(frame)
end
end
32 changes: 32 additions & 0 deletions test/desktop/platform_system_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,35 @@ defmodule Desktop.Platform.SystemTest do
assert PlatformSystem.os_description() == nil
end
end

defmodule Desktop.Platform.SystemCustomEventTest do
use ExUnit.Case, async: false

alias Desktop.Platform.System, as: PlatformSystem

defmodule StubBackend do
def set_env(_env), do: :ok

def custom_event(event, args) do
send(Process.get(:stub_test_pid), {:custom_event, event, args})
:ok
end
end

setup do
previous = Application.get_env(:desktop, :backend, :auto)
Application.put_env(:desktop, :backend, StubBackend)
Process.put(:stub_test_pid, self())

on_exit(fn ->
Application.put_env(:desktop, :backend, previous)
end)

:ok
end

test "custom_event delegates to backend" do
assert :ok = PlatformSystem.custom_event(:share, ["/tmp/a"])
assert_receive {:custom_event, :share, ["/tmp/a"]}
end
end
11 changes: 11 additions & 0 deletions test/desktop/regression/beam_wx_calls_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ defmodule Desktop.Regression.BeamWxCallsTest do
assert PlatformSystem.os_description() == "Mock OS"
end

test "custom_event uses bridge RPC not Hex Bridge GenServer" do
assert :ok = Json.custom_event(:open_with, ["/tmp/x"])
assert :ok = PlatformSystem.custom_event(:message_read_up_to, ["0x01", 1])
end

test "content reload uses bridge RPC not OTP :wxWebView" do
wx = Transport.ensure_started()
{:ok, _frame, webview} = Json.open(wx: wx, title: "t", size: {100, 100}, icon: nil)
assert :ok = Desktop.Platform.Content.reload(webview)
end

test "open_external_url on Json uses bridge RPC" do
assert :ok = Json.open_external_url("https://example.com")
assert :ok = PlatformSystem.open_external_url("https://example.com")
Expand Down
4 changes: 3 additions & 1 deletion test/support/guard_platform_abstraction.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ forbidden_patterns = [
{~r/:wxLocale\.getSystemLanguage\s*\(/,
"Json/mobile: pass :getSystemLanguage as a bridge atom to Protocol, never :wxLocale.getSystemLanguage/0"},
{~r/:wx_misc\.getOsDescription\s*\(/,
"use Desktop.Platform.System.os_description/0 instead of :wx_misc.getOsDescription/0"}
"use Desktop.Platform.System.os_description/0 instead of :wx_misc.getOsDescription/0"},
{~r/:wxWebView\.reload\s*\(/,
"use Desktop.Window.reload/1 or Desktop.Platform.Content.reload/1 instead of :wxWebView.reload/1"}
]

violations =
Expand Down
Loading