diff --git a/README.md b/README.md index 5fb4bc7..f760fd2 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,8 @@ https://evilfox.win/ - при неожиданном падении ядра снимается системный прокси и sentinel; - логи ядра и проверка туннеля (Probe) в UI; connect-on-launch и автопереподключение; - подписка удаляет устаревшие ноды прошлой синхронизации; «Лучший» без soft-ok UDP; -- трей (Windows), CI workflows, REQUIRE_CODESIGN для релиза; Android gated Naive/AWG + sync версии. +- трей (Windows), CI workflows, REQUIRE_CODESIGN для релиза; Android gated Naive/AWG + sync версии; +- 3.8.2+2: иконка в macOS Dock — буква N цвета морской волны при подключении, исходная при отключении. В 3.8.1: - меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера; diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 42d4aba..f0f167f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -13,8 +13,8 @@ android { minSdk = 26 targetSdk = 35 // versionCode = major*1_000_000 + minor*10_000 + patch*100 + build - versionCode = 3080201 - versionName = "3.8.2+1" + versionCode = 3080202 + versionName = "3.8.2+2" vectorDrawables.useSupportLibrary = true ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") diff --git a/assets/navis-icon-connected.png b/assets/navis-icon-connected.png new file mode 100644 index 0000000..4c359d3 Binary files /dev/null and b/assets/navis-icon-connected.png differ diff --git a/build-macos.bat b/build-macos.bat index 5747167..b133e7e 100644 --- a/build-macos.bat +++ b/build-macos.bat @@ -34,11 +34,11 @@ if errorlevel 1 exit /b 1 go build -o "tools\packmac\packmac.exe" .\tools\packmac 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.1 -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.2 -arch arm64 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.1 -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.2 -arch amd64 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.1 -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.2 -arch universal if errorlevel 1 exit /b 1 echo Built Mac GUI + CLI: diff --git a/dist/navis-release/darwin-arm64/Navis b/dist/navis-release/darwin-arm64/Navis index 781c8ac..572fa1a 100755 Binary files a/dist/navis-release/darwin-arm64/Navis and b/dist/navis-release/darwin-arm64/Navis differ diff --git a/dist/navis-release/darwin-arm64/Navis-cli b/dist/navis-release/darwin-arm64/Navis-cli index 616cb37..efe553e 100755 Binary files a/dist/navis-release/darwin-arm64/Navis-cli and b/dist/navis-release/darwin-arm64/Navis-cli differ diff --git a/dist/navis-release/darwin-arm64/Navis.app.zip b/dist/navis-release/darwin-arm64/Navis.app.zip index a0eb275..e078727 100644 Binary files a/dist/navis-release/darwin-arm64/Navis.app.zip and b/dist/navis-release/darwin-arm64/Navis.app.zip differ diff --git a/dist/navis-release/darwin-arm64/Navis.dmg b/dist/navis-release/darwin-arm64/Navis.dmg index e854b8d..7413d21 100644 Binary files a/dist/navis-release/darwin-arm64/Navis.dmg and b/dist/navis-release/darwin-arm64/Navis.dmg differ diff --git a/dist/navis-release/darwin-arm64/Navis.version b/dist/navis-release/darwin-arm64/Navis.version index da40605..50874a3 100644 --- a/dist/navis-release/darwin-arm64/Navis.version +++ b/dist/navis-release/darwin-arm64/Navis.version @@ -1 +1 @@ -3.8.2+1 +3.8.2+2 diff --git a/dist/navis-release/darwin-arm64/VERSION b/dist/navis-release/darwin-arm64/VERSION index b70b377..da7737b 100644 --- a/dist/navis-release/darwin-arm64/VERSION +++ b/dist/navis-release/darwin-arm64/VERSION @@ -1 +1 @@ -3.8.2.1 +3.8.2.2 diff --git a/dist/navis-release/update.json b/dist/navis-release/update.json index ad82b71..97d2ac2 100644 --- a/dist/navis-release/update.json +++ b/dist/navis-release/update.json @@ -1,6 +1,6 @@ { "version": "3.8.2", - "notes": "Navis 3.8.2+1: при падении ядра снимается системный прокси; логи и проверка туннеля в UI; автопереподключение / connect-on-launch; подписка чистит устаревшие ноды; «Лучший» без soft-ok UDP; CI и строгая подпись релиза.", + "notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.", "platform": "windows-amd64", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "c5990d22af050858354eb9a4e91828572eb65d4191e425500e0a08dd8fb3a63a", + "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "7efbaa11b60b7f732724ff509c805f37a34bd374e20dc56e0089bbeed9a82fbd", - "dmg_sha256": "1c8bbfae6abf04920f303e66dfa3bbbf2abaf7f5b50342983c66dedc015fe55f" + "zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", + "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/dist/update.json b/dist/update.json index ad82b71..97d2ac2 100644 --- a/dist/update.json +++ b/dist/update.json @@ -1,6 +1,6 @@ { "version": "3.8.2", - "notes": "Navis 3.8.2+1: при падении ядра снимается системный прокси; логи и проверка туннеля в UI; автопереподключение / connect-on-launch; подписка чистит устаревшие ноды; «Лучший» без soft-ok UDP; CI и строгая подпись релиза.", + "notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.", "platform": "windows-amd64", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "c5990d22af050858354eb9a4e91828572eb65d4191e425500e0a08dd8fb3a63a", + "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "7efbaa11b60b7f732724ff509c805f37a34bd374e20dc56e0089bbeed9a82fbd", - "dmg_sha256": "1c8bbfae6abf04920f303e66dfa3bbbf2abaf7f5b50342983c66dedc015fe55f" + "zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", + "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/internal/apphost/app.go b/internal/apphost/app.go index 47e8668..aa24926 100644 --- a/internal/apphost/app.go +++ b/internal/apphost/app.go @@ -18,6 +18,7 @@ import ( "vpnclient/internal/config" "vpnclient/internal/core" "vpnclient/internal/corebin" + "vpnclient/internal/dockicon" "vpnclient/internal/logbuf" "vpnclient/internal/netcheck" "vpnclient/internal/protocols/awg" @@ -264,18 +265,23 @@ func (a *App) ConnectProfile(name string) error { perr := a.Mgr.Probe(pctx, "") pcancel() if perr == nil { + dockicon.SetConnected(true) return nil } last = fmt.Errorf("туннель не прошёл проверку: %w", perr) a.Mgr.SetLastError(last.Error()) _ = a.Mgr.Disconnect() + dockicon.SetConnected(false) time.Sleep(400 * time.Millisecond) } + dockicon.SetConnected(false) return last } func (a *App) Disconnect() error { - return a.Mgr.Disconnect() + err := a.Mgr.Disconnect() + dockicon.SetConnected(false) + return err } func (a *App) GetLogs() string { @@ -303,9 +309,10 @@ func (a *App) SavePrefs(connectOnLaunch, autoReconnect, systemProxy bool) error }) } -// StartBackground runs reconnect-on-crash and optional connect-on-launch. +// StartBackground runs reconnect-on-crash, optional connect-on-launch, and Dock icon sync. func (a *App) StartBackground() { go a.watchdogLoop() + go a.dockIconLoop() prefs := a.Mgr.Prefs() if prefs.ConnectOnLaunch { go func() { @@ -318,6 +325,21 @@ func (a *App) StartBackground() { } } +func (a *App) dockIconLoop() { + dockicon.SetConnected(false) + var last bool + first := true + for { + time.Sleep(400 * time.Millisecond) + up := a.Mgr.Status().Connected + if first || up != last { + first = false + last = up + dockicon.SetConnected(up) + } + } +} + func (a *App) watchdogLoop() { for { time.Sleep(2 * time.Second) diff --git a/internal/dockicon/dockicon_darwin.go b/internal/dockicon/dockicon_darwin.go new file mode 100644 index 0000000..0c45efd --- /dev/null +++ b/internal/dockicon/dockicon_darwin.go @@ -0,0 +1,106 @@ +//go:build darwin + +package dockicon + +import ( + _ "embed" + "log" + "sync" + "unsafe" + + "github.com/ebitengine/purego" + "github.com/ebitengine/purego/objc" +) + +//go:embed icon_idle.png +var iconIdlePNG []byte + +//go:embed icon_connected.png +var iconConnectedPNG []byte + +var ( + once sync.Once + ready bool + mu sync.Mutex + last *bool + selSet objc.SEL + selPerf objc.SEL + selShared objc.SEL + selData objc.SEL + selAlloc objc.SEL + selInit objc.SEL + selRetain objc.SEL + clsApp objc.Class + clsData objc.Class + clsImage objc.Class +) + +func initAppKit() { + once.Do(func() { + if _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW); err != nil { + log.Printf("dockicon: Foundation: %v", err) + return + } + if _, err := purego.Dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", purego.RTLD_GLOBAL|purego.RTLD_NOW); err != nil { + log.Printf("dockicon: AppKit: %v", err) + return + } + clsApp = objc.GetClass("NSApplication") + clsData = objc.GetClass("NSData") + clsImage = objc.GetClass("NSImage") + if clsApp == 0 || clsData == 0 || clsImage == 0 { + log.Printf("dockicon: missing AppKit classes") + return + } + selShared = objc.RegisterName("sharedApplication") + selSet = objc.RegisterName("setApplicationIconImage:") + selPerf = objc.RegisterName("performSelectorOnMainThread:withObject:waitUntilDone:") + selData = objc.RegisterName("dataWithBytes:length:") + selAlloc = objc.RegisterName("alloc") + selInit = objc.RegisterName("initWithData:") + selRetain = objc.RegisterName("retain") + ready = true + }) +} + +// SetConnected swaps the Dock icon: sea-wave N when connected, original when idle. +func SetConnected(connected bool) { + initAppKit() + if !ready { + return + } + mu.Lock() + if last != nil && *last == connected { + mu.Unlock() + return + } + v := connected + last = &v + mu.Unlock() + + png := iconIdlePNG + if connected { + png = iconConnectedPNG + } + if len(png) == 0 { + return + } + + data := objc.ID(clsData).Send(selData, unsafe.Pointer(&png[0]), len(png)) + if data == 0 { + return + } + img := objc.ID(clsImage).Send(selAlloc) + img = img.Send(selInit, data) + if img == 0 { + return + } + img.Send(selRetain) // keep alive across async main-thread call + + app := objc.ID(clsApp).Send(selShared) + if app == 0 { + return + } + // AppKit must run on the main thread (glaze owns it). + app.Send(selPerf, selSet, img, false) +} diff --git a/internal/dockicon/dockicon_other.go b/internal/dockicon/dockicon_other.go new file mode 100644 index 0000000..304212c --- /dev/null +++ b/internal/dockicon/dockicon_other.go @@ -0,0 +1,6 @@ +//go:build !darwin + +package dockicon + +// SetConnected is a no-op outside macOS (Dock icons are a Darwin feature). +func SetConnected(connected bool) {} diff --git a/internal/dockicon/icon_connected.png b/internal/dockicon/icon_connected.png new file mode 100644 index 0000000..4c359d3 Binary files /dev/null and b/internal/dockicon/icon_connected.png differ diff --git a/internal/dockicon/icon_idle.png b/internal/dockicon/icon_idle.png new file mode 100644 index 0000000..11a504b Binary files /dev/null and b/internal/dockicon/icon_idle.png differ diff --git a/internal/update/update.go b/internal/update/update.go index bec38a1..e0e136f 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -22,7 +22,7 @@ const CurrentVersion = "3.8.2" // BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part, // macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build. -const BuildNumber = 1 +const BuildNumber = 2 // 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" diff --git a/scripts/build-macos-arm64.sh b/scripts/build-macos-arm64.sh index 7c2e35f..db769cf 100755 --- a/scripts/build-macos-arm64.sh +++ b/scripts/build-macos-arm64.sh @@ -77,9 +77,9 @@ h = hashlib.sha256(binp.read_bytes()).hexdigest() zh = hashlib.sha256(zipp.read_bytes()).hexdigest() if zipp.exists() else "" dh = hashlib.sha256(dmgp.read_bytes()).hexdigest() if dmgp.exists() else "" notes = ( - f"Navis {ver}+{build}: при падении ядра снимается системный прокси; логи и проверка туннеля в UI; " - f"автопереподключение / connect-on-launch; подписка чистит устаревшие ноды; «Лучший» без soft-ok UDP; " - f"CI и строгая подпись релиза." + f"Navis {ver}+{build}: иконка Dock — бирюзовая N при подключении; " + f"при падении ядра снимается системный прокси; логи и проверка туннеля; " + f"автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP." ) paths = [ Path('dist/update.json'), diff --git a/server/update.json b/server/update.json index ad82b71..97d2ac2 100644 --- a/server/update.json +++ b/server/update.json @@ -1,6 +1,6 @@ { "version": "3.8.2", - "notes": "Navis 3.8.2+1: при падении ядра снимается системный прокси; логи и проверка туннеля в UI; автопереподключение / connect-on-launch; подписка чистит устаревшие ноды; «Лучший» без soft-ok UDP; CI и строгая подпись релиза.", + "notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.", "platform": "windows-amd64", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "c5990d22af050858354eb9a4e91828572eb65d4191e425500e0a08dd8fb3a63a", + "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "7efbaa11b60b7f732724ff509c805f37a34bd374e20dc56e0089bbeed9a82fbd", - "dmg_sha256": "1c8bbfae6abf04920f303e66dfa3bbbf2abaf7f5b50342983c66dedc015fe55f" + "zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", + "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/versioninfo.json b/versioninfo.json index e28ec39..33b61e0 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -4,13 +4,13 @@ "Major": 3, "Minor": 8, "Patch": 2, - "Build": 1 + "Build": 2 }, "ProductVersion": { "Major": 3, "Minor": 8, "Patch": 2, - "Build": 1 + "Build": 2 }, "FileFlagsMask": "3f", "FileFlags": "00", @@ -21,12 +21,12 @@ "StringFileInfo": { "CompanyName": "EvilFox", "FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)", - "FileVersion": "3.8.2.1", + "FileVersion": "3.8.2.2", "InternalName": "Navis", "LegalCopyright": "Copyright (c) EvilFox", "OriginalFilename": "Navis.exe", "ProductName": "Navis", - "ProductVersion": "3.8.2.1", + "ProductVersion": "3.8.2.2", "Comments": "Open-source VPN/proxy client. https://evilfox.win/" }, "VarFileInfo": {