From 85029898a5f66b510188122cbca1c65bce87317e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Thu, 21 May 2026 16:35:25 +0200 Subject: [PATCH] per-platform tray menu icons and Windows-specific status row The Windows menu renderer paints leading bitmaps into the Win32 check-mark slot (SetMenuItemBitmaps), which differs from how Cocoa and GTK handle NSMenuItem.image / menu-row icons: - SM_CXMENUCHECK sizing: Windows expects ~16x16 at 100% DPI in the check-mark slot and visually overflows the row for anything bigger. - Disabled-state mask: Windows desaturates both the row text and the bitmap when MFS_DISABLED is set, so a disabled informational row renders the coloured status dot in greyscale. Per the platform icon guidelines: Platform | Size | Notes ---------|----------------|----------------------------------------- Windows | 16x16 | check-mark slot, status row stays enabled macOS | 22x22 (18-22) | NSMenuItem leading image, HIG Linux | 24x24 (22-48) | GTK4 menu-row icon channel Changes: * Split the menu-row icon embeds into icons_menu_{windows,darwin,linux}.go so each platform pulls its own size; the brand mark is rendered from assets/svg/netbird-menu.svg (new vector source) at 16/22/24 px with Inkscape, and the Windows status dots ship as 8x8 content centred on a 16x16 transparent canvas (the renderer upscales the bitmap, so the padding keeps the dot visually proportional to the row text). * Introduce statusRowEnabled() in tray_status_enabled_{windows,other}.go: true on Windows so the disabled-state mask does not strip the dot's colour; false on macOS/Linux where disabled menu rows fade the label without desaturating the leading bitmap, signalling that the row is informational. * Add an icon to the About submenu using the same brand mark. --- client/ui/assets/netbird-menu-16.png | Bin 0 -> 526 bytes client/ui/assets/netbird-menu-22.png | Bin 0 -> 687 bytes client/ui/assets/netbird-menu-24.png | Bin 0 -> 739 bytes .../assets/netbird-menu-dot-connected-16.png | Bin 0 -> 508 bytes .../assets/netbird-menu-dot-connected-22.png | Bin 0 -> 615 bytes .../assets/netbird-menu-dot-connecting-16.png | Bin 0 -> 520 bytes .../assets/netbird-menu-dot-connecting-22.png | Bin 0 -> 637 bytes .../ui/assets/netbird-menu-dot-error-16.png | Bin 0 -> 532 bytes .../ui/assets/netbird-menu-dot-error-22.png | Bin 0 -> 629 bytes client/ui/assets/netbird-menu-dot-idle-16.png | Bin 0 -> 490 bytes client/ui/assets/netbird-menu-dot-idle-22.png | Bin 0 -> 602 bytes .../ui/assets/netbird-menu-dot-login-16.png | Bin 0 -> 537 bytes .../ui/assets/netbird-menu-dot-login-22.png | Bin 0 -> 641 bytes .../ui/assets/netbird-menu-dot-offline-16.png | Bin 0 -> 512 bytes .../ui/assets/netbird-menu-dot-offline-22.png | Bin 0 -> 605 bytes client/ui/assets/svg/netbird-menu.svg | 7 +++ client/ui/icons.go | 27 +++-------- client/ui/icons_menu_darwin.go | 39 ++++++++++++++++ client/ui/icons_menu_linux.go | 40 +++++++++++++++++ client/ui/icons_menu_windows.go | 42 ++++++++++++++++++ client/ui/tray.go | 36 ++++++++++----- client/ui/tray_status_enabled_other.go | 12 +++++ client/ui/tray_status_enabled_windows.go | 13 ++++++ 23 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 client/ui/assets/netbird-menu-16.png create mode 100644 client/ui/assets/netbird-menu-22.png create mode 100644 client/ui/assets/netbird-menu-24.png create mode 100644 client/ui/assets/netbird-menu-dot-connected-16.png create mode 100644 client/ui/assets/netbird-menu-dot-connected-22.png create mode 100644 client/ui/assets/netbird-menu-dot-connecting-16.png create mode 100644 client/ui/assets/netbird-menu-dot-connecting-22.png create mode 100644 client/ui/assets/netbird-menu-dot-error-16.png create mode 100644 client/ui/assets/netbird-menu-dot-error-22.png create mode 100644 client/ui/assets/netbird-menu-dot-idle-16.png create mode 100644 client/ui/assets/netbird-menu-dot-idle-22.png create mode 100644 client/ui/assets/netbird-menu-dot-login-16.png create mode 100644 client/ui/assets/netbird-menu-dot-login-22.png create mode 100644 client/ui/assets/netbird-menu-dot-offline-16.png create mode 100644 client/ui/assets/netbird-menu-dot-offline-22.png create mode 100644 client/ui/assets/svg/netbird-menu.svg create mode 100644 client/ui/icons_menu_darwin.go create mode 100644 client/ui/icons_menu_linux.go create mode 100644 client/ui/icons_menu_windows.go create mode 100644 client/ui/tray_status_enabled_other.go create mode 100644 client/ui/tray_status_enabled_windows.go diff --git a/client/ui/assets/netbird-menu-16.png b/client/ui/assets/netbird-menu-16.png new file mode 100644 index 0000000000000000000000000000000000000000..d5dcab446feb5da6237b6c1705a0306aeda93bf0 GIT binary patch literal 526 zcmV+p0`dKcP)ug zK~y-6rISBz6G0Hgf3vr7;qM)J#i8=)pqN5-O$kA0)AUX;p zOQAvH3-AGGh+-&_1sx;`2!aBqJLh(X>JHx-B!s8ic{6X`Tg`~7GE115x&iwyMM;0T zYXW~+;mBm(yT(9N75Zn|SrGJ`+P1awGa0+>L-&Wf07-oAhQLFRg&OaxX7uKQB=OR9 zRM%^EJTN4gAF(jLhS9oJugdxG?(ns!DoMN)3#?Xag#C9ta0Yww6EdHdh>AQ7S2tHE zriRfl0)0H$?!GRfNdwI+`TH*FrGWMj6BUnAS~GfkOJM+@PH-PPzb9rE|M_4cEDrB% zY)uh>YIyq83aT&sb_epHYWiCngb&RFKORlytDkfAxeKg~Nav=uOVbA41#8?KWq&6C zT<#y3<2z#7X(cbMG;iJ87}V=41nJuHB}A`?QGyd&NH{KhLuc=Hy%|8tTj*c3Y4Ckh z(X%C*8}>(Cq?LVHeDd<}p9soTPT4)G=(8&7m^-%9)2uZmeOkeP#?06F3xMsMVLw1# Qf&c&j07*qoM6N<$f}PFco&W#< literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-22.png b/client/ui/assets/netbird-menu-22.png new file mode 100644 index 0000000000000000000000000000000000000000..87c6b599537515c7ef1d37eed3d76f3f1a2a15e7 GIT binary patch literal 687 zcmV;g0#N;lP)u`+*OoG%N9ihLc978f)*{Jm75$gOE4&E)2ayC z_7BWLY9&OJnl8dci^xQ*kx?5PXU6yLX<;Ke^F}Fh(cQg!zUOnl=bigQRF(Z0W?!iL z3HJUbsWRGk%=h)2h;-M+L@37m4b@V~UqgvA>k zX%%|S;pMH}7phs0z2wHHa#&2lZnyHtnI1>*5s1263;z6}dPk}Cjns>5q|u_Gjvc`x zAPQjGe_%WFHFm(X6$TpD65_aYx9@@)eN`(A7TO5DZT@qcF65F=$2xWc@4SSD&wE%N zhY4+G9t{j`xf$0|xwIR!4qUeR9>r!svUN%UuPbj{q7-R=$=#01N`yEPK_TG}%M%Uh+ocbdHf3UM#MBP_#-U1h8N-&mD!@!NAz~(@A zi*mxFqD_;sC1zn~v>{W7SqlgICNf*NT)OiLs3)qj`?b*KE`ajWU1vpP3}`L`7J(Th z^3rnL_eO_fR9&s~da0=)03{;i(p`51(v^E4_|l41o5yZl1yv^AA~W7$=LFX$0{EGt zcxrMGcuQkuU$R=Z<7aO;~vvGojL$Jq$ z5?&?leH`C;w_^;ExSqTTJS&-gIf(?j*DmV`^lJUit3WApYj5vvpZ#LIj`qvIiy~3M z!K^ey8N+;Q*x^DR*QBOrD+>VhjA~COFQMcOfL?4ujEWSHd<^kp04sT|B101Wr~2RK zywJIzzbEF+T5Tkx=wc&*Kq}ckx%TS<0DQh-=Z+wS-AS^O*=*g@@rpZ)bl_Ws z{w;C<<%T%zOWNBEI2;2C9rtu`46!)dH7!`Uw`D_l zW=8gu6$?3zZ)>}^lw;Fg#TT4RTiFg9uCEUkk?N>U|6}nyn(ssZ57y()eV>->FAJ{Y zVQ^UZYFlu=T=?6En*$7X>sG_x`?vobW*HZZUZhlH;S|t_@1`rL7Tk?SF88qNFlw{_n k7MCRE79jK(h8UVy8G($4=#f;Ez5~+a>FVdQ&MBb@0E!Z*0{{R3 literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-connected-22.png b/client/ui/assets/netbird-menu-dot-connected-22.png new file mode 100644 index 0000000000000000000000000000000000000000..78b068748396091b655aa0bdceaa1cd23fd612e7 GIT binary patch literal 615 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)63dtTpz6=aiY77hwEes65fI$sfT_}}{C)aurVgbUVw ziV`WR_Cl4W#5tg8YIRm^k^k6+O!)FsqbZ z`t|Q0i~a6DOvhDra{X1h2$Z@n$9n^)lrhQM-G!lpRn`N@;VkfoEC%{l6@(c*gH%2M z1=&kHeO=jKv5N9>aUJQH#tsyk;_2cTB5}F&VkBRSf&j~fGG-CR!uyQx>pdjXJB?oc zs!?$$XRHxA>M7*@!Dp7>DGAlcs>6F8ES=!x8Td)*f7a61$0S}S`vkv!wRY{hJFV9n zUzb!D+V430uV}}@`j;ovlk*LQWkr6DvalAcE-e$*gq-Y4UXSb6Mw<&;$URm(~pc literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-connecting-16.png b/client/ui/assets/netbird-menu-dot-connecting-16.png new file mode 100644 index 0000000000000000000000000000000000000000..f874706b5a357263a61b176ff373d688e8b164a0 GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`3dtTpz6=aiY77hwEes65fINBEI2;2CUG{Ww46!(!oFKv4bA#pXx)mGP_W#Vt{rY{sF*7ss zVZ*dzd$V1C^uN2b^YiQljCZb^M)d8in|$>2pXE^>{?{9_v9Yy%@99}@_dhjrtB~l- zZ=s)$$)|kvdBNsW@$*{S^q>AqU;O`{Pxjk4FDj&iU|ZE$^->9%h|?|H_5_ z{EvSmKk0@&(42$+|Eq$;lrk7rG8}FYKGXC2p0Uxs1gGFH^Co^g%9(bomW5YCK-})r zPkry-*>1OofGORp23_C4D_>K!P%m;c;wZt`|BqgyV z)hf9t6-Y4{85o)A8kp)D8H5;`Ss9sG8JcPv7+4t?BwhR|jiMnpKP5A*5{m`{hz7?k vc|i3H8gLs*GILXlOA>Pn5PA$l3{9+zK*mG#NGeL-0crAd^>bP0l+XkKnYp#4 literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-connecting-22.png b/client/ui/assets/netbird-menu-dot-connecting-22.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e5970f5605bfbd8a1080f7fbdfc78bce8ef0e6 GIT binary patch literal 637 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)63dtTpz6=aiY77hwEes65fI=ur6IsHAsMsIXOi_67NS zizNI0=P+DX*tbr+?3CcK6a1eK^M5$Zf0x1c-Wt`0DkY6wZ z6DJ?HqG#CzW|gu_zyAGWvETiN>A1>HuCl*M7nwXc4`pz^1i3TG+uensgH_f8$l)yT zh%5$rUKNBHJ%dy}00r4gJbhi+U$Khvad92#n8pqin(OJ}7$R}GbfUXZivbS{FI!F% zuUfjy&;P>WW}pK+vnarJN<6i+sxmICC{3q+y7m-tn)QA+tQyyS3CGw zThi+$vqN8Fv;E9kGV|(AXttRcutadma2#bkNBEI2;2C-S>2H46!(!oFKv4bAx5CxNZ{f?Oj)Iz4>3C&c?>p z7TnWQ9bQ>2e^*NU`|JgbYj&4!o)fh}q5J#W+gS(q*B_Mtf_uis`+w=^+{v0Y!Qk!o z)vx7KzMgr(cIN-r(|%9?{^nd%|F7e4&V!%d`+uK5KfkT*`ntLI3ckK#PWkHdf^Ej% z|L;|Pd|!XWUS(rF4-ZdM{eLfzSj#%O!wf-;ZH&t$B){)XPpdoFvh0Pe@#k)!gMM>J zO_(&P=*i{{1;4Y!riK5Pkd*vqXk@e}EwS=zWW)|zRn?~)R~HwG7yyIH>PTd3cQy+* z(5tE?t`Q|Ei6yC4$wjF^iowXh$V}J3RM*HL#L&#j$jr*nRNKJ7%D^D$;!kN54Y~O# znQ4_+G#EfMIBv-Us%OxE+fb63n_66wm|K9*V;Ev+Vr2v}9->E5QTh%@lc%en%Q~lo FCID9Cy}JMa literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-error-22.png b/client/ui/assets/netbird-menu-dot-error-22.png new file mode 100644 index 0000000000000000000000000000000000000000..d9bd013d66ec215d5926a805a2bed2c3bee2b026 GIT binary patch literal 629 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)63dtTpz6=aiY77hwEes65fI1Tq2zdAd=cXkHqdhg=$*~#gnvop}TiSJae11b5EAirP+CQd$X zMbEMc%qnGIh+L^k;On? ztAa42XOPMVpdfpRr>`sfD^^iHF0LaT)7XJRb39!fLnJPjUcAZItRTP|z@n?c^lta2 zfB((H*7|Nad*`?_)0vo-buKdmWEQ4Bdg3a%c)iNL#91F>JeDpwQ8}${tH$am>DO~i zcda}1XtD3@pBo+vO4nRBQ~%z)|AWm3=lT~XmOsej6qYZaC!YI0=c|$Omb^nd+ zwXEFYzim>I%ZwuXz#a#l7$zIe8UZPG3ArfI4G}BYR&4p-b$^NoHNBEI2;2CZS{0<46!(!oFKv4bAu(idfLWMA2&z-|MmIy%tj#S zZ7qGjynlaR*mRe_Y8ejS_w3X;G-poa|9=Hx#s&r*j;-;A8*B1wfB*fx<@^HVa@DsgLgQm@Ai)Sv;kp(HamwYVfPw*XC#5y*ImC6bELcYt~rJYD@<);T3K F0RRG?rY!&f literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-idle-22.png b/client/ui/assets/netbird-menu-dot-idle-22.png new file mode 100644 index 0000000000000000000000000000000000000000..675cf1ffecc5849c26f3f03d5a46500b8fa646f2 GIT binary patch literal 602 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)63dtTpz6=aiY77hwEes65fIP zpymV=oH~C4P%(q(r0U7Lfa>QgUO#8?2A~z$v5Uollw3)WUoZm`Cm*+>XW0a1m9k5} z{;}Ba{=;N{TxBQMU!{vooHvxj4**p%CV9KNFm$lWdH^|`1s;*bK#!_|Fr#OX$_Jnz zdx@v7EBh-}Q9drNBOTM&fkJ(rE{-7*mrE~3^0g=kuv`ce31Imid-3=Dpp-Nty_LUe zR2<3$YlT~SS{d&OAC2+QtdNN{@C;qn+kGx%)*lo7Q>Sj7J$h@E&ZquszPqcxuvg!{ zGH>_4^~=BCUD+(9|EeaW`ElmHm7gn>qIT46dd9u>P)@(ZMxn|1(|8Pbu-I_gbsS?n zyg0P+=pw0r%=caVw=9$$kAJRR26UNfiEBhjN@7W>RdP`(kYX@0Ff!9MFx53O3^6pZ zGBCC>HqtgQure^1sx)moiiX_$l+3hB+!~(L>v01$XuxeK$;?eHE=kNSK+|JrU}b0k VvBW2{))}aW!PC{xWt~$(695+S(e3~M literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-login-16.png b/client/ui/assets/netbird-menu-dot-login-16.png new file mode 100644 index 0000000000000000000000000000000000000000..6d65365a21d7691c13f59806aaf089f7e9bd217e GIT binary patch literal 537 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`3dtTpz6=aiY77hwEes65fINBEI2;2CJ@IsL46!(!oFKv4bAx61y1taVb@wj+x?j$n&ISZ( z=Y*?mdAG}-<%|6{djVs2zsIIEwj1qV-!*^tYX5)MqY^+cL-PK9p+~=NN##7W(7pET zr+muSGcVZwOq-GR{o(#wE&F*EJ>u!%;o<4&;rTCFv7`Rqzub!FyH34~Zoh8t5O%&L z@4)_iF~9o%++GF-f1JfqfBcRQcy!;KTfO9~1lt5I1D=FORvY_ge*DWFqN&D~cI>!@W&!7Rfp(HamwYVfPw*aBXFvQTr$_QjUM31DR^c|2U MPgg&ebxsLQ0PGF9+5i9m literal 0 HcmV?d00001 diff --git a/client/ui/assets/netbird-menu-dot-login-22.png b/client/ui/assets/netbird-menu-dot-login-22.png new file mode 100644 index 0000000000000000000000000000000000000000..563de48edbc6362be7d7b4ca2119864d01caf792 GIT binary patch literal 641 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)63dtTpz6=aiY77hwEes65fI_|3ZMt98>i%f??;U~_=>T9k|A0x89k zAirP+CQd$XMbEMc%qnGf{9eiJ&SeEck=Ce%swN)tKdO=io{XQ$4&*)js*VL zm^n>+c1V==PSMI~`*I?$9+Ugz5*Zx+G}wIY&9Bq%ZaJH|yKcj;CfQ%_EzU-ZuQE*i7XOjiC`x1H z6u(nCK!>W9xJHzuB$lLFB^RXvDF!10BQsqCQ(Ys25JNL7BQq;QQ*8qSD+7b1i$A4N zH00)|WTsVO(O>}4;J76ZsGdOsZbM0CZfbE!Vr~IKk70NBEI2;2Co%VEb46!)jC6&7I;J!10AXg!i=6lDj$G?>?NMQuI#T^ zMfte6j&w|82MSH_ba4!kxLkTMlCMQUfaO9Nv&hXm+r#lGhMP0%F`A6mE?~cb8I^v)~X}6 zno;}h{zt-EQ7Th~?llGg-KJXN8c~vxSdwa$T$Bo=7>o>z%ybP*b&U)|3{9*IjIE4K zwG9lc3=9HVS6HHG$jwj5OsmALLGgw_D^P<5+=i0O+|=Td#M}ZjJ%$EWh6WHzd@^gD QfqED`UHx3vIVCg!096^#6#xJL literal 0 HcmV?d00001 diff --git a/client/ui/assets/svg/netbird-menu.svg b/client/ui/assets/svg/netbird-menu.svg new file mode 100644 index 000000000..bd4e9d65d --- /dev/null +++ b/client/ui/assets/svg/netbird-menu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/ui/icons.go b/client/ui/icons.go index 3e8215b73..d5759cee7 100644 --- a/client/ui/icons.go +++ b/client/ui/icons.go @@ -59,24 +59,9 @@ var iconUpdateDisconnectedMacOS []byte //go:embed assets/netbird.png var iconWindow []byte -// Small colored dots shown next to the status menu entry. Rendered as -// regular NSImage/HBITMAP/GTK menu-item icons (not template), so the -// colours stay intact on every platform. - -//go:embed assets/netbird-menu-dot-connected.png -var iconMenuDotConnected []byte - -//go:embed assets/netbird-menu-dot-connecting.png -var iconMenuDotConnecting []byte - -//go:embed assets/netbird-menu-dot-login.png -var iconMenuDotLogin []byte - -//go:embed assets/netbird-menu-dot-error.png -var iconMenuDotError []byte - -//go:embed assets/netbird-menu-dot-idle.png -var iconMenuDotIdle []byte - -//go:embed assets/netbird-menu-dot-offline.png -var iconMenuDotOffline []byte +// Per-platform menu-row icons (status dots + NetBird brand mark) live in +// icons_menu_windows.go and icons_menu_other.go. Windows installs them +// into the Win32 check-mark slot, which expects SM_CXMENUCHECK-sized +// bitmaps (~16x16 at 100% DPI) — anything bigger gets cropped, anything +// smaller leaves blank space — so Windows ships its own 16x16 set +// while macOS/Linux keep the larger 24x24 assets that fit their menus. diff --git a/client/ui/icons_menu_darwin.go b/client/ui/icons_menu_darwin.go new file mode 100644 index 000000000..464e67096 --- /dev/null +++ b/client/ui/icons_menu_darwin.go @@ -0,0 +1,39 @@ +//go:build darwin + +package main + +import _ "embed" + +// 22x22 menu-row icons used on macOS. Apple's HIG recommends an 18–22 px +// glyph for NSMenuItem leading images; 22 sits at the top of that range +// and matches the visual weight of the surrounding row text. Windows +// ships a 16x16 variant (Win32 SM_CXMENUCHECK slot) and Linux a 24x24 +// variant (GTK menu row supports the larger range) — see the sibling +// icons_menu_*.go files. +// +// Regenerate the brand mark from assets/svg/netbird-menu.svg (vector +// source — re-rendering keeps the strokes crisp at every target size): +// inkscape assets/svg/netbird-menu.svg -o netbird-menu-22.png -w 22 -h 22 \ +// --export-background-opacity=0 +// Status dots are downscaled from the 24x24 originals with ImageMagick. + +//go:embed assets/netbird-menu-22.png +var iconMenuNetbird []byte + +//go:embed assets/netbird-menu-dot-connected-22.png +var iconMenuDotConnected []byte + +//go:embed assets/netbird-menu-dot-connecting-22.png +var iconMenuDotConnecting []byte + +//go:embed assets/netbird-menu-dot-login-22.png +var iconMenuDotLogin []byte + +//go:embed assets/netbird-menu-dot-error-22.png +var iconMenuDotError []byte + +//go:embed assets/netbird-menu-dot-idle-22.png +var iconMenuDotIdle []byte + +//go:embed assets/netbird-menu-dot-offline-22.png +var iconMenuDotOffline []byte diff --git a/client/ui/icons_menu_linux.go b/client/ui/icons_menu_linux.go new file mode 100644 index 000000000..8a562c639 --- /dev/null +++ b/client/ui/icons_menu_linux.go @@ -0,0 +1,40 @@ +//go:build linux + +package main + +import _ "embed" + +// 24x24 menu-row icons used on Linux. GTK4 menu rows accept icons in the +// 22–48 px range with no automatic downscaling at this size; 24 reads +// cleanly next to the row text across the GNOME / KDE / minimal-WM +// flavours we ship to. Windows ships a 16x16 variant (Win32 +// SM_CXMENUCHECK slot) and macOS a 22x22 variant — see the sibling +// icons_menu_*.go files. +// +// Regenerate the brand mark from assets/svg/netbird-menu.svg (vector +// source — re-rendering keeps the strokes crisp at every target size): +// inkscape assets/svg/netbird-menu.svg -o netbird-menu-24.png -w 24 -h 24 \ +// --export-background-opacity=0 +// Status dots are the canonical 24x24 originals used everywhere else +// in the legacy Fyne tray. + +//go:embed assets/netbird-menu-24.png +var iconMenuNetbird []byte + +//go:embed assets/netbird-menu-dot-connected.png +var iconMenuDotConnected []byte + +//go:embed assets/netbird-menu-dot-connecting.png +var iconMenuDotConnecting []byte + +//go:embed assets/netbird-menu-dot-login.png +var iconMenuDotLogin []byte + +//go:embed assets/netbird-menu-dot-error.png +var iconMenuDotError []byte + +//go:embed assets/netbird-menu-dot-idle.png +var iconMenuDotIdle []byte + +//go:embed assets/netbird-menu-dot-offline.png +var iconMenuDotOffline []byte diff --git a/client/ui/icons_menu_windows.go b/client/ui/icons_menu_windows.go new file mode 100644 index 000000000..d2b44af60 --- /dev/null +++ b/client/ui/icons_menu_windows.go @@ -0,0 +1,42 @@ +//go:build windows + +package main + +import _ "embed" + +// 16x16 menu-row icons used on Windows. The Win32 SetMenuItemBitmaps API +// paints the HBITMAP into the check-mark slot, sized to SM_CXMENUCHECK / +// SM_CYMENUCHECK (typically 16x16 at 100% DPI). Larger bitmaps overflow +// the row visually, so Windows ships its own scaled set instead of the +// 24x24 assets used on macOS/Linux. Regenerate the brand mark from +// assets/svg/netbird-menu.svg (vector source — re-rendering keeps the +// strokes crisp at every target size): +// inkscape assets/svg/netbird-menu.svg -o netbird-menu-16.png -w 16 -h 16 \ +// --export-background-opacity=0 +// The status dots are downscaled from the 24x24 originals with +// ImageMagick — simple solid-fill circles survive the bicubic resize +// without visible quality loss: +// magick netbird-menu-dot-.png -resize 16x16 \ +// -background none -gravity center -extent 16x16 \ +// netbird-menu-dot--16.png + +//go:embed assets/netbird-menu-16.png +var iconMenuNetbird []byte + +//go:embed assets/netbird-menu-dot-connected-16.png +var iconMenuDotConnected []byte + +//go:embed assets/netbird-menu-dot-connecting-16.png +var iconMenuDotConnecting []byte + +//go:embed assets/netbird-menu-dot-login-16.png +var iconMenuDotLogin []byte + +//go:embed assets/netbird-menu-dot-error-16.png +var iconMenuDotError []byte + +//go:embed assets/netbird-menu-dot-idle-16.png +var iconMenuDotIdle []byte + +//go:embed assets/netbird-menu-dot-offline-16.png +var iconMenuDotOffline []byte diff --git a/client/ui/tray.go b/client/ui/tray.go index eb1a4f227..f28aa4a83 100644 --- a/client/ui/tray.go +++ b/client/ui/tray.go @@ -242,7 +242,7 @@ func (t *Tray) reapplyMenuState() { if t.statusItem != nil && lastStatus != "" { t.statusItem.SetLabel(t.loc.StatusLabel(lastStatus)) - t.statusItem.SetEnabled(false) + t.statusItem.SetEnabled(statusRowEnabled()) t.applyStatusIndicator(lastStatus) } if t.sessionExpiresItem != nil { @@ -314,13 +314,18 @@ func (t *Tray) ShowWindow() { func (t *Tray) buildMenu() *application.Menu { menu := application.NewMenu() - // statusItem shows the daemon's current status. Disabled (and no - // OnClick handler) so clicks are no-ops — the row is informational - // only. The Connect entry below drives every actionable transition, - // including the SSO re-auth flow for NeedsLogin/SessionExpired - // (the daemon's Up RPC returns NeedsSSOLogin when applicable). + // statusItem shows the daemon's current status. Informational row + // with no OnClick handler — clicks are no-ops. Whether the row is + // kept enabled is platform-dependent (see statusRowEnabled): on + // Windows the disabled-state mask would desaturate the coloured + // status dot painted into the check-mark slot, so the row stays + // enabled there; macOS/Linux disable it so the greyed-out label + // signals that it is not clickable. The Connect entry below drives + // every actionable transition, including the SSO re-auth flow for + // NeedsLogin/SessionExpired (the daemon's Up RPC returns + // NeedsSSOLogin when applicable). t.statusItem = menu.Add(t.loc.T("tray.status.disconnected")). - SetEnabled(false). + SetEnabled(statusRowEnabled()). SetBitmap(iconMenuDotIdle) // sessionExpiresItem sits directly below the status row so the @@ -376,7 +381,11 @@ func (t *Tray) buildMenu() *application.Menu { menu.AddSeparator() - about := menu.AddSubmenu(t.loc.T("tray.menu.about")) + aboutLabel := t.loc.T("tray.menu.about") + about := menu.AddSubmenu(aboutLabel) + if aboutItem := menu.FindByLabel(aboutLabel); aboutItem != nil { + aboutItem.SetBitmap(iconMenuNetbird) + } about.Add(t.loc.T("tray.menu.github")).OnClick(func(*application.Context) { _ = t.app.Browser.OpenURL(urlGitHubRepo) }) @@ -621,11 +630,14 @@ func (t *Tray) applyStatus(st services.Status) { daemonUnavailable := strings.EqualFold(st.Status, services.StatusDaemonUnavailable) connecting := strings.EqualFold(st.Status, services.StatusConnecting) if t.statusItem != nil { - // Label-only: kept disabled (informational row). Swap the - // displayed text so the user sees a familiar phrase instead - // of the raw daemon enum. + // Label-only: row is informational (no OnClick). Enablement + // is platform-dependent via statusRowEnabled — Windows + // keeps it enabled so the Win32 disabled-state mask does + // not desaturate the coloured dot; macOS/Linux disable it. + // Swap the displayed text so the user sees a familiar + // phrase instead of the raw daemon enum. t.statusItem.SetLabel(t.loc.StatusLabel(st.Status)) - t.statusItem.SetEnabled(false) + t.statusItem.SetEnabled(statusRowEnabled()) t.applyStatusIndicator(st.Status) } if t.upItem != nil { diff --git a/client/ui/tray_status_enabled_other.go b/client/ui/tray_status_enabled_other.go new file mode 100644 index 000000000..1e4c8a13c --- /dev/null +++ b/client/ui/tray_status_enabled_other.go @@ -0,0 +1,12 @@ +//go:build !windows && !android && !ios && !freebsd && !js + +package main + +// statusRowEnabled reports whether the informational status row at the +// top of the tray menu should stay enabled. False on macOS and Linux: +// both platforms paint disabled menu rows at slightly reduced opacity +// without desaturating the leading bitmap, so the coloured status dot +// stays visible while the greyed-out label still signals to the user +// that the row is informational and not clickable. Windows opts in via +// the sibling tray_status_enabled_windows.go file. +func statusRowEnabled() bool { return false } diff --git a/client/ui/tray_status_enabled_windows.go b/client/ui/tray_status_enabled_windows.go new file mode 100644 index 000000000..cdfec41a6 --- /dev/null +++ b/client/ui/tray_status_enabled_windows.go @@ -0,0 +1,13 @@ +//go:build windows + +package main + +// statusRowEnabled reports whether the informational status row at the +// top of the tray menu should stay enabled. Always true on Windows: +// the Win32 disabled-state mask desaturates both the row text and the +// HBITMAP painted into the check-mark slot, so a disabled row would +// render the coloured status dot in greyscale and defeat the indicator. +// macOS/Linux disable the row (see tray_status_enabled_other.go) because +// neither platform applies that desaturation and the visual cue that +// the row is informational reads better. +func statusRowEnabled() bool { return true }