Compare commits

...

9 Commits

Author SHA1 Message Date
vkamn 6a79947865 Revert "chore: update licnese file (#2376)"
This reverts commit ec3ab2a03c.
2026-03-20 21:04:16 +08:00
vkamn ec3ab2a03c chore: update licnese file (#2376) 2026-03-20 21:04:13 +08:00
yyy-amnezia ddecfcad26 fix: apple platform network switch fix (#2359)
* Apple platform network switch fix

* macos_ne exclusion fixed
2026-03-20 20:51:36 +08:00
NickVs2015 67bd880cdf fix: swap buffers error (#2347) 2026-03-16 13:03:20 +08:00
vkamn 477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015 f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015 9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
8 changed files with 104 additions and 27 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.14.3)
set(AMNEZIAVPN_VERSION 4.8.14.5)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2114)
set(APP_ANDROID_VERSION_CODE 2118)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
+10
View File
@@ -109,6 +109,16 @@ void AmneziaApplication::init()
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
#ifdef Q_OS_ANDROID
QObject::connect(win, &QQuickWindow::sceneGraphError,
[](QQuickWindow::SceneGraphError, const QString &msg) {
qWarning() << "Scene graph error (suppressed):" << msg;
});
// Keep graphics context alive across hide/show cycles to avoid
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
win->show();
}
},
@@ -313,29 +313,27 @@ class AmneziaActivity : QtActivity() {
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT -> {
nativeGamepadKeyEvent(0, keyCode, pressed)
return true
nativeGamepadKeyEvent(0, keyCode, pressed)
return true
}
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
@@ -126,8 +126,13 @@ extension PacketTunnelProvider {
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
switch status {
case .reachableViaWiFi, .reachableViaWWAN:
ovpnLog(.info, message: "Reachability changed, reconnecting OpenVPN session")
self?.ovpnAdapter?.reconnect(afterTimeInterval: 1)
default:
break
}
}
startHandler = completionHandler
@@ -41,10 +41,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
private var isApplyingNetworkChange = false
var splitTunnelType: Int?
var splitTunnelSites: [String]?
@@ -78,14 +81,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard hasMeaningfulChange, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
if proto == .wireguard {
// OpenVPN and WireGuard/AWG handle network changes internally.
// Restarting them here can race their own reconnect logic and break tunnel setup.
if proto == .wireguard || proto == .openvpn {
return
}
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
self.scheduleNetworkChangeHandling(for: proto, path: path)
}
pathMonitor.start(queue: pathMonitorQueue)
@@ -259,9 +261,47 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
guard protoType == .xray else {
updateActiveInterfaceIndex(for: changePath)
completion(nil)
return
}
updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
reasserting = true
xrayLog(.info, message: "Applying network change to xray tunnel")
stopXray { }
startXray { [weak self] error in
self?.reasserting = false
completion(error)
}
}
private func scheduleNetworkChangeHandling(for proto: TunnelProtoType, path: Network.NWPath) {
guard proto == .xray else { return }
pendingNetworkChangeWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
guard let self else { return }
if self.isApplyingNetworkChange {
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
return
}
self.isApplyingNetworkChange = true
DispatchQueue.main.async {
self.handle(networkChange: path) { [weak self] _ in
self?.networkChangeQueue.async {
self?.isApplyingNetworkChange = false
}
}
}
}
pendingNetworkChangeWorkItem = workItem
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
}
}
+3 -2
View File
@@ -8,9 +8,10 @@ Item {
id: root
property StackView stackView: StackView.view
property bool enableTimer: true
onVisibleChanged: {
if (visible) {
if (visible && enableTimer) {
timer.start()
}
}
@@ -24,6 +25,6 @@ Item {
FocusController.setFocusOnDefaultItem()
}
repeat: false // Stop the timer after one trigger
running: true // Start the timer
running: enableTimer // Start the timer
}
}
@@ -13,6 +13,7 @@ import "../Components"
PageType {
id: root
enableTimer: (SettingsController.isOnTv()) ? false : true
ColumnLayout {
id: content
@@ -45,4 +46,22 @@ PageType {
}
}
}
Timer {
interval: 250
running: SettingsController.isOnTv()
repeat: true
onTriggered: {
startButton.forceActiveFocus()
if (startButton.activeFocus) {
running = false
}
}
}
onVisibleChanged: {
if (visible && SettingsController.isOnTv()) {
startButton.forceActiveFocus()
}
}
}
+7 -3
View File
@@ -21,10 +21,14 @@ Window {
function onStateChanged() {
if (Qt.platform.os === "android") {
if (Qt.application.state === Qt.ApplicationActive) {
root.visible = true
refreshTimer.restart()
} else if (Qt.application.state === Qt.ApplicationSuspended ||
Qt.application.state === Qt.ApplicationInactive) {
console.log("QML: Application going to background, state:", Qt.application.state)
} else if (Qt.application.state === Qt.ApplicationSuspended) {
// Hide window to stop the Qt render loop and prevent
// eglSwapBuffers from being called on a lost EGL context.
// NOTE: Do NOT hide on ApplicationInactive — that fires on any
// focus change (IME, notifications) and would blank the screen.
root.visible = false
}
}
}