From a0800d95c9a3e3f094fb7594f874a87de1c966e7 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 29 May 2026 23:34:14 +0300 Subject: [PATCH] feat(packaging): open files from argv, ship Windows MSI installer (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-platform prerequisite for OS-level file associations: the boot task now consumes `std::env::args_os().nth(1)` as a path and dispatches `Message::OpenRecent` for it. Flag-style args (starting with `-`) are ignored, and `OpenRecent` already does the file-existence check, so a bogus path lands as a clean command-line error instead of a panic. Linux: the AppImage's desktop entry adds `image/vnd.dwg` alongside the existing `image/vnd.dxf` MIME type and grows a `%f` placeholder in `Exec=` so the launcher forwards the picked file path. Once the AppImage is integrated with the system (xdg-mime / AppImageLauncher), double-clicking a .dwg / .dxf opens it in Open CAD Studio. Windows: new `packaging/windows/main.wxs` defines an MSI installer — per-machine install to `Program Files\Open CAD Studio`, Start Menu shortcut, `MajorUpgrade` so newer MSIs replace older ones, and a single ProgID `OpenCADStudio.Drawing` that owns both `.dwg` and `.dxf` and launches the exe with the file as `argv[1]`. The icon is pulled from the SVG logo, converted to multi-resolution ICO at build time with the runner's pre-installed ImageMagick. CI now also runs `candle` / `light` (WiX Toolset 3, pre-installed on `windows-latest`) and uploads both the bare `.exe` (portable) and `.msi` (installer) to the release. macOS Info.plist already declares the DWG / DXF UTIs; combined with the argv-handling change above, double-clicking a drawing in Finder will open it in Open CAD Studio without further packaging changes. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/release.yml | 46 ++++++++++++- packaging/OpenCADStudio.desktop | 6 +- packaging/windows/main.wxs | 118 ++++++++++++++++++++++++++++++++ src/app/mod.rs | 17 ++++- 4 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 packaging/windows/main.wxs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff096349..d5a54d00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,13 +78,53 @@ jobs: - name: Build run: cargo build --release - - name: Upload exe to release + - name: Convert SVG icon to ICO + shell: pwsh + run: | + # ImageMagick is pre-installed on windows-latest. The + # auto-resize generates every size Explorer / Add-Remove + # Programs / the Start Menu shortcut may request from a + # single source SVG. + magick assets/logo.svg ` + -define icon:auto-resize=16,24,32,48,64,128,256 ` + packaging/windows/AppIcon.ico + + - name: Build MSI installer + shell: pwsh + run: | + $version = "${{ github.ref_name }}" -replace '^v', '' + if (-not $version) { $version = "0.0.0" } + # WiX Toolset 3.x is pre-installed on windows-latest; $env:WIX + # points to the install root. candle compiles .wxs → .wixobj + # and light links it into the final .msi. + $candle = Join-Path $env:WIX "bin\candle.exe" + $light = Join-Path $env:WIX "bin\light.exe" + & $candle -arch x64 ` + -dVersion=$version ` + -dSource=target\release\OpenCADStudio.exe ` + -dIcon=packaging\windows\AppIcon.ico ` + packaging\windows\main.wxs ` + -out packaging\windows\main.wixobj + if ($LASTEXITCODE -ne 0) { throw "candle failed" } + & $light packaging\windows\main.wixobj -out OpenCADStudio.msi + if ($LASTEXITCODE -ne 0) { throw "light failed" } + + - name: Upload artifacts to release if: github.event_name == 'release' + shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mv target/release/OpenCADStudio.exe OpenCADStudio-${{ github.ref_name }}-windows-x86_64.exe - gh release upload ${{ github.ref_name }} OpenCADStudio-${{ github.ref_name }}-windows-x86_64.exe --clobber --repo ${{ github.repository }} + $tag = "${{ github.ref_name }}" + # Ship both the bare .exe (portable, no install) and the + # .msi (installer with Start Menu shortcut + .dwg / .dxf + # file association). Users pick the form they want. + Move-Item target\release\OpenCADStudio.exe "OpenCADStudio-$tag-windows-x86_64.exe" + Move-Item OpenCADStudio.msi "OpenCADStudio-$tag-windows-x86_64.msi" + gh release upload $tag ` + "OpenCADStudio-$tag-windows-x86_64.exe" ` + "OpenCADStudio-$tag-windows-x86_64.msi" ` + --clobber --repo ${{ github.repository }} build-macos: runs-on: macos-14 # Apple Silicon (arm64) only diff --git a/packaging/OpenCADStudio.desktop b/packaging/OpenCADStudio.desktop index 6b9fe993..bd685a97 100644 --- a/packaging/OpenCADStudio.desktop +++ b/packaging/OpenCADStudio.desktop @@ -1,11 +1,11 @@ [Desktop Entry] Name=Open CAD Studio Comment=A CAD application for 2D/3D drawing and design -Exec=OpenCADStudio +Exec=OpenCADStudio %f Icon=io.github.HakanSeven12.OpenCadStudio Terminal=false Type=Application Categories=Graphics;Engineering; -MimeType=image/vnd.dxf; -Keywords=CAD;DXF;drawing;design;engineering;2D;3D; +MimeType=image/vnd.dwg;image/vnd.dxf; +Keywords=CAD;DWG;DXF;drawing;design;engineering;2D;3D; StartupWMClass=OpenCADStudio diff --git a/packaging/windows/main.wxs b/packaging/windows/main.wxs new file mode 100644 index 00000000..c619d60f --- /dev/null +++ b/packaging/windows/main.wxs @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/mod.rs b/src/app/mod.rs index a929ffda..dceb55b4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1036,7 +1036,22 @@ impl OpenCADStudio { Message::UpdateCheckResult, ); let focus_cmd = s.focus_cmd_input(); - (s, Task::batch([open_main, check_update, focus_cmd])) + // Open a file path passed on the command line. Used by the + // OS file association: double-clicking a .dwg in the file + // explorer launches the binary with the file path as the + // first positional argument. Unknown / flag-style args are + // ignored. `Message::OpenRecent` does the existence check + // and reports a clean error if the path is bogus. + let cli_open: Task = std::env::args_os() + .nth(1) + .map(PathBuf::from) + .filter(|p| !p.as_os_str().to_string_lossy().starts_with('-')) + .map(|p| Task::done(Message::OpenRecent(p))) + .unwrap_or_else(Task::none); + ( + s, + Task::batch([open_main, check_update, focus_cmd, cli_open]), + ) } }