mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 06:24:35 +03:00
v1.1.0: unified Connect button, proxy mode, app splitting, Persian UI, MIPS build (#41)
Major feature release across Android + desktop. Six items the user asked for, verified end-to-end on the emulator. Android ------- * Unified Connect/Disconnect button. Single large button swaps between green "Connect" (when the service is down) and red "Disconnect" (when it's up). Tracks the real service state via a new process-wide `VpnState` singleton flipped from the service's startEverything() / teardown() — not optimistic, the button only reports what the service actually did. * Connection mode dropdown (issue #37). Two options: VPN (TUN) — routes every app — and Proxy only — user configures per-app via Wi-Fi proxy to 127.0.0.1:8080 (HTTP) / :1081 (SOCKS5). PROXY_ONLY skips VpnService.prepare() entirely (no OS VPN grant prompt) and the service just keeps the foreground listeners up. Default is VPN_TUN so existing behaviour is preserved for users who upgrade without looking at the dropdown. * App splitting. In VPN_TUN mode you can pick All / Only selected / All except selected, with a picker dialog that lists installed user-visible apps (LazyColumn with search, "show system apps" toggle, multi-select checkboxes). ONLY calls `Builder.addAllowedApplication()` for each chosen package; EXCEPT calls `addDisallowedApplication()` additive to the mandatory self-exclude. Requires QUERY_ALL_PACKAGES — added to the manifest along with a `<queries>` launcher-intent filter so the picker rows can render app labels, not just package strings. * Persian/English UI toggle with RTL. Top-bar TextButton cycles AUTO → FA → EN → AUTO. Persian strings live in `res/values-fa/strings.xml`; English in `res/values/strings.xml`. `AppCompatDelegate.setApplicationLocales()` is used as the persistence layer (plus `AppLocalesMetadataHolderService` meta and `locales_config.xml` for the per-app-language OS entry on API 33+). MainActivity overrides `attachBaseContext` to wrap the context with the right locale at the earliest possible moment — otherwise a saved preference wouldn't apply until the SECOND process after toggling. RTL swaps automatically because Persian is script="Arab" in Android's locale database. * Collapsible How-to-use card. The big instruction block that used to dominate the bottom of the screen now lives inside a CollapsibleSection that starts expanded for a fresh install (empty deployment URLs / auth_key) and collapsed otherwise. * Update check auto-fires on first composition, silent-on-up-to-date, snackbar-only-if-available. Still surfaces via the version badge tap for manual checks. * MhrvVpnService teardown guard was kept from v1.0.2 — `AtomicBoolean` makes the second caller a no-op, which is the SIGSEGV fix for "tap Stop, app closes" from before. Stress-tested under rapid Connect/Disconnect cycles. Desktop ------- * Fix: Advanced section silently resetting on every Save. `ConfigWire` was missing `fetch_ips_from_api` / `max_ips_to_scan` / `scan_batch_size` / `google_ip_validation` — every persist dropped them, every reload fell back to the serde defaults, user saw their Advanced toggles reset. Added the fields to the wire struct (issue surfaced by the user as "Advanced resets after reopening the app"). * Windows renderer fallback (issue #28). `eframe` is now built with BOTH `glow` (OpenGL 2+) and `wgpu` (DX12/Vulkan/Metal); runtime defaults to glow for compat but honours `MHRV_RENDERER=wgpu` for boxes that crash with "egui_glow requires opengl 2.0+" — old Windows hardware, RDP sessions, VMs without GPU acceleration. `run.bat` auto-retries the UI with `MHRV_RENDERER=wgpu` if the first launch exits non-zero, so users don't need to know about the flag. CI -- * Added OpenWRT mipsel-softfloat build target (issue #26). MT7621 routers specifically need soft-float because the CPU has no FPU; a hard-float binary segfaults on first fp op. Built via `messense/rust-musl-cross:mipsel-musl-softfloat` docker image + nightly Rust with `-Z build-std` (mipsel is Rust tier 3 since 1.72, no pre-built std). Marked `continue-on-error: true` — the tier-3 target occasionally regresses and we'd rather ship the rest of the release than block on MT7621 support. Signature / versioning ---------------------- * versionCode 110, versionName 1.1.0; Cargo bumped to 1.1.0. * Release APK signed with the committed `release.jks` (same as v1.0.2), so v1.0.2 → v1.1.0 upgrades install in-place without the uninstall-first dance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
be698f4928
commit
28be8f67d5
@@ -110,6 +110,21 @@ class MhrvVpnService : VpnService() {
|
||||
|
||||
val socks5Port = cfg.socks5Port ?: (cfg.listenPort + 1)
|
||||
|
||||
// PROXY_ONLY mode: the user wants just the 127.0.0.1 HTTP + SOCKS5
|
||||
// listeners up, with no VpnService / no TUN. Typical reasons:
|
||||
// another VPN app already owns the system VPN slot, the user
|
||||
// wants per-app opt-in via Wi-Fi proxy settings, or the device
|
||||
// is a sandboxed/rooted setup where VpnService is unwelcome.
|
||||
// We still run as a foreground service (required for the native
|
||||
// listener thread to survive backgrounding), we just skip every
|
||||
// VPN-specific step below. Issue #37.
|
||||
if (cfg.connectionMode == ConnectionMode.PROXY_ONLY) {
|
||||
Log.i(TAG, "PROXY_ONLY mode: listeners up, skipping VpnService/TUN")
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
|
||||
VpnState.setRunning(true)
|
||||
return
|
||||
}
|
||||
|
||||
// 2) Establish the TUN. Key Builder calls:
|
||||
// - addAddress(10.0.0.2/32): our local IP inside the tunnel.
|
||||
// - addRoute(0.0.0.0/0): capture ALL IPv4 traffic. IPv6 isn't added,
|
||||
@@ -135,6 +150,47 @@ class MhrvVpnService : VpnService() {
|
||||
Log.w(TAG, "addDisallowedApplication failed: ${e.message}")
|
||||
}
|
||||
|
||||
// Apply user-chosen app splitting on top of the mandatory
|
||||
// self-exclusion above.
|
||||
//
|
||||
// ALL — no extra restriction; every other app routes through
|
||||
// us. Matches pre-splitting behaviour.
|
||||
// ONLY — allow-list. addAllowedApplication() for each chosen
|
||||
// package; anything missing from the list bypasses the
|
||||
// VPN on the OS-native route. Note that ONLY and the
|
||||
// mandatory self-exclude are mutually exclusive in the
|
||||
// VpnService API, so if the user also put us in the
|
||||
// allow-list we skip the self-exclude (it's already
|
||||
// implicit via "we're not in the list").
|
||||
// EXCEPT — deny-list. addDisallowedApplication() for each chosen
|
||||
// package, additive with our self-exclude.
|
||||
//
|
||||
// Packages that are not installed (leftover selections from a
|
||||
// previous device) throw PackageManager.NameNotFoundException —
|
||||
// we log and skip rather than aborting the whole VPN start.
|
||||
when (cfg.splitMode) {
|
||||
SplitMode.ALL -> { /* no-op */ }
|
||||
SplitMode.ONLY -> {
|
||||
if (cfg.splitApps.isEmpty()) {
|
||||
Log.w(TAG, "ONLY mode with empty splitApps list — no app would get the VPN; falling back to ALL")
|
||||
} else {
|
||||
for (pkg in cfg.splitApps) {
|
||||
try { builder.addAllowedApplication(pkg) } catch (e: Throwable) {
|
||||
Log.w(TAG, "addAllowedApplication($pkg) failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SplitMode.EXCEPT -> {
|
||||
for (pkg in cfg.splitApps) {
|
||||
if (pkg == packageName) continue // already self-excluded above
|
||||
try { builder.addDisallowedApplication(pkg) } catch (e: Throwable) {
|
||||
Log.w(TAG, "addDisallowedApplication($pkg) failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val parcelFd = try {
|
||||
builder.establish()
|
||||
} catch (t: Throwable) {
|
||||
@@ -177,6 +233,12 @@ class MhrvVpnService : VpnService() {
|
||||
}, "tun2proxy").apply { start() }
|
||||
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
|
||||
|
||||
// Publish "running" state for the UI's Connect/Disconnect button
|
||||
// to observe. Only flipped true once everything above succeeded —
|
||||
// if we'd flipped it earlier the button would light up green for
|
||||
// a failed-to-establish run.
|
||||
VpnState.setRunning(true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,6 +317,9 @@ class MhrvVpnService : VpnService() {
|
||||
Log.e(TAG, "Native.stopProxy threw: ${t.message}", t)
|
||||
}
|
||||
}
|
||||
// Flip UI state last — the button reverts to Connect only after
|
||||
// the native-side cleanup actually happened, not optimistically.
|
||||
VpnState.setRunning(false)
|
||||
Log.i(TAG, "teardown: done")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user