diff --git a/CHANGELOG.md b/CHANGELOG.md index b27da79..1e74e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Optional `config :desktop, :backend, :auto | :wx | :json | :browser` override - 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) - 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) diff --git a/guides/faq.md b/guides/faq.md index d73f0bd..1899dae 100644 --- a/guides/faq.md +++ b/guides/faq.md @@ -48,6 +48,8 @@ Inspect what the active backend supports: ```elixir 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 ``` | Backend | `window` | `content` | `menu` | diff --git a/lib/desktop/backend/browser.ex b/lib/desktop/backend/browser.ex index 97fa2eb..50c2116 100644 --- a/lib/desktop/backend/browser.ex +++ b/lib/desktop/backend/browser.ex @@ -45,6 +45,9 @@ defmodule Desktop.Backend.Browser do @impl true def open_external_url(url), do: Desktop.Impl.HostBrowser.open(url) + @impl true + def os_description, do: nil + @impl true def activate_event_active?(_event), do: true diff --git a/lib/desktop/backend/json.ex b/lib/desktop/backend/json.ex index 88918f7..f8b4f30 100644 --- a/lib/desktop/backend/json.ex +++ b/lib/desktop/backend/json.ex @@ -77,6 +77,11 @@ defmodule Desktop.Backend.Json do :ok end + @impl true + def os_description do + Protocol.call(:wx_misc, :getOsDescription, []) + end + @impl true def activate_event_active?(_event), do: true diff --git a/lib/desktop/backend/wx.ex b/lib/desktop/backend/wx.ex index 219fbb1..de077cf 100644 --- a/lib/desktop/backend/wx.ex +++ b/lib/desktop/backend/wx.ex @@ -75,6 +75,11 @@ defmodule Desktop.Backend.Wx do end end + @impl true + def os_description do + Null.wx_call(:wx_misc, :getOsDescription, []) + end + @impl true def activate_event_active?(event) do if function_exported?(:wxActivateEvent, :getActive, 1) do diff --git a/lib/desktop/bridge/mock.ex b/lib/desktop/bridge/mock.ex index 9f154fc..4573b42 100644 --- a/lib/desktop/bridge/mock.ex +++ b/lib/desktop/bridge/mock.ex @@ -14,6 +14,7 @@ defmodule Desktop.Bridge.Mock do def handle_method([:wx, :getObjectType, [arg]]), do: Keyword.get(arg, :type) 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([type, :new | args]), do: [id: System.unique_integer([:positive]), type: type, args: args] diff --git a/lib/desktop/platform/system.ex b/lib/desktop/platform/system.ex index c53876a..746f893 100644 --- a/lib/desktop/platform/system.ex +++ b/lib/desktop/platform/system.ex @@ -17,6 +17,7 @@ defmodule Desktop.Platform.System do term() @callback wx_available?() :: boolean() @callback open_external_url(String.t()) :: :ok + @callback os_description() :: String.t() | charlist() | nil @callback activate_event_active?(event :: term()) :: boolean() def init_env, do: impl().init_env() @@ -32,6 +33,34 @@ defmodule Desktop.Platform.System do def wx_available?, do: impl().wx_available?() def open_external_url(url), do: Helpers.with_wx_env(fn -> impl().open_external_url(url) end) + @doc """ + Returns a human-readable OS / device description string. + + Replaces direct `:wx_misc.getOsDescription/0` calls so apps work on all + backends (Wx, Json/mobile bridge, Browser). Returns `nil` when unavailable. + """ + def os_description do + raw = + Helpers.with_wx_env(fn -> + case impl().os_description() do + desc when is_binary(desc) -> desc + desc when is_list(desc) -> List.to_string(desc) + _ -> nil + end + end) + + case raw do + nil -> + nil + + str -> + case String.trim(str) do + "" -> nil + trimmed -> trimmed + end + end + end + def activate_event_active?(event), do: Helpers.with_wx_env(fn -> impl().activate_event_active?(event) end) diff --git a/test/desktop/backend/browser_test.exs b/test/desktop/backend/browser_test.exs index 49c09f6..3e013e9 100644 --- a/test/desktop/backend/browser_test.exs +++ b/test/desktop/backend/browser_test.exs @@ -37,6 +37,7 @@ defmodule Desktop.Backend.BrowserTest do assert :ok = Browser.set_env(nil) assert Browser.get_env() == nil assert Browser.locale() == nil + assert Browser.os_description() == nil refute Browser.wx_available?() end end diff --git a/test/desktop/backend/json_test.exs b/test/desktop/backend/json_test.exs index 9545256..04e5297 100644 --- a/test/desktop/backend/json_test.exs +++ b/test/desktop/backend/json_test.exs @@ -27,6 +27,11 @@ defmodule Desktop.Backend.JsonTest do assert Json.locale() == "en" end + test "T-JSN: os_description via bridge RPC" do + assert Json.os_description() == ~c"Mock OS" + assert Desktop.Platform.System.os_description() == "Mock OS" + end + test "T-JSN: new frame handle shape" do wx = Transport.ensure_started() diff --git a/test/desktop/backend/wx_test.exs b/test/desktop/backend/wx_test.exs index 6bed0db..5fb6260 100644 --- a/test/desktop/backend/wx_test.exs +++ b/test/desktop/backend/wx_test.exs @@ -39,4 +39,9 @@ defmodule Desktop.Backend.WxTest do result = Wx.locale() assert result == nil or is_binary(result) end + + test "T-WX: os_description via Platform.System" do + result = Desktop.Platform.System.os_description() + assert result == nil or is_binary(result) + end end diff --git a/test/desktop/platform_system_test.exs b/test/desktop/platform_system_test.exs new file mode 100644 index 0000000..d6ecb71 --- /dev/null +++ b/test/desktop/platform_system_test.exs @@ -0,0 +1,43 @@ +defmodule Desktop.Platform.SystemTest do + use ExUnit.Case, async: false + + alias Desktop.Platform.System, as: PlatformSystem + + defmodule StubBackend do + # Minimal stand-in: Platform.System.os_description/0 uses with_wx_env/1, which may + # call set_env/1 when Desktop.Env is up. + def set_env(_env), do: :ok + def os_description, do: Process.get(:stub_os_description) + end + + setup do + previous = Application.get_env(:desktop, :backend, :auto) + Application.put_env(:desktop, :backend, StubBackend) + + on_exit(fn -> + Application.put_env(:desktop, :backend, previous) + Process.delete(:stub_os_description) + end) + + :ok + end + + test "os_description normalizes charlist and binary" do + Process.put(:stub_os_description, ~c"Mock OS") + assert PlatformSystem.os_description() == "Mock OS" + + Process.put(:stub_os_description, "Linux Desktop") + assert PlatformSystem.os_description() == "Linux Desktop" + end + + test "os_description trims blank values to nil" do + Process.put(:stub_os_description, " ") + assert PlatformSystem.os_description() == nil + + Process.put(:stub_os_description, ~c"") + assert PlatformSystem.os_description() == nil + + Process.put(:stub_os_description, nil) + assert PlatformSystem.os_description() == nil + end +end diff --git a/test/desktop/regression/beam_wx_calls_test.exs b/test/desktop/regression/beam_wx_calls_test.exs index 8a5d1ad..3de0832 100644 --- a/test/desktop/regression/beam_wx_calls_test.exs +++ b/test/desktop/regression/beam_wx_calls_test.exs @@ -31,6 +31,11 @@ defmodule Desktop.Regression.BeamWxCallsTest do assert Json.locale() == "en" end + test "os_description uses bridge RPC not OTP :wx_misc" do + assert Json.os_description() == ~c"Mock OS" + assert PlatformSystem.os_description() == "Mock OS" + 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") diff --git a/test/support/guard_platform_abstraction.exs b/test/support/guard_platform_abstraction.exs index 5001b23..3b2ba0c 100644 --- a/test/support/guard_platform_abstraction.exs +++ b/test/support/guard_platform_abstraction.exs @@ -17,7 +17,9 @@ forbidden_patterns = [ {~r/ensure_wx_env/, "use Desktop.Platform.Helpers.with_wx_env/1 via Platform facades, not ensure_wx_env"}, {~r/:wxLocale\.getSystemLanguage\s*\(/, - "Json/mobile: pass :getSystemLanguage as a bridge atom to Protocol, never :wxLocale.getSystemLanguage/0"} + "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"} ] violations =