From 527baf7ec50fc3db9fc29e700a4fc87e2d6c8ae7 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Mon, 21 Sep 2026 16:27:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(windows):=20=E6=89=98=E7=9B=98=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=E4=B8=8D=E6=98=BE=E7=A4=BA=E2=80=94=E2=80=94tray=5Fma?= =?UTF-8?q?nager=20=E5=8E=9F=E7=94=9F=20LoadImage=20=E5=8F=AA=E8=AE=A4=20.?= =?UTF-8?q?ico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户 Windows 实测:日志"托盘已就绪"无报错、不在折叠区,但任务栏无图标。 源码级定位:tray_manager 0.5.3 Windows 原生端用 LoadImage(IMAGE_ICON, LR_LOADFROMFILE) 加载图标文件——该 Win32 API 只认 .ico,传入 png 返回 NULL → Shell_NotifyIcon 添加成功但图标为空 → 任务栏 不显示且全程无任何报错(mac 走 base64+NSImage 另一条路径所以正常)。 修复:Windows 托盘改用 assets/tray_icon_win.ico(16/24/32 三尺寸 PNG-in-ICO, gen_app_icons.dart 生成,python struct 校验目录项 offset 严格对齐;ico 随 flutter_assets 打包,Debug/Release/Inno 全形态生效);删除无效的 tray_icon_win.png;AppTray 按平台选路径。 --- assets/tray_icon_win.ico | Bin 0 -> 1489 bytes assets/tray_icon_win.png | Bin 719 -> 0 bytes lib/ui/app_tray.dart | 2 +- pubspec.yaml | 3 ++- tool/gen_app_icons.dart | 36 ++++++++++++++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 assets/tray_icon_win.ico delete mode 100644 assets/tray_icon_win.png diff --git a/assets/tray_icon_win.ico b/assets/tray_icon_win.ico new file mode 100644 index 0000000000000000000000000000000000000000..a26d605907467a44aa698bb81fadf6975f9574ef GIT binary patch literal 1489 zcmaiyc~DbV6viJsNCHVP1(5`y1gfKeQ1+b!C`D3KqNwbau$5J?Kn1J_d zNP-o_xPV}*l8^v4!61TysH`PJSroAX6_P%6rqe$<(>rtTH{aYlXU_e72LO}+5Wrvn zNb!J)5&+l%0D#4Q@+`=QK^u?%)A^E^ww7RZ3^b}3<_B;eBQ~|&sV9;r> zqM$&sO!9O;oL(0}VPbeplaL^0qM{<|HK7CE zQ?Zt$4&s$=Ur%h5*1$3;M(5a1~OvC8QPy@Qhh1%=fS) zJu=v3#-z>D`>E^Hyl1q=V2}HF0sI5M*U3@>4cTvo((MdH)leEBJo|=jW>;5NyFYlL zT|!b#H**LH5rmD6w>C(cHsXcCRh5<2pgi-mh%g|UfVRh0D)LIf;a6zU=Md3EulfHOHMj_oC z%bs=OIG362J3+r1EAj)KbW^=ZRd$ePYDHm74!43}SSnNo^7mI&w0pJe{-1>K&^`WR zLMfS$I;<%H)G*d{PR%_ zj;0hoDL=^^n(D;sT?IM3TqUyPZF7-_6}?Lb5%5=H75@axX8*LTNh-{zefA`aoF%g> z&CN|v_#Y>QFAm$3+QSk=DJndZ-plidgK1ZC`Wo{yHc>CmcqD=?C4`2#*|gw$O>o%A z+B*RSCo42dnd9Sz>E#E-L0%o&rkX7N#N`tYwIms&c(R71RvPxRK z>0{Zu%zBW0zE;?6C@2<7ZnX_;F1cQM^Hm>z8}*G*%3}Xdd4d+HWYEd7C!Zg@W9Zuu zdR6+%!Lk|EtSptN@2L;h&ZlJOA!T!!7cRw|YmK}0=bq4O5ivAMbj18^kK~wws(Ql> z+u;r}$=0?uMfKobtqIACQ}-q2~E@XM0bd>;Ylw?A=w7a`STF9ac*9y?Y$( zn|JLL_IG7HjX&z0mJB}#;9lEhqEnfZ(7cGC2LwlCM4m-w&>O1+w_H2QFSdz`STW9A zwR6Yl3oW!c(QI;}S$%i6V=p>1Vp{AUl5-_A&*uhCjaB^HUzsb{y#c|6H z1yb7Y&%O`%$lSHO5lZ=SA{=^%T{S&I-=4pqd%gHqARcb2fXes2&51VteKY6 z9Sf19?53blD7rCZVe{<#h#hdn+>YnWEq&nd9^UtPpYP{9&*A;yW}nUN#Mv+m!;YUI z2tv^S?26`_vm1;hKq2C}Vpu`s1t^BN9f+I&#T4HSVh2De#H}FQ*$$-=&j;cDcc9eb zxgZ>^1EsKo0Z`2JtBOK0k-!%SSo&?3!EpvOP2=aM&%}p@5!QcDf9E!hgM)1US8hpS z?bS4mHwJ9Qa|Ey|$EfQFQFW?;$fJjO{S4iHLTv-Aj6R|1=5<@Xrw0bu`1T$DiQ{_% z&{Hb9uHlkAH1zkT>qcsG6Hi$=h@h()S~7{`^PwpUE=e+5(c_^aa0GR0E7RxpdYuEL zlAFZCA;OEx%(S)8{q8-U@ ze}4pJa)QqIl5>EC3+L&2lb&dhu4=UPEM2c-Xog{CW9zp args) { Directory('assets').createSync(recursive: true); File('assets/tray_icon_mac.png').writeAsBytesSync(encodePng(trayIcon(64))); - File('assets/tray_icon_win.png').writeAsBytesSync(encodePng(trayIcon(32))); - print('written assets/tray_icon_mac.png / tray_icon_win.png'); + print('written assets/tray_icon_mac.png'); + // Windows 托盘必须用 .ico:tray_manager 的原生端 LoadImage(IMAGE_ICON) 只认 + // ico 文件(传 png 返回 NULL → 图标空占位、任务栏不显示且无任何报错) + Uint8List buildIcoFrom(Image Function(int) generator, List sizes) { + final pngs = { + for (final s in sizes) s: Uint8List.fromList(encodePng(generator(s))), + }; + final b = BytesBuilder(); + // ICONDIR:reserved=0, type=icon, count + b.addByte(0); b.addByte(0); b.addByte(1); b.addByte(0); + b.addByte(sizes.length); b.addByte(0); + var offset = 6 + 16 * sizes.length; + // 目录项全部在前 + for (final s in sizes) { + final png = pngs[s]!; + b.addByte(s); b.addByte(s); b.addByte(0); b.addByte(0); + b.addByte(1); b.addByte(0); b.addByte(32); b.addByte(0); + for (var i = 0; i < 4; i++) { + b.addByte((png.length >> (8 * i)) & 0xff); + } + for (var i = 0; i < 4; i++) { + b.addByte((offset >> (8 * i)) & 0xff); + } + offset += png.length; + } + // PNG 数据全部在后 + for (final s in sizes) { + b.add(pngs[s]!); + } + return b.toBytes(); + } + final trayIco = buildIcoFrom(trayIcon, [16, 24, 32]); + File('assets/tray_icon_win.ico').writeAsBytesSync(trayIco); + print('written assets/tray_icon_win.ico (${trayIco.length} bytes)'); // ---------- 原样 logo ---------- File('assets/logo.png').writeAsBytesSync(encodePng(logo));