mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a79947865 | |||
| ec3ab2a03c | |||
| ddecfcad26 | |||
| 67bd880cdf | |||
| 477afb9d85 | |||
| f969fcdbb8 | |||
| b0ca16d861 | |||
| 9963359948 | |||
| ca639d293d |
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.14.3)
|
set(AMNEZIAVPN_VERSION 4.8.14.5)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
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")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -109,6 +109,16 @@ void AmneziaApplication::init()
|
|||||||
// install filter on main window
|
// install filter on main window
|
||||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||||
win->installEventFilter(this);
|
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();
|
win->show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -313,29 +313,27 @@ class AmneziaActivity : QtActivity() {
|
|||||||
KeyEvent.KEYCODE_BUTTON_Y,
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
KeyEvent.KEYCODE_BUTTON_START,
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
||||||
nativeGamepadKeyEvent(0, keyCode, pressed)
|
nativeGamepadKeyEvent(0, keyCode, pressed)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
||||||
val synthetic = KeyEvent(
|
val synthetic = KeyEvent(
|
||||||
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
||||||
event.repeatCount, event.metaState, -1, event.scanCode,
|
event.repeatCount, event.metaState, -1, event.scanCode,
|
||||||
event.flags, InputDevice.SOURCE_KEYBOARD
|
event.flags, InputDevice.SOURCE_KEYBOARD
|
||||||
)
|
)
|
||||||
return super.dispatchKeyEvent(synthetic)
|
return super.dispatchKeyEvent(synthetic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
|
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|||||||
@@ -126,8 +126,13 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
guard status == .reachableViaWiFi else { return }
|
switch status {
|
||||||
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
|
case .reachableViaWiFi, .reachableViaWWAN:
|
||||||
|
ovpnLog(.info, message: "Reachability changed, reconnecting OpenVPN session")
|
||||||
|
self?.ovpnAdapter?.reconnect(afterTimeInterval: 1)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
|
|||||||
@@ -41,10 +41,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
var ovpnAdapter: OpenVPNAdapter?
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
||||||
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
||||||
|
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
|
||||||
private let pathMonitor = NWPathMonitor()
|
private let pathMonitor = NWPathMonitor()
|
||||||
private var didReceiveInitialPathUpdate = false
|
private var didReceiveInitialPathUpdate = false
|
||||||
private var currentPath: Network.NWPath?
|
private var currentPath: Network.NWPath?
|
||||||
private var currentPathSignature: String?
|
private var currentPathSignature: String?
|
||||||
|
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
|
||||||
|
private var isApplyingNetworkChange = false
|
||||||
|
|
||||||
var splitTunnelType: Int?
|
var splitTunnelType: Int?
|
||||||
var splitTunnelSites: [String]?
|
var splitTunnelSites: [String]?
|
||||||
@@ -78,14 +81,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
||||||
|
|
||||||
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
|
// OpenVPN and WireGuard/AWG handle network changes internally.
|
||||||
if proto == .wireguard {
|
// Restarting them here can race their own reconnect logic and break tunnel setup.
|
||||||
|
if proto == .wireguard || proto == .openvpn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.scheduleNetworkChangeHandling(for: proto, path: path)
|
||||||
self.handle(networkChange: path) { _ in }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pathMonitor.start(queue: pathMonitorQueue)
|
pathMonitor.start(queue: pathMonitorQueue)
|
||||||
|
|
||||||
@@ -259,9 +261,47 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
|
guard protoType == .xray else {
|
||||||
|
updateActiveInterfaceIndex(for: changePath)
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updateActiveInterfaceIndex(for: changePath)
|
updateActiveInterfaceIndex(for: changePath)
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
reasserting = true
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property StackView stackView: StackView.view
|
property StackView stackView: StackView.view
|
||||||
|
property bool enableTimer: true
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible && enableTimer) {
|
||||||
timer.start()
|
timer.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,6 @@ Item {
|
|||||||
FocusController.setFocusOnDefaultItem()
|
FocusController.setFocusOnDefaultItem()
|
||||||
}
|
}
|
||||||
repeat: false // Stop the timer after one trigger
|
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 {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
enableTimer: (SettingsController.isOnTv()) ? false : true
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,14 @@ Window {
|
|||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
if (Qt.platform.os === "android") {
|
if (Qt.platform.os === "android") {
|
||||||
if (Qt.application.state === Qt.ApplicationActive) {
|
if (Qt.application.state === Qt.ApplicationActive) {
|
||||||
|
root.visible = true
|
||||||
refreshTimer.restart()
|
refreshTimer.restart()
|
||||||
} else if (Qt.application.state === Qt.ApplicationSuspended ||
|
} else if (Qt.application.state === Qt.ApplicationSuspended) {
|
||||||
Qt.application.state === Qt.ApplicationInactive) {
|
// Hide window to stop the Qt render loop and prevent
|
||||||
console.log("QML: Application going to background, state:", Qt.application.state)
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user