Release 3.8.2.7: Windows taskbar/tray sea-wave icon when connected.
ci / test (macos-latest) (push) Canceled after 0s
ci / test (ubuntu-latest) (push) Canceled after 0s
ci / test (windows-latest) (push) Canceled after 0s

Match macOS Dock behavior using the same idle/connected glyph assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-08-01 16:45:55 +03:00
co-authored by Cursor
parent 5d7c96f7fc
commit fed1ace33e
20 changed files with 395 additions and 24 deletions
+2 -1
View File
@@ -256,7 +256,8 @@ https://evilfox.win/
- 3.8.2+3: poll без os.Stat ядер + кэш host; Darwin sysproxy batch; Connect без Config.Clone; - 3.8.2+3: poll без os.Stat ядер + кэш host; Darwin sysproxy batch; Connect без Config.Clone;
- 3.8.2+4: единый GUI Windows/macOS на glaze + HTTP `/api` (вместо WebView2 Bind); автоинкремент номера сборки при compile; артефакты Windows с версией в имени (`Navis-x.y.z.b.exe`); - 3.8.2+4: единый GUI Windows/macOS на glaze + HTTP `/api` (вместо WebView2 Bind); автоинкремент номера сборки при compile; артефакты Windows с версией в имени (`Navis-x.y.z.b.exe`);
- 3.8.2+5: delta-poll UI (`getState` по rev); пауза опроса при скрытом окне; cancel lifecycle dock/watchdog; invalidate/лимит hostCache; PollUI без lock на `Engine.Running`; singleflight corebin; PingAll worker-pool; - 3.8.2+5: delta-poll UI (`getState` по rev); пауза опроса при скрытом окне; cancel lifecycle dock/watchdog; invalidate/лимит hostCache; PollUI без lock на `Engine.Running`; singleflight corebin; PingAll worker-pool;
- 3.8.2+6: поиск по серверам и хоткеи; Windows close→tray (Открыть/Выход); фоновый прогрев активного core; SSE `/api/events` + редкий poll fallback. - 3.8.2+6: поиск по серверам и хоткеи; Windows close→tray (Открыть/Выход); фоновый прогрев активного core; SSE `/api/events` + редкий poll fallback;
- 3.8.2+7: Windows — иконка в панели задач и трее цвета морской волны при подключении (как Dock на macOS).
В 3.8.1: В 3.8.1:
- меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера; - меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера;
+2 -2
View File
@@ -13,8 +13,8 @@ android {
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build // versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
versionCode = 3080206 versionCode = 3080207
versionName = "3.8.2+6" versionName = "3.8.2+7"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
ndk { ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
+3 -3
View File
@@ -42,11 +42,11 @@ if errorlevel 1 exit /b 1
go build -o "tools\packmac\packmac.exe" .\tools\packmac go build -o "tools\packmac\packmac.exe" .\tools\packmac
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 3.8.2 -build 3.8.2.6 -arch arm64 tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 3.8.2 -build 3.8.2.7 -arch arm64
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 3.8.2 -build 3.8.2.6 -arch amd64 tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 3.8.2 -build 3.8.2.7 -arch amd64
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 3.8.2 -build 3.8.2.6 -arch universal tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 3.8.2 -build 3.8.2.7 -arch universal
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
echo Built Mac GUI + CLI: echo Built Mac GUI + CLI:
+2
View File
@@ -15,6 +15,7 @@ import (
"vpnclient/internal/apphost" "vpnclient/internal/apphost"
"vpnclient/internal/config" "vpnclient/internal/config"
"vpnclient/internal/core" "vpnclient/internal/core"
"vpnclient/internal/dockicon"
"vpnclient/internal/logbuf" "vpnclient/internal/logbuf"
"vpnclient/internal/trayhost" "vpnclient/internal/trayhost"
"vpnclient/internal/update" "vpnclient/internal/update"
@@ -71,6 +72,7 @@ func main() {
}() }()
a.StartBackground() a.StartBackground()
dockicon.OnIconChange = trayhost.SetConnectedIcon
trayhost.Start("Navis", trayhost.Hooks{ trayhost.Start("Navis", trayhost.Hooks{
Connect: func() { _ = a.Connect() }, Connect: func() { _ = a.Connect() },
Disconnect: func() { _ = a.Disconnect() }, Disconnect: func() { _ = a.Disconnect() },
+3
View File
@@ -12,6 +12,8 @@ import (
"github.com/crgimenes/glaze" "github.com/crgimenes/glaze"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"vpnclient/internal/dockicon"
) )
var ( var (
@@ -68,6 +70,7 @@ func decorateWindow(w glaze.WebView) {
} }
hwnd := w.Window() hwnd := w.Window()
applyWindowIcon(hwnd) applyWindowIcon(hwnd)
dockicon.BindWindow(hwnd)
installCloseToHide(hwnd) installCloseToHide(hwnd)
} }
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -1,16 +1,16 @@
{ {
"version": "3.8.2", "version": "3.8.2",
"notes": "Navis 3.8.2+6: поиск/фильтр серверов и хоткеи (/, Ctrl+F, Space/Enter, Esc); Windows: закрытие окна = скрытие в трей, «Открыть»/Выход из трея; фоновый EnsureCore активного протокола; SSE /api/events для мгновенного статуса + poll 12 с как safety net.", "notes": "Navis 3.8.2+7: Windows — иконка в панели задач и трее цвета морской волны при подключении (как Dock на macOS); при отключении возвращается обычная.",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -1,16 +1,16 @@
{ {
"version": "3.8.2", "version": "3.8.2",
"notes": "Navis 3.8.2+6: поиск/фильтр серверов и хоткеи (/, Ctrl+F, Space/Enter, Esc); Windows: закрытие окна = скрытие в трей, «Открыть»/Выход из трея; фоновый EnsureCore активного протокола; SSE /api/events для мгновенного статуса + poll 12 с как safety net.", "notes": "Navis 3.8.2+7: Windows — иконка в панели задач и трее цвета морской волны при подключении (как Dock на macOS); при отключении возвращается обычная.",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
+6
View File
@@ -12,6 +12,9 @@ import (
"github.com/ebitengine/purego/objc" "github.com/ebitengine/purego/objc"
) )
// BindWindow is a no-op on macOS (Dock uses NSApplication icon image).
func BindWindow(h unsafe.Pointer) { _ = h }
//go:embed icon_idle.png //go:embed icon_idle.png
var iconIdlePNG []byte var iconIdlePNG []byte
@@ -103,4 +106,7 @@ func SetConnected(connected bool) {
} }
// AppKit must run on the main thread (glaze owns it). // AppKit must run on the main thread (glaze owns it).
app.Send(selPerf, selSet, img, false) app.Send(selPerf, selSet, img, false)
if OnIconChange != nil {
OnIconChange(connected)
}
} }
+7 -2
View File
@@ -1,6 +1,11 @@
//go:build !darwin //go:build !darwin && !windows
package dockicon package dockicon
// SetConnected is a no-op outside macOS (Dock icons are a Darwin feature). import "unsafe"
// SetConnected is a no-op on platforms without a Dock/taskbar icon swap.
func SetConnected(connected bool) {} func SetConnected(connected bool) {}
// BindWindow is a no-op outside Windows.
func BindWindow(h unsafe.Pointer) { _ = h }
+295
View File
@@ -0,0 +1,295 @@
//go:build windows
package dockicon
import (
"bytes"
_ "embed"
"image"
"image/png"
"sync"
"unsafe"
"golang.org/x/image/draw"
"golang.org/x/sys/windows"
)
//go:embed icon_idle.png
var iconIdlePNG []byte
//go:embed icon_connected.png
var iconConnectedPNG []byte
var (
user32 = windows.NewLazySystemDLL("user32.dll")
gdi32 = windows.NewLazySystemDLL("gdi32.dll")
procSendMessage = user32.NewProc("SendMessageW")
procCreateIconFromResource = user32.NewProc("CreateIconFromResourceEx")
procCreateIconIndirect = user32.NewProc("CreateIconIndirect")
procDestroyIcon = user32.NewProc("DestroyIcon")
procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
procCreateBitmap = gdi32.NewProc("CreateBitmap")
procDeleteObject = gdi32.NewProc("DeleteObject")
mu sync.Mutex
hwnd uintptr
last *bool
idleSmall windows.Handle
idleBig windows.Handle
connSmall windows.Handle
connBig windows.Handle
iconsReady bool
)
const (
wmSetIcon = 0x0080
iconSmall = 0
iconBig = 1
biRGB = 0
dibRGBColors = 0
)
type iconInfo struct {
FIcon int32
XHotspot uint32
YHotspot uint32
HbmMask windows.Handle
HbmColor windows.Handle
}
type bitmapInfoHeader struct {
BiSize uint32
BiWidth int32
BiHeight int32
BiPlanes uint16
BiBitCount uint16
BiCompression uint32
BiSizeImage uint32
BiXPelsPerMeter int32
BiYPelsPerMeter int32
BiClrUsed uint32
BiClrImportant uint32
}
type bitmapInfo struct {
Header bitmapInfoHeader
Colors [1]uint32
}
// BindWindow attaches the main HWND so SetConnected can update the taskbar icon.
func BindWindow(h unsafe.Pointer) {
mu.Lock()
defer mu.Unlock()
hwnd = uintptr(h)
if err := ensureIconsLocked(); err != nil {
return
}
connected := last != nil && *last
applyLocked(connected)
}
// SetConnected swaps the taskbar/window icon: sea-wave N when connected, original when idle.
func SetConnected(connected bool) {
mu.Lock()
if last != nil && *last == connected {
mu.Unlock()
return
}
v := connected
last = &v
if hwnd == 0 {
mu.Unlock()
if OnIconChange != nil {
OnIconChange(connected)
}
return
}
if err := ensureIconsLocked(); err != nil {
mu.Unlock()
return
}
applyLocked(connected)
mu.Unlock()
if OnIconChange != nil {
OnIconChange(connected)
}
}
// StatusIcon returns a shared HICON for the current connection state (do not DestroyIcon).
func StatusIcon(connected bool) windows.Handle {
mu.Lock()
defer mu.Unlock()
if err := ensureIconsLocked(); err != nil {
return 0
}
if connected {
return connBig
}
return idleBig
}
func applyLocked(connected bool) {
if hwnd == 0 {
return
}
small, big := idleSmall, idleBig
if connected {
small, big = connSmall, connBig
}
if small != 0 {
procSendMessage.Call(hwnd, wmSetIcon, iconSmall, uintptr(small))
}
if big != 0 {
procSendMessage.Call(hwnd, wmSetIcon, iconBig, uintptr(big))
}
}
func ensureIconsLocked() error {
if iconsReady {
return nil
}
var err error
idleSmall, err = hiconFromPNG(iconIdlePNG, 16)
if err != nil {
return err
}
idleBig, err = hiconFromPNG(iconIdlePNG, 32)
if err != nil {
return err
}
connSmall, err = hiconFromPNG(iconConnectedPNG, 16)
if err != nil {
return err
}
connBig, err = hiconFromPNG(iconConnectedPNG, 32)
if err != nil {
return err
}
iconsReady = true
return nil
}
func hiconFromPNG(src []byte, size int) (windows.Handle, error) {
resized, err := resizePNG(src, size)
if err != nil {
return 0, err
}
if h := createIconFromPNGResource(resized, size); h != 0 {
return h, nil
}
return createIconFromImage(resized, size)
}
func resizePNG(src []byte, size int) ([]byte, error) {
img, err := png.Decode(bytes.NewReader(src))
if err != nil {
return nil, err
}
dst := image.NewRGBA(image.Rect(0, 0, size, size))
draw.CatmullRom.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Over, nil)
var buf bytes.Buffer
if err := png.Encode(&buf, dst); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func createIconFromPNGResource(pngBytes []byte, size int) windows.Handle {
if len(pngBytes) == 0 {
return 0
}
h, _, _ := procCreateIconFromResource.Call(
uintptr(unsafe.Pointer(&pngBytes[0])),
uintptr(len(pngBytes)),
1,
0x00030000,
uintptr(size),
uintptr(size),
0,
)
return windows.Handle(h)
}
func createIconFromImage(pngBytes []byte, size int) (windows.Handle, error) {
img, err := png.Decode(bytes.NewReader(pngBytes))
if err != nil {
return 0, err
}
rgba, ok := img.(*image.RGBA)
if !ok {
tmp := image.NewRGBA(img.Bounds())
draw.Draw(tmp, tmp.Bounds(), img, img.Bounds().Min, draw.Src)
rgba = tmp
}
hbmColor, err := dibFromRGBA(rgba)
if err != nil {
return 0, err
}
// Monochrome mask: all zeros => fully opaque where color has alpha handled by 32-bit DIB.
hbmMask, _, err2 := procCreateBitmap.Call(uintptr(size), uintptr(size), 1, 1, 0)
if hbmMask == 0 {
procDeleteObject.Call(uintptr(hbmColor))
if err2 != nil {
return 0, err2
}
return 0, windows.ERROR_INVALID_HANDLE
}
ii := iconInfo{
FIcon: 1,
XHotspot: 0,
YHotspot: 0,
HbmMask: windows.Handle(hbmMask),
HbmColor: hbmColor,
}
h, _, err3 := procCreateIconIndirect.Call(uintptr(unsafe.Pointer(&ii)))
procDeleteObject.Call(uintptr(hbmMask))
procDeleteObject.Call(uintptr(hbmColor))
if h == 0 {
if err3 != nil {
return 0, err3
}
return 0, windows.ERROR_INVALID_HANDLE
}
return windows.Handle(h), nil
}
func dibFromRGBA(src *image.RGBA) (windows.Handle, error) {
w := src.Bounds().Dx()
h := src.Bounds().Dy()
bi := bitmapInfo{
Header: bitmapInfoHeader{
BiSize: uint32(unsafe.Sizeof(bitmapInfoHeader{})),
BiWidth: int32(w),
BiHeight: -int32(h), // top-down
BiPlanes: 1,
BiBitCount: 32,
BiCompression: biRGB,
},
}
var bits unsafe.Pointer
hbm, _, err := procCreateDIBSection.Call(0, uintptr(unsafe.Pointer(&bi)), dibRGBColors, uintptr(unsafe.Pointer(&bits)), 0, 0)
if hbm == 0 || bits == nil {
if err != nil {
return 0, err
}
return 0, windows.ERROR_INVALID_HANDLE
}
// Windows wants BGRA.
dst := unsafe.Slice((*byte)(bits), w*h*4)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
i := src.PixOffset(x, y)
o := (y*w + x) * 4
dst[o+0] = src.Pix[i+2] // B
dst[o+1] = src.Pix[i+1] // G
dst[o+2] = src.Pix[i+0] // R
dst[o+3] = src.Pix[i+3] // A
}
}
return windows.Handle(hbm), nil
}
+5
View File
@@ -0,0 +1,5 @@
package dockicon
// OnIconChange is invoked after SetConnected updates the platform icon.
// Used on Windows to keep the tray glyph in sync; unused on other platforms.
var OnIconChange func(connected bool)
+3
View File
@@ -8,3 +8,6 @@ func start(appName string, h Hooks) bool {
_ = h _ = h
return false return false
} }
// SetConnectedIcon is a no-op when tray is unsupported.
func SetConnectedIcon(connected bool) { _ = connected }
+53 -2
View File
@@ -4,8 +4,11 @@ package trayhost
import ( import (
"runtime" "runtime"
"sync"
"unsafe" "unsafe"
"vpnclient/internal/dockicon"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
@@ -90,6 +93,12 @@ type msg struct {
Pt point Pt point
} }
var (
trayMu sync.Mutex
trayNID *notifyIconData
trayLastUp *bool
)
func start(appName string, h Hooks) bool { func start(appName string, h Hooks) bool {
if h.Quit == nil { if h.Quit == nil {
return false return false
@@ -102,6 +111,35 @@ func start(appName string, h Hooks) bool {
return true return true
} }
// SetConnectedIcon updates the tray glyph (sea-wave N when connected).
func SetConnectedIcon(connected bool) {
trayMu.Lock()
nid := trayNID
if trayLastUp != nil && *trayLastUp == connected {
trayMu.Unlock()
return
}
v := connected
trayLastUp = &v
trayMu.Unlock()
if nid == nil {
return
}
icon := dockicon.StatusIcon(connected)
if icon == 0 {
return
}
trayMu.Lock()
if trayNID == nil {
trayMu.Unlock()
return
}
trayNID.HIcon = icon
trayNID.UFlags = nifMessage | nifIcon | nifTip
procShellNotify.Call(nimModify, uintptr(unsafe.Pointer(trayNID)))
trayMu.Unlock()
}
func runTray(appName string, h Hooks) { func runTray(appName string, h Hooks) {
className, _ := windows.UTF16PtrFromString("NavisTrayClass") className, _ := windows.UTF16PtrFromString("NavisTrayClass")
title, _ := windows.UTF16PtrFromString(appName) title, _ := windows.UTF16PtrFromString(appName)
@@ -155,17 +193,27 @@ func runTray(appName string, h Hooks) {
return return
} }
icon, _, _ := procLoadIcon.Call(0, uintptr(idiApplication)) icon := dockicon.StatusIcon(false)
if icon == 0 {
h, _, _ := procLoadIcon.Call(0, uintptr(idiApplication))
icon = windows.Handle(h)
}
var nid notifyIconData var nid notifyIconData
nid.CbSize = uint32(unsafe.Sizeof(nid)) nid.CbSize = uint32(unsafe.Sizeof(nid))
nid.Hwnd = windows.Handle(hwnd) nid.Hwnd = windows.Handle(hwnd)
nid.UID = 1 nid.UID = 1
nid.UFlags = nifMessage | nifIcon | nifTip nid.UFlags = nifMessage | nifIcon | nifTip
nid.UCallbackMessage = wmTray nid.UCallbackMessage = wmTray
nid.HIcon = windows.Handle(icon) nid.HIcon = icon
tip, _ := windows.UTF16FromString(appName) tip, _ := windows.UTF16FromString(appName)
copy(nid.SzTip[:], tip) copy(nid.SzTip[:], tip)
procShellNotify.Call(nimAdd, uintptr(unsafe.Pointer(&nid))) procShellNotify.Call(nimAdd, uintptr(unsafe.Pointer(&nid)))
trayMu.Lock()
trayNID = &nid
trayMu.Unlock()
if h.IsUp != nil {
SetConnectedIcon(h.IsUp())
}
var m msg var m msg
for { for {
@@ -176,6 +224,9 @@ func runTray(appName string, h Hooks) {
procTranslate.Call(uintptr(unsafe.Pointer(&m))) procTranslate.Call(uintptr(unsafe.Pointer(&m)))
procDispatch.Call(uintptr(unsafe.Pointer(&m))) procDispatch.Call(uintptr(unsafe.Pointer(&m)))
} }
trayMu.Lock()
trayNID = nil
trayMu.Unlock()
procShellNotify.Call(nimDelete, uintptr(unsafe.Pointer(&nid))) procShellNotify.Call(nimDelete, uintptr(unsafe.Pointer(&nid)))
} }
+1 -1
View File
@@ -22,7 +22,7 @@ const CurrentVersion = "3.8.2"
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part, // BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build. // macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
const BuildNumber = 6 const BuildNumber = 7
// DefaultManifestURL is the update feed (hosted in the project git repo). // DefaultManifestURL is the update feed (hosted in the project git repo).
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json" const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
+3 -3
View File
@@ -1,16 +1,16 @@
{ {
"version": "3.8.2", "version": "3.8.2",
"notes": "Navis 3.8.2+6: поиск/фильтр серверов и хоткеи (/, Ctrl+F, Space/Enter, Esc); Windows: закрытие окна = скрытие в трей, «Открыть»/Выход из трея; фоновый EnsureCore активного протокола; SSE /api/events для мгновенного статуса + poll 12 с как safety net.", "notes": "Navis 3.8.2+7: Windows — иконка в панели задач и трее цвета морской волны при подключении (как Dock на macOS); при отключении возвращается обычная.",
"platform": "windows-amd64", "platform": "windows-amd64",
"os": "windows", "os": "windows",
"arch": "amd64", "arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"mandatory": false, "mandatory": false,
"platforms": { "platforms": {
"windows-amd64": { "windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "aab618d452f8eddd7cc4400770e53a6a9c456b2bc6093190dd21f1b3fbcd765e", "sha256": "b7902251aeb671946dbfe7ab2f3470dea82cf29cc0cd68e42f6632653ea90605",
"os": "windows", "os": "windows",
"arch": "amd64" "arch": "amd64"
}, },
+4 -4
View File
@@ -4,13 +4,13 @@
"Major": 3, "Major": 3,
"Minor": 8, "Minor": 8,
"Patch": 2, "Patch": 2,
"Build": 6 "Build": 7
}, },
"ProductVersion": { "ProductVersion": {
"Major": 3, "Major": 3,
"Minor": 8, "Minor": 8,
"Patch": 2, "Patch": 2,
"Build": 6 "Build": 7
}, },
"FileFlagsMask": "3f", "FileFlagsMask": "3f",
"FileFlags": "00", "FileFlags": "00",
@@ -21,12 +21,12 @@
"StringFileInfo": { "StringFileInfo": {
"CompanyName": "EvilFox", "CompanyName": "EvilFox",
"FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)", "FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
"FileVersion": "3.8.2.6", "FileVersion": "3.8.2.7",
"InternalName": "Navis", "InternalName": "Navis",
"LegalCopyright": "Copyright (c) EvilFox", "LegalCopyright": "Copyright (c) EvilFox",
"OriginalFilename": "Navis.exe", "OriginalFilename": "Navis.exe",
"ProductName": "Navis", "ProductName": "Navis",
"ProductVersion": "3.8.2.6", "ProductVersion": "3.8.2.7",
"Comments": "Open-source VPN/proxy client. https://evilfox.win/" "Comments": "Open-source VPN/proxy client. https://evilfox.win/"
}, },
"VarFileInfo": { "VarFileInfo": {