Release 4.2.7+1: adaptive HiDPI window sizing (work-area DIPs + min size) and responsive CSS so the UI opens usable without stretch. Windows 4.2.7.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
|
||||
versionCode = 4_026_001
|
||||
versionName = "4.2.6+1"
|
||||
versionCode = 4_027_001
|
||||
versionName = "4.2.7+1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="3.9.0.3"
|
||||
version="4.2.7.1"
|
||||
processorArchitecture="*"
|
||||
name="EvilFox.Navis"
|
||||
type="win32"/>
|
||||
|
||||
@@ -76,6 +76,10 @@ func main() {
|
||||
}
|
||||
update.CleanupStaleDownloads()
|
||||
|
||||
// Before any HWND: declare DPI awareness so CreateWindow/SetSize sizes
|
||||
// are physical pixels and we can convert DIPs correctly.
|
||||
enableProcessDPIAware()
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
cfgPath, err := config.LocateConfig()
|
||||
@@ -114,15 +118,17 @@ func main() {
|
||||
|
||||
// Do NOT auto-download cores on startup — silent network+write looks like malware to AV.
|
||||
|
||||
// Sidebar layout (Hiddify-style) needs a wider window than the old column UI.
|
||||
// Size from monitor work area in DIPs, converted to physical pixels for
|
||||
// DPI-aware CreateWindow (fixed 920x680 physical collapses CSS <700px on HiDPI).
|
||||
initW, initH := initialWindowPhysicalSize()
|
||||
w := webview2.NewWithOptions(webview2.WebViewOptions{
|
||||
Debug: false,
|
||||
AutoFocus: true,
|
||||
DataPath: dataPath,
|
||||
WindowOptions: webview2.WindowOptions{
|
||||
Title: "EvilFox",
|
||||
Width: 920,
|
||||
Height: 680,
|
||||
Width: uint(initW),
|
||||
Height: uint(initH),
|
||||
Center: true,
|
||||
IconId: 1,
|
||||
},
|
||||
@@ -137,7 +143,7 @@ func main() {
|
||||
}()
|
||||
|
||||
applyWindowIcon(w.Window())
|
||||
w.SetSize(920, 680, webview2.HintNone)
|
||||
applyAdaptiveWindowSize(w)
|
||||
|
||||
mustBind(w, "getState", a.getState)
|
||||
mustBind(w, "connect", a.connect)
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/jchv/go-webview2"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (DPI_AWARENESS_CONTEXT)-4
|
||||
const dpiAwarenessContextPerMonitorAwareV2 = ^uintptr(3)
|
||||
|
||||
const (
|
||||
spiGetWorkArea = 0x0030
|
||||
monitorDefaultToNearest = 2
|
||||
)
|
||||
|
||||
type monitorInfo struct {
|
||||
CbSize uint32
|
||||
RcMonitor windows.Rect
|
||||
RcWork windows.Rect
|
||||
DwFlags uint32
|
||||
}
|
||||
|
||||
// enableProcessDPIAware opts into per-monitor DPI before any HWND is created.
|
||||
// The embedded app.manifest already declares PerMonitorV2; this is a belt-and-
|
||||
// suspenders call for builds that lose the manifest resource.
|
||||
func enableProcessDPIAware() {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("SetProcessDpiAwarenessContext"); p.Find() == nil {
|
||||
_, _, _ = p.Call(dpiAwarenessContextPerMonitorAwareV2)
|
||||
return
|
||||
}
|
||||
if p := user32.NewProc("SetProcessDPIAware"); p.Find() == nil {
|
||||
_, _, _ = p.Call()
|
||||
}
|
||||
}
|
||||
|
||||
func systemDPI() int {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForSystem"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
return 96
|
||||
}
|
||||
|
||||
func windowDPI(hwnd uintptr) int {
|
||||
if hwnd != 0 {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
if p := user32.NewProc("GetDpiForWindow"); p.Find() == nil {
|
||||
if r, _, _ := p.Call(hwnd); r >= 72 && r <= 480 {
|
||||
return int(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return systemDPI()
|
||||
}
|
||||
|
||||
func dipsToPhysical(dips, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (dips*dpi + 48) / 96
|
||||
}
|
||||
|
||||
func physicalToDIPs(px, dpi int) int {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
return (px*96 + dpi/2) / dpi
|
||||
}
|
||||
|
||||
func primaryWorkAreaPhysical() (w, h int, ok bool) {
|
||||
var r windows.Rect
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
p := user32.NewProc("SystemParametersInfoW")
|
||||
ret, _, _ := p.Call(spiGetWorkArea, 0, uintptr(unsafe.Pointer(&r)), 0)
|
||||
if ret == 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int(r.Right - r.Left), int(r.Bottom - r.Top), true
|
||||
}
|
||||
|
||||
func monitorWorkAreaPhysical(hwnd uintptr) (w, h int, ok bool) {
|
||||
if hwnd == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
fromWin := user32.NewProc("MonitorFromWindow")
|
||||
getInfo := user32.NewProc("GetMonitorInfoW")
|
||||
if fromWin.Find() != nil || getInfo.Find() != nil {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
hmon, _, _ := fromWin.Call(hwnd, monitorDefaultToNearest)
|
||||
if hmon == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
var mi monitorInfo
|
||||
mi.CbSize = uint32(unsafe.Sizeof(mi))
|
||||
ret, _, _ := getInfo.Call(hmon, uintptr(unsafe.Pointer(&mi)))
|
||||
if ret == 0 {
|
||||
return primaryWorkAreaPhysical()
|
||||
}
|
||||
return int(mi.RcWork.Right - mi.RcWork.Left), int(mi.RcWork.Bottom - mi.RcWork.Top), true
|
||||
}
|
||||
|
||||
// computeClientSizeDIPs picks a comfortable client size in DIPs from the
|
||||
// monitor work area (also expressed as physical px + dpi).
|
||||
// Target: ~min(1100, workW*0.72) x min(780, workH*0.78), floored near
|
||||
// 960x680 when the screen allows, always capped with margins.
|
||||
func computeClientSizeDIPs(workWPhys, workHPhys, dpi int) (w, h int) {
|
||||
if dpi <= 0 {
|
||||
dpi = 96
|
||||
}
|
||||
workW := physicalToDIPs(workWPhys, dpi)
|
||||
workH := physicalToDIPs(workHPhys, dpi)
|
||||
if workW < 320 {
|
||||
workW = 320
|
||||
}
|
||||
if workH < 240 {
|
||||
workH = 240
|
||||
}
|
||||
|
||||
const marginX, marginY = 64, 64
|
||||
maxW := workW - marginX
|
||||
maxH := workH - marginY
|
||||
if maxW < 480 {
|
||||
maxW = workW
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = workH
|
||||
}
|
||||
if maxW < 480 {
|
||||
maxW = 480
|
||||
}
|
||||
if maxH < 360 {
|
||||
maxH = 360
|
||||
}
|
||||
|
||||
prefW := int(float64(workW) * 0.72)
|
||||
if prefW > 1100 {
|
||||
prefW = 1100
|
||||
}
|
||||
prefH := int(float64(workH) * 0.78)
|
||||
if prefH > 780 {
|
||||
prefH = 780
|
||||
}
|
||||
|
||||
if prefW < 960 && maxW >= 960 {
|
||||
prefW = 960
|
||||
}
|
||||
if prefH < 680 && maxH >= 680 {
|
||||
prefH = 680
|
||||
}
|
||||
|
||||
if prefW > maxW {
|
||||
prefW = maxW
|
||||
}
|
||||
if prefH > maxH {
|
||||
prefH = maxH
|
||||
}
|
||||
if prefW < 640 && maxW >= 640 {
|
||||
prefW = 640
|
||||
}
|
||||
if prefH < 480 && maxH >= 480 {
|
||||
prefH = 480
|
||||
}
|
||||
return prefW, prefH
|
||||
}
|
||||
|
||||
// initialWindowPhysicalSize estimates CreateWindow size before an HWND exists
|
||||
// (primary work area + system DPI). go-webview2 treats Width/Height as physical
|
||||
// pixels when the process is DPI-aware.
|
||||
func initialWindowPhysicalSize() (w, h int) {
|
||||
dpi := systemDPI()
|
||||
ww, wh, ok := primaryWorkAreaPhysical()
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
return dipsToPhysical(960, dpi), dipsToPhysical(680, dpi)
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
return dipsToPhysical(dipW, dpi), dipsToPhysical(dipH, dpi)
|
||||
}
|
||||
|
||||
// applyAdaptiveWindowSize sets HintMin (~720x520 DIPs) then the preferred
|
||||
// client size from the window's monitor work area, both in physical pixels.
|
||||
func applyAdaptiveWindowSize(w webview2.WebView) {
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
hwnd := uintptr(w.Window())
|
||||
dpi := windowDPI(hwnd)
|
||||
ww, wh, ok := monitorWorkAreaPhysical(hwnd)
|
||||
if !ok || ww <= 0 || wh <= 0 {
|
||||
ww, wh, _ = primaryWorkAreaPhysical()
|
||||
}
|
||||
dipW, dipH := computeClientSizeDIPs(ww, wh, dpi)
|
||||
physW := dipsToPhysical(dipW, dpi)
|
||||
physH := dipsToPhysical(dipH, dpi)
|
||||
|
||||
minW := dipsToPhysical(720, dpi)
|
||||
minH := dipsToPhysical(520, dpi)
|
||||
if minW > physW {
|
||||
minW = physW
|
||||
}
|
||||
if minH > physH {
|
||||
minH = physH
|
||||
}
|
||||
|
||||
w.SetSize(minW, minH, webview2.HintMin)
|
||||
w.SetSize(physW, physH, webview2.HintNone)
|
||||
}
|
||||
Vendored
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "4.2.6",
|
||||
"notes": "4.2.6: fix sidebar RU/EN and theme toggles — applyTheme param shadowed i18n t(), so init threw and click listeners never bound",
|
||||
"version": "4.2.7",
|
||||
"notes": "4.2.7: adaptive window size on HiDPI (DIP work-area sizing + min size); responsive CSS breakpoints so sidebar/power button fit without stretch",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"android": {
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "4.2.6",
|
||||
"notes": "4.2.6: fix sidebar RU/EN and theme toggles — applyTheme param shadowed i18n t(), so init threw and click listeners never bound",
|
||||
"version": "4.2.7",
|
||||
"notes": "4.2.7: adaptive window size on HiDPI (DIP work-area sizing + min size); responsive CSS breakpoints so sidebar/power button fit without stretch",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"android": {
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
}
|
||||
|
||||
+99
-26
@@ -149,7 +149,9 @@
|
||||
[hidden] { display: none !important; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100dvh;
|
||||
font-family: Manrope, sans-serif;
|
||||
color: var(--ink);
|
||||
background: var(--page-bg);
|
||||
@@ -158,8 +160,12 @@
|
||||
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: 216px 1fr;
|
||||
grid-template-columns: 216px minmax(0, 1fr);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ==== Sidebar ==== */
|
||||
@@ -172,6 +178,8 @@
|
||||
backdrop-filter: blur(18px) saturate(1.15);
|
||||
border-right: 1px solid var(--line);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.side .brand-row {
|
||||
display: flex;
|
||||
@@ -329,15 +337,26 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
.pages {
|
||||
flex: 1;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 18px 20px 8px;
|
||||
padding: 18px clamp(12px, 2.2vw, 20px) 8px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
/* Inactive pages must not paint (WebView2 overflow ghosts from hidden siblings). */
|
||||
.page { display: none !important; max-width: 640px; margin: 0 auto; }
|
||||
.page {
|
||||
display: none !important;
|
||||
width: 100%;
|
||||
max-width: min(720px, 100%);
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.page.active { display: block !important; animation: rise .3s var(--ease) both; }
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
@@ -381,12 +400,13 @@
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--hero-bg);
|
||||
padding: 26px 16px 20px;
|
||||
padding: clamp(14px, 2.6vw, 26px) clamp(10px, 2vw, 16px) clamp(12px, 2vw, 20px);
|
||||
margin-bottom: 14px;
|
||||
text-align: center;
|
||||
transition: border-color .25s, box-shadow .25s, background .25s;
|
||||
min-height: 220px;
|
||||
min-height: clamp(160px, 28vh, 220px);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
.hero.on {
|
||||
border-color: rgba(13,138,102,.35);
|
||||
@@ -394,10 +414,11 @@
|
||||
background: var(--hero-bg-on);
|
||||
}
|
||||
.power-btn {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
min-width: 148px;
|
||||
min-height: 148px;
|
||||
--power-size: clamp(104px, 22vw, 148px);
|
||||
width: var(--power-size);
|
||||
height: var(--power-size);
|
||||
min-width: var(--power-size);
|
||||
min-height: var(--power-size);
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
@@ -407,7 +428,7 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
gap: clamp(4px, 1vw, 7px);
|
||||
margin: 4px auto 14px;
|
||||
transition: background .25s, color .25s, transform .12s, box-shadow .25s;
|
||||
box-shadow: 0 14px 34px rgba(8, 145, 178, .3);
|
||||
@@ -417,10 +438,13 @@
|
||||
.power-btn:hover { transform: translateY(-2px); }
|
||||
.power-btn:active { transform: none; }
|
||||
.power-btn:disabled { opacity: .6; cursor: not-allowed; transform: none; }
|
||||
.power-btn svg { width: 40px; height: 40px; }
|
||||
.power-btn svg {
|
||||
width: clamp(28px, 6vw, 40px);
|
||||
height: clamp(28px, 6vw, 40px);
|
||||
}
|
||||
.power-btn .power-label {
|
||||
font-family: Outfit, sans-serif;
|
||||
font-size: .64rem;
|
||||
font-size: clamp(.52rem, 1.5vw, .64rem);
|
||||
font-weight: 800;
|
||||
letter-spacing: .09em;
|
||||
line-height: 1;
|
||||
@@ -526,11 +550,12 @@
|
||||
/* Current server card (home, bottom) */
|
||||
.cur-server {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: clamp(8px, 1.5vw, 12px);
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
max-width: 100%;
|
||||
padding: clamp(10px, 1.8vw, 12px) clamp(10px, 1.8vw, 14px);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--surface-2);
|
||||
@@ -540,6 +565,7 @@
|
||||
text-align: left;
|
||||
margin-bottom: 12px;
|
||||
transition: border-color .15s, background .15s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cur-server:hover { border-color: rgba(8,145,178,.35); }
|
||||
.cur-server .flag {
|
||||
@@ -1217,10 +1243,13 @@
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--row-bg);
|
||||
padding: 12px 14px;
|
||||
padding: clamp(10px, 1.8vw, 12px) clamp(10px, 1.8vw, 14px);
|
||||
margin-bottom: 12px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.traffic-row {
|
||||
display: flex;
|
||||
@@ -1256,7 +1285,10 @@
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
padding: 14px;
|
||||
padding: clamp(10px, 1.8vw, 14px);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -1371,12 +1403,53 @@
|
||||
.modal h2 { font-family: Outfit, sans-serif; margin: 0 0 12px; font-size: 1.2rem; letter-spacing: -.02em; }
|
||||
.modal .actions { margin-top: 12px; grid-template-columns: 1fr 1fr; }
|
||||
|
||||
/* Medium: keep labels, slightly narrower sidebar */
|
||||
@media (max-width: 900px) and (min-width: 701px) {
|
||||
.app { grid-template-columns: 176px minmax(0, 1fr); }
|
||||
.side { padding: 14px 8px 10px; }
|
||||
.nav-btn { gap: 10px; padding: 10px 12px; font-size: .84rem; }
|
||||
.brand { font-size: 1.2rem; }
|
||||
}
|
||||
|
||||
/* Narrow: icon sidebar — main pane stays usable (fluid power/cards) */
|
||||
@media (max-width: 700px) {
|
||||
.app { grid-template-columns: 64px 1fr; }
|
||||
.side .brand-row { justify-content: center; padding-left: 0; padding-right: 0; }
|
||||
.app { grid-template-columns: 64px minmax(0, 1fr); }
|
||||
.side {
|
||||
padding: 12px 6px 10px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.side .brand-row { justify-content: center; padding-left: 0; padding-right: 0; padding-bottom: 10px; }
|
||||
.side .brand, .side .badge-ver, .nav-btn .nav-label, .side-foot .ver { display: none; }
|
||||
.nav-btn { justify-content: center; padding: 12px 0; }
|
||||
.side-foot { justify-content: center; }
|
||||
.side-foot {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
.side-foot-controls {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.lang-seg {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
width: 40px;
|
||||
}
|
||||
.lang-seg button {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
padding: 7px 0;
|
||||
}
|
||||
.theme-toggle { width: 40px; height: 40px; }
|
||||
.pages { padding: 12px 10px 8px; }
|
||||
.page { max-width: 100%; }
|
||||
.hero { min-height: 0; }
|
||||
.power-btn { --power-size: clamp(96px, 28vw, 132px); }
|
||||
.update-banner { margin-left: 10px; margin-right: 10px; }
|
||||
details.panel .grid2 { grid-template-columns: 1fr; }
|
||||
.tools { grid-template-columns: 1fr; }
|
||||
}
|
||||
@@ -1392,23 +1465,23 @@
|
||||
<span class="badge-ver" id="badgeVer" hidden></span>
|
||||
</div>
|
||||
|
||||
<button class="nav-btn active" type="button" data-page="guard">
|
||||
<button class="nav-btn active" type="button" data-page="guard" title="Защита" data-i18n-title="nav_guard">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7.5 3v5.2c0 4.6-3.1 8-7.5 9.8-4.4-1.8-7.5-5.2-7.5-9.8V6z"/><path d="M9 12l2.2 2.2L15.4 10"/></svg>
|
||||
<span class="nav-label" data-i18n="nav_guard">Защита</span>
|
||||
</button>
|
||||
<button class="nav-btn" type="button" data-page="profiles">
|
||||
<button class="nav-btn" type="button" data-page="profiles" title="Мои серверы" data-i18n-title="nav_profiles">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="6.5" rx="2"/><rect x="3.5" y="13.5" width="17" height="6.5" rx="2"/><path d="M7 7.2h.01M7 16.7h.01"/></svg>
|
||||
<span class="nav-label" data-i18n="nav_profiles">Мои серверы</span>
|
||||
</button>
|
||||
<button class="nav-btn" type="button" data-page="settings">
|
||||
<button class="nav-btn" type="button" data-page="settings" title="Настройки" data-i18n-title="nav_settings">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 13.5a7.6 7.6 0 0 0 0-3l2-1.5-2-3.4-2.3 1a7.7 7.7 0 0 0-2.6-1.5L14.2 2h-4l-.3 2.6a7.7 7.7 0 0 0-2.6 1.5l-2.3-1-2 3.4 2 1.5a7.6 7.6 0 0 0 0 3l-2 1.5 2 3.4 2.3-1a7.7 7.7 0 0 0 2.6 1.5l.3 2.6h4l.3-2.6a7.7 7.7 0 0 0 2.6-1.5l2.3 1 2-3.4z"/></svg>
|
||||
<span class="nav-label" data-i18n="nav_settings">Настройки</span>
|
||||
</button>
|
||||
<button class="nav-btn" type="button" data-page="logs">
|
||||
<button class="nav-btn" type="button" data-page="logs" title="Статистика" data-i18n-title="nav_stats">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 20V10M12 20V4M19 20v-7"/></svg>
|
||||
<span class="nav-label" data-i18n="nav_stats">Статистика</span>
|
||||
</button>
|
||||
<button class="nav-btn" type="button" data-page="about">
|
||||
<button class="nav-btn" type="button" data-page="about" title="Помощь" data-i18n-title="nav_help">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.6 9.2a2.5 2.5 0 1 1 3.5 2.3c-.8.4-1.1 1-1.1 1.8"/><path d="M12 16.6h.01"/></svg>
|
||||
<span class="nav-label" data-i18n="nav_help">Помощь</span>
|
||||
</button>
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
// CurrentVersion is the product/semver used for update eligibility (feed "version").
|
||||
// Keep major.minor.patch only — no build suffix here.
|
||||
const CurrentVersion = "4.2.6"
|
||||
// Keep major.minor.patch only — no build suffix here.
|
||||
const CurrentVersion = "4.2.7"
|
||||
|
||||
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
|
||||
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
|
||||
@@ -75,7 +75,7 @@ type Status struct {
|
||||
Checking bool `json:"checking,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ManifestURL string `json:"manifest_url"`
|
||||
StoreManaged bool `json:"store_managed,omitempty"` // MSIX / Microsoft Store — no in-app updates
|
||||
StoreManaged bool `json:"store_managed,omitempty"` // MSIX / Microsoft Store — no in-app updates
|
||||
}
|
||||
|
||||
// PlatformKey is "GOOS-GOARCH", e.g. windows-amd64, darwin-arm64.
|
||||
@@ -86,7 +86,7 @@ func PlatformKey() string {
|
||||
// AssetFor returns download URL + checksum for the given platform key.
|
||||
//
|
||||
// Rules (critical):
|
||||
// - Only the asset for this OS/arch is considered — never another OS.
|
||||
// - Only the asset for this OS/arch is considered — never another OS.
|
||||
// - If the feed has a newer version but no matching platform asset, ok=false
|
||||
// (caller must treat Available as false).
|
||||
// - Darwin may fall back to "darwin-universal" when the exact arch key is absent.
|
||||
@@ -196,7 +196,7 @@ func Check(ctx context.Context, manifestURL string) (Status, error) {
|
||||
if IsStorePackaged() {
|
||||
st.StoreManaged = true
|
||||
st.Available = false
|
||||
st.Notes = "Обновления через Microsoft Store"
|
||||
st.Notes = "Обновления через Microsoft Store"
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func Check(ctx context.Context, manifestURL string) (Status, error) {
|
||||
url, sha, ok := m.AssetFor(key)
|
||||
if !ok {
|
||||
// Higher feed version for another OS must NOT count as available here.
|
||||
st.Error = fmt.Sprintf("нет сборки для %s", key)
|
||||
st.Error = fmt.Sprintf("нет сборки для %s", key)
|
||||
st.Available = false
|
||||
return st, nil
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func productParts(v string) [3]int {
|
||||
|
||||
func splitVer(v string) []int {
|
||||
v = strings.TrimPrefix(strings.TrimSpace(v), "v")
|
||||
// Strip build metadata: 2.7.2+1 / 2.7.2-beta → compare product only.
|
||||
// Strip build metadata: 2.7.2+1 / 2.7.2-beta в†’ compare product only.
|
||||
if i := strings.IndexAny(v, "+-"); i >= 0 {
|
||||
v = v[:i]
|
||||
}
|
||||
@@ -353,10 +353,10 @@ func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha,
|
||||
if st.Error != "" {
|
||||
return "", "", "", "", "", fmt.Errorf("%s", st.Error)
|
||||
}
|
||||
return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest)
|
||||
return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest)
|
||||
}
|
||||
if !allowedDownloadURL(st.URL) {
|
||||
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.de4ima.uk, git.evilfox.cc или files.de4ima.uk")
|
||||
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.de4ima.uk, git.evilfox.cc или files.de4ima.uk")
|
||||
}
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<Identity
|
||||
Name="EvilFox.EvilFox"
|
||||
Publisher="CN=EvilFox"
|
||||
Version="4.2.6.1"
|
||||
Version="4.2.7.1"
|
||||
ProcessorArchitecture="x64" />
|
||||
|
||||
<Properties>
|
||||
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "4.2.6",
|
||||
"notes": "4.2.6: fix sidebar RU/EN and theme toggles — applyTheme param shadowed i18n t(), so init threw and click listeners never bound",
|
||||
"version": "4.2.7",
|
||||
"notes": "4.2.7: adaptive window size on HiDPI (DIP work-area sizing + min size); responsive CSS breakpoints so sidebar/power button fit without stretch",
|
||||
"platform": "windows-amd64",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"mandatory": false,
|
||||
"platforms": {
|
||||
"android": {
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"windows-amd64": {
|
||||
"url": "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/navis-release/windows/EvilFox.exe",
|
||||
"sha256": "843fd62f7fe0ae09261322fb90d7f281a48472b78e6a9c543b6a9637fe1cc83e",
|
||||
"sha256": "dd867e27845fafa9417e1658919fc6613947db3c4677c1f35a270cdc72b3c762",
|
||||
"os": "windows",
|
||||
"arch": "amd64"
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"FixedFileInfo": {
|
||||
"FileVersion": { "Major": 4, "Minor": 2, "Patch": 6, "Build": 1 },
|
||||
"ProductVersion": { "Major": 4, "Minor": 2, "Patch": 6, "Build": 1 },
|
||||
"FileVersion": { "Major": 4, "Minor": 2, "Patch": 7, "Build": 1 },
|
||||
"ProductVersion": { "Major": 4, "Minor": 2, "Patch": 7, "Build": 1 },
|
||||
"FileFlagsMask": "3f",
|
||||
"FileFlags": "00",
|
||||
"FileOS": "40004",
|
||||
@@ -11,12 +11,12 @@
|
||||
"StringFileInfo": {
|
||||
"CompanyName": "EvilFox",
|
||||
"FileDescription": "EvilFox — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
|
||||
"FileVersion": "4.2.6.1",
|
||||
"FileVersion": "4.2.7.1",
|
||||
"InternalName": "EvilFox",
|
||||
"LegalCopyright": "Copyright (c) EvilFox",
|
||||
"OriginalFilename": "EvilFox.exe",
|
||||
"ProductName": "EvilFox",
|
||||
"ProductVersion": "4.2.6.1",
|
||||
"ProductVersion": "4.2.7.1",
|
||||
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
||||
Reference in New Issue
Block a user