Compare commits

..

2 Commits

Author SHA1 Message Date
NickVs2015 cff36f797b fix: double call filepicker 2026-06-24 10:13:42 +03:00
NickVs2015 23cb118b1e fix: support filepicker android 9 2026-06-24 10:13:37 +03:00
8 changed files with 240 additions and 190 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.3)
set(AMNEZIAVPN_VERSION 4.9.0.2)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,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 2126)
set(APP_ANDROID_VERSION_CODE 2123)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -42,6 +42,7 @@ import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.File
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -763,7 +764,13 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
val intent = if (!isOnTv()) {
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
.firstOrNull { pkg ->
try { packageManager.getPackageInfo(pkg, 0); true }
catch (_: PackageManager.NameNotFoundException) { false }
}
val intent = if (!isOnTv() && systemPickerPackage != null) {
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
@@ -789,6 +796,7 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*"
}
}
`package` = systemPickerPackage
}
} else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
@@ -800,8 +808,11 @@ class AmneziaActivity : QtActivity() {
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
showNoFileBrowserAlertDialog()
}
val uri = it?.data?.apply {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
val uri = it?.data?.let { u ->
if (u.scheme == "content") {
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
}
u
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
if (uri.isNotEmpty()) {
@@ -841,7 +852,12 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Get fd for $fileName")
return blockingCall(Dispatchers.IO) {
try {
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
val uri = Uri.parse(fileName)
pfd = if (uri.scheme == "file") {
ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)
} else {
contentResolver.openFileDescriptor(uri, "r")
}
pfd?.fd ?: -1
} catch (e: Exception) {
Log.e(TAG, "Failed to get fd: $e")
@@ -1061,13 +1077,11 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun sendTouch(x: Float, y: Float) {
Log.v(TAG, "Send touch: $x, $y")
blockingCall {
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
}
}
}
private fun findQtWindow(view: View): View? {
@@ -1,30 +1,36 @@
package org.amnezia.vpn
import android.Manifest
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.amnezia.vpn.util.Log
import java.io.File
private const val TAG = "TvFilePicker"
private const val READ_STORAGE_REQUEST_CODE = 1001
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
// SAF launcher for Android 10+ where File API is blocked by scoped storage
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
if (activities.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
@@ -32,38 +38,140 @@ class TvFilePicker : ComponentActivity() {
}
return intent
}
}) {
}) { uri ->
setResult(RESULT_OK, Intent().apply {
data = it
data = uri
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
private val directoryStack = ArrayDeque<File>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
getFile()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
launchSaf()
} else {
checkPermissionAndBrowse()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent")
getFile()
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
navigateBack()
}
private fun getFile() {
private fun launchSaf() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
safLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
Log.w(TAG, "No SAF activity found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
finish()
} catch (e: Exception) {
Log.e(TAG, "Failed to get file: $e")
Log.e(TAG, "SAF launch failed: $e")
setResult(RESULT_CANCELED)
finish()
}
}
private fun checkPermissionAndBrowse() {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_STORAGE_REQUEST_CODE)
} else {
showRootDirectory()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == READ_STORAGE_REQUEST_CODE &&
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
showRootDirectory()
} else {
setResult(RESULT_CANCELED)
finish()
}
}
private fun showRootDirectory() {
@Suppress("DEPRECATION")
val primaryExternal = Environment.getExternalStorageDirectory()
val storageDir = File("/storage")
// Pre-seed stack with /storage so Back from primary storage goes there (USB drives etc.)
if (storageDir.exists() && storageDir.canonicalPath != primaryExternal.canonicalPath) {
directoryStack.addLast(storageDir)
}
showDirectory(primaryExternal)
}
private fun navigateBack() {
if (directoryStack.size > 1) {
directoryStack.removeLast()
val parent = directoryStack.removeLast()
showDirectory(parent)
} else {
setResult(RESULT_CANCELED)
finish()
}
}
private fun showDirectory(dir: File) {
directoryStack.addLast(dir)
Log.v(TAG, "Showing directory: ${dir.absolutePath}")
val entries = try {
dir.listFiles()
?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
?: emptyList()
} catch (e: Exception) {
Log.e(TAG, "Failed to list directory: $e")
emptyList()
}
val names = entries.map { if (it.isDirectory) "[${it.name}]" else it.name }.toTypedArray()
val builder = AlertDialog.Builder(this)
.setTitle(dir.absolutePath)
if (entries.isEmpty()) {
builder.setMessage("No files available")
} else {
builder.setItems(names) { dialog, which ->
dialog.dismiss()
val selected = entries[which]
if (selected.isDirectory) {
showDirectory(selected)
} else {
Log.v(TAG, "Selected file: ${selected.absolutePath}")
setResult(RESULT_OK, Intent().apply {
data = Uri.fromFile(selected)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
}
}
if (directoryStack.size > 1) {
builder.setNegativeButton("↑ Back") { dialog, _ ->
dialog.dismiss()
navigateBack()
}
} else {
builder.setNegativeButton(android.R.string.cancel) { _, _ ->
setResult(RESULT_CANCELED)
finish()
}
}
builder.setOnCancelListener {
setResult(RESULT_CANCELED)
finish()
}
builder.show()
}
}
@@ -62,29 +62,12 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr) {
if (config.m_vpnDisabledApps.length() > 0) {
logger.error() << "Split tunnel manager is not initialized";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
}
if (m_splitTunnelManager == nullptr)
return;
}
if (config.m_vpnDisabledApps.length() > 0) {
if (!m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex)) {
logger.error() << "Failed to start split tunnel";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return;
}
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
logger.error() << "Failed to apply split tunnel app exclusions";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
return;
}
if (!m_splitTunnelManager->isRunning()) {
logger.error() << "Split tunnel did not reach running state";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
}
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager->stop();
}
@@ -96,9 +79,7 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
// The Client has sent us a list of disabled apps, but we failed
// to init the the split tunnel driver.
// So let the client know this was not possible
logger.error() << "Split tunnel manager is not initialized";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
return false;
}
return true;
}
@@ -109,20 +90,14 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
}
if (config.m_vpnDisabledApps.length() > 0) {
if (!m_splitTunnelManager->start(m_inetAdapterIndex)) {
logger.error() << "Split tunnel start failed";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return false;
};
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
logger.error() << "Split tunnel app exclusion failed";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
return false;
};
// Now the driver should be running (State == 4)
if (!m_splitTunnelManager->isRunning()) {
logger.error() << "Split tunnel did not reach running state";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return false;
}
return true;
}
@@ -36,8 +36,6 @@
// ID for the Firewall Sublayer
DEFINE_GUID(ST_FW_WINFW_BASELINE_SUBLAYER_KEY, 0xc78056ff, 0x2bc1, 0x4211, 0xaa,
0xdd, 0x7f, 0x35, 0x8d, 0xef, 0x20, 0x2d);
DEFINE_GUID(ST_FW_WINFW_DNS_SUBLAYER_KEY, 0x60090787, 0xcca1, 0x4937, 0xaa,
0xce, 0x51, 0x25, 0x6e, 0xf4, 0x81, 0xf3);
// ID for the Mullvad Split-Tunnel Sublayer Provider
DEFINE_GUID(ST_FW_PROVIDER_KEY, 0xe2c114ee, 0xf32a, 0x4264, 0xa6, 0xcb, 0x3f,
0xa7, 0x99, 0x63, 0x56, 0xd9);
@@ -51,53 +49,6 @@ constexpr uint8_t LOW_WEIGHT = 0;
constexpr uint8_t MED_WEIGHT = 7;
constexpr uint8_t HIGH_WEIGHT = 13;
constexpr uint8_t MAX_WEIGHT = 15;
bool ensureSublayer(HANDLE wfp, const GUID& key, const wchar_t* name,
const wchar_t* description) {
FWPM_SUBLAYER0* maybeLayer = nullptr;
DWORD result = FwpmSubLayerGetByKey0(wfp, &key, &maybeLayer);
if (result == ERROR_SUCCESS) {
logger.debug() << "The Sublayer Already Exists:"
<< QString::fromWCharArray(name);
FwpmFreeMemory0(reinterpret_cast<void**>(&maybeLayer));
return true;
}
if (result != FWP_E_SUBLAYER_NOT_FOUND) {
logger.error() << "FwpmSubLayerGetByKey0 failed. Return value:.\n"
<< result;
return false;
}
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
FWPM_SUBLAYER0 subLayer;
memset(&subLayer, 0, sizeof(subLayer));
subLayer.subLayerKey = key;
subLayer.displayData.name = const_cast<PWSTR>(name);
subLayer.displayData.description = const_cast<PWSTR>(description);
subLayer.weight = 0xFFFF;
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(wfp);
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
return false;
}
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
logger.debug() << "Initialised Sublayer:" << QString::fromWCharArray(name);
return true;
}
} // namespace
WindowsFirewall* WindowsFirewall::create(QObject* parent) {
@@ -165,12 +116,47 @@ bool WindowsFirewall::initSublayer() {
}
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
return ensureSublayer(wfp, ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
L"Amnezia-SplitTunnel-Sublayer",
L"Filters that enforce a good baseline") &&
ensureSublayer(wfp, ST_FW_WINFW_DNS_SUBLAYER_KEY,
L"Amnezia-SplitTunnel-DNS-Sublayer",
L"DNS filters for split tunneling");
// Check if the Layer Already Exists
FWPM_SUBLAYER0* maybeLayer;
result = FwpmSubLayerGetByKey0(wfp, &ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
&maybeLayer);
if (result == ERROR_SUCCESS) {
logger.debug() << "The Sublayer Already Exists!";
FwpmFreeMemory0((void**)&maybeLayer);
return true;
}
// Step 1: Start Transaction
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
// Step 3: Add Sublayer
FWPM_SUBLAYER0 subLayer;
memset(&subLayer, 0, sizeof(subLayer));
subLayer.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
subLayer.displayData.name = (PWSTR)L"Amnezia-SplitTunnel-Sublayer";
subLayer.displayData.description =
(PWSTR)L"Filters that enforce a good baseline";
subLayer.weight = 0xFFFF;
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
return false;
}
// Step 4: Commit!
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
logger.debug() << "Initialised Sublayer";
return true;
}
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
@@ -24,7 +24,6 @@
#include <QFileInfo>
#include <QNetworkInterface>
#include <QScopeGuard>
#include <QUrl>
#pragma region
@@ -149,18 +148,6 @@ ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) {
return pi;
}
QString normalizeExecutablePath(const QString& path) {
QString normalized = path.trimmed();
if (normalized.startsWith("file:", Qt::CaseInsensitive)) {
const QString localPath = QUrl(normalized).toLocalFile();
if (!localPath.isEmpty()) {
normalized = localPath;
}
}
normalized.replace('/', '\\');
return normalized;
}
} // namespace
std::unique_ptr<WindowsSplitTunnel> WindowsSplitTunnel::create(
@@ -300,10 +287,6 @@ bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
auto config = generateAppConfiguration(appPaths);
if (config.empty()) {
logger.error() << "No valid split-tunnel application rules generated";
return false;
}
DWORD bytesReturned;
auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0],
@@ -331,7 +314,7 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr,
0, &bytesReturned, nullptr);
if (!ok) {
logger.error() << "Driver init failed. Error:" << GetLastError();
logger.error() << "Driver init failed";
return false;
}
}
@@ -340,16 +323,11 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
if (getState() == STATE_INITIALIZED) {
logger.debug() << "State is Init, requires process config";
auto config = generateProcessBlob();
if (config.empty()) {
logger.error() << "Process configuration blob is empty";
return false;
}
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_PROCESSES, &config[0],
(DWORD)config.size(), nullptr, 0, &bytesReturned,
nullptr);
if (!ok) {
logger.error() << "Failed to set Process Config. Error:"
<< GetLastError();
logger.error() << "Failed to set Process Config";
return false;
}
logger.debug() << "Set Process Config ok || new State:" << stateString();
@@ -362,16 +340,11 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
logger.debug() << "Driver is ready || new State:" << stateString();
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
if (config.empty()) {
logger.error() << "Network configuration blob is empty. Internet adapter:"
<< inetAdapterIndex << "VPN adapter:" << vpnAdapterIndex;
return false;
}
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
(DWORD)config.size(), nullptr, 0, &bytesReturned,
nullptr);
if (!ok) {
logger.error() << "Failed to set Network Config. Error:" << GetLastError();
logger.error() << "Failed to set Network Config";
return false;
}
logger.debug() << "New Network Config Applied || new State:" << stateString();
@@ -431,22 +404,13 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
size_t cummulated_string_size = 0;
QStringList dosPaths;
for (auto const& path : appPaths) {
const QString normalizedPath = normalizeExecutablePath(path);
auto dosPath = convertPath(normalizedPath);
if (dosPath.isEmpty()) {
logger.error() << "Rejecting split-tunnel app path with empty device "
"conversion:"
<< normalizedPath;
continue;
}
auto dosPath = convertPath(path);
dosPaths.append(dosPath);
cummulated_string_size += dosPath.toStdWString().size() * sizeof(wchar_t);
}
if (dosPaths.isEmpty()) {
return {};
logger.debug() << dosPath;
}
size_t bufferSize = sizeof(CONFIGURATION_HEADER) +
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size()) +
(sizeof(CONFIGURATION_ENTRY) * appPaths.size()) +
cummulated_string_size;
std::vector<uint8_t> outBuffer(bufferSize);
@@ -454,7 +418,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
auto entry = (CONFIGURATION_ENTRY*)(header + 1);
auto stringDest = &outBuffer[0] + sizeof(CONFIGURATION_HEADER) +
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size());
(sizeof(CONFIGURATION_ENTRY) * appPaths.size());
SIZE_T stringOffset = 0;
@@ -473,7 +437,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
stringOffset += stringLength;
}
header->NumEntries = dosPaths.length();
header->NumEntries = appPaths.length();
header->TotalLength = bufferSize;
return outBuffer;
@@ -485,7 +449,9 @@ std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
if (vpnAdapterIndex == 0) {
auto ifaces = QNetworkInterface::allInterfaces();
if (vpnAdapterIndex == 0) {
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
}
// Always the VPN
@@ -554,7 +520,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
auto process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
currentProcess.th32ProcessID);
if (process_handle == nullptr) {
if (process_handle == INVALID_HANDLE_VALUE) {
continue;
}
ProcessInfo info = getProcessInfo(process_handle, currentProcess);
@@ -674,48 +640,23 @@ bool WindowsSplitTunnel::isInstalled() {
}
QString WindowsSplitTunnel::convertPath(const QString& path) {
const QString normalizedPath = normalizeExecutablePath(path);
if (normalizedPath.isEmpty()) {
logger.error() << "Empty executable path for DOS device conversion";
return "";
}
auto parts = normalizedPath.split("\\", Qt::SkipEmptyParts);
if (parts.isEmpty()) {
logger.error() << "Invalid executable path for DOS device conversion:"
<< normalizedPath;
return "";
}
auto parts = path.split("/");
QString driveLetter = parts.takeFirst();
if (!driveLetter.contains(":") || parts.size() == 0) {
// device should contain : for e.g C:
logger.error() << "Invalid executable path for DOS device conversion:"
<< normalizedPath;
return "";
}
QByteArray buffer(2048 * sizeof(wchar_t), 0);
DWORD ok = 0;
DWORD err = ERROR_SUCCESS;
for (int attempt = 0; attempt < 4; ++attempt) {
ok = QueryDosDeviceW(reinterpret_cast<LPCWSTR>(driveLetter.utf16()),
reinterpret_cast<LPWSTR>(buffer.data()),
buffer.size() / sizeof(wchar_t));
if (ok != 0) {
break;
}
err = GetLastError();
if (err != ERROR_INSUFFICIENT_BUFFER) {
WindowsUtils::windowsLog("Err fetching dos path");
logger.error() << "QueryDosDeviceW failed for" << driveLetter
<< "error:" << err;
return "";
}
QByteArray buffer(2048, 0xFFu);
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
(wchar_t*)buffer.data(), buffer.size() / 2);
if (ok == ERROR_INSUFFICIENT_BUFFER) {
buffer.resize(buffer.size() * 2);
buffer.fill(0);
ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(),
buffer.size() / 2);
}
if (ok == 0) {
WindowsUtils::windowsLog("Err fetching dos path");
logger.error() << "QueryDosDeviceW failed after buffer growth for"
<< driveLetter << "error:" << err;
return "";
}
QString deviceName;
+17 -2
View File
@@ -17,6 +17,8 @@ import "../Controls2/TextTypes"
PageType {
id: root
property bool isRestoringBackup: false
Connections {
target: SettingsController
@@ -134,9 +136,14 @@ PageType {
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
enabled: !root.isRestoringBackup
text: qsTr("Restore from backup")
clickedFunc: function() {
if (root.isRestoringBackup) {
return
}
var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
@@ -148,6 +155,10 @@ PageType {
}
function restoreBackup(filePath) {
if (root.isRestoringBackup) {
return
}
var headerText = qsTr("Import settings from a backup file?")
var descriptionText = qsTr("All current settings will be reset");
var yesButtonText = qsTr("Continue")
@@ -157,9 +168,13 @@ PageType {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot restore backup settings during active connection"))
} else {
root.isRestoringBackup = true
PageController.showBusyIndicator(true)
SettingsController.restoreAppConfig(filePath)
PageController.showBusyIndicator(false)
Qt.callLater(function() {
SettingsController.restoreAppConfig(filePath)
PageController.showBusyIndicator(false)
root.isRestoringBackup = false
})
}
}
var noButtonFunction = function() {
@@ -16,6 +16,8 @@ import "../Config"
PageType {
id: root
property bool isRestoringBackup: false
Connections {
target: ImportController
@@ -228,6 +230,8 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: imageSource
enabled: !root.isRestoringBackup
onClicked: { handler() }
Keys.onEnterPressed: this.clicked()
@@ -314,12 +318,19 @@ PageType {
property string imageSource: "qrc:/images/controls/archive-restore.svg"
property bool isVisible: PageController.isStartPageVisible()
property var handler: function() {
if (root.isRestoringBackup) {
return
}
var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)"))
if (filePath !== "") {
root.isRestoringBackup = true
PageController.showBusyIndicator(true)
SettingsController.restoreAppConfig(filePath)
PageController.showBusyIndicator(false)
Qt.callLater(function() {
SettingsController.restoreAppConfig(filePath)
PageController.showBusyIndicator(false)
root.isRestoringBackup = false
})
}
}
}