From 91469a2b641ba665006c91d558b9fee82d457112 Mon Sep 17 00:00:00 2001 From: Sparsh Sam <110058692+sparshsam@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:32:59 -0400 Subject: [PATCH 1/3] v1.2.1: First public Microsoft Store release candidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Version bumped to 1.2.1 (MSIX 1.2.1.0) - New application icon (1024x1024 source → .ico, MSIX assets, macOS iconset) - About dialog cleaned up: removed beta/dev/validation wording - Updated manifests: AppxManifest.xml, AppInstaller.xml - Updated docs: CHANGELOG, README, RELEASE, VERSIONING, packaging - Frozen identity unchanged (SparshSam.OpenReader, CN=E6186421-...) - UI/branding only — no architectural changes Co-Authored-By: Claude --- CHANGELOG.md | 10 + README.md | 11 +- RELEASE.md | 19 +- VERSIONING.md | 4 +- assets/AppIcon.iconset/icon_128x128.png | Bin 0 -> 564 bytes assets/AppIcon.iconset/icon_128x128@2x.png | Bin 0 -> 1027 bytes assets/AppIcon.iconset/icon_16x16.png | Bin 0 -> 196 bytes assets/AppIcon.iconset/icon_16x16@2x.png | Bin 0 -> 226 bytes assets/AppIcon.iconset/icon_256x256.png | Bin 0 -> 1027 bytes assets/AppIcon.iconset/icon_256x256@2x.png | Bin 0 -> 2373 bytes assets/AppIcon.iconset/icon_32x32.png | Bin 0 -> 226 bytes assets/AppIcon.iconset/icon_32x32@2x.png | Bin 0 -> 371 bytes assets/AppIcon.iconset/icon_512x512.png | Bin 0 -> 2373 bytes assets/AppIcon.iconset/icon_512x512@2x.png | Bin 0 -> 6552 bytes assets/icon-150x150.png | Bin 0 -> 650 bytes assets/icon-310x150.png | Bin 0 -> 678 bytes assets/icon-44x44.png | Bin 0 -> 282 bytes assets/icon-620x300.png | Bin 0 -> 1591 bytes assets/icon-71x71.png | Bin 0 -> 383 bytes assets/pdfreader_by_sparsh.ico | Bin 13808 -> 2974 bytes docs/msix-update-validation.md | 8 +- docs/store-submission-checklist.md | 16 +- main.py | 8 +- packaging/msix/AppInstaller.xml | 2 +- packaging/msix/AppxManifest.xml | 2 +- packaging/msix/README.md | 26 +-- tools/create_icon.py | 230 +++++++++++++-------- tools/create_msix_placeholder_pngs.py | 82 +++++--- 28 files changed, 247 insertions(+), 171 deletions(-) create mode 100644 assets/AppIcon.iconset/icon_128x128.png create mode 100644 assets/AppIcon.iconset/icon_128x128@2x.png create mode 100644 assets/AppIcon.iconset/icon_16x16.png create mode 100644 assets/AppIcon.iconset/icon_16x16@2x.png create mode 100644 assets/AppIcon.iconset/icon_256x256.png create mode 100644 assets/AppIcon.iconset/icon_256x256@2x.png create mode 100644 assets/AppIcon.iconset/icon_32x32.png create mode 100644 assets/AppIcon.iconset/icon_32x32@2x.png create mode 100644 assets/AppIcon.iconset/icon_512x512.png create mode 100644 assets/AppIcon.iconset/icon_512x512@2x.png create mode 100644 assets/icon-150x150.png create mode 100644 assets/icon-310x150.png create mode 100644 assets/icon-44x44.png create mode 100644 assets/icon-620x300.png create mode 100644 assets/icon-71x71.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1f56b..5266f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.2.1 — First Public Microsoft Store Release Candidate — 2026-06-18 + +- **Version:** Bumped `__version__` to `1.2.1`. +- **MSIX version:** `1.2.1.0`. +- **Purpose:** First public Microsoft Store release candidate. +- **UI/Branding only:** New application icon (1024×1024 source). No architectural changes. +- **Assets regenerated:** Windows .ico, MSIX brand images (44×44, 71×71, 150×150, 310×150, 620×300), macOS .iconset. +- **About dialog cleaned up:** Removed beta/dev/validation wording. Shows clean "OpenReader — Version 1.2.1 — Release Notes". +- **Frozen identity unchanged:** `SparshSam.OpenReader`, `CN=E6186421-BF8A-47E0-A89C-0F513DFF91C0`, PFN `SparshSam.OpenReader_yh0byntbzd2qw`. + ## v1.2.0-beta.6 — MSIX Update Validation — 2026-06-18 - **Version:** Bumped `__version__` to `1.2.0-beta.6-dev`. diff --git a/README.md b/README.md index 4c19aaf..ed0055e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ OpenReader is a **stable, local-first desktop PDF utility** built with Python, P The app is intentionally local-first: PDFs are opened, rendered, searched, merged, split, annotated, and compressed on your computer — no uploads, no accounts, no telemetry. -**v1.2.0-beta.6** (current release) validates the MSIX update pipeline ahead of Microsoft Store submission. Windows distribution uses MSIX/App Installer with Windows-native updates — the app never replaces itself. The beta.5 → beta.6 in-place MSIX upgrade has been confirmed on Windows 11. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. +**v1.2.1** (current release) is the first public Microsoft Store release candidate. Windows distribution uses MSIX/App Installer with Windows-native updates — the app never replaces itself. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. ## Download @@ -64,11 +64,11 @@ Windows may show a SmartScreen warning because community builds are not code-sig **v1.2.0 update change:** In-app self-updating has been removed. OpenReader now uses **Windows-native updates** — the app never downloads or runs installers. -- **Existing v1.0.x and v1.1.x users must manually install a v1.2.0 beta MSIX once.** Future updates are handled by Windows App Installer or the Microsoft Store. +- **Existing v1.0.x and v1.1.x users** must manually install a v1.2.0+ MSIX once. Future updates are handled by the Microsoft Store or Windows App Installer. - **v1.2.0+ users:** Windows App Installer manages updates on launch and in the background. The app's Help → Check for Updates opens the GitHub Releases page in your browser. - Source builds should be updated with `git pull` and rebuilt locally. -> **⚠️ Production auto-updates via App Installer are not yet proven.** The beta.5 → beta.6 in-place MSIX upgrade has been validated locally with test signing, but the hosted App Installer workflow (automatic updates from a web endpoint) and Microsoft Store-managed updates require a Store submission. Until then, users update by downloading the latest MSIX from GitHub and installing manually. +> **ℹ️ Microsoft Store-managed updates** will provide automatic updates after Store approval. Until then, users update by downloading the latest MSIX from GitHub Releases and installing manually (Developer Mode required for unsigned packages). ## Features @@ -331,14 +331,13 @@ sudo pacman -S tesseract tesseract-data-eng - [x] Add App Installer template for Windows-managed updates - [x] Update GitHub Actions workflow to build MSIX - [x] Add architecture docs (`docs/windows-distribution.md`, `docs/updater-architecture.md`) -- [x] Validate MSIX install and in-place upgrade (beta.5 → beta.6, confirmed on Windows 11) -- [ ] **Store submission** — next milestone. Store signing replaces self-procured code-signing cert. +- [x] Validate MSIX install and in-place upgrade (confirmed on Windows 11) +- [x] Store submission — v1.2.1 is the first Microsoft Store release candidate ### Near-Term Items in active or planned development. - **Local AI summarization** — generate document summaries and extract key points using a local LLM (e.g. Ollama, llama.cpp); no data ever leaves your machine -- **Microsoft Store release** — signed MSIX distribution through the Microsoft Store, removing SmartScreen warnings and enabling Store-managed automatic updates - **Stronger sandboxing guidance** — documented approaches for running the app in an OS sandbox when opening documents from untrusted sources ### Long-Term Vision diff --git a/RELEASE.md b/RELEASE.md index ea899ba..c46371c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,10 +4,10 @@ OpenReader uses semantic version tags to publish packaged builds. ## Version Source of Truth -- Development source keeps `__version__` in `main.py` as a `-dev` version. -- Packaged release builds inject the release version from the Git tag. -- Tags must use the format `vMAJOR.MINOR.PATCH`, for example `v1.2.0`. -- The injected runtime version removes the leading `v`, so `v1.2.0` becomes `__version__ = "1.2.0"` in packaged builds. +- `__version__` in `main.py` is the canonical source. Set it to the next release version. +- Tags must use the format `vMAJOR.MINOR.PATCH`, for example `v1.2.1`. +- The injected runtime version removes the leading `v`, so `v1.2.1` becomes `__version__ = "1.2.1"` in packaged builds. +- CI injects the tag version for release builds via `scripts/inject_version.py`. ## Release Architecture (v1.2.0+) @@ -45,8 +45,8 @@ no separate code-signing certificate is needed. 4. Create and push a semantic version tag: ```bash - git tag v1.2.0 - git push origin v1.2.0 + git tag v1.2.1 + git push origin v1.2.1 ``` 5. GitHub Actions runs `.github/workflows/release.yml`. @@ -97,9 +97,10 @@ curl https://api.github.com/repos/sparshsam/pdfreader-by-sparsh/releases/latest The MSIX package is currently unsigned. The distribution plan is: -1. **Short term** — Submit the unsigned MSIX to the Microsoft Store. The Store - signs the package automatically with its Store identity. -2. **Beta/sideloading** — Unsigned MSIX from GitHub Releases requires Windows +1. **Microsoft Store** — Submit the unsigned MSIX to the Microsoft Store. The Store + signs the package automatically with its Store identity. **v1.2.1 is the first + Store release candidate.** +2. **Sideloading** — Unsigned MSIX from GitHub Releases requires Windows Developer Mode. Local test-signing scripts are in `packaging/msix/`. 3. **No self-procured code-signing cert** — The Store handles production signing. Do not purchase a separate code-signing certificate. diff --git a/VERSIONING.md b/VERSIONING.md index c8f8271..38904cb 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -12,8 +12,8 @@ OpenReader follows [Semantic Versioning 2.0](https://semver.org/): The current version is tracked in the `__version__` variable in `main.py`. -- **Source builds** — use a `-dev` suffix (e.g., `1.2.0-beta.2-dev`). -- **Packaged releases** — the version is injected from the Git tag during the release workflow (see `scripts/inject_version.py`). +- **Source builds** — update `__version__` in `main.py` to match the target release. +- **Packaged releases** — the version can be overridden from the Git tag during the CI release workflow (see `scripts/inject_version.py`). ## Tag Format diff --git a/assets/AppIcon.iconset/icon_128x128.png b/assets/AppIcon.iconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..43169bf6f04eef14237c057de5f6daa855f86a7d GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVEpar;uumf=j}~Lufq-!?jQdz z*%~1#dW3n$i%VOlf8b-~e|*)Z>A67QgiAf$T(RuzkAB?aPy6o}XXml(%Gqsh5r^+E zXT-5Fq%#x@bz=LR>W2DMiAhG^1ujcCc)y>2GUEBXBq8P>YbPCcwvSBv|7S{7^~^Wl zJYOb@N;q(T-SF@D@%AgTSMOXt^9Vy!)o+h==O?e9E6tE;DC4ir_(PHdEli%*H%vQL zWmEfy&7fh~xv-NPJgzxcX)XYU;`-OC_peZkoO0#&?kkr=?WGs&V!ZWZr=X7hdd;ot zzn^4?+7%(6^kM}gb4I4j^LYtI_RJ4?7|t;~pp1AYeN|KUW`$gi956L7c)I$ztaD0e F0sz?|yB`1m literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_128x128@2x.png b/assets/AppIcon.iconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9031452388c7b0447a750d686909998edf783bea GIT binary patch literal 1027 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFdQ=Tr4Ar*7p-nGsXaTIa5 zXrAmYq3F)i;&G3mIzimy9_Q}_<(xB*k{$hpeK;~N>)tP3axOk7e5u~bS6l3C)-kKh z{VvCFqo2WH#wZ%@A#mgEJ>~=UbI%(u-`igClcgcK>X%*d&K|BDZw8Acr;qa+=f6vv zmikPEX?8?7xa?#A6dt>aUH{~5(dqC#e-@9kto;~wAyxRZMB2I?(lFv_H zugQO(UZ)$!e)~dZZAHv?Ii`P~zC8Y=Gvn%hO>2g}m;cVqZusBGP~dYq^*yJM471aa cOU^gsy*?XAF5j;21zopr0Iu&#i2wiq literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_16x16.png b/assets/AppIcon.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2e129eca89cfbd9baa1a02b333e02922cc11de09 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`-JULvAr*7pPPXPc?7-u4e~q|I z5r4ak!h}mZTNGp8-EW)Fe|=5NjuYwKCw6!WhB_@UH&8w~Yx2eghbBF>e-O@aQC~HH z;okXU>=PaowcGjr<$i3fz_E`1(?^GUxeC0(M|dYJkU#NTcW1Vu>WX>SPJZQ1+Ysj{ wYp*-w@j>2mOQ+elN-#`KHb0=D#3I2sRbBDY$E6?CfzD#^boFyt=akR{0P%uL)c^nh literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_16x16@2x.png b/assets/AppIcon.iconset/icon_16x16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a4cf89ae850f28de7ef5bcb4fadad1003196c5af GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJm7Xq+Ar*7pUbp5uWWdw#@V;PZ ztBbhb0VZzi>kk#~v^STgKD+4BC26#&qTu8sAGt+8x@9A~UVL0t6069bC^10#n6 z1Cszl!&$B6YA-ofh~8t}pjg8gnz-Hde7l%5PhG>52S1(k)ldCp+c_iZ;eDpRod0-M zaLwoOHVbu#`Omt_fbT_J*2af|Kf)YXrZt`EpLV;z^uJig0gD;i*Bf_Sxp(#5Vhe`5 ajPY^THE$koxO)faXa-MLKbLh*2~7aR5?WsX literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_256x256.png b/assets/AppIcon.iconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..9031452388c7b0447a750d686909998edf783bea GIT binary patch literal 1027 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFdQ=Tr4Ar*7p-nGsXaTIa5 zXrAmYq3F)i;&G3mIzimy9_Q}_<(xB*k{$hpeK;~N>)tP3axOk7e5u~bS6l3C)-kKh z{VvCFqo2WH#wZ%@A#mgEJ>~=UbI%(u-`igClcgcK>X%*d&K|BDZw8Acr;qa+=f6vv zmikPEX?8?7xa?#A6dt>aUH{~5(dqC#e-@9kto;~wAyxRZMB2I?(lFv_H zugQO(UZ)$!e)~dZZAHv?Ii`P~zC8Y=Gvn%hO>2g}m;cVqZusBGP~dYq^*yJM471aa cOU^gsy*?XAF5j;21zopr0Iu&#i2wiq literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_256x256@2x.png b/assets/AppIcon.iconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e6d642ab6925eaa17c6d3444de1f84d2cdb9cbc2 GIT binary patch literal 2373 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzZe)ec|2VlLn`LHy=|CxB|yaC z;`5UV-x{X~-C&%eqs;m{U#Zn$(MC;yfJaJ6Pwo^idH<{Q|EtLQn|bfIF){@CU6*5E z`0<~Gfq{jSfq_Gafk8lpfkDB8fx%${14DxXBLjmY(8i`w1*73GnjS{;#c26JxSX83 z-j?A&qR#0vMQ;@!q;fDc?5(poAEURJse+e<;eg!q(rfGE(`x_cJ$qUC>!uAeOM}voES|06s~H*&-$+Zl{ZGzfRjx1t!|u8M#;1)wKeH)LZamevmWd%@|L=U` z@BGI6{Y(wXyK^?@Y%l*7!BD-Y*0^}z#(&R4H!w2Xc>VF@)AOqp85q9pinN*c=&Aq% tLv?PwT|q^V*l2wkk#~v^STgKD+4BC26#&qTu8sAGt+8x@9A~UVL0t6069bC^10#n6 z1Cszl!&$B6YA-ofh~8t}pjg8gnz-Hde7l%5PhG>52S1(k)ldCp+c_iZ;eDpRod0-M zaLwoOHVbu#`Omt_fbT_J*2af|Kf)YXrZt`EpLV;z^uJig0gD;i*Bf_Sxp(#5Vhe`5 ajPY^THE$koxO)faXa-MLKbLh*2~7aR5?WsX literal 0 HcmV?d00001 diff --git a/assets/AppIcon.iconset/icon_32x32@2x.png b/assets/AppIcon.iconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..68a3d717a79104c5558eb7123c8d09e19819f911 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV6^gdaSW-L^LF+|uOkj349|1V z1v%zy6?yxYWB-9$^&i&fZV}k|xi3X`QO3#*iQi60bF_%ePQ39UNF{K+ytekT8%$=B z2gEj&rm7_;EXS)H9 z@AUWf4Do_(pNdUXjxW&tpx^Xt@`DPdkc3qNx%=PszxyvfL(Rf|Y22K%&wF3iv-7aV z-RpRM{n>j48P^7`95#zg>wo)R+A}z;E$(2A_$zS+tJl`p9Z(OusiSOsAT|*gm<*n- KelF{r5}E)x8!uAeOM}voES|06s~H*&-$+Zl{ZGzfRjx1t!|u8M#;1)wKeH)LZamevmWd%@|L=U` z@BGI6{Y(wXyK^?@Y%l*7!BD-Y*0^}z#(&R4H!w2Xc>VF@)AOqp85q9pinN*c=&Aq% tLv?PwT|q^V*l2wSD84q$M- zm_J2ug@CNrX|6Sn(=8=rG?fgOePwWo|74rq6dwOl_x8_CyghX1br`e=7;{rQ{5 z3^#VK)dPw!1So(=hXxSIz{mt5SvWwXfC7jdl^6{YDkp}5S+>B|X>w%Vcf)<_H>Wdf z_+Bi3x@=$0=5kPi>wgLk@X?eA3XIV_2nvPKVrjHM2Bw11s%W$-0wx30GO^xyg>>zO SWP3|cIpOK*=d#Wzp$P!Q$w1=( literal 0 HcmV?d00001 diff --git a/assets/icon-150x150.png b/assets/icon-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..39bd2c660845842e38dcf7426ef1676c8f19a842 GIT binary patch literal 650 zcmeAS@N?(olHy`uVBq!ia0vp^(?FPm4M^HB7CvHNU<&kfaSW-L^Y)f+mWZQ7!^8E* zT|}loaw?Gi!174&iwI-?+gsW{gpDSb8CEn`zV!TU6Yx9#V~}h3*G>2Cu4fi6yHUmP z%`-x!#nov95P93=8fIGR7@ywsRYY8M&05{lXU@0JJpTAh-z}kQ)25|9`)Kj{dH?3R z3iD!{w`tFTVtl91tZ%oh{cYqQZ=Y*>)|E3m`?TdcOWU)5{uzHxV!t1}dc(TIiI(hN z^KHvlzh+Z9q@<`Lghu4uTYqrZIUC!#*X8aluzeZRSeh2McaEh@+PyvVYNx2K$z5w) zTs-6H@BYYp`{!0xSg?P)v)1@@Rr&rr+vD}`E3E$C-Jo2yCcB{8xOn$hf$6GivNybY qf8oZ##&f1ZVw{N+1*l1M?k_hs-1|S}y*n_CF?hQAxvXd<$Ag}hE&XXd&@9O#!Om-eO9-R1UwfUUfL3a*mnV;WEpXG^f_jkGR zAby>Gape!|*Wb_2JbrxU-N>rX*M!V0HtoBA{#T)WaqYaD@6XLUEST6_Ua{}Y8q?1u zGjGP9ivtVX*tWfY_H%jZ3CcMReH$1p53xPrpp>(R>CPnu{fXxui2&0dgQu&X%Q~lo FCICRh{PF+* literal 0 HcmV?d00001 diff --git a/assets/icon-44x44.png b/assets/icon-44x44.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e859ddbfd3bc3a80bad3fe1447caf763bae440 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8NeHLr)jSkcv5PZy2s(G8AZdSidQ= zr9JMzu}6u?AKyJteY2nb`XCbgPQIV&jHI`>Ru(mgX}b6H9o_ zloQ*qmU}}f<2Jnm*H~}tVu%(;2;Qu974*1db4Of<=|A(7eU)7H8#ul&?}}SATlC7W zKW0Yu1wuPS-rcr;m3ZBz_y0r<<~v$bm@@y&{l33suERTp^cTw*)^Rzv{=3E^c3u8f zdFxM}*gp;1bYJg2?D?R?wJ+VF;bX>s<%yR+^K9q)ukmc|lx1^kO?Pg0fx74&|(CdyfDB literal 0 HcmV?d00001 diff --git a/assets/icon-620x300.png b/assets/icon-620x300.png new file mode 100644 index 0000000000000000000000000000000000000000..1145f7f7a513a5eca742f6a32a86295f36e24470 GIT binary patch literal 1591 zcmeAS@N?(olHy`uVBq!ia0y~yV9H@&VASDY1B%qCzQ4l2!1~YA#WAE}&f7cIc_EGh z4i~EqAD^VGwA>}9$yQ+_bJ>Hpw>QnauEHT``JmTS{I$$V>vbPiU3ryT`*R~3L&KWO zTiF>H9;7z$GAQ_MU}QMLX~xiCagdEcK>Y>-!y^`H28THb%nTjEKx+#}6^#bLXet=Z z2*i{H)%9-~8GfA4t=D@nmBWmo&vO0Dt?|`d6_VQbpm+F4(J=B3%6pTi;@b4Tw6gT#&22`^WrC zyDo73yLp4VL;Zt|-rjch4;yyUvaB@ zzWP3G)%Wt1=GT4QzqT8H|2J_;?53M$)&IYA8~3%Z7VLAkyz+js@zldlxBa?!R4Y~MIsfneezOZ}AEx}frYRYJf09xDN;7+l@9oCrJG1N-#m`x3 jBL6Uuuag-UtYti|q+MO}>RAggU>Q7J{an^LB{Ts5)_1bP literal 0 HcmV?d00001 diff --git a/assets/pdfreader_by_sparsh.ico b/assets/pdfreader_by_sparsh.ico index d9d1283f6f25731f028df63b9573e8132267a0fb..af082cdbb88f413f8bc8b8a388eea60912f947fb 100644 GIT binary patch literal 2974 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 Q)j=g54}P%l-(Tqb4{&T18UO$Q diff --git a/docs/msix-update-validation.md b/docs/msix-update-validation.md index 169932a..d6bdfdc 100644 --- a/docs/msix-update-validation.md +++ b/docs/msix-update-validation.md @@ -1,6 +1,10 @@ -# MSIX Update Validation Guide +# MSIX Update Validation Guide (Archived) -**Purpose:** Validate that MSIX updates work correctly for OpenReader. +> **ℹ️ Historical record.** MSIX beta validation (beta.5 → beta.6) completed +> successfully. v1.2.1 is the first Microsoft Store release candidate — Store-managed +> updates replace sideloaded MSIX update testing. + +**Purpose:** Validate that MSIX updates work correctly for OpenReader (historical). **Test versions:** - v1.2.0-beta.5 (MSIX 1.2.0.5) — first installable MSIX baseline diff --git a/docs/store-submission-checklist.md b/docs/store-submission-checklist.md index ea7edeb..02bd37f 100644 --- a/docs/store-submission-checklist.md +++ b/docs/store-submission-checklist.md @@ -1,11 +1,11 @@ # Microsoft Store Submission Checklist — OpenReader -**Target version:** v1.2.0 stable (MSIX version `1.2.0.0`) +**Target version:** v1.2.1 stable (MSIX version `1.2.1.0`) **Store ID:** `9MXDVW2645LL` **PFN:** `SparshSam.OpenReader_yh0byntbzd2qw` **Status:** 🔜 Ready for submission (privacy policy published) **Privacy policy URL:** https://sparshsam.github.io/pdfreader-by-sparsh/privacy/ -**Upload artifact:** `OpenReader.msix` from v1.2.0 GitHub Release (built by release.yml workflow) +**Upload artifact:** `OpenReader.msix` from v1.2.1 GitHub Release (built by release.yml workflow) --- @@ -24,7 +24,7 @@ Get-AppxPackage SparshSam.OpenReader | Select Name, Version, PackageFamilyName ```text Name Version PackageFamilyName ---- ------- ----------------- -SparshSam.OpenReader 1.2.0.0 SparshSam.OpenReader_yh0byntbzd2qw +SparshSam.OpenReader 1.2.1.0 SparshSam.OpenReader_yh0byntbzd2qw ``` ### 1.2 Manifest Audit @@ -44,7 +44,7 @@ Select-Xml -Path .\msix-check\AppxManifest.xml -XPath "//*[local-name()='Identit - [ ] `` - [ ] `` -- [ ] Version is `1.2.0.0` +- [ ] Version is `1.2.1.0` - [ ] `OpenReader` - [ ] `Sparsh Sam` - [ ] Executable is `OpenReader.exe` @@ -111,7 +111,7 @@ Start-Process "OpenReader" 1. Navigate to **Partner Center** → OpenReader → **Packages** 2. Upload the **unsigned** `OpenReader.msix` from the GitHub Release 3. The Store will automatically sign the package with its Store identity -4. Set `1.2.0.0` as the version in Partner Center (must match manifest) +4. Set `1.2.1.0` as the version in Partner Center (must match manifest) 5. Submit for certification > **ℹ️** Upload the MSIX produced by the GitHub Actions release workflow directly. @@ -150,7 +150,7 @@ Start-Process "OpenReader" | **`runFullTrust` capability** | Store may ask why a desktop app needs full trust | Expected for Win32 desktop bridge apps. Document in submission notes: *"Desktop PDF reader using PySide6 — requires full trust for file system access and window management."* | | **App description claims** | Store may reject if claims are unrealistic | Keep description factual and shipping-feature-only. Remove roadmap items from Store description. | | **Unsplash/mock screenshots** | Store requires real app screenshots | Use actual app screenshots from `assets/` | -| **Version mismatch** | Upload rejected if manifest version ≠ Partner Center version | Verify `1.2.0.0` matches everywhere | +| **Version mismatch** | Upload rejected if manifest version ≠ Partner Center version | Verify `1.2.1.0` matches everywhere | | **Store ID reuse** | Cannot reuse Store ID for a different app | Reserved ID `9MXDVW2645LL` is tied to OpenReader — do not reassign | ### 4.2 Certification Notes for Submission @@ -203,7 +203,7 @@ Get-AppxPackage SparshSam.OpenReader | Select Name, Version, PackageFamilyName ```text Name Version PackageFamilyName ---- ------- ----------------- -SparshSam.OpenReader 1.2.0.0 SparshSam.OpenReader_yh0byntbzd2qw +SparshSam.OpenReader 1.2.1.0 SparshSam.OpenReader_yh0byntbzd2qw ``` ### 5.3 Functional Smoke Test @@ -249,7 +249,7 @@ Write-Host "Publisher check: $($matches.Count -gt 0 ? 'PASS' : 'FAIL')" - [ ] Prepare Winget manifest for `SparshSam.OpenReader` (optional, medium priority) - [ ] Monitor Partner Center certification report - [ ] After acceptance: test Store install on clean Windows VM -- [ ] After acceptance: test Store upgrade over existing sideloaded beta.6 +- [ ] After acceptance: test Store upgrade over existing sideloaded installation - [ ] Update `README.md` to reflect Store availability - [ ] Update `docs/windows-distribution.md` with Store channel details diff --git a/main.py b/main.py index 79cc06f..a1e4d5e 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ ) -__version__ = "1.2.0-beta.6-dev" +__version__ = "1.2.1" GITHUB_REPO = "sparshsam/pdfreader-by-sparsh" IPC_SERVER_NAME = "OpenReader-IPC" RECENT_FILES_MAX = 10 @@ -2784,12 +2784,6 @@ def _show_about(self): ver_label.setAlignment(Qt.AlignCenter) layout.addWidget(ver_label) - # MSIX update validation label (beta.4) - msix_label = QLabel("

" - "MSIX update validation — beta.6

") - msix_label.setAlignment(Qt.AlignCenter) - layout.addWidget(msix_label) - layout.addSpacing(8) # Description diff --git a/packaging/msix/AppInstaller.xml b/packaging/msix/AppInstaller.xml index bf14556..bd9ae0c 100644 --- a/packaging/msix/AppInstaller.xml +++ b/packaging/msix/AppInstaller.xml @@ -18,7 +18,7 @@ diff --git a/packaging/msix/AppxManifest.xml b/packaging/msix/AppxManifest.xml index 9fa6913..8196853 100644 --- a/packaging/msix/AppxManifest.xml +++ b/packaging/msix/AppxManifest.xml @@ -24,7 +24,7 @@ + Version="1.2.1.0" /> OpenReader diff --git a/packaging/msix/README.md b/packaging/msix/README.md index c4d9778..e429525 100644 --- a/packaging/msix/README.md +++ b/packaging/msix/README.md @@ -134,13 +134,11 @@ MSIX follows a `major.minor.patch.build` version scheme. | Git Tag | MSIX Version | Description | |---------|--------------|-------------| -| `v1.2.0-beta.1` | `1.2.0.0` | Beta release | -| `v1.2.0` | `1.2.0.0` | Stable release (same as beta) | -| `v1.2.1` | `1.2.1.0` | Patch release | +| `v1.2.0` | `1.2.0.0` | Initial MSIX release | +| `v1.2.1` | `1.2.1.0` | First Microsoft Store release candidate | The CI workflow automatically extracts the version from the Git tag and injects it -into the manifest as `{tag}.0` (padded to 4 parts). Source builds use `1.2.0.0` -as the development version. +into the manifest as `{tag}.0` (padded to 4 parts). ## Visual Assets @@ -217,22 +215,16 @@ identity exactly: `CN=E6186421-BF8A-47E0-A89C-0F513DFF91C0`. # 1. Install the test certificate (requires admin) .\packaging\msix\install-test-cert.ps1 -# 2. Install beta.3 MSIX (baseline) -# Double-click OpenReader.msix from v1.2.0-beta.3 release - -# 3. Install beta.4 MSIX (update test) -# Double-click OpenReader.msix from v1.2.0-beta.4 release -# Windows should perform an in-place upgrade +# 2. Install the MSIX package +# Double-click OpenReader.msix from the target release ``` ### What to Verify -After installing the beta.3 MSIX and updating to beta.4: - -- [ ] MSIX installs without Developer Mode -- [ ] Update is **in-place** (not side-by-side) -- [ ] Package Family Name remains `SparshSam.OpenReader_yh0byntbzd2qw` -- [ ] About dialog shows the beta.4 release label +- [ ] MSIX installs without Developer Mode (when test-signed) +- [ ] Package Family Name is `SparshSam.OpenReader_yh0byntbzd2qw` +- [ ] New branding appears in Start Menu and taskbar +- [ ] About dialog shows the correct version - [ ] Previous settings (theme, recent files) persist - [ ] No duplicate application entries in Start Menu or Apps list - [ ] PDF file associations remain intact diff --git a/tools/create_icon.py b/tools/create_icon.py index 9ea47dd..13a3986 100644 --- a/tools/create_icon.py +++ b/tools/create_icon.py @@ -1,96 +1,121 @@ -from pathlib import Path -import argparse -import struct -import sys +#!/usr/bin/env python3 +"""Generate all app icon assets from a source PNG. -from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QPointF, QRectF, Qt -from PySide6.QtGui import QColor, QFont, QImage, QLinearGradient, QPainter, QPainterPath, QPen -from PySide6.QtWidgets import QApplication +Usage: + python tools/create_icon.py --source [--ico assets/icon.ico] +Generates: + - Windows .ico (16, 24, 32, 48, 64, 128, 256 px) + - MSIX-branded PNGs (44x44, 71x71, 150x150, 310x150, 620x300) + - macOS .iconset directory (for iconutil -> .icns) +""" -ROOT = Path(__file__).resolve().parents[1] -ASSETS = ROOT / "assets" -ICON_PATH = ASSETS / "pdfreader_by_sparsh.ico" - - -def make_png(size: int) -> bytes: - image = QImage(size, size, QImage.Format_ARGB32) - image.fill(Qt.transparent) +import argparse +import io +import struct +import sys +from pathlib import Path - painter = QPainter(image) - painter.setRenderHint(QPainter.Antialiasing) +from PIL import Image - scale = size / 256 - shadow = QPainterPath() - shadow.addRoundedRect(QRectF(42 * scale, 24 * scale, 158 * scale, 210 * scale), 22 * scale, 22 * scale) - painter.fillPath(shadow.translated(7 * scale, 8 * scale), QColor(0, 0, 0, 44)) +def load_source(source: Path) -> Image.Image: + """Load source PNG, ensure RGBA mode.""" + img = Image.open(source) + return img.convert("RGBA") - page = QPainterPath() - page.addRoundedRect(QRectF(38 * scale, 20 * scale, 162 * scale, 214 * scale), 22 * scale, 22 * scale) - painter.fillPath(page, QColor("#f8fafc")) - painter.setPen(QPen(QColor("#cbd5e1"), max(1, int(3 * scale)))) - painter.drawPath(page) - fold = QPainterPath() - fold.moveTo(158 * scale, 20 * scale) - fold.lineTo(200 * scale, 62 * scale) - fold.lineTo(166 * scale, 72 * scale) - fold.quadTo(158 * scale, 72 * scale, 158 * scale, 64 * scale) - fold.closeSubpath() - painter.fillPath(fold, QColor("#e2e8f0")) +def make_png_square(img: Image.Image, size: int) -> Image.Image: + """Resize to a square, cropping centered if needed.""" + w, h = img.size + if w != h: + # Crop to center square first + min_dim = min(w, h) + left = (w - min_dim) // 2 + top = (h - min_dim) // 2 + img = img.crop((left, top, left + min_dim, top + min_dim)) + return img.resize((size, size), Image.LANCZOS) - accent = QLinearGradient(QPointF(54 * scale, 142 * scale), QPointF(184 * scale, 214 * scale)) - accent.setColorAt(0, QColor("#ef4444")) - accent.setColorAt(1, QColor("#b91c1c")) - painter.setBrush(accent) - painter.setPen(Qt.NoPen) - painter.drawRoundedRect(QRectF(54 * scale, 142 * scale, 132 * scale, 58 * scale), 16 * scale, 16 * scale) - painter.setPen(QPen(QColor("#94a3b8"), max(1, int(7 * scale)), Qt.SolidLine, Qt.RoundCap)) - for y in (82, 104, 126): - painter.drawLine(QPointF(62 * scale, y * scale), QPointF(156 * scale, y * scale)) +def make_png_rect(img: Image.Image, width: int, height: int) -> Image.Image: + """Resize to a rectangle, cropping centered if needed to match aspect ratio.""" + src_w, src_h = img.size + target_ratio = width / height + src_ratio = src_w / src_h - font = QFont("Segoe UI", max(10, int(44 * scale)), QFont.Bold) - painter.setFont(font) - painter.setPen(QColor("white")) - painter.drawText(QRectF(54 * scale, 137 * scale, 132 * scale, 66 * scale), Qt.AlignCenter, "S") + if abs(src_ratio - target_ratio) > 0.01: + # Crop source to target ratio + if src_ratio > target_ratio: + # Source is wider, crop sides + new_w = int(src_h * target_ratio) + left = (src_w - new_w) // 2 + img = img.crop((left, 0, left + new_w, src_h)) + else: + # Source is taller, crop top/bottom + new_h = int(src_w / target_ratio) + top = (src_h - new_h) // 2 + img = img.crop((0, top, src_w, top + new_h)) - small_font = QFont("Segoe UI", max(7, int(22 * scale)), QFont.Bold) - painter.setFont(small_font) - painter.setPen(QColor("#991b1b")) - painter.drawText(QRectF(58 * scale, 204 * scale, 118 * scale, 24 * scale), Qt.AlignCenter, "PDF") + return img.resize((width, height), Image.LANCZOS) - painter.end() - data = QByteArray() - buffer = QBuffer(data) - buffer.open(QIODevice.WriteOnly) - image.save(buffer, "PNG") - return bytes(data) +def write_ico(img: Image.Image, path: Path, sizes=(16, 24, 32, 48, 64, 128, 256)): + """Write a multi-resolution .ico file from source image.""" + path.parent.mkdir(parents=True, exist_ok=True) + pngs = [] + for size in sizes: + resized = make_png_square(img, size) + buf = io.BytesIO() + resized.save(buf, "PNG") + pngs.append(buf.getvalue()) -def write_ico(path: Path, sizes=(16, 24, 32, 48, 64, 128, 256)): - pngs = [(size, make_png(size)) for size in sizes] header = struct.pack("= 256 else size + height = 0 if size >= 256 else size + directory.extend( + struct.pack( + " {path} ({len(sizes)} sizes)") + + +def write_msix_assets(img: Image.Image, output_dir: Path): + """Write MSIX brand images at the sizes the manifest declares.""" + manifest_sizes = [ + ("icon-44x44.png", 44, 44), + ("icon-71x71.png", 71, 71), + ("icon-150x150.png", 150, 150), + ("icon-310x150.png", 310, 150), + ("icon-620x300.png", 620, 300), + ] + + output_dir.mkdir(parents=True, exist_ok=True) + for name, w, h in manifest_sizes: + path = output_dir / name + if w == h: + resized = make_png_square(img, w) + else: + resized = make_png_rect(img, w, h) + resized.save(path, "PNG") + print(f" MSIX asset -> {path} ({w}x{h})") + + +def write_macos_iconset(img: Image.Image, output_dir: Path): + """Write macOS .iconset directory (for iconutil -> .icns on macOS).""" icon_sizes = { "icon_16x16.png": 16, "icon_16x16@2x.png": 32, @@ -103,22 +128,51 @@ def write_png_iconset(path: Path): "icon_512x512.png": 512, "icon_512x512@2x.png": 1024, } - path.mkdir(parents=True, exist_ok=True) - for file_name, size in icon_sizes.items(): - image = QImage() - image.loadFromData(make_png(size), "PNG") - image.save(str(path / file_name), "PNG") + output_dir.mkdir(parents=True, exist_ok=True) + for name, size in icon_sizes.items(): + resized = make_png_square(img, size) + path = output_dir / name + resized.save(path, "PNG") + print(f" macOS iconset -> {output_dir}/ ({len(icon_sizes)} files)") -if __name__ == "__main__": - app = QApplication.instance() or QApplication(sys.argv) - parser = argparse.ArgumentParser(description="Create app icon assets.") - parser.add_argument("--ico", default=str(ICON_PATH), help="Path for the Windows .ico output.") - parser.add_argument("--png-iconset", help="Optional macOS .iconset directory to create.") + +def main(): + parser = argparse.ArgumentParser(description="Create app icon assets from source PNG.") + parser.add_argument("--source", required=True, help="Path to source PNG (1024x1024 recommended)") + parser.add_argument("--ico", default="assets/pdfreader_by_sparsh.ico", help="Output .ico path") + parser.add_argument("--msix-dir", default="assets", help="Output directory for MSIX PNGs") + parser.add_argument("--macos-iconset", default="assets/AppIcon.iconset", help="Output macOS .iconset dir") args = parser.parse_args() - write_ico(Path(args.ico)) - print(args.ico) - if args.png_iconset: - write_png_iconset(Path(args.png_iconset)) - print(args.png_iconset) + source_path = Path(args.source) + if not source_path.exists(): + print(f"ERROR: Source not found: {source_path}") + sys.exit(1) + + print(f"Loading source: {source_path}") + img = load_source(source_path) + print(f" Source size: {img.size}") + + root = Path(__file__).resolve().parents[1] + + # 1. Windows .ico + ico_path = root / args.ico + print(f"\nGenerating Windows .ico...") + write_ico(img, ico_path) + + # 2. MSIX brand PNGs + msix_dir = root / args.msix_dir + print(f"\nGenerating MSIX brand assets...") + write_msix_assets(img, msix_dir) + + # 3. macOS iconset + iconset_dir = root / args.macos_iconset + print(f"\nGenerating macOS iconset...") + write_macos_iconset(img, iconset_dir) + + print(f"\n✅ All icon assets generated from {source_path.name}") + + +if __name__ == "__main__": + main() diff --git a/tools/create_msix_placeholder_pngs.py b/tools/create_msix_placeholder_pngs.py index 856a60f..58b5904 100644 --- a/tools/create_msix_placeholder_pngs.py +++ b/tools/create_msix_placeholder_pngs.py @@ -1,39 +1,62 @@ -"""Generate minimal placeholder PNGs for MSIX packaging. +"""Generate MSIX brand image assets from the project's source icon. -Creates 1x1 pixel PNG files at specified sizes. These are real valid PNGs -that MakeAppx accepts. Replace with proper icon assets before production. +Creates MSIX-required PNGs at the sizes declared in AppxManifest.xml. +Uses the project's source icon for real brand images instead of placeholders. """ -import struct -import zlib + import sys from pathlib import Path +from PIL import Image -def create_png(filepath: Path, width: int, height: int): - """Create a minimal valid 1x1 pixel PNG.""" - # PNG signature - signature = b'\x89PNG\r\n\x1a\n' - - # IHDR chunk: width, height, bit_depth=8, color_type=2(RGB) - ihdr_data = struct.pack('>IIBBBBB', width, height, 8, 2, 0, 0, 0) - ihdr_crc = zlib.crc32(b'IHDR' + ihdr_data) & 0xffffffff - ihdr = struct.pack('>I', 13) + b'IHDR' + ihdr_data + struct.pack('>I', ihdr_crc) - # IDAT chunk: minimal compressed pixel data (1 pixel red) - raw_data = b'\x00' + b'\xff\x00\x00' # filter byte + RGB red - compressed = zlib.compress(raw_data) - idat_crc = zlib.crc32(b'IDAT' + compressed) & 0xffffffff - idat = struct.pack('>I', len(compressed)) + b'IDAT' + compressed + struct.pack('>I', idat_crc) +def make_png_square(img: Image.Image, size: int) -> Image.Image: + w, h = img.size + if w != h: + min_dim = min(w, h) + left = (w - min_dim) // 2 + top = (h - min_dim) // 2 + img = img.crop((left, top, left + min_dim, top + min_dim)) + return img.resize((size, size), Image.LANCZOS) - # IEND chunk - iend_crc = zlib.crc32(b'IEND') & 0xffffffff - iend = struct.pack('>I', 0) + b'IEND' + struct.pack('>I', iend_crc) - filepath.parent.mkdir(parents=True, exist_ok=True) - filepath.write_bytes(signature + ihdr + idat + iend) +def make_png_rect(img: Image.Image, width: int, height: int) -> Image.Image: + src_w, src_h = img.size + target_ratio = width / height + src_ratio = src_w / src_h + if abs(src_ratio - target_ratio) > 0.01: + if src_ratio > target_ratio: + new_w = int(src_h * target_ratio) + left = (src_w - new_w) // 2 + img = img.crop((left, 0, left + new_w, src_h)) + else: + new_h = int(src_w / target_ratio) + top = (src_h - new_h) // 2 + img = img.crop((0, top, src_w, top + new_h)) + return img.resize((width, height), Image.LANCZOS) def main(): + root = Path(__file__).resolve().parents[1] + source_path = root / "assets" / "pdfreader_by_sparsh.ico" + output_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "assets" + + # Load from .ico or find source PNG + if source_path.exists(): + img = Image.open(source_path) + # .ico loads first frame; get full size + img = img.convert("RGBA") + else: + # Fallback: try a source PNG + png_path = root / "assets" / "icon-150x150.png" + if png_path.exists(): + img = Image.open(png_path).convert("RGBA") + else: + print("ERROR: No source icon found. Run tools/create_icon.py first.") + sys.exit(1) + + output_dir.mkdir(parents=True, exist_ok=True) + sizes = [ ("icon-44x44.png", 44, 44), ("icon-150x150.png", 150, 150), @@ -42,15 +65,14 @@ def main(): ("icon-620x300.png", 620, 300), ] - output_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("assets") - for name, w, h in sizes: path = output_dir / name - if path.exists(): - print(f"SKIP {path} (exists)") + if w == h: + resized = make_png_square(img, w) else: - create_png(path, w, h) - print(f"CREATED {path} ({w}x{h})") + resized = make_png_rect(img, w, h) + resized.save(path, "PNG") + print(f"GENERATED {path} ({w}x{h})") if __name__ == "__main__": From d014559db3fb5c78b037c0e9423111921e88b4fa Mon Sep 17 00:00:00 2001 From: Sparsh Sam <110058692+sparshsam@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:39:02 -0400 Subject: [PATCH 2/3] Add Pillow dependency for icon generation in macOS CI Co-Authored-By: Claude --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a4df59e..12d8ac9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ PySide6==6.11.1 PyMuPDF==1.27.2.3 pyinstaller==6.20.0 +Pillow>=10.0.0 From 6994c086dd5df9d7466c7b174634c52796819d76 Mon Sep 17 00:00:00 2001 From: Sparsh Sam <110058692+sparshsam@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:40:33 -0400 Subject: [PATCH 3/3] Fix macOS CI: add source icon to repo, update workflows for new create_icon.py CLI - Copy source PNG as assets/app-icon-source.png (canonical source) - Update build-macos.yml: --source assets/app-icon-source.png --macos-iconset - Update release.yml: same fix for macOS build step - Add Pillow to requirements.txt for macOS CI Co-Authored-By: Claude --- .github/workflows/build-macos.yml | 2 +- .github/workflows/release.yml | 2 +- assets/app-icon-source.png | Bin 0 -> 7312 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100755 assets/app-icon-source.png diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index c8bbac4..b244e37 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -48,7 +48,7 @@ jobs: - name: Build app bundle run: | - QT_QPA_PLATFORM=offscreen python tools/create_icon.py --png-iconset assets/AppIcon.iconset + QT_QPA_PLATFORM=offscreen python tools/create_icon.py --source assets/app-icon-source.png --macos-iconset assets/AppIcon.iconset iconutil -c icns assets/AppIcon.iconset -o assets/pdfreader_by_sparsh.icns pyinstaller --windowed --onedir --noupx --name "OpenReader" --argv-emulation --icon "assets/pdfreader_by_sparsh.icns" main.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd12fc4..7e49f0b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -263,7 +263,7 @@ jobs: - name: Build macOS app run: | - QT_QPA_PLATFORM=offscreen python tools/create_icon.py --png-iconset assets/AppIcon.iconset + QT_QPA_PLATFORM=offscreen python tools/create_icon.py --source assets/app-icon-source.png --macos-iconset assets/AppIcon.iconset iconutil -c icns assets/AppIcon.iconset -o assets/pdfreader_by_sparsh.icns pyinstaller --windowed --onedir --noupx --name "OpenReader" --argv-emulation --icon "assets/pdfreader_by_sparsh.icns" main.py diff --git a/assets/app-icon-source.png b/assets/app-icon-source.png new file mode 100755 index 0000000000000000000000000000000000000000..c431a92e41600389f5a74e7d2f87052e778ff0b8 GIT binary patch literal 7312 zcmeHLPiP!f7=N3lF$uIyjr7n$87pSQIy--o*=DD^+io(gS(*)+Y;C-xnVosN8L~Sw znVIcwBQ-??Enf5>q$)NC@l>kCLJzH&ViA!Ff(20#ycMKrOF<7dHGXeqyV6j@HT!(L#xRHAmBDvCrgj(X^86?Mt7rjAUIz z9GQ3V#Qa!JpP$v^2D>lSov0~5P{zK-)XF8xQ))>z;8j2-%{&Vr*C;6C>cKh$YDsp+ z_Z@}jtJP|>+8?#uA}_|{abA#kN#X#(dB-hZt8te11Tb_n0f&k`-8CKGv@M2kYI(cj zCt0?esZ*zY-!=0McFT)uK}&_GL}AjrW_Y|9709p{CK2M5wBeCz*vSN*3o2o)WClGs zn(N`*aR(>aaqQU@SH}SZQL2*+yIAwFk&*-{#tAV_98B;Jy&`?AE2x^UrC#crtJwZt z!`4GV2@5qnlHC*ctg@L28=eV^iih16;Ni4doFB^$#gDWC^R8wYEyy8)JS4i6L(hlp zCmQ1mM$$UC12sQmq+)U`mXFIKhYK?11_rRmX@V|uvLyA(xFEy^^Z5o#sM1PvFb^!U zBugpN=Wz*_vE>sB66+RZ=!$RpCA@Aw>5|`SKj0WjNwbPcw#FH_pjApf zn>uU-Y6fFq&n#LD$H0XDGO;>Wf^X{Pe@x41@;u@jK>#3L0WV-q{q;FPO-Wb$8QV=w z5W}BgCbV*bfF3l1KL&&SPm2oyqp`Xzqo`?4lWzh{34F?SbGBVd4QtjhjR8kVHaQ1F z93t7|1WaJ~$tGh@@DDTn?dZb^L6m}c#W$iFR2p#+TM!IghIyVc(D+ER0!^Cq zs6Y=$^jJtw&GcKL=?m%Oo4?+>Q9Tox`elWMh57vDh1K7U!f$u)uRaNX+7^-U$&Zjb zM|)A>*t_tdWwtF}vw9H{vS{)9GQ^V)yHNk7EJDizk?F$4E`%2Q(6yT}p!&A`}xXh$r*at?m% OqRfFY_1w_(D}Msb7h19a literal 0 HcmV?d00001