From 20a5d6dffa1f330a431dac7497e30e324072156d Mon Sep 17 00:00:00 2001 From: rxm Date: Mon, 15 Jun 2026 04:09:56 +0200 Subject: [PATCH] fix(brand): crisp taskbar & tray icons via optical-sizing .ico The taskbar and tray showed a pixelated icon: the only runtime icon source was the 1024px dark-tile PNG, downscaled by Windows/Electron to ~16-32px. At that size the droplet shrank, the white arrow collapsed, and the near- black tile vanished against the dark taskbar. electron-builder can only downscale a single PNG, so it can't fix this either. Fix = optical sizing with real multi-size .ico files: - icons/icon-small.svg: high-contrast violet tile for small sizes - build-icon.mjs now renders both SVGs (one offscreen window + crop, then high-quality resize) and packs Windows .ico files dependency-free: icons/icon.ico 16/24/32/48 violet + 64/128/256 dark tile icons/tray.ico 16/24/32 violet - build.win.icon -> icons/icon.ico (crisp taskbar/installer/exe) - BrowserWindow icon + Tray now use the .ico on Windows (icon.png elsewhere) - icon.png (1024 dark tile) unchanged; still build.icon for macOS/default Co-Authored-By: Claude Opus 4.8 (1M context) --- icons/icon-small.svg | 7 ++ icons/icon.ico | Bin 0 -> 32251 bytes icons/tray.ico | Bin 0 -> 3704 bytes package.json | 1 + scripts/build-icon.mjs | 159 +++++++++++++++++++++++++++-------------- src/main/index.ts | 32 ++++++--- 6 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 icons/icon-small.svg create mode 100644 icons/icon.ico create mode 100644 icons/tray.ico diff --git a/icons/icon-small.svg b/icons/icon-small.svg new file mode 100644 index 0000000..f483280 --- /dev/null +++ b/icons/icon-small.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/icon.ico b/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..31fad416b499b1a10e7823272a29231cd4be6912 GIT binary patch literal 32251 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�%Se>PV``shz+b3}A%Pm=NG$`B6>dKRco=|T*qY#J*rnr3EC3dmtr5oq zzJ+`PBCboaKpnzdM^b*995JZx{Y81oIJoWk=R&b!mG(`<3+x*Q^v3T9L7#XYl1p~C z4`I|I3TTCGPMuOqn*W$T2{SO8V`8$EBTp`g<$BPU{+ux?a(k>$1i3Duiu|~fB(E?vmw?OfS#q{Ech>!b5DNUF zmGNw_eG-kJDhLM_Ly;vGfTWKJ$GCS9S(SO!pwA{Z9P_by^^W6dif;ysR3xz_-nQ)O+pLb-T3uRGnl&s0(P0 zJkh-DFSFUo1};9)Tj4aKTN&gk4Gnq}I;wb(bu2QRr{O=Z^z&a}>&-c4R?nKpMBIV8=3(s-!-JWicBMy6eL zgT@%Cfku5sLms}A%IRmYO$`~<2Xg}wo~_YKenDy1BxChfu+`UL-~5J6WX*7R*v5Xw z#h10rBE4DjKi&`e3D^2r;mixGv*Lxgi`|OczI-lf3E)xBx8?@r8x$yBGo1CIyW$!! zciDdI7Kxo-rqV6ZykKXZiJ#V;)|)m!bLpyL)WNFkJcnoVgSla&OJ{Q1%<->2kD7k8=8FXC7kRasMBPOZ}qF6&UJH8%5NKxxCi|&zlWjxs9sZ; zb_<%9;5pMCqm*#nJ3p>P#!%MZTNoYPJ3A$=GlhZAG+h3-l^lqM;w{b_J58a zLj0~Nh!3Scvi;KO{PC=fM7u)Pl!3KOA|AlxwC(=$WjSZDz6GKjSFac~SEvDG8I>}{{CvL35e_fKXsFSX1D7F- zrrK`&WKCYDkzY*i8*x$tp();M-jCNY-qKuhCu6ncSxvT@1_ftAiwjRcaE`I-L}kaHfR!96V)e?Wow$}ISwbI6VQF>$hl zCLo#M{oCk(!L(*0B^TgCbESi)`^oD@xWq!EqRu% z6Y3mp85|0$``7{_BT-c>VxzBX_9HH~PyH6SMsNpUPm;^`$N(wz$RN5l1qVraV<_1S z7ofW#*j{oWr?hI(mm~brdYP#KZpRnu($kK7VQsyLZ%P~t{m3s%mF$9|hh z5^*Z*u{gW#uhgtA5btn89q}@J;%yrIZU=T*Xxc{fs#tVgE4x_E2R|h3lsMCMb^M0} zLq`vPczU0fs0JSsD_#}^>lw#$#C;qvL3jDTJmfg~DYT(<5C$m8@NpW>l%lk6!f}_B zB86QOFv%p(E}g}J7`f>2KK_MEwmwQ$fEa}RqrHx?1PheqW6V1lmDNfLswnaca_0i& zPkCp}H%NHz>?|V$hfTTD|GSYNMOq$BLB=x2X2RCb;9E7mQ~)&(ydB2c{>iId{<+d! zs7%26#Ix(@sr5Odz`911&rH(^FUS26n)hS2!+1cgv14pdIpZd$uWSyybGbuRye9$r z&f;8D%WkLom%XO_Lwg3p3tgD%Ydr9i72^3`p!Dk9l5lo>MQOidZRhR16?R&dyDaxv zB+_}@lLU=4@U{x{Er*A3=J*ZKR|o76+X4POGd06SMb-lh-Y1@B=K7uGeI?@gOgKr- z11Kl2+C|zuoo;=^}zGK+y51tk#i#D950!; ztD;W#5kxVrg?H$xd@|GYfoU~gr8NO&cZ62s%y5`$hc$<4$6v%2x%7XKib47jQjbz% zgjq$_#OjNl*6Cz!E3p8l14WGX_Y4~*(NY7vH0NR)^S z^OHp6TjJq`$25{~4&Pmxj=HN7d5i0He+@B-EJ9&s|s!xDCmOKWp24}w$N+_h^RM9XJ2{6Y=`~ zXoUY@qZ*v&P0|(Q^eL5`DFTl)dHUN-ue~x?I9QwF4uC$g)7Y$WiP|HkRt6_EVi4e& z$H}2{D0PhrK~)Uf;~&_uFumnA7sKO4gas}WRQs>-kMmg-T;E*vAD3htUrc;on`5Nz zt0@*+{<$i?!>6c=mnPthtN6Fi_P#lJ32dd7+R%|q9J|X-Sv8fUK4c<_32ZIo(xC3U zousk`O3X2e33soT{0umKlG*b5!|utS)}!X(I$3s?&TuBX7oydc9{Amg`)nT> za_nec`;wOY()*%haHxnaQ(U7ACzNG@QTk%)23&!xcSSNxL^^*DS!H+%saA!GuDcLTyJ(x~wWDQBAM&oYti0GKR0?f;Ayb zN}2%jsD=Ghi9tba8{h|r>wpWx!bFwu6rjF`?9HD475Vv(vOg=~Fkd1XT^C?6ewJ7g zgk8QP&e~iIXKr4gF%v0)qeN*(|u=ubj16~pK(AWm4}juSmR;JU!=-XTu!CDwb=yB7F5i~{kDN%KaB zWUL0(jC@`|{d@apgWo6WgXVX`6HshK@(A@^v&*jDz21-vCTUiYmk1l1 zKz0s&B(MzvTPV|KrWDwxdjpnLCz6~s>pttM4N#u3w)V2{0Ac}*IYn#GSG(eP7u2uv zQXB^3cjFlpk{61^z(knm!_&eiPED<5HwKzckDX&EDd>WiD;(GUe*!PRqZ?S~cv)zKF~859EATHjl7TB3gal64T>8+N zoWL>oc>m7$lc&<(FM8AemVr7B<$Nh(ymC*>&SR= z&q+y1LWL8Ga{W`tpVZ)m9DopCYCp;G_biOW~L6o_BjRAJnM}LKNG(K;v4jQP_(8xyJH0R*$zaDze zMo6h8xQOonM9eYThR2Z>ab4V|jDIzvc9ER9aJrj5;!l<^U6Hl7#lmqTma3jdh2MK(b0 z3g{EPXGTa`)jLZ}m@7yV@&K50J&J|C^LdKqjN3$?pIFo=pP4T=qw{n{^wPa;S+(n=j?B=BuW{ z=BsoM?lxW$)kEA)o$#z0Q1E4sWVeAF{T?`4-i()7K;i*_MjLBBQiu~Luto>$^i$8X z*~Vz>!tJ5m{|pXHNu7_ma~4jm&-$hz$?=<$WfH)M8VCsHvyes;v?T?)Z8z9V{!aRN zoy>b4czJjRZ>|Qd_>1NSCOheMQ$cqK5rwrlo^Su6GTs4zn8G1rM?NQP&5>=+Nm;66McmObghF%NhFn?y_`yC?zi)chmPg;DLhrQlx_IEup z<7|YO-gzr2sadG!_W1`1;10m^XDP)i#|olBKVQKPn0h5@`gg%R>~UhkZxOu`0PX9o zA=&pX2Yr%uIQLw9*Ywad>wpWM1H3jq?jQr~u3`%S6D1E2&P7=+ zDzoen?<@gh1UdB+V>4rv!>=dCwCXVQK!IA+(=P&^j9SPI{7TQR)Li>eyfZdA)=`(Y z?}_-&6I4oV3lWD)PWH>K!LsiN{hNSnE=KqQukNYKv|dfO&vG&(S?e&wf0e+}^p|tl z3fy$$2>bcA{{?PAb#6DxhtG&4(=!x_jsL7KdxxjyF7VwmfNY|LB}++^OZn`O=mq=> zp4*=M0K5CQ(;~^IjO1}H`YZs5)G<)cS|GM@me`i*%3_3yNq@d-{I+<6G=I5h`NFgr z`i=cg0xN4V6oq-&ISDd>AX5G8>nIig`Ke%pZhzKeHT*JLMF-f))0H|a6Y@1<3^UL8 z07`Tns+w}O6z~fELysG&#>fVn^v_LJOqhnextOv!A_sw(NMnDM7a%?BwCfVED$@d` zc^je;^)xi*pHh69s{`<+@@j{zT77_*n%4HD(Rw8n6urP zgp1ua?i$yN?Sum~;g)0z=M;Z18aZw^QqwC$>;aXI@cqi>d@J*>@VOWlPDJsKXkZU< zJC$1OG>#xJpmdq|b3SVNBy3)k9?ROicj49y?b5d@{#w$8czz?OjQB;7NQmyE3c^T zm|&M7b(IW8Xt3C?!H7S;E8!0SC&9pTlEsU`a^HWF@54*}z|is0p(>;DE!GWV?V;yM zi)d*tCS7a|-ECt;c;5VxA4a%5_PmkGb{oJi-N!gor>`5>w)?N@p(nY$vm1b*s^iqS3WBNaHQbToqmKot&@RPEI5I9l#XtWwnTzxEFK`~WxF{@HE_>EEX)#p|(M!7N?zHn_WUr@0LL3PRA zV>A2Q>-(e^b(rjDUHBE%DT?Z>gU30ylc5)BiwQCQTT8@ z# ze{7neKc%ht*j#RV4@!Elf!rW~y9hCC3&WV-3|A?S=cc6i6Bj;=OR>ODVSI?{H2eLw zs}6OrOx(N_`pLxrZ{*ZK)szBNk_+^HtWu~=CvlhA)w#c&jp@8-$*u>o&fD}q1%r+T zUE@*wUz(&IJOz=R|~bqr$fSOTKgeoVqvo z&6sNg=C^@-*YMngadZYOFNP7GF&upSNBQp7%LnKZ{ED61E3(RD2-2Kr)tR4ugQuhh zhd=ls_K2(dsj8Fe5#D1=ffD&Fc$RhZY&brOuL9osAo>o5+N%x+I;##x+6x0;a)|+2 zMn^(kaSzbTYK@RXckw8n;1Y^O_rt&!=(esph8r6-Fn~@u->@yHanGabIXj2&zZ)Nu z&U)vyFW5thout@GsW{pXrrDdeVkKL?6J+tYs`9#ael?tL@jx34B%5t*)knNZRTEL^ zs2AT%e$`xk>t#|I2Smb;Mfz>N&fvg!_u)PB7+@5MC247dQ-vgJtbL%DEk$K0oiy0Q z)VAsNi#-MaKZSJ162sCWmN4zfWf=0aA4t{}*aCW8qx5RV+N0f#CV3y-;lZed4^u*| zM^wWXH3!je{xL%5!bW?Jzp4NrG8sj)sd&s@N*JxN9yFLTAYnc0e}r792{=eRnS%wh zhhP76(K=j$crWWS&m4O*^yBDBey9_2Io&)ftUZ2^2~F- z>VlT91peqBIAY*!XwH^tNd8MJy=$daxYU#C}m{ewAv2pdBU zTmhehzn$-GA%Rf4LCpIodchmtN_ifW#vGHiODy!aB58eo!Xz|xE0ky zj-Yk4%;!1hnmJhpOF$!LfUN~Jt;Qa{JepskGB~36wSD|4wxVRTY*KAIXf9^Fb;w#g z0Kd^)8`j;$yV=4Cm<;^khDV@x;BWGHfBD*{%0RAlQqrki?tywtVkwwqI{89&DK_w| z^`^`lKw%2$So@DD6*^TLnkaq3r;>fxU%rE{Kj;mMrOb3SMIo$RbF`F2iua(&lxCYZxFUSz;$qg zY)ML)Wgs;=_Z6?#^^b<`BRgk#2j<($JYTEg-NxI_2Ak+8`RmBSS7(pIS{-Cb&W}?( z8KsfesLsnkHrqLB{I%w(+`})%pDgj!;>M_f-bQ#|?7IPxXB)bTJIlcP!EEeq7D#@| za9L*b-Sr^h1N*<*U+L}!QiGU2P1<&SbFDBiKum?j>6gEU1X)DCZEV|})|LoEB zyz`x{>%5+h({E3ktMRgBKTn~Z_RsZvbGQ#~d#ppU>(4_kKae9|aUcr{3Bvd$sr>L! zPH|1)jwSefM59f^_>GtPDczSZW37adeAg6gmHH&HS}Hj2XMrY|F5{qcl+!BG3&T61 zn_&o{q#Orm0s4e#68S?{uN6e%%=pUmB8;^N{r2fIhya%0oqz}7!N-&D`Fe@aSet`vt>#`Xm)MDxwj8Dj}c=|0&)7L$&Hvp*ln1t_;M&d!O>DyDECH|)#?T9prsW-$%?WCn?vKuOVa=JXeudS)=_l?tRDAbYJ}@lrhUy2N9TDvo6ZDu9+q<^ z{1@5s%{TQqUNaS4hQx`;5z1RqMVQHd`M@A*O1m-xe+T@U(g02Qw2`8;{)8l!>|@|H zeQe*np^(ZsIA|@H_-`wv4B2cqiMBDRK!w9>!4v9(&ziUE*0shm}Ww|>`A!v&cbVerpikjax z%ZOB+Icz0+^*k`3y`!JO!RKy<2JI=(5H#)rnTgN5ppj!~j-r-UJ;qAedLKcxG#11>arHmje&@d_r zd20%NquU;v&aW>2&S!1av7<&K3=)*&$k(S)QTMU)MkKuGDavV6+mwglbvuO$hGyaP zt6RRHbb7rQ9u0nDne=W-UBOp>^chs*BiqU5BtbIz#ZE!IXeGMSWk2*E@A)WPqy#*) zvbB)}@oxa#5g#xnN-UXtf#5$sH_nF_^_Eo}9tXt<6a?ok-y=6?U!V8S8k@&oL{QO6qU?Xr z7+hbVDMSgpZ|0X1oxM!h81ku=x;=P0bR}yyNh8-z$LV7iop%2W_*lt+noIE4Gm_c6FedpMj zg*PbA!m)EqQ>&)c&^htp$}dKaSg+-i{|*M3lIjYKbhc7xlPR;O#n?W6$5%Xvu2z4C z39+}pMw_7r6Iv{zdzWmcZ`U*h}S^vS*!g6tE#6%}m8XiC zDt7G8=QX-G=%xmLVans-oqSqT0#{kMZhyLpTk?@hC6qeJznL0VjJ|Rs2#gFAFprt| zE%6c*2EhE@QU;Rs(`|dqO+fk5-3V2FC@b5Xtt{Y5loQ+tPo}`AJS{ zTe0(-Y*+-ndM{OhwhE96cffaffPxdDy^tKa`92FvoqfAYi6`5yDdMN~{S>*wVJ{^m z>p$6LO|h%`8CkoC5%f|kZYoZEOjE_F%D;;Yj;n}Ro3V|Bir+(C1tPau9ONJc>yEm3I=o7fqy4ZbA3CmMZze>C#$(>wx* zlZWbZL<9)rZDS%vRXVx$Vlpx8iz`}Ait#-)gs`iBMWDijGUOCKI5L8QH&HC1LMYG= zE&lMIYClF(SmBG8+0$Z6pS~p+ODLh)kEWTo*~6O}s9QFTV0NSH_vchC7U2JgSJf zA#;?=(TR~{ru-uJ$)U#AF-q*LJnTB~&sOIv!H_%EprL&RVL&4C&*CzL*8wb+^|NB* zsa;U`^t&;}d)WZ4ji$uW_ioL$YL;#k9`lm%wNCRU+t7ia#&+ zYe!IP{Ay&WYu{&&v8e@8_HVBW3LT`|Mk6k!W}oXmy3cOv*-B3m8I#C?7W9rGmt!*B z0h9Coy!tbNrN!LQl&t_P-z+YB=Zj^P^6H85 zc+R^U$z;SIc+ywavXTfPm#M9?Csv(jScEUY1Vr7EbfIz& zcm9r4b#h-Tcf}`6$bfKzm?Sd4@{N;h+|Bx)N`ggKf03=^`abN*`JzKzX@?e!b^SJG zfE=3HL9l8B`?z(x0Dk~I5rf-dujBRr>ynRdFYn(jP}PO7BCMNc+lYU`vkNIS(@t!W z`I)-Gv4KSN%x`v;+1RF^jZzkHI|c@e-x6p`)PLNI^Ic&)5YhNnjp#4>tMX-r>mjg6 zVXRwKN=G0y0{3PQtFZ*{Gr)h>Aaub>niTK^Oq&kh%d%v$33NQ zA0g=9Q|Rz8{W{Tq$zF(%3C6&Az+ezuXRG?75X|WT{8afMn-MIb%rGhKkUX_}@D znqV8)G~rK_$iPK?33ynm@&|*94z@V{X%%*Bg3o z-6L%G9@4uh)E7n>t_F(wNhCHUMU&ubzhNt`n?zr+3)&;WUm8o?ggp!Im7xkoSVjJ1 zWQMg9ZB0~aq_y=kn5uG>at6^{xMygOA7@;dhH)|-EUYGWCH(s4Cyph{1_=w8rxX-N z=F1cBS^A8F!I3_ZgSQ9Q?+h4%C7l&6kUgPP&xsR}sHj{Olg)ej+%q%c8`H&hl@A|r ztp!m1!_TB0WQHRbhXbcao0cLdfs=et$tm0$z*D z+0EPP;rMI3kRHjM1M3mc24t2kEivY}Z`$vZXg-VjNS;VdP14KuDUH+r%3(|KnD1DC zt$WWVd6l(081^p3@XK2`NvyiGE9*1kt}!g2Yb~Cd1T3X#XFxfOeM0^11i80|;&`)o z_)rdnfPWnM?;b4`Vb>drV!LnJK+VDF|G^Hh-qbGApCFr^-4DD8LBoM`Do%q}ZMs?M zkCTfxFXo|}LB$?U6YF$z++5U?lwUHJYfuW%CAjo^fiq@OAqTU1PkPm+vM(5sE~rc) zb^iO+d=g+p{Mnm_-y>gx)w|65b+&5SyG-q7KhFnwzPDDXDg1jG-z8 z&-rZy#|Vo|e?@Q-YK+GOMYxrfa2<{%^+%l1Qj-ise3JDqO||ZSwvB2;E>BOKa~suQ zjm1<7SU`XU$gkqJ$Iqfxo%r$Gzr9o~B)?5O{8h zFx@+)Ex!klB&P$RiPgua(@>48?9m+}EYn6!Ifw zao(j3M5;#g(L3FqXOLSjC_cx_I$O%XQdiP4)=?zh=)iJscR2`!vlgQO;iEfKv1+D6g0ba2$`S)8z!;Cu9NI)H$u`&-1 z#FbBh-odSJsynXaV=qP2K3x$AVEyvRM%oID9s%LXstGvgjYsoQ9`um&xw^pzHA zv|xEl)qf=D91UHDe6hwXm^yq*AYO2E+E>{?XbzrCBNN zso9tWOESL%eW~oY^G&a6|COw#GP_0C&!q!0g7B702-rW4X6GQJn0WYXS2l=IW;yFW z=H-OG=7!J|PFjB?-EKq#nIy>B)iss5z;sODC$*@T#5IKHT6Nm)Md--=Uj2xh+J9{37l>ky6h?C*BU3!t%Y|E4<|*&))UHj^-LRY zD`%Aj&1(=3yc>Z~E0qH7)e#DsGLXkbnbpdF3WQCOhx95A8Nh|kmu6$)6jQ2he%mf* z>>t4XoGQ)Ls#GRd8K(}lH>flPymJb*e=zlZyt-n|dvf}(@uN2&i8s&1{&haKul9)N zz`~Ye40n_IFBNj!FIKTwPm{w?rT^`G%=Opn$6pZCotz&F9rUem_&_?y-lvHIZ2mnm ziLYVr`@v2a%yiF}IyK$%VdR|x9fJI5sa>Bpnoo8*bFKQtnteuDW9<$H=8nRq`rFd> zcY;|b$rP=(oiZfne!d71#j4=jQE@myf$U!VkN<6CMIN;OJ{72F!~gUDKB4#j0Xcbl ATL1t6 literal 0 HcmV?d00001 diff --git a/icons/tray.ico b/icons/tray.ico new file mode 100644 index 0000000000000000000000000000000000000000..5719c76745231876d9939e5fc5fdb2bf74283341 GIT binary patch literal 3704 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 n%^(IjMqK*&)$fo icons/icon.png (1024x1024, RGBA) using the Electron -// binary that already ships with this project — no extra native dependency and -// no network access. electron-builder generates the installer .ico from this -// PNG, and the tray reuses it (see src/main/index.ts resolveTrayIcon). +// Generates the DropPilot icon assets from SVG — dependency-free, via the +// Electron binary already in the project (no extra npm dep, no network). // -// Run: npm run build:icon (after editing icons/icon.svg) +// Optical sizing: small sizes use the high-contrast violet tile +// (icon-small.svg) so the taskbar/tray icon stays crisp and readable on the +// dark Windows chrome; large sizes use the dark tile (icon.svg) that looks best +// as a desktop/installer icon. electron-builder cannot do this from a single +// PNG (it only downscales), which is why the .ico files are built here. +// +// icons/icon.png 1024 dark tile -> build.icon (macOS/default) +// icons/icon.ico 16-256 optical (mixed) -> build.win.icon + BrowserWindow +// icons/tray.ico 16/24/32 violet -> system tray +// +// Both SVGs are rasterized in ONE offscreen window + ONE load (a second +// offscreen window/data-load fails with ERR_FAILED), then cropped apart and +// resized down with a high-quality filter for each target size. +// +// Run: npm run build:icon (after editing icon.svg / icon-small.svg) import { app, BrowserWindow } from "electron"; import { readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -const SIZE = 1024; -const svgPath = join(root, "icons", "icon.svg"); -const outPath = join(root, "icons", "icon.png"); +const iconsDir = join(root, "icons"); +const tileSvg = readFileSync(join(iconsDir, "icon.svg"), "utf8"); +const smallSvg = readFileSync(join(iconsDir, "icon-small.svg"), "utf8"); -function fail(msg) { - console.error("[build-icon] " + msg); - process.exit(1); -} +const TILE = 1024; +const SMALL = 512; +const W = TILE + SMALL; +const H = TILE; +// Force devicePixelRatio 1 so the offscreen window paints exactly W×H pixels. +app.commandLine.appendSwitch("force-device-scale-factor", "1"); app.disableHardwareAcceleration(); -const giveUp = setTimeout(() => fail("timed out waiting for a rendered frame"), 20000); -app.whenReady().then(async () => { - const svg = readFileSync(svgPath, "utf8"); - const html = - "" + - "" + - svg; +const wait = (ms) => new Promise((r) => setTimeout(r, ms)); - const win = new BrowserWindow({ - width: SIZE, - height: SIZE, - show: false, - frame: false, - transparent: true, - backgroundColor: "#00000000", - useContentSize: true, - webPreferences: { offscreen: true }, - }); +function pngAt(baseImage, size) { + const img = + baseImage.getSize().width === size + ? baseImage + : baseImage.resize({ width: size, height: size, quality: "best" }); + const png = img.toPNG(); + if (!png || png.length === 0) throw new Error("empty PNG at size " + size); + return png; +} - let captured = null; - win.webContents.on("paint", (_e, _dirty, image) => { - if (image && !image.isEmpty()) captured = image; +// Pack PNG-compressed entries into a Windows .ico (Vista+/Win10/11 support PNG +// payloads at every size). Sizes < 64 use the violet small art, >= 64 the tile. +function buildIco(base, sizes) { + const entries = sizes + .map((size) => ({ size, png: pngAt(size < 64 ? base.small : base.tile, size) })) + .sort((a, b) => a.size - b.size); + const count = entries.length; + const header = Buffer.alloc(6 + count * 16); + header.writeUInt16LE(0, 0); // reserved + header.writeUInt16LE(1, 2); // type: icon + header.writeUInt16LE(count, 4); + let offset = 6 + count * 16; + const blobs = []; + entries.forEach((e, i) => { + const at = 6 + i * 16; + header.writeUInt8(e.size >= 256 ? 0 : e.size, at + 0); // width (0 = 256) + header.writeUInt8(e.size >= 256 ? 0 : e.size, at + 1); // height + header.writeUInt8(0, at + 2); // palette count + header.writeUInt8(0, at + 3); // reserved + header.writeUInt16LE(1, at + 4); // color planes + header.writeUInt16LE(32, at + 6); // bits per pixel + header.writeUInt32LE(e.png.length, at + 8); + header.writeUInt32LE(offset, at + 12); + offset += e.png.length; + blobs.push(e.png); }); + return Buffer.concat([header, ...blobs]); +} - await win.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(html)); - await new Promise((r) => setTimeout(r, 800)); // let the compositor settle +app.whenReady().then(async () => { + try { + const html = + "" + + "" + + `
${tileSvg}
` + + `
${smallSvg}
`; - let image = captured; - if (!image || image.isEmpty()) { - try { - image = await win.webContents.capturePage(); - } catch (e) { - return fail("offscreen paint empty and capturePage failed: " + e.message); - } - } - if (!image || image.isEmpty()) return fail("renderer produced an empty image"); + const win = new BrowserWindow({ + width: W, + height: H, + show: false, + frame: false, + transparent: true, + backgroundColor: "#00000000", + useContentSize: true, + webPreferences: { offscreen: true }, + }); + let captured = null; + win.webContents.on("paint", (_e, _d, image) => { + if (image && !image.isEmpty()) captured = image; + }); + await win.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(html)); + await wait(500); + const frame = captured || (await win.webContents.capturePage()); + if (frame.isEmpty()) throw new Error("offscreen produced an empty frame"); - const size = image.getSize(); - if (size.width !== SIZE || size.height !== SIZE) { - image = image.resize({ width: SIZE, height: SIZE, quality: "best" }); - } + const base = { + tile: frame.crop({ x: 0, y: 0, width: TILE, height: TILE }), + small: frame.crop({ x: TILE, y: 0, width: SMALL, height: SMALL }), + }; + win.destroy(); + + writeFileSync(join(iconsDir, "icon.png"), pngAt(base.tile, 1024)); + writeFileSync(join(iconsDir, "icon.ico"), buildIco(base, [16, 24, 32, 48, 64, 128, 256])); + writeFileSync(join(iconsDir, "tray.ico"), buildIco(base, [16, 24, 32])); - writeFileSync(outPath, image.toPNG()); - clearTimeout(giveUp); - console.log("[build-icon] wrote " + outPath + " (" + SIZE + "x" + SIZE + ")"); - win.destroy(); - app.quit(); - process.exit(0); + console.log("[build-icon] wrote icon.png (1024), icon.ico (16-256), tray.ico (16-32)"); + app.quit(); + process.exit(0); + } catch (e) { + console.error("[build-icon] " + (e && e.stack ? e.stack : e)); + process.exit(1); + } }); diff --git a/src/main/index.ts b/src/main/index.ts index ae6345c..1344ca1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -216,6 +216,7 @@ function createWindow( minWidth: 1100, minHeight: 760, title: "DropPilot", + icon: resolveWindowIcon(), show: !startHidden, autoHideMenuBar: !isMac, // Windows: hide the OS title bar entirely; the renderer Titlebar draws @@ -326,20 +327,31 @@ function createWindow( return win; } -function resolveTrayIcon() { - // Package icons along with extraResources; fall back to repo icons in dev - const prodIcon = join(process.resourcesPath, "icons", "icon.png"); +function resolveIconPath(filename: string) { + // Packaged: icons ship via extraResources. Dev: fall back to repo icons. if (app.isPackaged) { - return nativeImage.createFromPath(prodIcon); + return join(process.resourcesPath, "icons", filename); } const devCandidates = [ - join(process.cwd(), "icons", "icon.png"), - join(app.getAppPath(), "icons", "icon.png"), - join(app.getAppPath(), "..", "icons", "icon.png"), - join(app.getAppPath(), "..", "..", "icons", "icon.png"), + join(process.cwd(), "icons", filename), + join(app.getAppPath(), "icons", filename), + join(app.getAppPath(), "..", "icons", filename), + join(app.getAppPath(), "..", "..", "icons", filename), ]; - const devIcon = devCandidates.find((candidate) => existsSync(candidate)) ?? devCandidates[0]; - return nativeImage.createFromPath(devIcon); + return devCandidates.find((candidate) => existsSync(candidate)) ?? devCandidates[0]; +} + +// Windows: a window icon picks the right small size from the optical .ico, so +// the taskbar stays crisp. Other platforms use the square PNG. +function resolveWindowIcon() { + return resolveIconPath(process.platform === "win32" ? "icon.ico" : "icon.png"); +} + +function resolveTrayIcon() { + // Windows: a dedicated small multi-size .ico stays crisp in the tray (a + // downscaled 1024px PNG looks pixelated). Other platforms scale the PNG. + const file = process.platform === "win32" ? "tray.ico" : "icon.png"; + return nativeImage.createFromPath(resolveIconPath(file)); } function createTray(win: BrowserWindow) {