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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions guides/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
3 changes: 3 additions & 0 deletions lib/desktop/backend/browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions lib/desktop/backend/json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions lib/desktop/backend/wx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/desktop/bridge/mock.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
29 changes: 29 additions & 0 deletions lib/desktop/platform/system.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Comment on lines +42 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Piping the result of a block directly into a case statement is generally considered an anti-pattern in Elixir (as highlighted by tools like Credo). It can make the code harder to read and debug.

Instead, bind the result of Helpers.with_wx_env/1 to a local variable first, and then perform the case match on that variable.

  def os_description do
    raw_desc =
      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_desc do
      nil ->
        nil

      str ->
        case String.trim(str) do
          "" -> nil
          trimmed -> trimmed
        end
    end
  end
References
  1. Avoid piping into control flow structures like case, if, or cond to maintain readability and idiomatic Elixir style. (link)


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

Expand Down
1 change: 1 addition & 0 deletions test/desktop/backend/browser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions test/desktop/backend/json_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
5 changes: 5 additions & 0 deletions test/desktop/backend/wx_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
43 changes: 43 additions & 0 deletions test/desktop/platform_system_test.exs
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions test/desktop/regression/beam_wx_calls_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
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 @@ -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 =
Expand Down
Loading