From be5bea6bbea256ea160f37db2482ce3c8d154ceb Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Tue, 1 Sep 2026 10:03:14 +0200 Subject: [PATCH 1/2] Replace rcedit with libpe --set-icon for Windows PE icons. Embed the app icon via Mix.Tasks.Pe.Update (libpe 1.3 RT_GROUP_ICON + RT_ICON) instead of WinRun4J rcedit /I, remove the bundled rcedit.exe, and depend on elixir-desktop/libpe master until 1.3.0 is on Hex. Co-authored-by: Cursor --- lib/package.ex | 5 +- mix.exs | 3 +- rel/win32/rcedit.exe | Bin 17920 -> 0 bytes scripts/verify_otp_icons.exs | 47 ++++++++++++++++++ test/pe_update_test.exs | 92 +++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 3 deletions(-) delete mode 100644 rel/win32/rcedit.exe create mode 100644 scripts/verify_otp_icons.exs create mode 100644 test/pe_update_test.exs diff --git a/lib/package.ex b/lib/package.ex index fca237b..707546c 100644 --- a/lib/package.ex +++ b/lib/package.ex @@ -134,16 +134,17 @@ defmodule Desktop.Deployment.Package do for bin <- [new_name, beam, erlexec] do # Unsafe binary removal of "Erlang", needs same length! file_replace(bin, "Erlang", binary_part(pkg.name <> <<0, 0, 0, 0, 0, 0>>, 0, 6)) - cmd!(toolpath("rel/win32/rcedit.exe"), ["/I", bin, icon]) # Host-first: keep the CUI subsystem so OTP 26's user/logger get a console when # DesktopWebView spawns the release (GUI subsystem causes nouser / invalid handle). # Release-first: mark GUI so launching erl.exe does not flash a console window. pe_args = if pkg.windows_layout == :host_first do - ["--set-manifest", Path.join(build_root, "app.exe.manifest")] + ["--set-icon", icon, "--set-manifest", Path.join(build_root, "app.exe.manifest")] else [ + "--set-icon", + icon, "--set-subsystem", "IMAGE_SUBSYSTEM_WINDOWS_GUI", "--set-manifest", diff --git a/mix.exs b/mix.exs index f54288b..e53244d 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,8 @@ defmodule Desktop.Deployment.MixProject do # Credo {:httpoison, "~> 2.0", runtime: false}, {:credo, "~> 1.5", only: [:dev, :test], runtime: false}, - {:libpe, "~> 1.2"}, + # 1.3.0+ (icon embedding) is on GitHub; switch to Hex once published. + {:libpe, github: "elixir-desktop/libpe", branch: "master"}, {:poison, "~> 3.0"} ] end diff --git a/rel/win32/rcedit.exe b/rel/win32/rcedit.exe deleted file mode 100644 index fc4cde64fd91038b09e93340f2ee0b30f0c4acda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17920 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 diff --git a/scripts/verify_otp_icons.exs b/scripts/verify_otp_icons.exs new file mode 100644 index 0000000..013f140 --- /dev/null +++ b/scripts/verify_otp_icons.exs @@ -0,0 +1,47 @@ +# Verifies packaging-style Pe.Update --set-icon on real OTP PE binaries. +# Usage: mix run scripts/verify_otp_icons.exs + +bins = + System.get_env("BINS") + |> String.split("|", trim: true) + +icon = System.fetch_env!("ICON") +manifest = System.fetch_env!("MANIFEST") +tmpdir = System.fetch_env!("TMPDIR") + +Enum.each(bins, fn bin -> + name = Path.basename(bin) + tmp = Path.join(tmpdir, name) + File.cp!(bin, tmp) + + :ok = + Mix.Tasks.Pe.Update.run([ + "--set-icon", + icon, + "--set-manifest", + manifest, + "--set-info", + "ProductName", + "Diode Collab", + "--set-info", + "FileVersion", + "1.22.8", + "--set-info", + "ProductVersion", + "1.22.8", + tmp + ]) + + {:ok, pe} = LibPE.parse_file(tmp) + rsrc = LibPE.get_resources(pe) + group = LibPE.Icon.get(rsrc) + checksum_ok = pe.coff_header.checksum == LibPE.update_checksum(pe).coff_header.checksum + + IO.puts("#{name}: group=#{group != nil} checksum=#{checksum_ok} size=#{File.stat!(tmp).size}") + + if group == nil or not checksum_ok do + System.halt(1) + end +end) + +IO.puts("OK") diff --git a/test/pe_update_test.exs b/test/pe_update_test.exs new file mode 100644 index 0000000..245cdb3 --- /dev/null +++ b/test/pe_update_test.exs @@ -0,0 +1,92 @@ +defmodule Desktop.Deployment.PeUpdateTest do + use ExUnit.Case, async: false + + alias LibPE.ResourceTable + alias LibPE.ResourceTable.{DataBlob, DirEntry} + + @fixtures Path.join([Mix.Project.deps_path(), "libpe", "test", "fixtures"]) + @hello Path.join([@fixtures, "base", "hello.exe"]) + @logo Path.join([@fixtures, "logo.ico"]) + + @manifest """ + + + + + """ + + @tag :windows_pe + test "packaging Pe.Update args embed icon, manifest, version, checksum" do + unless File.exists?(@hello) and File.exists?(@logo) do + flunk("libpe fixtures missing at #{@fixtures} (run mix deps.get)") + end + + tmp = Path.join(System.tmp_dir!(), "dep_pe_#{System.unique_integer([:positive])}.exe") + + manifest = + Path.join(System.tmp_dir!(), "dep_manifest_#{System.unique_integer([:positive])}.xml") + + File.cp!(@hello, tmp) + File.write!(manifest, @manifest) + + on_exit(fn -> + File.rm(tmp) + File.rm(manifest) + end) + + # Same shape as package.ex host-first path (no subsystem change) + args = [ + "--set-icon", + @logo, + "--set-manifest", + manifest, + "--set-info", + "CompanyName", + "TestCo", + "--set-info", + "ProductName", + "TestApp", + "--set-info", + "FileVersion", + "1.0.0", + "--set-info", + "ProductVersion", + "1.0.0", + tmp + ] + + :ok = Mix.Tasks.Pe.Update.run(args) + + {:ok, pe} = LibPE.parse_file(tmp) + rsrc = LibPE.get_resources(pe) + + assert find_type(rsrc, "RT_GROUP_ICON") + assert find_type(rsrc, "RT_ICON") + assert find_type(rsrc, "RT_MANIFEST") + assert find_type(rsrc, "RT_VERSION") + assert LibPE.Icon.get(rsrc) + assert pe.coff_header.checksum == LibPE.update_checksum(pe).coff_header.checksum + + icons = icon_payloads(rsrc) + assert length(icons) >= 1 + refute Enum.any?(icons, fn data -> binary_part(data, 0, 4) == <<0, 0, 1, 0>> end) + end + + defp icon_payloads(rsrc) do + case find_type(rsrc, "RT_ICON") do + %DirEntry{entry: %ResourceTable{entries: names}} -> + Enum.map(names, fn %DirEntry{entry: %ResourceTable{entries: langs}} -> + %DirEntry{entry: %DataBlob{data: data}} = hd(langs) + data + end) + + _ -> + [] + end + end + + defp find_type(%ResourceTable{entries: entries}, type_name) do + type = LibPE.ResourceTypes.encode(type_name) + Enum.find(entries, fn %DirEntry{name: n} -> n == type end) + end +end From cdd443da90548ba089839ead19e7a02a19cb11d4 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Tue, 1 Sep 2026 10:04:53 +0200 Subject: [PATCH 2/2] Fix credo length/1 warning in Pe.Update test. Co-authored-by: Cursor --- test/pe_update_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pe_update_test.exs b/test/pe_update_test.exs index 245cdb3..f461afe 100644 --- a/test/pe_update_test.exs +++ b/test/pe_update_test.exs @@ -68,7 +68,7 @@ defmodule Desktop.Deployment.PeUpdateTest do assert pe.coff_header.checksum == LibPE.update_checksum(pe).coff_header.checksum icons = icon_payloads(rsrc) - assert length(icons) >= 1 + assert icons != [] refute Enum.any?(icons, fn data -> binary_part(data, 0, 4) == <<0, 0, 1, 0>> end) end