From e21719b66c61c189d0c2ed5cd08acabfb86fbbba Mon Sep 17 00:00:00 2001 From: oliver Date: Tue, 13 Oct 2026 20:32:33 +0800 Subject: [PATCH] Modernize the Windows installer, tray and runtime control; fix lifecycle bugs. Installer: dynamic light/dark Win11 style, new brand wizard images, auto language, exclusive run mode (tray / Windows service) plus autostart and auto-update on the Tasks page (pre-selected from what is installed on upgrades, untouched by silent updates), 5 localized Start Menu entries. Tray: theme-aware rounded menu with live status, icons and status-dot tray icon; health polled off the UI thread; Settings submenu toggles autostart and daily updates; browser opened de-elevated via explorer. Fixes: - tray Stop/Start was ignored in service mode (service_run restarted the children) - all start/stop now goes through runtime_ctl.ps1 - logon tray task and SYSTEM service task were killed after 72h (default ExecutionTimeLimit); autostart was registered for the elevating account and opened a browser on every logon - uninstall left the NetXTray/NetXUpdate tasks behind - silent auto-update killed the tray without restarting it - repair_venv.ps1 was never staged into the release - wildcard NETX_HOST (0.0.0.0) broke health checks - ICO frames were PNG, which System.Drawing decodes as noise; icons now use the current brand mark Co-Authored-By: Claude Opus 5.5 --- packaging/README.md | 5 +- packaging/_common.ps1 | 31 + packaging/assets/README.md | 8 +- packaging/assets/gen_icon.py | 135 ++- packaging/assets/netx-256.png | Bin 2374 -> 18631 bytes packaging/assets/netx-64.png | Bin 575 -> 4217 bytes packaging/assets/netx.ico | Bin 16065 -> 369214 bytes packaging/assets/wizard-100.png | Bin 0 -> 10634 bytes packaging/assets/wizard-125.png | Bin 0 -> 13340 bytes packaging/assets/wizard-150.png | Bin 0 -> 15928 bytes packaging/assets/wizard-200.png | Bin 0 -> 21403 bytes packaging/assets/wizard-small-100.png | Bin 0 -> 3417 bytes packaging/assets/wizard-small-125.png | Bin 0 -> 4507 bytes packaging/assets/wizard-small-150.png | Bin 0 -> 5301 bytes packaging/assets/wizard-small-200.png | Bin 0 -> 7377 bytes packaging/build_release.ps1 | 2 +- packaging/check_update.ps1 | 12 + packaging/install_autostart.ps1 | 15 +- packaging/install_service.ps1 | 4 +- packaging/install_update_task.ps1 | 5 + packaging/installer/netx.iss | 181 +++- packaging/launch_shortcut.ps1 | 7 +- packaging/netx_tray.ps1 | 1402 ++++++++++++++----------- packaging/runtime_ctl.ps1 | 52 + packaging/service_lifecycle.ps1 | 16 +- packaging/start_netx_app.ps1 | 19 +- scripts/start_netx.ps1 | 2 + 27 files changed, 1209 insertions(+), 687 deletions(-) create mode 100644 packaging/assets/wizard-100.png create mode 100644 packaging/assets/wizard-125.png create mode 100644 packaging/assets/wizard-150.png create mode 100644 packaging/assets/wizard-200.png create mode 100644 packaging/assets/wizard-small-100.png create mode 100644 packaging/assets/wizard-small-125.png create mode 100644 packaging/assets/wizard-small-150.png create mode 100644 packaging/assets/wizard-small-200.png create mode 100644 packaging/runtime_ctl.ps1 diff --git a/packaging/README.md b/packaging/README.md index 2045204..42c6b6d 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -61,7 +61,10 @@ Optional local zip only: `build_release.ps1 -CreateZip` (not for release upload) - **Reinstall** — same Database wizard as first install (`ApplyDatabaseConfig`); does **not** delete ProgramData (use Uninstall → delete data for a wipe). 4. Files → `%ProgramFiles%\NetX\` (program root). 5. Data → `%ProgramData%\NetX\` (`.env`, `pgdata`, spool, secrets). -6. Start menu: **Start NetX** / **Stop NetX** / **Open NetX UI** / **Reconfigure database**. +6. **Tasks page — run mode (exclusive):** *Desktop tray* (default; optional *start when I sign in* → `\NetX\NetXTray` logon task for local Administrators, quiet start without a browser) or *Windows service* (WinSW, runs at boot). Optional *daily automatic updates* (`\NetX\NetXUpdate`). On upgrades the page pre-selects what is actually installed; silent installs leave these alone unless `/TASKS=` is passed (e.g. `/TASKS="runservice,autoupdate"`). +7. Start menu: **NetX** (tray) / **Start NetX** / **Stop NetX** / **Check for updates** / **Reconfigure database** (names follow the Setup language). Autostart and auto-update can also be toggled later from tray → Settings. + +Start/stop from the tray or shortcuts go through `packaging\runtime_ctl.ps1`, which drives the Windows service / SYSTEM task when one is installed (stopping its children directly would just get them restarted). Uninstall removes the service and all `\NetX\` scheduled tasks. **Elevation (required on fresh PCs):** all end-user entry points (`NetX-Start.cmd`, `NetX-Tray.cmd`, `NetX-Stop.cmd`, `NetX-FirstRun.cmd`, tray, start/stop/setup/update) **self-elevate via UAC**. Program Files installs cannot start reliably as a normal user (`.venv` repair / writes). Approve the UAC prompt once per launch (tray stays elevated afterward). diff --git a/packaging/_common.ps1 b/packaging/_common.ps1 index 194e8f7..085306b 100644 --- a/packaging/_common.ps1 +++ b/packaging/_common.ps1 @@ -282,6 +282,37 @@ function Test-NetxApiHealthy { } } +function Get-NetxClientHost { + param([string]$BindHost = "") + # A wildcard bind address is not connectable on Windows; probe/open via loopback. + $h = "$BindHost".Trim() + if (-not $h -or $h -in @("0.0.0.0", "::", "[::]", "*")) { return "127.0.0.1" } + return $h +} + +function Open-NetxUrl { + param([Parameter(Mandatory = $true)][string]$Url) + # Packaged scripts run elevated; Start-Process would launch the browser + # elevated too. explorer.exe hands the URL to the user's non-elevated shell. + try { + Start-Process -FilePath (Join-Path $env:SystemRoot "explorer.exe") -ArgumentList "`"$Url`"" | Out-Null + return + } catch {} + try { Start-Process $Url | Out-Null } catch {} +} + +function Get-NetxManagedRunner { + # "service" (WinSW) / "task" (SYSTEM startup task) own the runtime when present; + # killing their children directly would just make service_run.ps1 restart them. + $svc = Get-Service -Name "NetX" -ErrorAction SilentlyContinue + if ($svc -and [string]$svc.StartType -ne "Disabled") { return "service" } + try { + $t = Get-ScheduledTask -TaskName "NetXService" -TaskPath "\NetX\" -ErrorAction Stop + if ([string]$t.State -ne "Disabled") { return "task" } + } catch {} + return "" +} + function Wait-NetxApiHealthy { param( [string]$HostName = "127.0.0.1", diff --git a/packaging/assets/README.md b/packaging/assets/README.md index 2310893..27b599c 100644 --- a/packaging/assets/README.md +++ b/packaging/assets/README.md @@ -1,6 +1,6 @@ -# NetX packaging assets +# NetX packaging assets -- `netx.ico` — installer / shortcuts / tray (regenerate with `python gen_icon.py`) -- `netx-256.png` / `netx-64.png` — previews +- `netx.ico` 鈥?installer / shortcuts / tray (regenerate with `python gen_icon.py`) +- `netx-256.png` / `netx-64.png` 鈥?previews -Mark matches `web/public/favicon.svg` (network “N” nodes on navy). +Mark matches `web/public/favicon.svg` (network 鈥淣鈥?nodes on navy). diff --git a/packaging/assets/gen_icon.py b/packaging/assets/gen_icon.py index f6e556c..e5b96d5 100644 --- a/packaging/assets/gen_icon.py +++ b/packaging/assets/gen_icon.py @@ -1,54 +1,113 @@ -"""Generate NetX Windows .ico from brand mark (matches web/public/favicon.svg).""" +"""Generate NetX Windows .ico/.png from the brand mark. + +Mirrors web/public/favicon.svg and web/src/layout/BrandMark.tsx (24x24 viewBox): +blue gradient tile, an "X" of links, four nodes around a hub, one node lit cyan. +""" from __future__ import annotations import os from PIL import Image, ImageDraw OUT_DIR = os.path.dirname(os.path.abspath(__file__)) -BG = (15, 39, 68, 255) -LINE = (126, 182, 232, 255) -NODE = (232, 242, 252, 255) -ACCENT = (61, 130, 247, 255) +GRAD_A = (0x3B, 0x82, 0xF6) # #3b82f6 (top-left) +GRAD_B = (0x1D, 0x4E, 0xD8) # #1d4ed8 (bottom-right) +WHITE = (255, 255, 255, 255) +LINK = (255, 255, 255, int(255 * 0.55)) +LIVE = (0x67, 0xE8, 0xF9, 255) # #67e8f9 +SS = 4 # supersample factor -def make(size: int) -> Image.Image: - img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) - d = ImageDraw.Draw(img) - radius = max(2, int(size * 0.22)) - d.rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=BG) - s = size / 32.0 - - def xy(x: float, y: float) -> tuple[float, float]: - return (x * s, y * s) - - stroke = max(2, int(round(2.2 * s))) - for a, b in ( - (xy(9.5, 8.5), xy(9.5, 23.5)), - (xy(9.5, 8.5), xy(22.5, 23.5)), - (xy(22.5, 8.5), xy(22.5, 23.5)), - ): - d.line([a, b], fill=LINE, width=stroke) - - def circle(cx: float, cy: float, rad: float, fill: tuple[int, int, int, int]) -> None: - x, y = xy(cx, cy) - rr = rad * s - d.ellipse([x - rr, y - rr, x + rr, y + rr], fill=fill) - - circle(9.5, 8.5, 2.35, NODE) - circle(9.5, 23.5, 2.35, NODE) - circle(22.5, 8.5, 2.35, NODE) - circle(22.5, 23.5, 2.35, NODE) - circle(16, 16, 1.7, ACCENT) +def _gradient(px: int) -> Image.Image: + # Diagonal gradient along (0,0)->(24,24), like the SVG userSpaceOnUse gradient. + img = Image.new("RGBA", (px, px)) + pix = img.load() + span = 2 * (px - 1) or 1 + for y in range(px): + for x in range(px): + t = (x + y) / span + pix[x, y] = tuple(round(a + (b - a) * t) for a, b in zip(GRAD_A, GRAD_B)) + (255,) return img +def make(size: int) -> Image.Image: + px = size * SS + u = px / 24.0 + + tile = _gradient(px) + mask = Image.new("L", (px, px), 0) + ImageDraw.Draw(mask).rounded_rectangle([0, 0, px - 1, px - 1], radius=6.5 * u, fill=255) + img = Image.new("RGBA", (px, px), (0, 0, 0, 0)) + img.paste(tile, (0, 0), mask) + + links = Image.new("RGBA", (px, px), (0, 0, 0, 0)) + ld = ImageDraw.Draw(links) + w = max(1, round(1.6 * u)) + for (x1, y1), (x2, y2) in (((7, 7), (17, 17)), ((17, 7), (7, 17))): + ld.line([(x1 * u, y1 * u), (x2 * u, y2 * u)], fill=LINK, width=w) + for cx, cy in ((x1, y1), (x2, y2)): # round caps + r = w / 2 + ld.ellipse([cx * u - r, cy * u - r, cx * u + r, cy * u + r], fill=LINK) + img = Image.alpha_composite(img, links) + + d = ImageDraw.Draw(img) + + def node(cx: float, cy: float, r: float, fill: tuple[int, int, int, int]) -> None: + d.ellipse([(cx - r) * u, (cy - r) * u, (cx + r) * u, (cy + r) * u], fill=fill) + + node(12, 12, 2.6, WHITE) + node(7, 7, 1.9, WHITE) + node(7, 17, 1.9, WHITE) + node(17, 17, 1.9, WHITE) + node(17, 7, 2.1, LIVE) + return img.resize((size, size), Image.LANCZOS) + + +WIZ_TOP = (0x0A, 0x12, 0x22) # near-black navy +WIZ_BOTTOM = (0x10, 0x2A, 0x55) # deep blue + + +def make_wizard(w: int, h: int) -> Image.Image: + """Inno Setup side image: dark navy gradient, faint link mesh, brand mark.""" + img = Image.new("RGBA", (w, h)) + pix = img.load() + for y in range(h): + t = y / max(1, h - 1) + row = tuple(round(a + (b - a) * t) for a, b in zip(WIZ_TOP, WIZ_BOTTOM)) + (255,) + for x in range(w): + pix[x, y] = row + + mesh = Image.new("RGBA", (w * SS, h * SS), (0, 0, 0, 0)) + md = ImageDraw.Draw(mesh) + pts = [(0.12, 0.62), (0.42, 0.70), (0.80, 0.60), (0.28, 0.86), (0.66, 0.90), (0.95, 0.80), (0.05, 0.95)] + pts = [(x * w * SS, y * h * SS) for x, y in pts] + line = (0x60, 0xA5, 0xFA, 46) + for a, b in ((0, 1), (1, 2), (0, 3), (1, 3), (1, 4), (2, 4), (2, 5), (4, 5), (3, 6)): + md.line([pts[a], pts[b]], fill=line, width=max(1, SS)) + for i, (x, y) in enumerate(pts): + r = (2.4 if i != 2 else 3.0) * SS * w / 164 + fill = (0x67, 0xE8, 0xF9, 150) if i == 2 else (0xBF, 0xDB, 0xFE, 90) + md.ellipse([x - r, y - r, x + r, y + r], fill=fill) + img = Image.alpha_composite(img, mesh.resize((w, h), Image.LANCZOS)) + + side = round(w * 0.42) + mark = make(side) + img.alpha_composite(mark, ((w - side) // 2, round(h * 0.26))) + return img.convert("RGB") + + def main() -> None: - sizes = [16, 24, 32, 48, 64, 128, 256] - images = [make(s) for s in sizes] + sizes = [16, 20, 24, 32, 40, 48, 64, 128, 256] + images = {s: make(s) for s in sizes} ico_path = os.path.join(OUT_DIR, "netx.ico") - images[-1].save(ico_path, format="ICO", sizes=[(s, s) for s in sizes]) - images[-1].save(os.path.join(OUT_DIR, "netx-256.png")) - make(64).save(os.path.join(OUT_DIR, "netx-64.png")) + images[256].save(ico_path, format="ICO", sizes=[(s, s) for s in sizes], bitmap_format="bmp", + append_images=[images[s] for s in sizes[:-1]]) + images[256].save(os.path.join(OUT_DIR, "netx-256.png")) + images[64].save(os.path.join(OUT_DIR, "netx-64.png")) + # Inno Setup wizard images at 100/125/150/200% DPI (modern style base sizes). + for pct in (100, 125, 150, 200): + k = pct / 100 + make_wizard(round(164 * k), round(314 * k)).save(os.path.join(OUT_DIR, f"wizard-{pct}.png")) + make(round(55 * k)).save(os.path.join(OUT_DIR, f"wizard-small-{pct}.png")) print(f"wrote {ico_path} ({os.path.getsize(ico_path)} bytes)") diff --git a/packaging/assets/netx-256.png b/packaging/assets/netx-256.png index 167327b1e26e931f2013740f1fc462b1e4fbf0e7..073acea052ef2a0b7765d1ac618bdf7a90fb3b3e 100644 GIT binary patch literal 18631 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 UQ)jzp5&uWP^9$l$m>0e4U(vMeasU7T diff --git a/packaging/assets/netx-64.png b/packaging/assets/netx-64.png index afeb76f450a04897dc213711af3b3f61bfa5fc9d..0bbcfd13a0bd8886f6aa43ec02c555d03aac7ad5 100644 GIT binary patch literal 4217 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 P00000NkvXXu0mjfz@Rg& delta 551 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%0D6c-Q8(;H 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 a1d_xBBR^2Rz>wg{o4avyhW+nZ)c*jlM8dZK diff --git a/packaging/assets/wizard-100.png b/packaging/assets/wizard-100.png new file mode 100644 index 0000000000000000000000000000000000000000..040de083f9a18fb5b105e81dfc0049f979d324c6 GIT binary patch literal 10634 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 literal 0 HcmV?d00001 diff --git a/packaging/assets/wizard-150.png b/packaging/assets/wizard-150.png new file mode 100644 index 0000000000000000000000000000000000000000..94cc8205c80c76f741042b1ff55551a133338c7f GIT binary patch literal 15928 zcmbumbyS>9w>4G*u4LYXcOca)^*%s@-|M1wjR3N$nc9H#`X_v zBx8cZ5wxapQ$54QE`TQWWB%Np_V@9ZX2wv>-Qd0X+rP?em> zPezZ7s53xM1IOh>@$h5=4Ckcz0q}!xQmAn0j1Ozi>4+cCMF(lXxGT7#(No{Ic3{N{ zY&DPX&;))@nr@zDb^jy-SnTXU?z%^FsOv|alY9nqG!Tnz7^UX1h`S`N{2xANJ9!Y{y{T zaNsF;zHU>B@3cm|`5DaoRDAlo>n6gio`KU>JA85z`1IIg;FpP)i1^-O!zeXztMqi< zzg4F+R0k2)LE@$Ly3{}HVJE57`LobPNrW?~PDkvz=%v-;;#ji?{XP;4_*aS4x7ncx z5iWeKwZE}TA9lkv-GWVfceh%gb{+*zPr1(FHPcZyf*>~wD@m^56OQvf3RSR(XqOzF1q!_-@}HN z-vpQWyfH34lc+NiIq&VVY0*|H3Ouve&pLnM4#&iIFtgg76n&=Tje_<^l>PZY5Z}73 z*{yy0q-M37x>!NiYxnYG@t6F!oiG~f$z#^r^=r0|*_lcoTcP-SDs0L)U6*-88X)>= zK*`MmjWdOTuS*MogY2{O$zXfw$)kc;3s|RGh!yyx__o9KU_GSyN%d2Wdge>#803u~ zy$sX2Zt1w#HkUY6r36|*nWg62kSd{xHu`qQJ0r=7M3UhOp&^*1!L2zaH}OR0xd{y) zmk~)m=df~i>0io>|4$qp2yO(c zJ2DA;^4ZEA$KUQ#K^h335b)72pVQ{X!L0r7XikA9Lw54|HOFZv9u46go{FqDvwXKe zUyMaQY}bD@3Wp_}{PArGMqO`~fO#CFL=Y;8fJKK$!R;-1P(XjKxEpc?64}C!$g&KW zlcj@;GP5GIw5eLpmmZY`i5yGUb@Mo?-!1{zRyFsxLdBw}Us*UDmrM7!9h3#)rGfvz zBzA%Az3V=c)2j>5i`=7jz>&1W+;jcbJRF|)ZY4kJE$Xs^RXF311W7Yu0QN#2GHYQ8 z5pY)>!LBx4riF;}NEbp9=+*5^{J2Ezy#69^nA%Gcb&FB3vFdiQ7Z>LLSXH~((9sLn zukok5ru4Quu-v*rxd@RP#poiSDfI4UCEL;N-)vs8olxNNE1+`F>Q&MzHW>r=o%tNND}qm8vO}EoWPHd#-ydHe zwtf)u$~!txv7je9vGjqCdNfmm4Nf5rP-ePc*mfAt2MDc&7zA%Hvst|lE2aI+Wu&G; zUe5mnn#+*to_u%=u6YSCk}Z|YE=4o9Xhxi7=>1FMPdD0 zyGwY#w_v_Brdi(Dy7gFgU1I)|xvf8J_sopSA~m3}?41)&o|<9L$rdEodFAm3@N@LB zuX}vi^20{^Q6R>BApw;lkf zd-pIeC_@vyR26m>A?(m;Ky?$VL4V@tO!cZ>kkDAsO(lR-pl&4mVc!05LA(HMkh&*y zP1Vh<0+5oDe}Hi~E+L6p)k&Z{ckNaYUihc!R%xP_b=BJTy@;a3kl<=cHBQmrE+2xeJi=F*t`)o%^o zD4FCrGRCWZmSJ_NO;F7oOMg6-DkM3M^NnWCxs<738WJ7u{CDU~Ro8(W1h*`_UiT6C z#<~mbTP&J!t2}3=qlw1^x;86QS!a`MZdY~-asCzDO%}5o9Pig!=|VC&kFL}dt|+X( zTuFt4~gfP`*H2S;E|lP zyQVfq58vY4Ir-_$=JI91)u$$3DsIo`hy#3-&INyhH^ahrO8{gpBSvIs#Y9eiwD3Lzmtl*7AA1ico^Hf^hzStg^`|LHQoAxO-o zpdOr&fB#|N*k01kB?Iq`^q}AQrq`GiZ`3=2I}VmDPV_(;<;CXQsLps{WS2rIB6svpq*Nwi3h2=G1)u~d#9r4;<18kQu(TydQ zA<0L;oE0ZS6{3V*Q*zPgl6V*!vzVsBQ?)Dz3q*+<>r6+1;>I>nPDezU z&PaqnGI1O;iqR*~rhDY7s2fxZnch&Y$C~B-5gsl9yG{!Gu9P&7iJNv$f+XQ_D=wzI zyP7%3Bf)8Lg>3(44?&M8PBzGke{aM0g&BL3{*%fVZWGy>2SA)PyWixy+vgZx>yc=hDBgCVIw zN+3oM%E^%L4f*;Gk(OirTOLX11Rzoq4mUvY6H*(otNq8@rUk*GZg6d8ZP%U6R!`$1 zhp82^z8oUU*+SCSB!xe9A+Tb=Ytbt4gB706Lx#Z}Z0szm;?BG2XBVvZM}m*3!#g%O zk@#>Uh&bz6-gh-*6DI2FTx5WbA)g29%cdgaJY$?cQE5p7Cb79b@BQwToga9%3LY+Y zv2G0hiYhMdQP~?BsyNHZn(1~Wawp`y9kZJprC&#w((wI|Pgh%eCy-A8^m$C69Sz%% zkROa4gT}3m=;X#@$8h(rOyc)QJURg7J%j{b9d6<=I#5{AC7!l4eQQN+Z}>&&XLGN$ z`*SJwM3a_8z!L~I81`B)kIE<*(m4CW?{k2Lwo@Ln&K;^cb)rP9v_JI8@QKz5J6xI*2#Z5zb{2dX3j<45L ze2*Nsd}OYwD#4z1_Ma~+esZL-#u509M0j5LHebe~>sz&JOA`e@HuGZNoroS2T&`-z z;h&LKoq`H2Pj_*TfDh*A2ID%2A&FOqd3{ivafp1T=xQ%21 z;`YPaUGu=Mc!KZA4`kIKPZ9a>3;X6wv9D`0VIad?&{On`=2yN?FNm!7#g#A} ztiWcO$j1GrW9*^QhjyT33?DV^8K5r4hbCY}TFghRC#rKvjg%P#>d6ardp z#ApoSxD9>z+{Ri8@u}ND1%KhZ_LZHbOdg9zA}ZfPc0GQByk#^aaMt`rC@HD@-$~?i zJ>Ny)K|dgeRsW}@8jxtM-7jbDdxbF~Dc8(vl;uaR)l;V(6c@84KE(eBs|JUa0iGrM z59^FUi9Y8%CYEgNGL|2@&NxnA#n=MJ&8Kc2zOt~MZyp%T$1eD~4>%#nfX-7!bx$6? z-m}icwP5(|p%v|(PyATN)_P!0N`#SuK%Or9cccc8WZ~X;1dvS;3j|JSU5E81YkD$sdU6TfIbp zbvVQlH0lnMGO>7SBs(FPF|9CS0xB-BI7&i2qweyk4OyN!lEf`dB1$ zT-hV8t}RJ#+IrO~qde}FKpYUWzt8&j;2!fSm;_0rKvRq&WS@e)ALW;!0e#**TinN+ zgwqN8mG)NM;=Gpz_YlB;pt?nBwx<-4%1RsYz7*iPSE5SCE$EVn9NRu zwI#+~((aj~PjL}HbW{2LK7Z`Hn~CFJca6Hf;hWDWV&YnS(}C~;Fu~08V1!cMF-OO} zeEriirbM(qOKW4WQSg9)BdMh!x@r+7Y2kdBqDyWCfpj0hV<%0$@RXi>}vTSwO)oDAyoUFiHcfl?0}fe}WlK$+*& zUB~&x6Vghsy{FoGlpWclK9yhaE&LMGl}sk8!l2Y-UH%W#Y2zv9ufANqm(*}Xn)8vj zauDaBvuk9I*1tDf@*-Fj$|fxo6KO3V`>4HRlbX{Bbl-M?XoBibE327vOd^A_1`-IG zqlVIm2-SoUh~{B!KBX1@L-t)qynnhi{QOJ(+e#O0& zcws2$G1ph8^7~08KPq5~TX)av2N89_&`#HSjy~$A99|*^)Jfi|gq?tvsyzvB+oX{e zMHI`n(_l{L4%cf3VK3Jj$-pI=Y|%e-LLL)F?&G1-vXYmL>w6D_1-sK+{&xv&)V0|$ zC!H&e!Eb}NKemh`;Et&24i~&!Xjtv27XI$3Q}AVrD#Vxk{P=S^A7?F1)MYdtwvN_H zlJl;R0HdGUr`YKmqf-^aSeW8oT_r=;OYyjrh6Q=9b% zq(G(V=C;>;8&?-Ty0Y+90Z_D~+s&V?ws*#(QO2I$2zXNhKw}gWew`CxRvxvs6MG8r zd(nPS&>P@@r=)z%KRGcu#Ut*X5-~7+tu0)G^9UH^*xx;-aF#QGWz5QQ;vfI)Ik=Fs zJ+3{^5rw>Ri@j5dO>PyN;rAPY^|>4T*FZnwmO^4{<`02@o-FdZFLQb9KtJj@dZ|pZ z+I2O+2`(C!TTh0>-oC480RGH0&k+<&AhT)Ai@UgKe+>zM`%lNIYivP<_ZO6>sN$9}f9=R4c?PH?q)zI>=N znAKio8Eesp3$(nXiYkIPH2`CH87bj7iKp*deIs?NBA(0^vyUMo=cS zzgK2`4-B5cwtwhvF#JE3d%Bil^%R3_sh*m<+W)kBgGnBP4h>GEqwr3VVS0In6&DB_ ztdJA97ElG%Q(9LiesCd1_1g!I0OS~f*N-&TbE3g!mv66M4C{)hY>Q|63(&ZidDu)% z---jS!`HJ}+o_?9pcPJ(<| zIwK4kEA^mU61%>;Q=!y6)qrUYUjQXs==(UppGR%UPmgDvAuF-^RR|vYT37cq3zAR% z6~v<40}i&1goSw?Brn2GZ+_xW@^;D_e~G9?|k` zD&5wN!7!$RYKIc7Q1u%wEESj(H-SSF=3vCX=b^(9hXZmJt`_*-8o-@c5n_BjwmngM zG0Me!trzf5rzGOn9syv8m8!I^xpDokmb)_{FD|(S{+rx(5*OPs%5Uoo6ShAGzg~}- zheH*f4bePqd5k@>y&In&9Ec z^j_M0V7+D&eF*#Q*Cy-9wMNNalQ4%TjxK<=-|kl!GV}#kN?yO^yyX|~M}fjOG???b z=z_+9ma*@HC{dOcHwdfeC*ZktwX%GV#%Z{L;c_%H_cXOR&& zuSOn;b9{HCkHy#Y+6U-n6Qqf-$*S?e%a8+UJk8f^H=`{GZZNH~!xmm}R1SJnmMk+0 zkRz21jore3)jVc2!`Pc5qaEfdzWXA`*J?(yav?5&z>-5LCmWx1p&eea7~)GFFyNg| z@+E2`C__vz9Ddj6j?kRS!1ytHb5xUQ8OXdM3BQV_5zjZ%a+c8o zW8Y>n)aNqvWLqDdk@|hVT1vL?yzFR28o_J`31A9sz-9NJ>4I6o(baM4QS>o#CU-}v zroG%q-l=H3_RSG2F1$1fLFN_{h~$upE|}70Yxz;0YAm=|8{68@+!hu6(3inN^ti_O;r=WdFFs@!V@HNi$8>@7mRHT?_3Q%+cKaTs4w32jN?B# z8ng*%QygGY&)~SvA#!AbpDE5#-uYwvz6Wgm1XSB;dL{>jX5B3W9r}|RKDihd@_F`q z_b_sPH+Hj2!C;2y3BiKoo$%mf3Gx`Fy{OkO``#z%_|ELwXhma5F@ZTXj%Q6U=AT76 zo{2I)f!T7B?>3rLYr!j{t?3OLB7DFdV*hUTHM3ou4;Icunmyma4Xa{cf%MM`X>m(8 z&C%2haTwprYwqrF4#*EyIBp&YLb%lCBr&bs%%bvlMvA(1&B zLPb%=-fTolFV#aP88*?w50qN6`Nx?X-`M&~m|3#Po7q}`wnX#Fb|KEdm{|s|I zQ1};)q3dqiGlKaa3Cw9!UU4J17<4Jb*4mJk0$h=i=*dR#dlV#@L6*1t?vzrbXuVhc zc4dYthAuQT+sNLq8-uuxt*V<(5t_Rk9461O=FT0Rb2e`0SBS2YUQW#aHmCVKS-`H)Y^=)cFe7&=e-~DksS!h@R}nJ+^F$ z7($$}L_}U$p_o0NG{8wqvAkfzH7}wjal2X#&y)ge#xxhPp4ccKd8Xg(qb=SAMVBVN7CRYx^|(CS14eE8 zAU}!KLt`Cz%3DDp8ByHh!H!Fs+pIW~vaS@&qpG?3iZmjAr(RKb7$!M3k8lTr|P zG5bz1?Na=-=l=4?Y2$c;Jp9P9y+9v_Kii=eZi#V+-@RB`a?Xg_#C$5(54%ah2o+l+ z_S6%<5d(^qg5>e>@i7i|vFo+dc^e)35h6mtMVKJ3YJdkXDx8$r>;8%(Ojeq>(m+yM zl4MhKJhzcU6tjpysfW9s;~}=eil@idhj=XD2UPSe&;jP?V3R8=?R%}fxJU?7_xJ8y zQyXD)0!P)6d`<2%{odMFg`!P!os@Z2?hN`ESwuqnJpKb0teMS~9JzC`+m9TqD7s?~ zbrH2s^=`Fp)y!IQ_yBvCO7MWSS$+ZOH|CIZV^OTR3;!mukzasGrfHm0q}juV{Nn6AX z939taOxd>RR>*SAzZ=Kp&7RH{ZLAHO=)5#12~|_7ce5(kU#y|G_}XjdT|EV-BG_3P z7l#~xO1ADg?6w+Qfbn*GVO}rT+bTjsimwvT@7MYC{N(Mf_BIU-E+F2NXrv@1ik_>| zo?F?wouzgE0|TbVPoELyO&q8?2X`jo-1U_4-)H3x`Y8mp1rGHG@`W$&;Oz9(O|fNg z1z4RXzdt1q!%+Ju=U9`OI!#bfF&Y$}#)uMw1;y~1vR7w)e0Qe58>L8a2^MQ}zF;YR zPe<-yQlh)plq)Q&^P6+M&;?oSsAv|k2CtvoSR;4j%Njr(Dvb^afn0mk=20g4_-^a$ z#5tPXEPO{Y6tfB*z7N!zN>xGt{bD7;Ootz{^WUs9J3dhGL-PwlBu=2nJsdpjVTm5? z?-~8K7i$b!6XS)nIlr?QYaT5e_Yjt?aT|Q-@Zh*QCu){8)V334z`TnSL`J#qqOY;s zQ(oj&!jBt{$N7DT!jy2@BWx$afGuiLV25n#w9)s2ncZt?I4!F>3cK}cWKSTZWliXU z+;$-gx=tH0Gjb^vT*dwCEDT>bpWJVRL@jaARRx-4xUNvVuEt|bAM3-XhcCH)c39Ow zffCcd+*qdBcv1X#gf}zPOl1}jR_eo2E0&cfjtTd@(g=suRtEBbfkZWn2cXMkzF;j$ zA~=QD@i|}U?D^tg!@0=Q*Vp7`bma4{OfPZL_Q^e9puEJuIcO&o#Z9Hn1m5#O z0V_CRp{%yQGcl#eyr(Ni^zQ5tCH!^m|R{iu_Q` zsrOHK`|(nOX|QpNZ4lGxUi9M^S`*pD4HRS|w%$ai6mg%F<7aupj#^E$&R;ofJ0-h~ zlefgT`5ZuIRC83*OFsAJy+VFkpQ#KC^0`g&a_MH~W*?$(we~Es`;y5FVy|5=*7SC< zN@6fiTb}6UUsF?M#q4rHER{RfA67#x%0O4tPLNlKte=C8grF};hC77L#9Uu!j+mny zo8e16@H*vN>G~EGLd|wz12!!6f)g;dyzOZGK2DsgsKAJ34`?&-3A@ zPoq9C(;7<$F=UW*B15uf?{L(p`oubk-IpsHmTAk@a->YKw6Au05`PZjCpD;DE2GMj z2GkX;2RS&spVz8&51!xPhh;3{AT9^*$0|)}vx1CZ&^~$uwPM)H-QWB2dj|C0<^CV6 zySxg-J}34{6{xp+;msUFimKo3MD7PGup5ZJv&1Sl7$cDj98K6B;(FO%qcbY`?bd2% z$_`%{>exCnt-qAO&hd3wihHT>;mY5@H6H3)DQ z4dxtIc2VsL|4`FlTd>88kE-zfzK259jut8n>mUp7Qh0Z;m~3Mjd5B;_;q7QL4>93D zn&ofM!RCvVdG?r2@Ha^l1aoZ`08N}^t_(G^6M~bN7(inXM0q}*Su8*IJ6qzuVrhjj z(5h9@Y*41>#}O5`u?#hr69!n?bjEZ#7zo6h5u=H#x}Gr^iv{llen|d&PK|QMG!B{^ z__%sJr&mIHDeHza!16Q)gL|A~S2WDdVfg;l4zpjc_59u)Uu3(tO`h?p#E14b#>{=j zIUbMzl}9FB)>I>m)!@g$A~z4!^kanS>Gg+qv{2{r)>z3Mo?~ggWRwm^$3d}_#+ouU{yS-V$5D;)_41}P*OKJoc za-{~@SYkXFZ&~p*mjaoca|{in%(q=Xy8>K@4BSl}rjk>f9-GbLfc?1gCDy~8I#~!5 zqJ@m>yT6+2X4y@tlfDm01fbG}NBoAO_-gW=;X0oBb?fNUFMVlRnjC6|ti2llJp{xq zzdyd*s%4veGNdg?DeOf^Z&))op5t9^y!G@Y)yrY1spz7v4^F&{e*HdOX+!@$vFn|4 zvGQ?lHg+EBsG%u!FLS5{U&o-rzA;~RM^avo`!hIUiGW!htklDjm6gRsh%q5{D>I7O z9o`0YZ~@z=oe5CSD=Y|K{JLN2g$F-*??sEWXCyj!%?_sQy$N4#VjLuUEKBUJKv`cc zpdX0M!|aV0WyTG5vRE1CXkNVl--1@y*(jqhziEVjL*>nJjjnI;d4j`FjAy8G?)&qt zU128*h&epm!cMboRUQxAPE?_KC&R52ZkxffW++XW=RE+QgG_8L1G`f;SUQ;xR4CZ7 z9MP+D4zXkFXcdS|YQjZFp;NrmO&UgM{TMwiUIEKImJc#?ja7&8qHNEpj_mX@%Xii+ za4PC^ad3F%T9Gl;NaK?rgS_r}X_aivG2j1q%yI-5&ElQz1&!$DcMs#(uq{t1b?8^n zglcSqIhKgfgSlf1t#W->QDs(|*hhG=E|;faeNk_l1^Pb{F)3@Sitg9R``n`JK?*}_ zU4xY1Vps~Nd3<^f2hJk?Bs#EvsgQWN2+>-Xjn@$nd}GsIEZkL}Se(BHM|qw<5$j>a zhRax@9sc+XkQ3F45Iodzp25H^*e&r`3ii)kZgo~ojH+=O;UFHiG=2WOz0j61CzOll zWR4pv)MCo^g^!tG9q>UyMxof{E_lasMQ=0AW*0-V=*{cFvr`ncM4?K+_VA^Y=>`qu zdF(v_m3%+Vmml(M=f7W%_K;gIvJ|23g?(G(L}WoD_bGmtnhn>T--HI|7^xl9_Glvt zHIR~zP((`or1a#vOIRph^x9z@v^xwOBAa|S8XUvAr5_h7Pg_&}C0!if6RS?io$aA0 zojLYa98^27hZ{BS^;)|tYoeNoKJ`U5$9#U9qmDo(;j}SC8ydnL`;^&I%xLB?s6;4h zEH2nMdC%t7z3Led3=YDnK{7MWR7EIfICs56gk;9Qqz}Pm8)ZlOYKSoBbZsh-&*~N#;uXmE7BWyO0hkyU2Lk>nNpgyuN#A%1{xfOY%qKffdouor9B6h6qrrgxSA|u( z>h~y*Cqz|JN))9*!7zp(J4Hhn>oY3T!F~gu*S`S>SU7ZI$NHH{T2Tnn_9xus#j0i-Qkxo5Riuwq(~_5M=$7B26ER~ zP0X2Dc~4?c&tuh66A18=N8Q(B`)FC&a;3vU@01O1B4>G&^IIjmVWOc@Ih{g)9_osU zcTS#XaO0Y(?*C?6Ke5H|<3l6n9qVIxPLJzfoi?1fG6E)r5{`?T57=8JRZ_Upb~mPM z%j`;GzV;G`Wn4AKQl#D>JkH2Rufy(}7<^M_rrSzhbEcK{S3LFrZ#Jl-c{eL z{>|jj5DrmFp4E{#B2#C&p6zeeg98|Yk^}5v@lOCM(ePiDicN1K=mUQ#`fT?ygBykl zy2Z>Pml>3Y+`@MDRNj-C_b5Wht$*!K%sk=Dtt?K!BcbC$wW~6?IKCf=Pp81C#{%ZCd5ty7KO-#&m&F6M#-b2z1sKn$@>j&(cn+|Ek~MzX$)vj7q7J`WP~+fuRVE=lQzm3ECwuJP*Rv zcL3FaW>s&mHfe*Fzc@Vy?-QGK@nDBiL~7nwFN(SU#Q4Ue%XvZ8L8J)&>?N9hq<>x& zs$SDNOETSXAz|38c%R%U@ukKIBk9BQSnX=l=HH^iC3M*(>88T5pWv)s=2~kkr;o%z z@dM~tRX9&=zpJ=C`KexmLJyT^pJ}7_QyGq}^*X3vfxhPRy!>7=30l4zPvc{jB+93b zCSFyLS?hfrYSM>rTB+pdSv(DE2e~LLyDK4ox-5LaJ?nF=50M}1vCq2;i#0p{yc!}V z$f!>`MPzu39O>BbHq%K+5<{#P)rUl#2mq1JGVXP0nX{F}Pyz<+$vbn1QTi&Us+FKB z_Pt=Q^URuhy<@!fj@I1oKHcAIY_t{l8onv%@eIeK)T{gIGvePQsB=#%_`4667Y&`! zzhy7kI5RENu(L(TYi8CP>}XB?dOEu4_@)c3+9Zg9$I06W{b6;a#&%^B-1#b);Xo&Bhz3@3zP@IUqoLyIAENdw z3jrE!q%Xu(rj&orp3`bDp3l#5vA)NvvBq2X8t~GPZ@O11)j6GlUvJ+xQ`Zp_6f6hy ze`D;rL)^o^#zz>O8+4Fqx)&WCGDHgmJ2g?9$I6v1d|><7#F#7KdLg}zkUq}-8d$1k zQk%|%&%i{6%9?gk)`>v6R5NXPN;JJ-5q?qw5FtwBPnG^H+iac^cXRScNU@R5CK_x#Ah^%f4IDB^_ zK7*D$eKgp!9W6=EYY%#`*3BV|5EB(BX_o+Ja-Sp54u%#xE*1)~Yc?=L@ z|Anc5ko>G^#i5zwVJFQv6xY7uqR#PCQzpaqxE4wa@>qv=w6{l=&KQKRL&=#}0`h@BTW%Ym_bj-q!B6FXUv{N!Mg^fwG<+7it zx0HyflpZyyw}f7zWz75maqE47eBI~hBt$w|BhTNss%jCJO_W>oueDrujQ<$nDDltT z{CfupWi^i`61DW~;#d#5%NzQaPb)~Mqy0( zE;;`7(9#<_X(5!FDQp#QKsI)GVp6yj-eCc?nC(z!gde_0$`wKyy5s#kJ?*S=vc+~@ zQiJa$ev(Kl;^i?_xVp&TlBN|pv=Bzl6t=YyK62TODXM2ER+kKIxBh9elOV!zk|{eHRi+f4+1rHsP_zZvt4 zjJD27$HmcsSW)h^6H;;xqKgI|{))JT&Le#)kpD(*5`tfUx=B#D2yvyMT1-!d4;?DK z!$OGE`7lLjCTs}Nclr}@E-xhb{xo&3XIb9dw|qbQ$|KmE`Fz*$IzRUY{`hox@!pJ( zU*05j%`M%>Cg8P}*n)Fr@u;1qnX$4yI}~sOqv3T;I@wjVyu2skL__g4!YUp}$Wcij zAd3u8qvj>S%^HlPv^^9Gd_kzG166y)`S=7yebkKKw?2p=5y9T4Wtxt(O#+KoJ@}TQEU4BK57A*exiaMfwtE`l~`7Xu?de358!-Q5>7hx)@uXcMx zuWC%L`d)Z6kRFtCuHIP>qMY_n7Q5CFvrBJ>u@*`za z@_tPyKB0h3szE)*jE|!pHLDU04u1wG?e9JNLN5gK5snGz=%?XdxIi`n!7_6a1Gd); zt|j3^HwN$asQz=nIp|gOWYY`FJ>jF?uQV%E^SL_vTPq>XekY3fVU2&t0gMc}*m>W7 z?rc$p#=NYIr_AuYGk;W?Yf4e32#K-rZ_+X~n_%rFdmV>q-oWh*cE*UgF8YM^Z4&qi*P|ciZpV9H)BJcwa8#n{QLGi(;wRYaTc&(CBus zaDrX~1+YTXUXeTtlls|RO-3@pN3V^ov(tMspYnVy=C+m;7%|e*i(9*Uh9(@>@Gdngfb;SW zXwBEIdPN^h8>jwP$H*58Te7oN>1@rnw_^FQza9oX3tegz9v;INLo7fm(lhaWA9cL# ziolp6SesCD zEv_8iCOHX{6GfhoTm5I&WBzS*q%WqK;H&*?sH)%Y$8Es_EyxmI z#ukUu(C8>TV{nD=wHk@ccT39wTA*^sP=!&*kZGG0Q8CJTLnQer^s&%%DQ3=AZI9tY zzX4-}3Xs9YN+9Z$`D%l5=9#esw7=h@g2Ab&PXEv1#`%v=2k}~MAP{_V=?b?nfjw@? zTqWM=!O(^WDFBIqGt8P>RPPs}tG)f2yPcgQ`$(I|vrN^OHDAR<8`FzvP|tR}C{)~z zs+~xH^zD{-2?9i9LMXWPf}&{^);~?amKH}PD#9nKf~wyJ`rTH~OA1>@Ps(ZyG)mu{ z`b&wR6uXN?$*pMHwF z3MRB9K0MT$3AjD%LutLrp*w|NyUwXyn{Aw~G~ENwzON&l{Jb;UKcZ=U^LsZkB!gbF zh)JHY7@NfoL6zA0z32Av7q#&=XK?c_<=fp@+UUayy-XMt&X#!WlG=VY&1Uvu%=Nda zgx2?a)iVq+BT)wgW(kz@f@*u|IcyR5BlbPv+-~6FjMt-DN?S3;RvitAwV$Tno;s?7 zDF&q9+r@dOiwQR3B4P0;|JrJ%BQZMA6PE zn0EHu2CcOjHTb5lu6#Z@YodMWG+nmtdV1}*qUYW&-b445Ce+4W(}KCL{$KAhd(Ft; zp$w7~fd0_Ud#IqtdfC2bh2yUVyS^Q(ilnT}Jr`Nsnj72LEyUoVg|%r4i1v>2MY z%b(HNZK2uFXyty7XdSuqdn@St3iafc+ z8#ddqKjM)jr=wraXNBL5QhrOG*R4KEADld*WU3kBZXG0$vRh%mTsdWm5oG)g66yFO zo@CdjEo#3v;$A>8E3jPhSmr(*rmN=AI-uH2^6;ptg$|ueW9k_5XfCOK=bg)!2vgD% zQ`@sBZ_sDMP!LKry;z0~_dw70S9u299>Y6!?d3HsTIEWG2*Prko#ZMNh$JWEL+O}j zTx5USH;eUYQZJIwKOHaa_fR%d5fE8_MGoB8dhT<1YmVM|V1v)%(7`v|zOi_%|)aPQyvr>^R{jV`>|4dXPjNgLVG0^b)?=QE5^ z4~YOSa-xtYIi~lA*Iu}HK3ojS_iD+Rq54ae3`)G3*Ztoa#+CeF@hNQcg4Gy%24jv# z-xC~$*lMy7;z?6OuzKt5CoYYBuXVUhyDyHZNg`aJI=YSLRDlY{?8L8Y@Lz>4V!LU#H1KeHM14jWZ=zUI^Qzz2v(}U`&iJphpG>5xNK+BIBE)RpY|HvgjtPzIE8Ji94guVutzN53 zVDj3FMiGbz5YgUx+T%VRG2d+I!}w}5)=b&LfzxPM=0DP7!D=%dPd6)8yOy}Ynj|^i z*NtjiJgOBS)@%YdLFV}~$RaW=#O-0c{UlqGUjrY108JoT^t8jgExDq`7%xG(3NW^) zYzSMu#JFtWYcM4lUUOjyW9J;D3%+T%&yi2snZ@a1%wyy4eYbC^8O%K}W@?98YFO&V zZ@&Hma^_LizGKJN&n<6Cg4?>%At32UT}URACu&wMc@5%BV0*NUA9{0YvBI?R|1Qt literal 0 HcmV?d00001 diff --git a/packaging/assets/wizard-200.png b/packaging/assets/wizard-200.png new file mode 100644 index 0000000000000000000000000000000000000000..a2da6b3723e42fb6a5359adc628842aa252547ae GIT binary patch literal 21403 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 V{Ykbh1Y|8i`uB|QmO>rh{0Hl_klg?P literal 0 HcmV?d00001 diff --git a/packaging/assets/wizard-small-100.png b/packaging/assets/wizard-small-100.png new file mode 100644 index 0000000000000000000000000000000000000000..65ba4ea929b86a3e3b2cc59ad9fdf19734aea2c6 GIT binary patch literal 3417 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 literal 0 HcmV?d00001 diff --git a/packaging/assets/wizard-small-125.png b/packaging/assets/wizard-small-125.png new file mode 100644 index 0000000000000000000000000000000000000000..a67a6a2e97506d9aa184f902dfeabecdca2eac11 GIT binary patch literal 4507 zcmV;M5oGR(P)2 z3yfUVdH&A1ubJ7WSwm&OJp6tMW8)W)blZ~BrcGV7bdxrfh}0I7pr#6yqL89=!-@7}{d++(H|3CN6&d$#6&MY1^ zIRAQl@0>a3p8J3IKmY5Ta}{PHA~mobdi$qU%wHFM@nCTA-1)y`!su53^>L87QfYlD znV3LI5h!U_dx>evl5%NRQ&cUhX;QpQ=rNSN`}W`0^+7#fErBxdJ#tt&uKUXA9zWOdWe|PY z@dMw4F-)aE24oaJ7yg4%(@I0TC8V~Ek;?oZA06#xp`0vCW$Xh$|MO`11!i5DmQr8@%<%HVGLv_QY@nix*;R8k&qbxX$VpsATxku2rx>! zt`qbis*D}fmBJtW=>E%|Px+h?l>VgR#=ji7&GE9^VBp4;LZp;(L?WhQ80pj{y55$!`kugSr0hxx8pLswge2GaZt=$e4l}EM= zHvmS}>yV9ipSjJ=_A($bOsoN|E*wCTHpURNYqXnlqT<;t!w=1WuvW(i!-IpQw{BHt zf^!#qkrHyO0AM+KZNUS7^H5cyhj-)Yho{8 zK!mg+6N+4Do9tNy|FJIitICGBMv$CV=csIzj+*n_E>G6G^E zkZGS|rZnqHfW%%9z>k;Z-pt~G5E

lnL{1sF8E4-Gb@3(er{Oaiu^|s%1b}9mj@H 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 tNr|XiR=#%;W845# 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 Hu0mjfEI#ta literal 0 HcmV?d00001 diff --git a/packaging/assets/wizard-small-200.png b/packaging/assets/wizard-small-200.png new file mode 100644 index 0000000000000000000000000000000000000000..eb828689ddb181b39ab016f7b59595ae263b3a2c GIT binary patch literal 7377 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 Applying update via silent Setup.exe" $setupArgs = "/VERYSILENT /NORESTART /SUPPRESSMSGBOXES /DIR=`"$prog`" /InstallMode=update" + # Setup stops the tray (StopForUpdate -KillTray); remember to bring it back. + $trayWasRunning = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { + $_.CommandLine -and $_.CommandLine -match 'netx_tray\.ps1' + }).Count -gt 0 $sp = Start-Process -FilePath $pkgPath -ArgumentList $setupArgs -Wait -PassThru if ($null -eq $sp.ExitCode -or $sp.ExitCode -ne 0) { $code = if ($null -eq $sp.ExitCode) { "null" } else { $sp.ExitCode } @@ -475,6 +479,14 @@ try { -ProgramRoot $prog -DataRoot $data -SkipBrowser if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } + if ($trayWasRunning) { + $trayPs1 = Join-Path $prog "packaging\netx_tray.ps1" + if (Test-Path -LiteralPath $trayPs1) { + Write-Host "==> Restarting NetX tray" + Start-NetxPowerShell -File $trayPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-QuietStart") ` + -WorkingDirectory $prog -WindowStyle Hidden | Out-Null + } + } } else { Write-Host "==> Applying legacy zip update via update_netx.ps1" & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "update_netx.ps1") ` diff --git a/packaging/install_autostart.ps1 b/packaging/install_autostart.ps1 index 4cba106..b0ac9ef 100644 --- a/packaging/install_autostart.ps1 +++ b/packaging/install_autostart.ps1 @@ -28,11 +28,18 @@ if ($Remove) { exit 0 } -$arg = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$tray`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -StartOnLaunch" +# -QuietStart: bring NetX up at logon without popping a browser window. +$arg = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$tray`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -StartOnLaunch -QuietStart" $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $arg -WorkingDirectory $prog -$trig = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME -$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest +# Any user logon, run for the local Administrators group: Setup runs elevated +# (often as a different admin account), so $env:USERNAME is not the desktop user. +$trig = New-ScheduledTaskTrigger -AtLogOn +$trig.Delay = "PT10S" +$admins = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate( + [System.Security.Principal.NTAccount]).Value +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable ` + -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances Parallel +$principal = New-ScheduledTaskPrincipal -GroupId $admins -RunLevel Highest Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath ` -Action $action -Trigger $trig -Settings $settings -Principal $principal -Force | Out-Null diff --git a/packaging/install_service.ps1 b/packaging/install_service.ps1 index 02ea09c..434013c 100644 --- a/packaging/install_service.ps1 +++ b/packaging/install_service.ps1 @@ -129,7 +129,9 @@ if ($Mode -eq "winsw") { $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $arg -WorkingDirectory $prog $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) + # ExecutionTimeLimit 0: the default (72h) would kill the long-running supervisor. + $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) ` + -ExecutionTimeLimit ([TimeSpan]::Zero) Register-ScheduledTask -TaskName "NetXService" -TaskPath "\NetX\" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null if ($Start) { Start-ScheduledTask -TaskName "NetXService" -TaskPath "\NetX\" diff --git a/packaging/install_update_task.ps1 b/packaging/install_update_task.ps1 index afcf712..abdecce 100644 --- a/packaging/install_update_task.ps1 +++ b/packaging/install_update_task.ps1 @@ -22,6 +22,11 @@ $checkPs1 = Join-Path $PSScriptRoot "check_update.ps1" if ($Remove) { Unregister-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Confirm:$false -ErrorAction SilentlyContinue + # Tray also applies updates on launch when NETX_UPDATE_AUTO=true; turn that off too. + $envFile = Join-Path $data ".env" + if (Test-Path $envFile) { + Write-DotEnvValue -Path $envFile -Values @{ "NETX_UPDATE_AUTO" = "false" } + } Write-Host "==> Update task removed" exit 0 } diff --git a/packaging/installer/netx.iss b/packaging/installer/netx.iss index d195fae..488e976 100644 --- a/packaging/installer/netx.iss +++ b/packaging/installer/netx.iss @@ -14,11 +14,16 @@ ; Stage directory produced by build_release.ps1 (relative to this .iss) #define StageDir "..\release\netx-win64" #define IconFile "..\assets\netx.ico" +#define AssetsDir "..\assets" [Setup] AppId={{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001} AppName={#MyAppName} AppVersion={#MyAppVersion} +AppVerName={#MyAppName} {#MyAppVersion} +VersionInfoVersion={#MyAppVersion} +VersionInfoProductName={#MyAppName} +UninstallDisplayName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} DefaultDirName={autopf}\NetX @@ -34,7 +39,11 @@ SetupIconFile={#IconFile} UninstallDisplayIcon={app}\packaging\assets\netx.ico Compression=lzma2 SolidCompression=yes -WizardStyle=modern +; Follows the Windows light/dark setting; windows11 = flat Fluent-style controls. +WizardStyle=modern dynamic windows11 hidebevels +WizardImageFile={#AssetsDir}\wizard-100.png,{#AssetsDir}\wizard-125.png,{#AssetsDir}\wizard-150.png,{#AssetsDir}\wizard-200.png +WizardSmallImageFile={#AssetsDir}\wizard-small-100.png,{#AssetsDir}\wizard-small-125.png,{#AssetsDir}\wizard-small-150.png,{#AssetsDir}\wizard-small-200.png +WizardImageBackColor=#0a1222 ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible PrivilegesRequired=admin @@ -42,7 +51,8 @@ PrivilegesRequired=admin ; Do NOT use CloseApplications=yes — it can freeze/beep on the Tasks/Ready ; pages while scanning locked NetX tray/service processes with no visible dialog. CloseApplications=no -ShowLanguageDialog=yes +; Pick from the Windows UI language; no extra dialog. +ShowLanguageDialog=auto [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" @@ -51,10 +61,17 @@ Name: "chinesesimplified"; MessagesFile: "Languages\ChineseSimplified.isl" [CustomMessages] english.CreateDesktopIcon=Create a desktop shortcut english.SetupOptions=After install: -english.StartTrayNow=Start NetX tray now -english.EnableAutostart=Start NetX tray at Windows logon -english.EnableAutoUpdate=Enable daily silent auto-update (needs network later) -english.InstallService=Install as Windows Service (uses bundled WinSW, offline) +english.StartTrayNow=Start NetX now +english.RunModeGroup=How NetX runs: +english.RunModeTray=Desktop tray (recommended) - control NetX from the notification area +english.EnableAutostart=Start automatically when I sign in to Windows +english.RunModeService=Windows service - runs at boot without anyone signed in (servers) +english.MaintenanceGroup=Maintenance: +english.EnableAutoUpdate=Install updates automatically every day (needs network) +english.ServiceInstallFailed=NetX was installed, but the Windows service could not be registered (exit %1).%n%nYou can still start NetX from the Start Menu. Log: %2 +english.IconStart=Start NetX +english.IconStop=Stop NetX +english.IconUpdate=Check for updates english.UninstallDeleteData=Also delete NetX data (%ProgramData%\NetX)?%n%nYes = remove database, settings, and secrets%nNo = keep data for a later reinstall english.DbPageCaption=Database english.DbPageDescription=Choose built-in or external PostgreSQL. Optional credential key: paste from an existing install, or leave empty to auto-generate. @@ -86,10 +103,17 @@ english.ModeUpdateHint=Replaces program files only. ProgramData\.env and databas english.ModeReinstallHint=Shows the Database page again. Does not delete ProgramData unless you uninstall and choose to. chinesesimplified.CreateDesktopIcon=创建桌面快捷方式 chinesesimplified.SetupOptions=安装完成后: -chinesesimplified.StartTrayNow=立即启动 NetX 托盘 -chinesesimplified.EnableAutostart=开机时自动启动 NetX 托盘 -chinesesimplified.EnableAutoUpdate=启用每日静默自动更新(以后需要联网) -chinesesimplified.InstallService=安装为 Windows 服务(使用已捆绑的 WinSW,可离线) +chinesesimplified.StartTrayNow=立即启动 NetX +chinesesimplified.RunModeGroup=运行方式: +chinesesimplified.RunModeTray=桌面托盘(推荐)— 在任务栏通知区域控制 NetX +chinesesimplified.EnableAutostart=登录 Windows 时自动启动 +chinesesimplified.RunModeService=Windows 服务 — 开机即运行,无需登录(适合服务器) +chinesesimplified.MaintenanceGroup=维护: +chinesesimplified.EnableAutoUpdate=每天自动安装更新(需要联网) +chinesesimplified.ServiceInstallFailed=NetX 已安装,但 Windows 服务注册失败(退出码 %1)。%n%n仍可从开始菜单启动 NetX。日志:%2 +chinesesimplified.IconStart=启动 NetX +chinesesimplified.IconStop=停止 NetX +chinesesimplified.IconUpdate=检查更新 chinesesimplified.UninstallDeleteData=是否同时删除 NetX 数据(%ProgramData%\NetX)?%n%n是 = 删除数据库、配置和密钥%n否 = 保留数据以便以后重装 chinesesimplified.DbPageCaption=数据库 chinesesimplified.DbPageDescription=选择内置或外置 PostgreSQL。可选粘贴已有 NETX_CREDENTIAL_SECRET_KEY;留空则自动生成。 @@ -121,6 +145,12 @@ chinesesimplified.ModeUpdateHint=仅替换程序文件,保留 ProgramData\.env chinesesimplified.ModeReinstallHint=再次显示数据库页。不会删除 ProgramData(除非卸载时选择删除)。 [Tasks] +; Run mode is exclusive: tray + service together would race for the same port. +; On upgrades CurPageChanged pre-selects whatever is actually installed. +Name: "runtray"; Description: "{cm:RunModeTray}"; GroupDescription: "{cm:RunModeGroup}"; Flags: exclusive +Name: "runtray\autostart"; Description: "{cm:EnableAutostart}" +Name: "runservice"; Description: "{cm:RunModeService}"; GroupDescription: "{cm:RunModeGroup}"; Flags: exclusive unchecked +Name: "autoupdate"; Description: "{cm:EnableAutoUpdate}"; GroupDescription: "{cm:MaintenanceGroup}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce [Files] @@ -163,26 +193,30 @@ Type: filesandordirs; Name: "{app}\.venv" Type: files; Name: "{commondesktop}\NetX Start.lnk" Type: files; Name: "{commondesktop}\NetX First-time setup.lnk" Type: files; Name: "{commondesktop}\首次配置(内置或外置数据库).lnk" +; Start Menu entries from <= 0.4.16 (English-only names; toggles moved into the tray / Tasks page). +Type: files; Name: "{group}\NetX Tray.lnk" +Type: files; Name: "{group}\Start NetX.lnk" +Type: files; Name: "{group}\Stop NetX.lnk" +Type: files; Name: "{group}\Check for updates.lnk" +Type: files; Name: "{group}\Open NetX UI.url" +Type: files; Name: "{group}\Install Windows Service (admin).lnk" +Type: files; Name: "{group}\Enable silent auto-update (daily).lnk" +Type: files; Name: "{group}\Enable start at logon.lnk" [Icons] ; Prefer .cmd targets so Server 2012 / classic Start Menu shows normal program entries. -Name: "{group}\NetX Tray"; Filename: "{app}\NetX-Tray.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Start NetX"; Filename: "{app}\NetX-Start.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Stop NetX"; Filename: "{app}\NetX-Stop.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Check for updates"; Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\packaging\launch_shortcut.ps1"" -Action update -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Open NetX UI"; Filename: "http://127.0.0.1:8890/"; IconFilename: "{app}\packaging\assets\netx.ico" +; Autostart / auto-update / service toggles live in the tray Settings menu and the Tasks page. +Name: "{group}\NetX"; Filename: "{app}\NetX-Tray.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\{cm:IconStart}"; Filename: "{app}\NetX-Start.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\{cm:IconStop}"; Filename: "{app}\NetX-Stop.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" +Name: "{group}\{cm:IconUpdate}"; Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\launch_shortcut.ps1"" -Action update -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" Name: "{group}\{cm:ReconfigureDb}"; Filename: "{app}\NetX-FirstRun.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Install Windows Service (admin)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Enable silent auto-update (daily)"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -Name: "{group}\Enable start at logon"; Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico" -; One desktop shortcut only (tray = start + tray UI). Start Menu keeps Start/Stop entries. +; One desktop shortcut only (tray = start + tray UI). Name: "{commondesktop}\NetX"; Filename: "{app}\NetX-Tray.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\packaging\assets\netx.ico"; Tasks: desktopicon [Run] +; Autostart / service / auto-update are applied from the Tasks page in CurStepChanged. Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\netx_tray.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -StartOnLaunch"; WorkingDir: "{app}"; Description: "{cm:StartTrayNow}"; Flags: postinstall runhidden nowait skipifsilent; Check: NetxShouldStartTray -Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\install_autostart.ps1"" -ProgramRoot ""{app}"""; WorkingDir: "{app}"; Description: "{cm:EnableAutostart}"; Flags: postinstall runhidden skipifsilent unchecked -Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\install_update_task.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"""; WorkingDir: "{app}"; Description: "{cm:EnableAutoUpdate}"; Flags: postinstall runhidden skipifsilent unchecked -Filename: "powershell.exe"; Parameters: "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{app}\packaging\install_service.ps1"" -ProgramRoot ""{app}"" -DataRoot ""{commonappdata}\NetX"" -Start"; WorkingDir: "{app}"; Description: "{cm:InstallService}"; Flags: postinstall runhidden skipifsilent unchecked ; Stop runtime BEFORE files are deleted. Do not also Exec prepare from ; InitializeUninstall (that previously raced/killed unins000 or hung the UI). @@ -232,6 +266,7 @@ var GPgToolsReady: Boolean; GDbConnOk: Boolean; GNextBtnCaption: String; + GTasksSynced: Boolean; function EnvHasDbMode(): Boolean; var @@ -822,6 +857,7 @@ begin EdCredKey.Left := ScaleX(120); EdCredKey.Width := DbPage.SurfaceWidth - ScaleX(120); EdCredKey.Text := ''; + EdCredKey.PasswordChar := '*'; TopPos := TopPos + ScaleY(24); LblCredHint := TNewStaticText.Create(DbPage); @@ -1120,6 +1156,106 @@ begin Exec('powershell.exe', Params, ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode); end; +function NetxTaskExists(const Name: String): Boolean; +var + RC: Integer; +begin + Result := Exec(ExpandConstant('{sys}\schtasks.exe'), '/Query /TN "\NetX\' + Name + '"', '', + SW_HIDE, ewWaitUntilTerminated, RC) and (RC = 0); +end; + +function NetxServiceExists(): Boolean; +var + RC: Integer; +begin + Result := Exec(ExpandConstant('{sys}\sc.exe'), 'query NetX', '', SW_HIDE, ewWaitUntilTerminated, RC) and (RC = 0); +end; + +procedure CurPageChanged(CurPageID: Integer); +begin + { Existing install: show the run mode / toggles that are really in place + (older versions set them from Finish-page checkboxes Inno never recorded). } + if (CurPageID = wpSelectTasks) and (not GTasksSynced) then + begin + GTasksSynced := True; + if FileExists(ExpandConstant('{app}\packaging\netx_tray.ps1')) then + begin + if NetxServiceExists() then + WizardSelectTasks('runservice') + else + WizardSelectTasks('runtray'); + if NetxTaskExists('NetXTray') then + WizardSelectTasks('runtray\autostart') + else + WizardSelectTasks('!runtray\autostart'); + if NetxTaskExists('NetXUpdate') then + WizardSelectTasks('autoupdate') + else + WizardSelectTasks('!autoupdate'); + end; + end; +end; + +function RunNetxScript(const Script, Extra: String): Integer; +var + Params: String; +begin + Params := + '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "' + ExpandConstant('{app}\packaging\' + Script) + '"' + + ' -ProgramRoot "' + ExpandConstant('{app}') + '"' + + ' -DataRoot "' + ExpandConstant('{commonappdata}\NetX') + '"' + Extra; + if not Exec('powershell.exe', Params, ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, Result) then + Result := -1; +end; + +function TasksGivenOnCommandLine(): Boolean; +var + i: Integer; + P: String; +begin + Result := False; + for i := 1 to ParamCount do + begin + P := Uppercase(ParamStr(i)); + if (Pos('/TASKS=', P) = 1) or (Pos('/MERGETASKS=', P) = 1) then + begin + Result := True; + Exit; + end; + end; +end; + +procedure ApplyRunModeTasks(); +var + Code: Integer; +begin + { Silent auto-updates keep whatever is installed unless /TASKS is passed. } + if WizardSilent and (not TasksGivenOnCommandLine()) then + Exit; + if not FileExists(ExpandConstant('{commonappdata}\NetX\.env')) then + Exit; + + if WizardIsTaskSelected('runservice') then + begin + Code := RunNetxScript('install_service.ps1', ' -Start'); + if Code <> 0 then + SuppressibleMsgBox(FmtMessage(ExpandConstant('{cm:ServiceInstallFailed}'), [IntToStr(Code), + ExpandConstant('{commonappdata}\NetX\data\runtime')]), mbError, MB_OK, IDOK); + end + else if NetxServiceExists() then + RunNetxScript('install_service.ps1', ' -Uninstall'); + + if WizardIsTaskSelected('runtray\autostart') then + RunNetxScript('install_autostart.ps1', '') + else + RunNetxScript('install_autostart.ps1', ' -Remove'); + + if WizardIsTaskSelected('autoupdate') then + RunNetxScript('install_update_task.ps1', '') + else if NetxTaskExists('NetXUpdate') then + RunNetxScript('install_update_task.ps1', ' -Remove'); +end; + procedure CurStepChanged(CurStep: TSetupStep); var UninstallKey: String; @@ -1138,6 +1274,7 @@ begin UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{A7E3C2D1-9F40-4B8E-9C1A-NETXWIN64001}_is1'; RegDeleteValue(HKLM, UninstallKey, 'QuietUninstallString'); + ApplyRunModeTasks(); ResumeNetxServiceAfterInstall(); end; end; diff --git a/packaging/launch_shortcut.ps1 b/packaging/launch_shortcut.ps1 index 7f13188..fcc5fa2 100644 --- a/packaging/launch_shortcut.ps1 +++ b/packaging/launch_shortcut.ps1 @@ -79,7 +79,7 @@ try { } } "stop" { - $p = Invoke-NetxHiddenPs1 -File (Join-Path $PSScriptRoot "stop_netx_app.ps1") + $p = Invoke-NetxHiddenPs1 -File (Join-Path $PSScriptRoot "runtime_ctl.ps1") -ExtraArgs @("-Action", "stop") if ($null -ne $p.ExitCode -and $p.ExitCode -ne 0) { throw "stop_exit_$($p.ExitCode)" } } "update" { @@ -102,10 +102,11 @@ try { if ($map["NETX_HOST"]) { $hostBind = $map["NETX_HOST"] } if ($map["NETX_PORT"]) { try { $port = [int]$map["NETX_PORT"] } catch {} } } - $p = Invoke-NetxHiddenPs1 -File (Join-Path $PSScriptRoot "start_netx_app.ps1") -ExtraArgs @("-SkipBrowser") + $hostBind = Get-NetxClientHost -BindHost $hostBind + $p = Invoke-NetxHiddenPs1 -File (Join-Path $PSScriptRoot "runtime_ctl.ps1") -ExtraArgs @("-Action", "start") if ($p.ExitCode -ne 0) { throw "start_exit_$($p.ExitCode)" } if (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 180) { - try { Start-Process "http://${hostBind}:${port}/" } catch {} + Open-NetxUrl -Url "http://${hostBind}:${port}/" } else { throw (T ` "NetX started but /health not ready within 180s.`nSee: $logDir\netx.err.log" ` diff --git a/packaging/netx_tray.ps1 b/packaging/netx_tray.ps1 index bffcc9c..21364b0 100644 --- a/packaging/netx_tray.ps1 +++ b/packaging/netx_tray.ps1 @@ -2,12 +2,16 @@ [string]$ProgramRoot = "", [string]$DataRoot = "", [switch]$StartOnLaunch = $true, + # Logon autostart / post-update relaunch: bring NetX up but never pop a browser. + [switch]$QuietStart = $false, [switch]$CheckUpdateOnLaunch = $false, [switch]$AutoUpdate = $false ) # System-tray controller for NetX (Windows packaged installs). # Always elevated: Start/Stop/Update touch Program Files + services. +# The UI thread never blocks: /health and scheduled-task state are polled in a +# background runspace, child scripts are watched by a timer. $ErrorActionPreference = "Stop" . "$PSScriptRoot\_common.ps1" @@ -16,102 +20,141 @@ Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundP Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing -# Custom tray menu: hover/selected highlight + flat light chrome (default OS menu looks dead). if (-not ("NetxMenuRenderer" -as [type])) { Add-Type -ReferencedAssemblies System.Windows.Forms, System.Drawing -TypeDefinition @" +using System; using System.Drawing; using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; using System.Windows.Forms; -public sealed class NetxMenuColorTable : ProfessionalColorTable { - static readonly Color Hover = Color.FromArgb(232, 240, 252); - static readonly Color Press = Color.FromArgb(214, 228, 248); - static readonly Color Border = Color.FromArgb(170, 198, 240); - static readonly Color Edge = Color.FromArgb(210, 214, 220); - static readonly Color Sep = Color.FromArgb(228, 230, 235); - static readonly Color Bg = Color.FromArgb(252, 252, 253); +public static class NetxNative { + [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); + [DllImport("user32.dll")] public static extern bool DestroyIcon(IntPtr handle); + [DllImport("dwmapi.dll")] static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int value, int size); - public override Color MenuItemSelected { get { return Hover; } } - public override Color MenuItemSelectedGradientBegin { get { return Hover; } } - public override Color MenuItemSelectedGradientEnd { get { return Hover; } } - public override Color MenuItemBorder { get { return Border; } } - public override Color MenuItemPressedGradientBegin { get { return Press; } } - public override Color MenuItemPressedGradientMiddle { get { return Press; } } - public override Color MenuItemPressedGradientEnd { get { return Press; } } - public override Color MenuBorder { get { return Edge; } } - public override Color ToolStripDropDownBackground { get { return Bg; } } - public override Color ImageMarginGradientBegin { get { return Bg; } } - public override Color ImageMarginGradientMiddle { get { return Bg; } } - public override Color ImageMarginGradientEnd { get { return Bg; } } - public override Color SeparatorDark { get { return Sep; } } - public override Color SeparatorLight { get { return Sep; } } + // Windows 11: rounded popup corners + themed 1px border drawn by DWM. + public static bool RoundCorners(IntPtr hwnd, Color border) { + try { + int pref = 2; // DWMWCP_ROUND + if (DwmSetWindowAttribute(hwnd, 33, ref pref, 4) != 0) return false; + int c = ColorTranslator.ToWin32(border); + DwmSetWindowAttribute(hwnd, 34, ref c, 4); + return true; + } catch { return false; } + } +} + +public sealed class NetxTheme { + public Color Back, Hover, Text, Muted, Sep, Border; + public bool DwmRounded; +} + +public sealed class NetxMenuColorTable : ProfessionalColorTable { + readonly NetxTheme t; + public NetxMenuColorTable(NetxTheme theme) { t = theme; UseSystemColors = false; } + public override Color ToolStripDropDownBackground { get { return t.Back; } } + public override Color ImageMarginGradientBegin { get { return t.Back; } } + public override Color ImageMarginGradientMiddle { get { return t.Back; } } + public override Color ImageMarginGradientEnd { get { return t.Back; } } + public override Color MenuBorder { get { return t.Border; } } + public override Color MenuItemBorder { get { return t.Hover; } } + public override Color MenuItemSelected { get { return t.Hover; } } + public override Color MenuItemSelectedGradientBegin { get { return t.Hover; } } + public override Color MenuItemSelectedGradientEnd { get { return t.Hover; } } + public override Color MenuItemPressedGradientBegin { get { return t.Hover; } } + public override Color MenuItemPressedGradientMiddle { get { return t.Hover; } } + public override Color MenuItemPressedGradientEnd { get { return t.Hover; } } + public override Color SeparatorDark { get { return t.Sep; } } + public override Color SeparatorLight { get { return t.Sep; } } } public sealed class NetxMenuRenderer : ToolStripProfessionalRenderer { - public NetxMenuRenderer() : base(new NetxMenuColorTable()) { + readonly NetxTheme t; + readonly float scale; + + public NetxMenuRenderer(NetxTheme theme, float dpiScale) : base(new NetxMenuColorTable(theme)) { + t = theme; + scale = dpiScale; RoundedEdges = false; } - protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { - ToolStripMenuItem item = e.Item as ToolStripMenuItem; - if (item == null || !item.Selected || !item.Enabled) { - base.OnRenderMenuItemBackground(e); - return; - } - Rectangle r = new Rectangle(2, 0, e.Item.Width - 4, e.Item.Height); - using (SolidBrush brush = new SolidBrush(Color.FromArgb(232, 240, 252))) - using (Pen pen = new Pen(Color.FromArgb(170, 198, 240))) { - e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; - e.Graphics.FillRectangle(brush, r); - e.Graphics.DrawRectangle(pen, r.X, r.Y, r.Width - 1, r.Height - 1); - } + int S(int px) { return (int)Math.Round(px * scale); } + + static GraphicsPath Round(Rectangle r, int radius) { + int d = radius * 2; + GraphicsPath p = new GraphicsPath(); + p.AddArc(r.X, r.Y, d, d, 180, 90); + p.AddArc(r.Right - d, r.Y, d, d, 270, 90); + p.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90); + p.AddArc(r.X, r.Bottom - d, d, d, 90, 90); + p.CloseFigure(); + return p; } + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) { + using (SolidBrush b = new SolidBrush(t.Back)) e.Graphics.FillRectangle(b, e.AffectedBounds); + } + + protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) { } + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { + if (t.DwmRounded) return; // DWM draws the rounded border on Windows 11 Rectangle r = new Rectangle(0, 0, e.AffectedBounds.Width - 1, e.AffectedBounds.Height - 1); - using (Pen pen = new Pen(Color.FromArgb(210, 214, 220))) { - e.Graphics.DrawRectangle(pen, r); + using (Pen pen = new Pen(t.Border)) e.Graphics.DrawRectangle(pen, r); + } + + protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { + ToolStripItem item = e.Item; + if (!item.Selected || !item.Enabled) return; + Rectangle r = new Rectangle(S(4), S(1), item.Width - S(8), item.Height - S(2)); + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (GraphicsPath path = Round(r, S(4))) + using (SolidBrush b = new SolidBrush(t.Hover)) e.Graphics.FillPath(b, path); + } + + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { + // Drawn directly: the base renderer forces grey on disabled (header) items. + string role = e.Item.Tag as string; + Color c; + if (role == "header") c = t.Text; + else if (role == "status") c = t.Muted; + else c = e.Item.Enabled ? t.Text : t.Muted; + TextRenderer.DrawText(e.Graphics, e.Text, e.TextFont, e.TextRectangle, c, e.TextFormat); + } + + protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { + string role = e.Item.Tag as string; + if (e.Image != null && (e.Item.Enabled || role == "status" || role == "header")) { + e.Graphics.DrawImage(e.Image, e.ImageRectangle); + return; } + base.OnRenderItemImage(e); + } + + protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { + e.ArrowColor = t.Muted; + base.OnRenderArrow(e); } protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) { int y = e.Item.Height / 2; - using (Pen pen = new Pen(Color.FromArgb(228, 230, 235))) { - e.Graphics.DrawLine(pen, 10, y, e.Item.Width - 10, y); - } + using (Pen pen = new Pen(t.Sep)) e.Graphics.DrawLine(pen, S(12), y, e.Item.Width - S(12), y); } } "@ } +# Crisp text on high-DPI screens (powershell.exe is DPI-unaware by default). +try { [void][NetxNative]::SetProcessDPIAware() } catch {} +[System.Windows.Forms.Application]::EnableVisualStyles() + $prog = Get-NetxProgramRoot -Override $ProgramRoot $data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot -$hostBind = "127.0.0.1" -$port = 8890 $envPath = Join-Path $data ".env" -$map = @{} -if (Test-Path $envPath) { - $map = Read-DotEnv -Path $envPath - if ($map["NETX_HOST"]) { $hostBind = $map["NETX_HOST"] } - if ($map["NETX_PORT"]) { try { $port = [int]$map["NETX_PORT"] } catch {} } -} -$autoVal = "" -if ($map["NETX_UPDATE_AUTO"]) { $autoVal = $map["NETX_UPDATE_AUTO"] } -elseif ($env:NETX_UPDATE_AUTO) { $autoVal = $env:NETX_UPDATE_AUTO } -if ($autoVal -match '^(1|true|yes|on)$') { - $AutoUpdate = $true - $CheckUpdateOnLaunch = $true -} -$uiUrl = "http://${hostBind}:${port}/" -$script:ver = Get-NetxVersion -ProgramRoot $prog -$ver = $script:ver -$dbMode = Get-DbMode -EnvMap $map -$dbModeLabel = if ($dbMode -eq "bundled") { - if (([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')) { "内置库" } else { "built-in DB" } -} else { - if (([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)')) { "外置库" } else { "external DB" } -} -$zh = ([cultureinfo]::CurrentUICulture.Name -match '^(zh|zh-)') +$logDir = Join-Path $data "data\runtime" + +$zh = ([cultureinfo]::CurrentUICulture.Name -match '^zh') function T([string]$En, [string]$Zh) { if ($zh) { return $Zh } else { return $En } } @@ -128,18 +171,39 @@ if (-not (Test-Path $envPath)) { exit 1 } -# Only one tray icon per machine session (desktop + postinstall + autostart can race). +function Read-NetxTrayConfig { + $map = Read-DotEnv -Path $envPath + $bind = if ($map["NETX_HOST"]) { $map["NETX_HOST"] } else { "127.0.0.1" } + $p = 8890 + if ($map["NETX_PORT"]) { try { $p = [int]$map["NETX_PORT"] } catch {} } + $script:clientHost = Get-NetxClientHost -BindHost $bind + $script:port = $p + $script:uiUrl = "http://$($script:clientHost):$p/" + $script:dbModeLabel = if ((Get-DbMode -EnvMap $map) -eq "bundled") { (T "Built-in DB" "内置库") } else { (T "External DB" "外置库") } + $script:autoUpdateEnv = ([string]$map["NETX_UPDATE_AUTO"] -match '^(1|true|yes|on)$') -or + ([string]$env:NETX_UPDATE_AUTO -match '^(1|true|yes|on)$') +} +Read-NetxTrayConfig +if ($script:autoUpdateEnv) { + $AutoUpdate = $true + $CheckUpdateOnLaunch = $true +} +$script:ver = Get-NetxVersion -ProgramRoot $prog + +# Only one tray icon per logon session (desktop + postinstall + autostart can race). $script:netxTrayMutex = $null try { $createdNew = $false $script:netxTrayMutex = New-Object System.Threading.Mutex($true, "Local\NetX.WinTray.SingleInstance", [ref]$createdNew) if (-not $createdNew) { - if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) { - try { Start-Process $uiUrl } catch {} - } elseif ($StartOnLaunch) { - Start-NetxPowerShell -File (Join-Path $PSScriptRoot "start_netx_app.ps1") ` - -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-SkipBrowser") ` - -WorkingDirectory $prog -WindowStyle Hidden | Out-Null + if (-not $QuietStart) { + if (Test-NetxApiHealthy -HostName $script:clientHost -Port $script:port -TimeoutSec 2) { + Open-NetxUrl -Url $script:uiUrl + } elseif ($StartOnLaunch) { + Start-NetxPowerShell -File (Join-Path $PSScriptRoot "runtime_ctl.ps1") ` + -Arguments @("-Action", "start", "-ProgramRoot", $prog, "-DataRoot", $data) ` + -WorkingDirectory $prog -WindowStyle Hidden | Out-Null + } } exit 0 } @@ -147,211 +211,196 @@ try { # If mutex fails, continue with a tray (better than no UI control). } -$startPs1 = Join-Path $PSScriptRoot "start_netx_app.ps1" -$stopPs1 = Join-Path $PSScriptRoot "stop_netx_app.ps1" +$runtimePs1 = Join-Path $PSScriptRoot "runtime_ctl.ps1" $checkPs1 = Join-Path $PSScriptRoot "check_update.ps1" $setupPs1 = Join-Path $PSScriptRoot "setup_first_run.ps1" +$autostartPs1 = Join-Path $PSScriptRoot "install_autostart.ps1" +$updateTaskPs1 = Join-Path $PSScriptRoot "install_update_task.ps1" -function Invoke-NetxScript { - param( - [string]$File, - [string[]]$ExtraArgs = @(), - [switch]$Wait = $false - ) - return Start-NetxPowerShell -File $File -Arguments (@("-ProgramRoot", $prog, "-DataRoot", $data) + $ExtraArgs) ` - -WorkingDirectory $prog -WindowStyle Hidden -PassThru -Wait:$Wait -} - -function Update-NetxTrayTip { - # Refresh version after in-place updates (tray process may outlive version.json). - try { - $nowVer = Get-NetxVersion -ProgramRoot $prog - if ($nowVer -and $nowVer -ne $script:ver) { - $script:ver = $nowVer - if ($null -ne $script:miHeader) { $script:miHeader.Text = "NetX $script:ver" } - } - } catch {} - $running = Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 1 - $state = if ($running) { - (T "Running" "运行中") - } else { - (T "Stopped" "已停止") - } - $v = $script:ver - $tip = "NetX $v · $state · $dbModeLabel`n$uiUrl" - if ($tip.Length -gt 63) { - # NotifyIcon.Text max ~63 chars on older Windows. - $tip = "NetX $v · $state · $dbModeLabel" - } - $notify.Text = $tip -} - -function Close-NetxTrayMenu { - # ContextMenuStrip stays modal if Click handlers block; close before dialogs/waits. - try { $menu.Close() } catch {} - try { [System.Windows.Forms.Application]::DoEvents() } catch {} -} - -function Start-NetxTrayDeferred { - param([scriptblock]$Work) - Close-NetxTrayMenu - $timer = New-Object System.Windows.Forms.Timer - $timer.Interval = 50 - $script:netxTrayDeferredWork = $Work - $timer.add_Tick({ - $t = $script:netxTrayDeferredTimer - try { if ($t) { $t.Stop(); $t.Dispose() } } catch {} - $script:netxTrayDeferredTimer = $null - $w = $script:netxTrayDeferredWork - $script:netxTrayDeferredWork = $null - if ($w) { & $w } +# ---------------------------------------------------------------- background probe +# /health + runner/task state off the UI thread; the UI timer only reads $sync. +$script:sync = [hashtable]::Synchronized(@{ + HealthUrl = "$($script:uiUrl)health" + Healthy = $false + Seen = $false + Runner = "" + Autostart = $false + AutoUpdate = $false + Stop = $false }) - $script:netxTrayDeferredTimer = $timer - $timer.Start() -} - -function Start-NetxTrayWatchProcess { - # Never Start-Process -Wait on the WinForms UI thread — it freezes the tray menu. - param( - [Parameter(Mandatory = $true)]$Process, - [scriptblock]$OnExit - ) - if ($null -eq $Process) { - if ($OnExit) { & $OnExit $null } - return - } - $script:netxWatchProc = $Process - $script:netxWatchOnExit = $OnExit - if ($script:netxWatchTimer) { - try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {} - } - $timer = New-Object System.Windows.Forms.Timer - $timer.Interval = 400 - $timer.add_Tick({ - $p = $script:netxWatchProc - if ($null -eq $p) { - try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {} - $script:netxWatchTimer = $null - return - } - $done = $false - try { $done = [bool]$p.HasExited } catch { $done = $true } - if (-not $done) { return } - try { $script:netxWatchTimer.Stop(); $script:netxWatchTimer.Dispose() } catch {} - $script:netxWatchTimer = $null - $cb = $script:netxWatchOnExit - $script:netxWatchOnExit = $null - $script:netxWatchProc = $null - if ($cb) { & $cb $p } - }) - $script:netxWatchTimer = $timer - $timer.Start() -} - -function Restart-NetxTraySelf { - # Fresh process picks up new version.json / scripts after in-place update. - try { - $notify.ShowBalloonTip( - 2500, "NetX", - (T "Restarting tray…" "正在重启托盘…"), - [System.Windows.Forms.ToolTipIcon]::Info - ) - } catch {} - $trayFile = Join-Path $PSScriptRoot "netx_tray.ps1" - $arg = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$trayFile`" -ProgramRoot `"$prog`" -DataRoot `"$data`"" - try { - Start-Process -FilePath "powershell.exe" -ArgumentList $arg -WindowStyle Hidden | Out-Null - } catch {} - try { $notify.Visible = $false } catch {} - try { $form.Close() } catch {} - [System.Windows.Forms.Application]::Exit() -} - -function Test-NetxSetupCancelled { - param([AllowNull()][Nullable[int]]$ExitCode) - if ($null -eq $ExitCode) { return $false } - # STATUS_CONTROL_C_EXIT (0xC000013A): console closed / Ctrl+C — not a setup failure. - return ([int]$ExitCode -eq -1073741510) -} - -function Complete-NetxTrayReconfig { - # Reload .env after reconfigure (host/port/mode may change). - if (Test-Path $envPath) { - $map = Read-DotEnv -Path $envPath - if ($map["NETX_HOST"]) { $script:hostBind = $map["NETX_HOST"]; $hostBind = $script:hostBind } - if ($map["NETX_PORT"]) { +$script:probeRunspace = [runspacefactory]::CreateRunspace() +$script:probeRunspace.Open() +$script:probeRunspace.SessionStateProxy.SetVariable("sync", $script:sync) +$script:probePs = [powershell]::Create() +$script:probePs.Runspace = $script:probeRunspace +[void]$script:probePs.AddScript({ + $i = 0 + while (-not $sync.Stop) { + $ok = $false try { - $script:port = [int]$map["NETX_PORT"] - $port = $script:port + $r = Invoke-WebRequest -Uri $sync.HealthUrl -UseBasicParsing -TimeoutSec 2 -MaximumRedirection 0 + if ($r.StatusCode -eq 200) { $ok = (($r.Content | ConvertFrom-Json).status -eq "ok") } } catch {} - } - $script:uiUrl = "http://${hostBind}:${port}/" - $uiUrl = $script:uiUrl - $dbMode = Get-DbMode -EnvMap $map - $script:dbModeLabel = if ($dbMode -eq "bundled") { - (T "built-in DB" "内置库") - } else { - (T "external DB" "外置库") - } - $dbModeLabel = $script:dbModeLabel - if ($miSub) { $miSub.Text = "$dbModeLabel · ${hostBind}:$port" } - } - - $restart = [System.Windows.Forms.MessageBox]::Show( - (T "Database configuration saved.`nStart NetX now?" "数据库配置已保存。`n是否立即启动 NetX?"), - "NetX", - [System.Windows.Forms.MessageBoxButtons]::YesNo, - [System.Windows.Forms.MessageBoxIcon]::Information - ) - if ($restart -eq [System.Windows.Forms.DialogResult]::Yes) { - Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null - Open-NetxUiWhenReady - } else { - Update-NetxTrayTip - } -} - -function Open-NetxUiWhenReady { - param([int]$TimeoutSec = 180) - $notify.ShowBalloonTip( - 5000, - "NetX", - (T "Starting… first run may take a minute." "正在启动…首次迁移可能需要一分钟。"), - [System.Windows.Forms.ToolTipIcon]::Info - ) - # Poll on a timer — do not block the tray UI thread with Wait-NetxApiHealthy. - if ($script:netxUiReadyTimer) { - try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {} - } - $script:netxUiReadyTicks = 0 - $script:netxUiReadyMax = [Math]::Max(1, [int]($TimeoutSec * 2)) - $timer = New-Object System.Windows.Forms.Timer - $timer.Interval = 500 - $timer.add_Tick({ - $script:netxUiReadyTicks++ - if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) { - try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {} - $script:netxUiReadyTimer = $null - try { Start-Process $uiUrl } catch {} - Update-NetxTrayTip - return - } - if ($script:netxUiReadyTicks -ge $script:netxUiReadyMax) { - try { $script:netxUiReadyTimer.Stop(); $script:netxUiReadyTimer.Dispose() } catch {} - $script:netxUiReadyTimer = $null - $notify.ShowBalloonTip( - 8000, - "NetX", - (T "UI not ready yet. Use tray → Open UI when ready, or check data\runtime\netx.err.log." "界面尚未就绪。就绪后请用托盘「打开界面」,或查看 data\runtime\netx.err.log。"), - [System.Windows.Forms.ToolTipIcon]::Warning - ) - Update-NetxTrayTip + $sync.Healthy = $ok + $sync.Seen = $true + if (($i % 6) -eq 0) { + $runner = "" + $svc = Get-Service -Name "NetX" -ErrorAction SilentlyContinue + if ($svc -and [string]$svc.StartType -ne "Disabled") { $runner = "service" } + $tasks = @{} + try { + Get-ScheduledTask -TaskPath "\NetX\" -ErrorAction Stop | ForEach-Object { $tasks[$_.TaskName] = [string]$_.State } + } catch {} + if (-not $runner -and $tasks["NetXService"] -and $tasks["NetXService"] -ne "Disabled") { $runner = "task" } + $sync.Runner = $runner + $sync.Autostart = [bool]($tasks["NetXTray"] -and $tasks["NetXTray"] -ne "Disabled") + $sync.AutoUpdate = [bool]($tasks["NetXUpdate"] -and $tasks["NetXUpdate"] -ne "Disabled") + } + $i++ + for ($k = 0; $k -lt 15 -and -not $sync.Stop -and -not $sync.Poke; $k++) { Start-Sleep -Milliseconds 100 } + $sync.Poke = $false } }) - $script:netxUiReadyTimer = $timer - $timer.Start() +$script:probeHandle = $script:probePs.BeginInvoke() + +function Request-NetxProbe { $script:sync.Poke = $true } + +# ---------------------------------------------------------------- theme + drawing +$script:dpiScale = 1.0 +try { + $g = [System.Drawing.Graphics]::FromHwnd([IntPtr]::Zero) + $script:dpiScale = $g.DpiX / 96.0 + $g.Dispose() +} catch {} +function Get-NetxPx([int]$Px) { return [int][Math]::Round($Px * $script:dpiScale) } + +$script:glyphFont = "Segoe MDL2 Assets" +try { + $installed = (New-Object System.Drawing.Text.InstalledFontCollection).Families | ForEach-Object { $_.Name } + if ($installed -contains "Segoe Fluent Icons") { $script:glyphFont = "Segoe Fluent Icons" } +} catch {} +$script:uiFontName = if ($zh) { "Microsoft YaHei UI" } else { "Segoe UI" } + +function Test-NetxDarkMode { + try { + $v = Get-ItemPropertyValue -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "AppsUseLightTheme" -ErrorAction Stop + return ([int]$v -eq 0) + } catch { return $false } } +function New-NetxTheme([bool]$Dark) { + $t = New-Object NetxTheme + $c = { param($r, $g, $b) [System.Drawing.Color]::FromArgb($r, $g, $b) } + if ($Dark) { + $t.Back = & $c 28 32 40 + $t.Hover = & $c 44 54 72 + $t.Text = & $c 232 237 244 + $t.Muted = & $c 140 150 166 + $t.Sep = & $c 50 56 68 + $t.Border = & $c 58 66 82 + } else { + $t.Back = & $c 251 252 253 + $t.Hover = & $c 228 237 251 + $t.Text = & $c 26 31 38 + $t.Muted = & $c 108 117 130 + $t.Sep = & $c 229 232 237 + $t.Border = & $c 212 218 226 + } + return $t +} + +$script:stateColors = @{ + running = [System.Drawing.Color]::FromArgb(34, 197, 94) + stopped = [System.Drawing.Color]::FromArgb(148, 160, 178) + starting = [System.Drawing.Color]::FromArgb(245, 166, 35) + stopping = [System.Drawing.Color]::FromArgb(245, 166, 35) + checking = [System.Drawing.Color]::FromArgb(148, 160, 178) +} + +function New-NetxGlyph([char]$Glyph, [System.Drawing.Color]$Color) { + $sz = Get-NetxPx 16 + $bmp = New-Object System.Drawing.Bitmap($sz, $sz) + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAliasGridFit + $font = New-Object System.Drawing.Font($script:glyphFont, [float]($sz * 0.78), [System.Drawing.GraphicsUnit]::Pixel) + $brush = New-Object System.Drawing.SolidBrush($Color) + $fmt = New-Object System.Drawing.StringFormat + $fmt.Alignment = [System.Drawing.StringAlignment]::Center + $fmt.LineAlignment = [System.Drawing.StringAlignment]::Center + $g.DrawString([string]$Glyph, $font, $brush, (New-Object System.Drawing.RectangleF(0, 1, $sz, $sz)), $fmt) + $fmt.Dispose(); $brush.Dispose(); $font.Dispose(); $g.Dispose() + return $bmp +} + +function New-NetxDot([System.Drawing.Color]$Color) { + $sz = Get-NetxPx 16 + $bmp = New-Object System.Drawing.Bitmap($sz, $sz) + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias + $d = [Math]::Max(6, [int]($sz * 0.5)) + $o = [int](($sz - $d) / 2) + $brush = New-Object System.Drawing.SolidBrush($Color) + $g.FillEllipse($brush, $o, $o, $d, $d) + $brush.Dispose(); $g.Dispose() + return $bmp +} + +$script:glyphs = @{ + open = [char]0xE8A7 + start = [char]0xE768 + stop = [char]0xE71A + restart = [char]0xE72C + settings = [char]0xE713 + update = [char]0xE895 + repair = [char]0xE90F + folder = [char]0xE8B7 + log = [char]0xE8A5 + check = [char]0xE73E + exit = [char]0xE8BB + power = [char]0xE7E8 +} + +# Brand mark from the 256px PNG (small .ico frames do not convert cleanly). +$script:brandPng = $null +try { $script:brandPng = [System.Drawing.Image]::FromFile((Join-Path $PSScriptRoot "assets\netx-256.png")) } catch {} +function New-NetxBrandBitmap([int]$Size) { + $bmp = New-Object System.Drawing.Bitmap($Size, $Size) + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $rect = New-Object System.Drawing.Rectangle(0, 0, $Size, $Size) + if ($script:brandPng) { $g.DrawImage($script:brandPng, $rect) } + else { $g.DrawIcon([System.Drawing.SystemIcons]::Application, $rect) } + $g.Dispose() + return $bmp +} + +# Tray icons with a status dot, built once per state. +$script:iconHandles = @() +function New-NetxStateIcon([System.Drawing.Color]$Dot) { + $sz = [System.Windows.Forms.SystemInformation]::SmallIconSize.Width + $bmp = New-NetxBrandBitmap $sz + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias + $d = [Math]::Max(5, [int][Math]::Round($sz * 0.42)) + $x = $sz - $d + $y = $sz - $d + $ring = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(15, 23, 36)) + $g.FillEllipse($ring, $x - 1, $y - 1, $d + 1, $d + 1) + $brush = New-Object System.Drawing.SolidBrush($Dot) + $g.FillEllipse($brush, $x + 1, $y + 1, $d - 3, $d - 3) + $ring.Dispose(); $brush.Dispose(); $g.Dispose() + $h = $bmp.GetHicon() + $bmp.Dispose() + $script:iconHandles += $h + return [System.Drawing.Icon]::FromHandle($h) +} +$script:trayIcons = @{} +foreach ($k in $script:stateColors.Keys) { + try { $script:trayIcons[$k] = New-NetxStateIcon $script:stateColors[$k] } catch {} +} + +# ---------------------------------------------------------------- window + icon $form = New-Object System.Windows.Forms.Form $form.Text = "NetX" $form.ShowInTaskbar = $false @@ -360,390 +409,539 @@ $form.Visible = $false $form.Size = New-Object System.Drawing.Size(0, 0) $notify = New-Object System.Windows.Forms.NotifyIcon +$notify.Icon = if ($script:trayIcons["checking"]) { $script:trayIcons["checking"] } else { [System.Drawing.SystemIcons]::Application } +$notify.Text = "NetX" $notify.Visible = $true -$iconPath = Join-Path $PSScriptRoot "assets\netx.ico" -try { - if (Test-Path $iconPath) { - $notify.Icon = New-Object System.Drawing.Icon $iconPath - } else { - $notify.Icon = [System.Drawing.SystemIcons]::Application - } -} catch { - try { $notify.Icon = [System.Drawing.SystemIcons]::Application } catch {} -} -Update-NetxTrayTip -function New-NetxMenuItem { - param( - [string]$Text, - [switch]$Header, - [switch]$Primary, - [switch]$Muted +function Show-NetxToast([string]$Text, [string]$Kind = "Info", [int]$Ms = 4000) { + try { $notify.ShowBalloonTip($Ms, "NetX", $Text, [System.Windows.Forms.ToolTipIcon]::$Kind) } catch {} +} + +function Show-NetxDialog([string]$Text, [string]$Kind = "Information", [string]$Buttons = "OK") { + return [System.Windows.Forms.MessageBox]::Show( + $Text, "NetX", + [System.Windows.Forms.MessageBoxButtons]::$Buttons, + [System.Windows.Forms.MessageBoxIcon]::$Kind ) +} + +# ---------------------------------------------------------------- child processes +# Never Start-Process -Wait on the WinForms UI thread — it freezes the tray menu. +$script:watches = New-Object System.Collections.ArrayList +$script:watchTimer = New-Object System.Windows.Forms.Timer +$script:watchTimer.Interval = 300 +$script:watchTimer.add_Tick({ + foreach ($w in @($script:watches)) { + $done = $true + try { $done = [bool]$w.Proc.HasExited } catch {} + if (-not $done) { continue } + [void]$script:watches.Remove($w) + if ($w.OnExit) { + try { & $w.OnExit $w.Proc $w.Context } catch { Show-NetxToast $_.Exception.Message "Warning" } + } + } + if ($script:watches.Count -eq 0) { $script:watchTimer.Stop() } + }) + +function Watch-NetxProcess($Process, [scriptblock]$OnExit, [hashtable]$Context = @{}) { + # OnExit runs later from the timer: pass anything it needs via -Context + # (function locals are gone by then). + if ($null -eq $Process) { + if ($OnExit) { & $OnExit $null $Context } + return + } + [void]$script:watches.Add(@{ Proc = $Process; OnExit = $OnExit; Context = $Context }) + $script:watchTimer.Start() +} + +function Start-NetxChild([string]$File, [string[]]$ExtraArgs = @(), [string]$WindowStyle = "Hidden") { + return Start-NetxPowerShell -File $File -Arguments (@("-ProgramRoot", $prog, "-DataRoot", $data) + $ExtraArgs) ` + -WorkingDirectory $prog -WindowStyle $WindowStyle -PassThru +} + +function Get-NetxExitCode($Process) { + if ($null -eq $Process) { return $null } + try { return $Process.ExitCode } catch { return $null } +} + +function Test-NetxSetupCancelled($ExitCode) { + # STATUS_CONTROL_C_EXIT (0xC000013A): console closed / Ctrl+C — not a failure. + return ($null -ne $ExitCode -and [int]$ExitCode -eq -1073741510) +} + +# ---------------------------------------------------------------- runtime state +# pending = an in-flight start/stop/restart; it clears once the child exits and +# /health matches the target (or on timeout). +$script:pending = $null +$script:openWhenReady = $null +$script:lastState = "" + +function Invoke-NetxRuntime([ValidateSet("start", "stop", "restart")][string]$Action, [switch]$OpenUi, [scriptblock]$Then) { + if ($script:pending) { return } + $kind = if ($Action -eq "stop") { "stopping" } else { "starting" } + $timeout = if ($Action -eq "stop") { 90 } else { 200 } + $proc = $null + try { + $proc = Start-NetxChild -File $runtimePs1 -ExtraArgs @("-Action", $Action) + } catch { + Show-NetxToast ((T "Could not run NetX control script: " "无法执行 NetX 控制脚本:") + $_.Exception.Message) "Error" + return + } + $script:pending = @{ + Kind = $kind + Target = ($Action -ne "stop") + Done = $false + Until = (Get-Date).AddSeconds($timeout) + Then = $Then + } + if ($OpenUi) { $script:openWhenReady = (Get-Date).AddSeconds($timeout) } + Update-NetxTrayState + Watch-NetxProcess $proc -Context @{ Action = $Action } -OnExit { + param($p, $ctx) + $code = Get-NetxExitCode $p + $pend = $script:pending + if ($null -ne $code -and $code -ne 0) { + $script:pending = $null + $script:openWhenReady = $null + Show-NetxToast ((T "NetX $($ctx.Action) failed (exit $code). Logs: " "NetX 操作失败(退出码 $code)。日志:") + $logDir) "Error" 8000 + } elseif ($pend) { + $pend.Done = $true + } + Request-NetxProbe + Update-NetxTrayState + if ($pend -and $pend.Then) { & $pend.Then $code } + } +} + +function Get-NetxTrayState { + $healthy = [bool]$script:sync.Healthy + $p = $script:pending + if ($p) { + if ((Get-Date) -gt $p.Until) { + $script:pending = $null + } elseif ($p.Done -and ($healthy -eq $p.Target)) { + $script:pending = $null + } else { + return $p.Kind + } + } + if (-not $script:sync.Seen) { return "checking" } + if ($healthy) { return "running" } + return "stopped" +} + +$script:stateText = @{ + running = (T "Running" "运行中") + stopped = (T "Stopped" "已停止") + starting = (T "Starting…" "正在启动…") + stopping = (T "Stopping…" "正在停止…") + checking = (T "Checking…" "检测中…") +} + +function Update-NetxTrayState { + $state = Get-NetxTrayState + if ($script:openWhenReady) { + if ($state -eq "running") { + $script:openWhenReady = $null + Open-NetxUrl -Url $script:uiUrl + } elseif ((Get-Date) -gt $script:openWhenReady -or ($state -eq "stopped" -and -not $script:pending)) { + $script:openWhenReady = $null + Show-NetxToast (T "NetX did not become ready. Check data\runtime\netx.err.log." "NetX 未能就绪,请查看 data\runtime\netx.err.log。") "Warning" 8000 + } + } + $runner = [string]$script:sync.Runner + $key = "$state|$runner|$($script:ver)" + if ($key -eq $script:lastState) { return } + $prevState = ($script:lastState -split '\|')[0] + $script:lastState = $key + + $text = $script:stateText[$state] + $miStatus.Text = $text + $miStatus.Image = $script:dots[$state] + $mode = if ($runner -eq "service") { (T "Service" "服务") } elseif ($runner -eq "task") { (T "Startup task" "启动任务") } else { $script:dbModeLabel } + $miStatus.ShortcutKeyDisplayString = "$mode · $($script:clientHost):$($script:port)" + $miHeader.ShortcutKeyDisplayString = "v$($script:ver)" + + $busy = ($state -in @("starting", "stopping", "checking")) + $miStart.Enabled = (-not $busy) -and ($state -eq "stopped") + $miStop.Enabled = (-not $busy) -and ($state -eq "running") + $miRestart.Enabled = (-not $busy) -and ($state -eq "running") + $miOpen.Enabled = ($state -ne "stopping") + + if ($script:trayIcons[$state]) { $notify.Icon = $script:trayIcons[$state] } + $tip = "NetX $($script:ver) · $text" + if ($tip.Length -gt 63) { $tip = $tip.Substring(0, 63) } + $notify.Text = $tip + + # Requested stops pass through "stopping"; a direct running→stopped is external. + if ($prevState -eq "running" -and $state -eq "stopped") { + Show-NetxToast (T "NetX has stopped." "NetX 已停止运行。") "Warning" + } +} + +# ---------------------------------------------------------------- menu +$menu = New-Object System.Windows.Forms.ContextMenuStrip +$menu.ShowImageMargin = $true +$menu.ShowCheckMargin = $false +$menu.ImageScalingSize = New-Object System.Drawing.Size((Get-NetxPx 16), (Get-NetxPx 16)) +$menu.Font = New-Object System.Drawing.Font($script:uiFontName, 9.0) +$menu.Padding = New-Object System.Windows.Forms.Padding((Get-NetxPx 4), (Get-NetxPx 6), (Get-NetxPx 4), (Get-NetxPx 6)) +$menu.DropShadowEnabled = $true + +$script:itemGlyph = @{} +function New-NetxMenuItem([string]$Text, [string]$Glyph = "", [string]$Role = "", [switch]$Bold) { $item = New-Object System.Windows.Forms.ToolStripMenuItem $item.Text = $Text + $item.Tag = $Role $item.AutoSize = $true - $item.Padding = New-Object System.Windows.Forms.Padding(10, 5, 28, 5) + $item.Padding = New-Object System.Windows.Forms.Padding((Get-NetxPx 6), (Get-NetxPx 6), (Get-NetxPx 18), (Get-NetxPx 6)) $item.Margin = New-Object System.Windows.Forms.Padding(0) - if ($Header) { - $item.Enabled = $false - $item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0, [System.Drawing.FontStyle]::Bold) - $item.ForeColor = [System.Drawing.Color]::FromArgb(55, 65, 80) - $item.Padding = New-Object System.Windows.Forms.Padding(10, 6, 28, 2) - } elseif ($Muted) { - $item.Enabled = $false - $item.Font = New-Object System.Drawing.Font("Segoe UI", 8.25) - $item.ForeColor = [System.Drawing.Color]::FromArgb(120, 128, 140) - $item.Padding = New-Object System.Windows.Forms.Padding(10, 1, 28, 6) - } elseif ($Primary) { - $item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0, [System.Drawing.FontStyle]::Bold) - $item.ForeColor = [System.Drawing.Color]::FromArgb(20, 40, 70) - } else { - $item.Font = New-Object System.Drawing.Font("Segoe UI", 9.0) - $item.ForeColor = [System.Drawing.Color]::FromArgb(35, 40, 48) - } + if ($Glyph) { $script:itemGlyph[$item] = $Glyph } + $style = if ($Bold) { [System.Drawing.FontStyle]::Bold } else { [System.Drawing.FontStyle]::Regular } + $size = if ($Role -eq "status") { 8.5 } else { 9.0 } + $item.Font = New-Object System.Drawing.Font($script:uiFontName, [float]$size, $style) + if ($Role -in @("header", "status")) { $item.Enabled = $false } return $item } function New-NetxMenuSeparator { $sep = New-Object System.Windows.Forms.ToolStripSeparator - $sep.Margin = New-Object System.Windows.Forms.Padding(0, 3, 0, 3) - $sep.Padding = New-Object System.Windows.Forms.Padding(0) + $sep.Margin = New-Object System.Windows.Forms.Padding(0, (Get-NetxPx 3), 0, (Get-NetxPx 3)) return $sep } -$menu = New-Object System.Windows.Forms.ContextMenuStrip -$menu.ShowImageMargin = $false -$menu.ShowCheckMargin = $false -$menu.Font = New-Object System.Drawing.Font("Segoe UI", 9.0) -$menu.BackColor = [System.Drawing.Color]::FromArgb(252, 252, 253) -$menu.ForeColor = [System.Drawing.Color]::FromArgb(35, 40, 48) -$menu.Padding = New-Object System.Windows.Forms.Padding(4, 4, 4, 4) -$menu.Renderer = New-Object NetxMenuRenderer +function Set-NetxDropDownChrome($DropDown) { + $DropDown.Renderer = $script:renderer + $DropDown.add_HandleCreated({ + param($s, $e) + [void][NetxNative]::RoundCorners($s.Handle, $script:theme.Border) + }) +} -# --- header (info only) --- -$script:miHeader = New-NetxMenuItem -Text "NetX $script:ver" -Header -$miHeader = $script:miHeader -[void]$menu.Items.Add($miHeader) +$miHeader = New-NetxMenuItem -Text "NetX" -Role "header" -Bold +$miStatus = New-NetxMenuItem -Text $script:stateText["checking"] -Role "status" +$miOpen = New-NetxMenuItem -Text (T "Open NetX" "打开界面") -Glyph "open" -Bold +$miStart = New-NetxMenuItem -Text (T "Start" "启动") -Glyph "start" +$miStop = New-NetxMenuItem -Text (T "Stop" "停止") -Glyph "stop" +$miRestart = New-NetxMenuItem -Text (T "Restart" "重启") -Glyph "restart" +$miSettings = New-NetxMenuItem -Text (T "Settings" "设置") -Glyph "settings" +$miAutostart = New-NetxMenuItem -Text (T "Start with Windows" "登录 Windows 时自动启动") -Glyph "check" +$miAutoUpdate = New-NetxMenuItem -Text (T "Daily automatic updates" "每日自动更新") -Glyph "check" +$miReconfig = New-NetxMenuItem -Text (T "Reconfigure database…" "重新配置数据库…") -Glyph "repair" +$miLogs = New-NetxMenuItem -Text (T "Open log folder" "打开日志目录") -Glyph "log" +$miData = New-NetxMenuItem -Text (T "Open data folder" "打开数据目录") -Glyph "folder" +$miUpdate = New-NetxMenuItem -Text (T "Check for updates…" "检查更新…") -Glyph "update" +$miExit = New-NetxMenuItem -Text (T "Exit tray (keep NetX running)" "退出托盘(NetX 保持运行)") -Glyph "exit" +$miStopExit = New-NetxMenuItem -Text (T "Stop NetX and exit" "停止 NetX 并退出") -Glyph "power" -$miSub = New-NetxMenuItem -Text "$dbModeLabel · ${hostBind}:$port" -Muted -[void]$menu.Items.Add($miSub) +[void]$miSettings.DropDownItems.AddRange(@( + $miAutostart, $miAutoUpdate, (New-NetxMenuSeparator), + $miReconfig, $miLogs, $miData + )) +[void]$menu.Items.AddRange(@( + $miHeader, $miStatus, (New-NetxMenuSeparator), + $miOpen, (New-NetxMenuSeparator), + $miStart, $miStop, $miRestart, (New-NetxMenuSeparator), + $miSettings, $miUpdate, (New-NetxMenuSeparator), + $miExit, $miStopExit + )) -[void]$menu.Items.Add((New-NetxMenuSeparator)) +$script:themeDark = $null +function Set-NetxTrayTheme { + $dark = Test-NetxDarkMode + if ($dark -eq $script:themeDark) { return } + $script:themeDark = $dark + $script:theme = New-NetxTheme $dark + $script:theme.DwmRounded = ([Environment]::OSVersion.Version.Build -ge 22000) + $script:renderer = New-Object NetxMenuRenderer($script:theme, [float]$script:dpiScale) + $menu.Renderer = $script:renderer + $miSettings.DropDown.Renderer = $script:renderer + $script:glyphImages = @{} + foreach ($k in $script:glyphs.Keys) { + $script:glyphImages[$k] = New-NetxGlyph $script:glyphs[$k] $script:theme.Text + } + $script:glyphImages["open"] = New-NetxGlyph $script:glyphs["open"] ([System.Drawing.Color]::FromArgb(64, 140, 255)) + foreach ($it in $script:itemGlyph.Keys) { + $gk = $script:itemGlyph[$it] + if ($gk -ne "check") { $it.Image = $script:glyphImages[$gk] } + } + $script:dots = @{} + foreach ($k in $script:stateColors.Keys) { $script:dots[$k] = New-NetxDot $script:stateColors[$k] } + if (-not $miHeader.Image) { $miHeader.Image = New-NetxBrandBitmap (Get-NetxPx 16) } + $script:lastState = "" +} +Set-NetxTrayTheme +Set-NetxDropDownChrome $menu +Set-NetxDropDownChrome $miSettings.DropDown -# --- primary --- -$miOpen = New-NetxMenuItem -Text (T "Open UI" "打开界面") -Primary -$miOpen.add_Click({ - Start-NetxTrayDeferred { - if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) { - Start-Process $uiUrl +$menu.add_Opening({ + Set-NetxTrayTheme + if ($menu.IsHandleCreated) { [void][NetxNative]::RoundCorners($menu.Handle, $script:theme.Border) } + Update-NetxTrayState + }) +$miSettings.DropDown.add_Opening({ + $dd = $miSettings.DropDown + if ($dd.IsHandleCreated) { [void][NetxNative]::RoundCorners($dd.Handle, $script:theme.Border) } + $miAutostart.Image = if ($script:sync.Autostart) { $script:glyphImages["check"] } else { $null } + $miAutoUpdate.Image = if ($script:sync.AutoUpdate) { $script:glyphImages["check"] } else { $null } + }) + +function Invoke-NetxTrayDeferred([scriptblock]$Work) { + # Close the menu first: ContextMenuStrip stays modal while a Click handler runs. + try { $menu.Close() } catch {} + $script:deferredWork = $Work + $t = New-Object System.Windows.Forms.Timer + $t.Interval = 30 + $t.add_Tick({ + param($s, $e) + $s.Stop(); $s.Dispose() + $w = $script:deferredWork + $script:deferredWork = $null + if ($w) { & $w } + }) + $t.Start() +} + +function Open-NetxUi { + if ($script:sync.Healthy) { + Open-NetxUrl -Url $script:uiUrl + } elseif ($script:pending -and $script:pending.Kind -eq "starting") { + $script:openWhenReady = $script:pending.Until + } else { + Show-NetxToast (T "NetX is not running. Starting…" "NetX 未运行,正在启动…") + Invoke-NetxRuntime -Action start -OpenUi + } +} + +$miOpen.add_Click({ Invoke-NetxTrayDeferred { Open-NetxUi } }) +$miStart.add_Click({ Invoke-NetxTrayDeferred { Invoke-NetxRuntime -Action start -OpenUi:(-not $QuietStart) } }) +$miStop.add_Click({ Invoke-NetxTrayDeferred { Invoke-NetxRuntime -Action stop } }) +$miRestart.add_Click({ Invoke-NetxTrayDeferred { Invoke-NetxRuntime -Action restart } }) +$miLogs.add_Click({ Invoke-NetxTrayDeferred { Open-NetxUrl -Url $logDir } }) +$miData.add_Click({ Invoke-NetxTrayDeferred { Open-NetxUrl -Url $data } }) + +function Switch-NetxTaskToggle([string]$Script, [string]$SyncKey, [string]$OnText, [string]$OffText) { + $enable = -not [bool]$script:sync[$SyncKey] + $extra = if ($enable) { @() } else { @("-Remove") } + try { + $p = Start-NetxChild -File $Script -ExtraArgs $extra + } catch { + Show-NetxToast $_.Exception.Message "Error" + return + } + $ctx = @{ Key = $SyncKey; Enable = $enable; Text = $(if ($enable) { $OnText } else { $OffText }) } + Watch-NetxProcess $p -Context $ctx -OnExit { + param($proc, $c) + $code = Get-NetxExitCode $proc + if ($null -eq $code -or $code -eq 0) { + $script:sync[$c.Key] = $c.Enable + Show-NetxToast $c.Text } else { - $notify.ShowBalloonTip(4000, "NetX", (T "NetX is not running. Starting…" "NetX 未运行,正在启动…"), [System.Windows.Forms.ToolTipIcon]::Info) - Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null - Open-NetxUiWhenReady + Show-NetxToast (T "Setting change failed (exit $code)." "设置修改失败(退出码 $code)。") "Error" } } -}) -[void]$menu.Items.Add($miOpen) +} -[void]$menu.Items.Add((New-NetxMenuSeparator)) +$miAutostart.add_Click({ + Invoke-NetxTrayDeferred { + Switch-NetxTaskToggle $autostartPs1 "Autostart" ` + (T "NetX will start when you sign in." "已开启:登录 Windows 时自动启动 NetX。") ` + (T "Start with Windows turned off." "已关闭登录自动启动。") + } + }) +$miAutoUpdate.add_Click({ + Invoke-NetxTrayDeferred { + Switch-NetxTaskToggle $updateTaskPs1 "AutoUpdate" ` + (T "Daily automatic updates turned on." "已开启每日自动更新。") ` + (T "Daily automatic updates turned off." "已关闭每日自动更新。") + } + }) -# --- service control --- -$miStart = New-NetxMenuItem -Text (T "Start" "启动") -$miStart.add_Click({ - Start-NetxTrayDeferred { - Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null - Open-NetxUiWhenReady +# --- reconfigure database (console wizard) --- +function Complete-NetxTrayReconfig { + Read-NetxTrayConfig + $script:sync.HealthUrl = "$($script:uiUrl)health" + $script:lastState = "" + $ans = Show-NetxDialog (T "Database configuration saved.`nStart NetX now?" "数据库配置已保存。`n是否立即启动 NetX?") "Information" "YesNo" + if ($ans -eq [System.Windows.Forms.DialogResult]::Yes) { + Invoke-NetxRuntime -Action start -OpenUi } -}) -[void]$menu.Items.Add($miStart) + Update-NetxTrayState +} -$miStop = New-NetxMenuItem -Text (T "Stop" "停止") -$miStop.add_Click({ - Start-NetxTrayDeferred { - $notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info) - Invoke-NetxScript -File $stopPs1 | Out-Null - Update-NetxTrayTip +function Start-NetxReconfigWizard { + $null = Ensure-NetxDataAcl -DataRoot $data -Quiet + try { + $p = Start-NetxChild -File $setupPs1 -WindowStyle Normal + } catch { + Show-NetxDialog ((T "Database setup failed: " "数据库配置失败:") + $_.Exception.Message) "Error" | Out-Null + return } -}) -[void]$menu.Items.Add($miStop) + Watch-NetxProcess $p { + param($sp) + $code = Get-NetxExitCode $sp + if (Test-NetxSetupCancelled $code) { + Show-NetxToast (T "Database setup cancelled." "已取消数据库配置。") + return + } + if ($null -ne $code -and $code -ne 0) { + Show-NetxDialog ((T "Database setup failed (exit $code).`nLog: " "数据库配置失败(退出码 $code)。`n日志:") + $logDir) "Error" | Out-Null + return + } + Complete-NetxTrayReconfig + } +} -[void]$menu.Items.Add((New-NetxMenuSeparator)) - -# --- setup / update --- -$miReconfig = New-NetxMenuItem -Text (T "Reconfigure database…" "重新配置数据库…") $miReconfig.add_Click({ - Start-NetxTrayDeferred { - $ans = [System.Windows.Forms.MessageBox]::Show( - (T ` - "NetX will stop while you reconfigure the database.`n`nChoose built-in or external PostgreSQL in the console window.`nContinue?" ` - "重新配置数据库时会先停止 NetX。`n`n请在随后的控制台窗口中选择内置或外置 PostgreSQL。`n是否继续?"), - (T "Reconfigure database" "重新配置数据库"), - [System.Windows.Forms.MessageBoxButtons]::YesNo, - [System.Windows.Forms.MessageBoxIcon]::Question - ) - if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return } - - $notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info) - Invoke-NetxScript -File $stopPs1 | Out-Null - - # ProgramData\.env is often admin-owned after Setup; repair ACL before reconfigure. - $null = Ensure-NetxDataAcl -DataRoot $data -Quiet - - try { - $p = Start-NetxPowerShell -File $setupPs1 ` - -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) ` - -WorkingDirectory $prog -WindowStyle Normal -PassThru - Start-NetxTrayWatchProcess -Process $p -OnExit { - param($sp) - if ($null -eq $sp) { Update-NetxTrayTip; return } - if (Test-NetxSetupCancelled -ExitCode $sp.ExitCode) { - $notify.ShowBalloonTip( - 4000, "NetX", - (T "Database setup cancelled." "已取消数据库配置。"), - [System.Windows.Forms.ToolTipIcon]::Info - ) - Update-NetxTrayTip - return - } - if ($null -ne $sp.ExitCode -and $sp.ExitCode -ne 0) { - $elev = [System.Windows.Forms.MessageBox]::Show( - (T ` - "Database setup failed (exit $($sp.ExitCode)).`n`nIf this was a permission error on %ProgramData%\NetX\.env, click Yes to retry as Administrator." ` - "数据库配置失败(退出码 $($sp.ExitCode))。`n`n若是 %ProgramData%\NetX\.env 权限问题,点「是」将以管理员身份重试。"), - "NetX", - [System.Windows.Forms.MessageBoxButtons]::YesNo, - [System.Windows.Forms.MessageBoxIcon]::Warning - ) - if ($elev -ne [System.Windows.Forms.DialogResult]::Yes) { - Update-NetxTrayTip - return - } - $arg = "-NoProfile -ExecutionPolicy Bypass -File `"$setupPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`"" - try { - $ep = Start-Process -FilePath "powershell.exe" -ArgumentList $arg ` - -WorkingDirectory $prog -Verb RunAs -PassThru - } catch { - Update-NetxTrayTip - return - } - Start-NetxTrayWatchProcess -Process $ep -OnExit { - param($ep2) - if ($null -eq $ep2 -or (Test-NetxSetupCancelled -ExitCode $ep2.ExitCode)) { - Update-NetxTrayTip - return - } - if ($null -ne $ep2.ExitCode -and $ep2.ExitCode -ne 0) { - [System.Windows.Forms.MessageBox]::Show( - (T "Elevated database setup still failed (exit $($ep2.ExitCode))." "管理员配置仍失败(退出码 $($ep2.ExitCode))。"), - "NetX", - [System.Windows.Forms.MessageBoxButtons]::OK, - [System.Windows.Forms.MessageBoxIcon]::Error - ) - Update-NetxTrayTip - return - } - Complete-NetxTrayReconfig - } - return - } - Complete-NetxTrayReconfig + Invoke-NetxTrayDeferred { + $ans = Show-NetxDialog (T ` + "NetX will stop while you reconfigure the database.`n`nChoose built-in or external PostgreSQL in the console window that opens.`nContinue?" ` + "重新配置数据库时会先停止 NetX。`n`n请在随后打开的控制台窗口中选择内置或外置 PostgreSQL。`n是否继续?") "Question" "YesNo" + if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return } + if ($script:sync.Healthy -or $script:sync.Runner) { + Invoke-NetxRuntime -Action stop -Then { param($c) Start-NetxReconfigWizard } + } else { + Start-NetxReconfigWizard } - } catch { - [System.Windows.Forms.MessageBox]::Show( - (T "Database setup failed: $($_.Exception.Message)" "数据库配置失败:$($_.Exception.Message)"), - "NetX", - [System.Windows.Forms.MessageBoxButtons]::OK, - [System.Windows.Forms.MessageBoxIcon]::Error - ) - Update-NetxTrayTip } - } -}) -[void]$menu.Items.Add($miReconfig) + }) + +# --- updates --- +function Restart-NetxTraySelf { + $trayFile = Join-Path $PSScriptRoot "netx_tray.ps1" + try { + Start-NetxPowerShell -File $trayFile -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-QuietStart") ` + -WorkingDirectory $prog -WindowStyle Hidden | Out-Null + } catch {} + Exit-NetxTray +} + +function Start-NetxUpdateApply { + Show-NetxToast (T "Downloading and applying the update… keep the console open." "正在下载并应用更新…请勿关闭控制台窗口。") "Info" 5000 + try { + $ap = Start-NetxChild -File $checkPs1 -ExtraArgs @("-Apply") -WindowStyle Normal + } catch { + Show-NetxToast (T "Update cancelled." "已取消更新。") + return + } + Watch-NetxProcess $ap { + param($p) + $code = Get-NetxExitCode $p + if (Test-NetxSetupCancelled $code) { + Show-NetxToast (T "Update cancelled." "已取消更新。") + return + } + if ($code -eq 0) { + $newVer = Get-NetxVersion -ProgramRoot $prog + Show-NetxDialog (T "Updated to $newVer.`nThe tray will restart." "已更新到 $newVer。`n托盘即将重启。") | Out-Null + Restart-NetxTraySelf + return + } + Show-NetxDialog (T "Update failed (exit $code). Retry Check for updates, or run NetX-Setup manually." "更新失败(退出码 $code)。请重试「检查更新」,或手动运行 Setup 安装包。") "Warning" | Out-Null + } +} -$miUpdate = New-NetxMenuItem -Text (T "Check for updates…" "检查更新…") $miUpdate.add_Click({ - Start-NetxTrayDeferred { - try { - $notify.ShowBalloonTip( - 3000, "NetX", - (T "Checking for updates…" "正在检查更新…"), - [System.Windows.Forms.ToolTipIcon]::Info - ) - # No -Wait on UI thread (freezes ContextMenuStrip / tray). - $p = Start-NetxPowerShell -File $checkPs1 -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data) ` - -WorkingDirectory $prog -WindowStyle Normal -PassThru - Start-NetxTrayWatchProcess -Process $p -OnExit { - param($cp) - try { - if ($null -eq $cp) { return } - if (Test-NetxSetupCancelled -ExitCode $cp.ExitCode) { return } - if ($cp.ExitCode -eq 10) { - $ans = [System.Windows.Forms.MessageBox]::Show( - (T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?"), - (T "NetX update" "NetX 更新"), - [System.Windows.Forms.MessageBoxButtons]::YesNo, - [System.Windows.Forms.MessageBoxIcon]::Question - ) - if ($ans -ne [System.Windows.Forms.DialogResult]::Yes) { return } - $arg = "-NoProfile -ExecutionPolicy Bypass -File `"$checkPs1`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -Apply" - $notify.ShowBalloonTip( - 5000, "NetX", - (T "Downloading / applying update… keep the console open." "正在下载/应用更新…请勿关闭控制台窗口。"), - [System.Windows.Forms.ToolTipIcon]::Info - ) - try { - $ap = Start-Process -FilePath "powershell.exe" -ArgumentList $arg ` - -WorkingDirectory $prog -Verb RunAs -PassThru - } catch { - $notify.ShowBalloonTip(4000, "NetX", (T "Update cancelled (UAC)." "已取消更新(UAC)。"), [System.Windows.Forms.ToolTipIcon]::Info) - return - } - Start-NetxTrayWatchProcess -Process $ap -OnExit { - param($ap2) - if ($null -eq $ap2) { return } - if (Test-NetxSetupCancelled -ExitCode $ap2.ExitCode) { - $notify.ShowBalloonTip(4000, "NetX", (T "Update cancelled." "已取消更新。"), [System.Windows.Forms.ToolTipIcon]::Info) - return - } - if ($null -ne $ap2.ExitCode -and $ap2.ExitCode -eq 0) { - $newVer = Get-NetxVersion -ProgramRoot $prog - [System.Windows.Forms.MessageBox]::Show( - (T "Updated to $newVer.`nTray will restart." "已更新到 $newVer。`n即将重启托盘。"), - (T "NetX update" "NetX 更新"), - [System.Windows.Forms.MessageBoxButtons]::OK, - [System.Windows.Forms.MessageBoxIcon]::Information - ) - Restart-NetxTraySelf - return - } - [System.Windows.Forms.MessageBox]::Show( - (T "Update failed (exit $($ap2.ExitCode)). Re-run Check for updates, or install NetX-Setup manually." "更新失败(退出码 $($ap2.ExitCode))。请重试「检查更新」,或手动运行 Setup 安装包。"), - (T "NetX update" "NetX 更新"), - [System.Windows.Forms.MessageBoxButtons]::OK, - [System.Windows.Forms.MessageBoxIcon]::Warning - ) - Update-NetxTrayTip - } - } elseif ($cp.ExitCode -eq 0) { - [System.Windows.Forms.MessageBox]::Show( - (T "NetX is up to date ($script:ver)." "已是最新版本 ($script:ver)。"), - (T "NetX update" "NetX 更新") - ) - } - } catch { - [System.Windows.Forms.MessageBox]::Show( - (T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"), - "NetX" - ) + Invoke-NetxTrayDeferred { + Show-NetxToast (T "Checking for updates…" "正在检查更新…") "Info" 3000 + try { + $cp = Start-NetxChild -File $checkPs1 -WindowStyle Normal + } catch { + Show-NetxDialog ((T "Update check failed: " "检查更新失败:") + $_.Exception.Message) "Warning" | Out-Null + return + } + Watch-NetxProcess $cp { + param($p) + $code = Get-NetxExitCode $p + if (Test-NetxSetupCancelled $code) { return } + if ($code -eq 10) { + $ans = Show-NetxDialog (T "A newer NetX is available. Download and apply now?" "发现新版本,是否立即下载并更新?") "Question" "YesNo" + if ($ans -eq [System.Windows.Forms.DialogResult]::Yes) { Start-NetxUpdateApply } + } elseif ($code -eq 0) { + Show-NetxDialog (T "NetX is up to date ($($script:ver))." "已是最新版本($($script:ver))。") | Out-Null + } else { + Show-NetxDialog (T "Update check failed (exit $code)." "检查更新失败(退出码 $code)。") "Warning" | Out-Null } } - } catch { - [System.Windows.Forms.MessageBox]::Show( - (T "Update check failed: $($_.Exception.Message)" "检查更新失败:$($_.Exception.Message)"), - "NetX" - ) } - } -}) -[void]$menu.Items.Add($miUpdate) - -[void]$menu.Items.Add((New-NetxMenuSeparator)) + }) # --- exit --- -$miExit = New-NetxMenuItem -Text (T "Exit tray" "退出托盘") -$miExit.ToolTipText = (T "Leave NetX services running" "NetX 服务继续运行") -$miExit.add_Click({ - Close-NetxTrayMenu +function Exit-NetxTray { + try { $menu.Close() } catch {} $notify.Visible = $false $form.Close() [System.Windows.Forms.Application]::Exit() -}) -[void]$menu.Items.Add($miExit) - -$miStopExit = New-NetxMenuItem -Text (T "Stop and exit" "停止并退出") +} +$miExit.add_Click({ Invoke-NetxTrayDeferred { Exit-NetxTray } }) $miStopExit.add_Click({ - Start-NetxTrayDeferred { - $notify.ShowBalloonTip(3000, "NetX", (T "Stopping…" "正在停止…"), [System.Windows.Forms.ToolTipIcon]::Info) - Invoke-NetxScript -File $stopPs1 -Wait | Out-Null - $notify.Visible = $false - $form.Close() - [System.Windows.Forms.Application]::Exit() - } -}) -[void]$menu.Items.Add($miStopExit) + Invoke-NetxTrayDeferred { + if (-not $script:sync.Healthy -and -not $script:sync.Runner) { Exit-NetxTray; return } + Show-NetxToast (T "Stopping NetX…" "正在停止 NetX…") "Info" 3000 + $script:pending = $null + Invoke-NetxRuntime -Action stop -Then { param($c) Exit-NetxTray } + } + }) $notify.ContextMenuStrip = $menu -$notify.add_DoubleClick({ - if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) { - Start-Process $uiUrl - } else { - Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null - Open-NetxUiWhenReady - } -}) +# Single left click opens the menu like modern tray apps; double click opens the UI. +$notify.add_MouseUp({ + param($s, $e) + if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Left) { + $mi = [System.Windows.Forms.NotifyIcon].GetMethod("ShowContextMenu", [System.Reflection.BindingFlags]"Instance,NonPublic") + if ($mi) { $mi.Invoke($notify, $null) } + } + }) +$notify.add_DoubleClick({ Invoke-NetxTrayDeferred { Open-NetxUi } }) -# Refresh tooltip periodically (running / stopped). -$script:tipTimer = New-Object System.Windows.Forms.Timer -$script:tipTimer.Interval = 5000 -$script:tipTimer.add_Tick({ Update-NetxTrayTip }) -$script:tipTimer.Start() +# ---------------------------------------------------------------- lifecycle +$script:uiTimer = New-Object System.Windows.Forms.Timer +$script:uiTimer.Interval = 400 +$script:uiTimer.add_Tick({ + # Pick up in-place updates of version.json. + if (((Get-Date) - $script:verCheckedAt).TotalSeconds -gt 30) { + $script:verCheckedAt = Get-Date + try { $script:ver = Get-NetxVersion -ProgramRoot $prog } catch {} + } + Update-NetxTrayState + }) +$script:verCheckedAt = Get-Date + +function Start-NetxOnLaunch { + if (-not $StartOnLaunch) { return } + Invoke-NetxRuntime -Action start -OpenUi:(-not $QuietStart) + if (-not $QuietStart) { + Show-NetxToast (T "Starting NetX… the first run may take a minute." "正在启动 NetX…首次运行可能需要一分钟。") + } +} $form.add_Shown({ - if ($AutoUpdate) { - $notify.ShowBalloonTip(4000, "NetX", (T "Checking for updates…" "正在检查更新…"), [System.Windows.Forms.ToolTipIcon]::Info) - Start-NetxPowerShell -File $checkPs1 ` - -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-Apply", "-Quiet", "-AutoOnly") ` - -WorkingDirectory $prog -WindowStyle Hidden -Wait | Out-Null - } elseif ($CheckUpdateOnLaunch) { - Start-NetxPowerShell -File $checkPs1 ` - -Arguments @("-ProgramRoot", $prog, "-DataRoot", $data, "-Quiet") ` - -WorkingDirectory $prog -WindowStyle Hidden | Out-Null - } - if ($StartOnLaunch) { - Invoke-NetxScript -File $startPs1 -ExtraArgs @("-SkipBrowser") | Out-Null - $script:netxUiWaitTicks = 0 - $script:netxUiWaitMax = 360 # 360 * 500ms = 180s - $script:netxUiTimer = New-Object System.Windows.Forms.Timer - $script:netxUiTimer.Interval = 500 - $script:netxUiTimer.add_Tick({ - $script:netxUiWaitTicks++ - if (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2) { - $script:netxUiTimer.Stop() - $script:netxUiTimer.Dispose() - try { Start-Process $uiUrl } catch {} - Update-NetxTrayTip - return + $script:uiTimer.Start() + if ($AutoUpdate) { + # Apply silently first, then start; watched so the tray stays responsive. + try { + $up = Start-NetxChild -File $checkPs1 -ExtraArgs @("-Apply", "-Quiet", "-AutoOnly") + Watch-NetxProcess $up { param($p) Start-NetxOnLaunch } + } catch { + Start-NetxOnLaunch } - if ($script:netxUiWaitTicks -eq 1) { - $notify.ShowBalloonTip( - 5000, - "NetX", - (T "Starting… first run may take a minute." "正在启动…首次迁移可能需要一分钟。"), - [System.Windows.Forms.ToolTipIcon]::Info - ) + } else { + if ($CheckUpdateOnLaunch) { + try { Start-NetxChild -File $checkPs1 -ExtraArgs @("-Quiet") | Out-Null } catch {} } - if ($script:netxUiWaitTicks -ge $script:netxUiWaitMax) { - $script:netxUiTimer.Stop() - $script:netxUiTimer.Dispose() - $notify.ShowBalloonTip( - 8000, - "NetX", - (T "UI not ready yet. Use tray → Open UI later, or check data\runtime\netx.err.log." "界面尚未就绪。请稍后用托盘「打开界面」,或查看 data\runtime\netx.err.log。"), - [System.Windows.Forms.ToolTipIcon]::Warning - ) - Update-NetxTrayTip - } - }) - $script:netxUiTimer.Start() - } -}) + Start-NetxOnLaunch + } + }) $form.add_FormClosing({ - try { $script:tipTimer.Stop(); $script:tipTimer.Dispose() } catch {} - $notify.Visible = $false - $notify.Dispose() -}) + try { $script:uiTimer.Stop() } catch {} + try { $script:watchTimer.Stop() } catch {} + $script:sync.Stop = $true + $notify.Visible = $false + $notify.Dispose() + foreach ($h in $script:iconHandles) { try { [void][NetxNative]::DestroyIcon($h) } catch {} } + }) [System.Windows.Forms.Application]::Run($form) +try { $script:probePs.EndInvoke($script:probeHandle) } catch {} +try { $script:probePs.Dispose(); $script:probeRunspace.Close() } catch {} diff --git a/packaging/runtime_ctl.ps1 b/packaging/runtime_ctl.ps1 new file mode 100644 index 0000000..6a7e5f3 --- /dev/null +++ b/packaging/runtime_ctl.ps1 @@ -0,0 +1,52 @@ +param( + [ValidateSet("start", "stop", "restart")] + [string]$Action = "start", + [string]$ProgramRoot = "", + [string]$DataRoot = "" +) + +# Single start/stop/restart entry for tray + shortcuts. Routes through the +# Windows service / SYSTEM task when one is installed, else the plain scripts. + +$ErrorActionPreference = "Stop" +. "$PSScriptRoot\_common.ps1" +Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden + +$prog = Get-NetxProgramRoot -Override $ProgramRoot +$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot + +function Invoke-NetxChildScript([string]$Name, [string[]]$Extra = @()) { + & powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot $Name) ` + -ProgramRoot $prog -DataRoot $data @Extra + return $LASTEXITCODE +} + +$runner = Get-NetxManagedRunner +switch ($runner) { + "service" { + switch ($Action) { + "start" { Start-Service -Name "NetX" } + "stop" { Stop-Service -Name "NetX" -Force } + "restart" { Restart-Service -Name "NetX" -Force } + } + } + "task" { + if ($Action -ne "start") { + Stop-ScheduledTask -TaskName "NetXService" -TaskPath "\NetX\" + $null = Invoke-NetxChildScript "stop_netx_app.ps1" + } + if ($Action -ne "stop") { + Start-ScheduledTask -TaskName "NetXService" -TaskPath "\NetX\" + } + } + default { + if ($Action -ne "start") { + $null = Invoke-NetxChildScript "stop_netx_app.ps1" + } + if ($Action -ne "stop") { + $code = Invoke-NetxChildScript "start_netx_app.ps1" @("-SkipBrowser") + if ($code) { exit $code } + } + } +} +exit 0 diff --git a/packaging/service_lifecycle.ps1 b/packaging/service_lifecycle.ps1 index 1014ca2..c3f7e4f 100644 --- a/packaging/service_lifecycle.ps1 +++ b/packaging/service_lifecycle.ps1 @@ -84,11 +84,12 @@ try { 'ResumeAfterUpdate' { if (-not (Test-Path -LiteralPath $stateFile)) { break } $state = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json - if ($state.serviceWasRunning) { + # Setup may have switched run mode (service -> tray) in between: skip what is gone. + if ($state.serviceWasRunning -and (Get-Service -Name 'NetX' -ErrorAction SilentlyContinue)) { Write-LifecycleLog 'Restarting Windows service NetX after update' Start-Service -Name 'NetX' -ErrorAction Stop } - if ($state.taskWasRunning) { + if ($state.taskWasRunning -and (Get-NetxTask)) { Write-LifecycleLog 'Restarting scheduled task NetXService after update' Start-ScheduledTask -TaskName 'NetXService' -TaskPath '\NetX\' -ErrorAction Stop } @@ -96,6 +97,17 @@ try { } 'RemoveForUninstall' { $null = Stop-NetxRunners -Remove + # Logon tray + daily update tasks would otherwise outlive the program files. + foreach ($t in @('NetXTray', 'NetXUpdate')) { + Unregister-ScheduledTask -TaskName $t -TaskPath '\NetX\' -Confirm:$false -ErrorAction SilentlyContinue + } + try { + $sched = New-Object -ComObject Schedule.Service + $sched.Connect() + $root = $sched.GetFolder('\') + $nx = $root.GetFolder('\NetX') + if ($nx.GetTasks(1).Count -eq 0 -and $nx.GetFolders(0).Count -eq 0) { $root.DeleteFolder('NetX', 0) } + } catch {} $stopScript = Join-Path $prog 'packaging\stop_netx_app.ps1' if (Test-Path -LiteralPath $stopScript) { & powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File $stopScript ` diff --git a/packaging/start_netx_app.ps1 b/packaging/start_netx_app.ps1 index 55b8d6d..d25a0d4 100644 --- a/packaging/start_netx_app.ps1 +++ b/packaging/start_netx_app.ps1 @@ -43,15 +43,16 @@ if (Test-Path (Join-Path $dist "index.html")) { $mode = Get-DbMode -EnvMap $map $hostBind = if ($env:NETX_HOST) { $env:NETX_HOST } else { "127.0.0.1" } $port = if ($env:NETX_PORT) { [int]$env:NETX_PORT } else { 8890 } +$clientHost = Get-NetxClientHost -BindHost $hostBind Write-Host "==> Program: $prog" Write-Host "==> Data: $data" Write-Host "==> DB mode: $mode" -if (-not $Force -and (Test-NetxApiHealthy -HostName $hostBind -Port $port)) { - Write-Host "==> NetX already healthy at http://${hostBind}:${port}/ — skip start (use -Force to restart)" -ForegroundColor Green +if (-not $Force -and (Test-NetxApiHealthy -HostName $clientHost -Port $port)) { + Write-Host "==> NetX already healthy at http://${clientHost}:${port}/ — skip start (use -Force to restart)" -ForegroundColor Green if (-not $SkipBrowser) { - try { Start-Process "http://${hostBind}:${port}/" } catch {} + Open-NetxUrl -Url "http://${clientHost}:${port}/" } exit 0 } @@ -106,8 +107,8 @@ try { exit 1 } -if (-not (Test-NetxTcpPortFree -HostName $hostBind -Port $port)) { - if (Test-NetxApiHealthy -HostName $hostBind -Port $port) { +if (-not (Test-NetxTcpPortFree -HostName $clientHost -Port $port)) { + if (Test-NetxApiHealthy -HostName $clientHost -Port $port) { Write-Host "==> Port $port already serves NetX — skip start" -ForegroundColor Green exit 0 } @@ -132,14 +133,14 @@ if ($InlineSchedulers) { $startArgs["InlineSchedulers"] = $true } & $startScript @startArgs if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$url = "http://${hostBind}:${port}/" -if (-not (Test-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 2)) { - if (-not (Wait-NetxApiHealthy -HostName $hostBind -Port $port -TimeoutSec 30)) { +$url = "http://${clientHost}:${port}/" +if (-not (Test-NetxApiHealthy -HostName $clientHost -Port $port -TimeoutSec 2)) { + if (-not (Wait-NetxApiHealthy -HostName $clientHost -Port $port -TimeoutSec 30)) { Write-Host "[ERR] NetX started but /health not ready: $url" -ForegroundColor Red exit 1 } } Write-Host "==> Open: $url" -ForegroundColor Green if (-not $SkipBrowser) { - try { Start-Process $url } catch {} + Open-NetxUrl -Url $url } diff --git a/scripts/start_netx.ps1 b/scripts/start_netx.ps1 index 9faf35c..50cb267 100644 --- a/scripts/start_netx.ps1 +++ b/scripts/start_netx.ps1 @@ -147,6 +147,8 @@ function Show-LogTail { function Test-NetxApiListening { param([string]$HostName, [int]$LocalPort, [int]$WaitSec = 45) $deadline = (Get-Date).AddSeconds($WaitSec) + # Wildcard binds are not connectable on Windows; probe via loopback. + if ($HostName -in @("0.0.0.0", "::", "[::]", "*")) { $HostName = "127.0.0.1" } $healthUrl = "http://${HostName}:${LocalPort}/health" while ((Get-Date) -lt $deadline) { try {