Merge remote-tracking branch 'remotes/origin/dev' into feature/qt6-libssh-support
This commit is contained in:
commit
4aba34c18b
28 changed files with 594 additions and 264 deletions
|
@ -65,6 +65,7 @@ include_directories(
|
|||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
|
||||
|
@ -92,6 +93,7 @@ if(NOT IOS)
|
|||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||
|
|
|
@ -51,50 +51,10 @@
|
|||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="org.amnezia.vpn.qt.IMPORT_CONFIG" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
|
@ -118,6 +78,56 @@
|
|||
<activity
|
||||
android:name=".qt.CameraActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".qt.ImportConfigActivity"
|
||||
android:exported="true" >
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="AmneziaVPN">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".VPNService"
|
||||
|
|
|
@ -75,7 +75,7 @@ android {
|
|||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qt5AndroidDir - holds the path to qt android files
|
||||
* - qtAndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
|
@ -106,7 +106,7 @@ android {
|
|||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
androidTest.assets.srcDirs += files("${qt5AndroidDir}/schemas".toString())
|
||||
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ android {
|
|||
versionName "2.0.10" // Change to a higher number
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments = [
|
||||
"room.schemaLocation": "${qt5AndroidDir}/schemas".toString()
|
||||
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
|
||||
]
|
||||
}
|
||||
|
||||
|
|
5
client/android/res/layout/activity_import_config.xml
Normal file
5
client/android/res/layout/activity_import_config.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
|
@ -35,7 +35,7 @@ androidExtensions {
|
|||
}
|
||||
|
||||
//def lifecycleVersion = '2.0.0'
|
||||
//def roomVersion = '2.0.0'
|
||||
def roomVersion = "2.4.3"
|
||||
//def preferencexVersion = '1.0.0'
|
||||
dependencies {
|
||||
|
||||
|
@ -45,13 +45,12 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
|
||||
|
||||
|
||||
implementation "androidx.core:core-ktx:1.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
|
||||
implementation "androidx.room:room-runtime:2.2.5" // runtime
|
||||
implementation "androidx.room:room-runtime:$roomVersion" // runtime
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||
implementation "androidx.browser:browser:1.3.0-alpha01"
|
||||
|
@ -65,6 +64,7 @@ dependencies {
|
|||
// api "com.takisoft.preferencex:preferencex:1.0.0"
|
||||
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
|
||||
api 'org.connectbot.jsocks:jsocks:1.0.0'
|
||||
kapt "androidx.room:room-compiler:2.2.5"
|
||||
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:2.4.0"
|
||||
}
|
||||
|
|
|
@ -287,6 +287,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
|||
}
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (value) {
|
||||
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "")
|
||||
mConnectionTime = System.currentTimeMillis()
|
||||
|
@ -886,45 +888,4 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
|||
class CloseableFd(val fd: FileDescriptor) : Closeable {
|
||||
override fun close() = Os.close(fd)
|
||||
}
|
||||
|
||||
fun saveAsFile(configContent: String?, suggestedFileName: String): String {
|
||||
val rootDirPath = cacheDir.absolutePath
|
||||
val rootDir = File(rootDirPath)
|
||||
|
||||
if (!rootDir.exists()) {
|
||||
rootDir.mkdirs()
|
||||
}
|
||||
|
||||
val fileName = if (!TextUtils.isEmpty(suggestedFileName)) suggestedFileName else "amnezia.cfg"
|
||||
|
||||
val file = File(rootDir, fileName)
|
||||
|
||||
try {
|
||||
file.bufferedWriter().use { out -> out.write(configContent) }
|
||||
return file.toString()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
fun shareFile(attachmentFile: String?) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/*"
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
val file = File(attachmentFile)
|
||||
val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
val createChooser = Intent.createChooser(intent, "Config sharing")
|
||||
createChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(createChooser)
|
||||
} catch (e: Exception) {
|
||||
Log.i(tag, e.message.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
const val resumeActivate = 7
|
||||
const val setNotificationText = 8
|
||||
const val setFallBackNotification = 9
|
||||
const val shareConfig = 10
|
||||
const val importConfig = 11
|
||||
}
|
||||
|
||||
|
@ -139,20 +138,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
return true
|
||||
}
|
||||
|
||||
ACTIONS.shareConfig -> {
|
||||
val byteArray = data.createByteArray()
|
||||
val json = byteArray?.let { String(it) }
|
||||
val config = JSONObject(json)
|
||||
val configContent = config.getString("data")
|
||||
val suggestedName = config.getString("suggestedName")
|
||||
|
||||
val filePath = mService.saveAsFile(configContent, suggestedName)
|
||||
Log.i(tag, "save file: $filePath")
|
||||
|
||||
mService.shareFile(filePath)
|
||||
return true
|
||||
}
|
||||
|
||||
ACTIONS.importConfig -> {
|
||||
val buffer = data.readString()
|
||||
|
||||
|
@ -196,7 +181,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
try {
|
||||
mListener?.let {
|
||||
if (it.isBinderAlive) {
|
||||
Log.i(tag, "Dispatching event: binder alive")
|
||||
val data = Parcel.obtain()
|
||||
data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
|
||||
it.transact(code, data, Parcel.obtain(), 0)
|
||||
|
|
|
@ -2,11 +2,11 @@ package org.amnezia.vpn.qt
|
|||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.*
|
||||
|
@ -17,9 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import org.amnezia.vpn.R
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import org.amnezia.vpn.R
|
||||
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
|
@ -78,7 +78,7 @@ class CameraActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
@SuppressLint("UnsafeOptInUsageError", "ClickableViewAccessibility")
|
||||
private fun configureVideoPreview() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
val imageCapture = ImageCapture.Builder().build()
|
||||
|
@ -101,7 +101,20 @@ class CameraActivity : AppCompatActivity() {
|
|||
try {
|
||||
preview.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase)
|
||||
val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase)
|
||||
viewFinder.setOnTouchListener(View.OnTouchListener { view: View, motionEvent: MotionEvent ->
|
||||
when (motionEvent.action) {
|
||||
MotionEvent.ACTION_DOWN -> return@OnTouchListener true
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val factory = viewFinder.meteringPointFactory
|
||||
val point = factory.createPoint(motionEvent.x, motionEvent.y)
|
||||
val action = FocusMeteringAction.Builder(point).build()
|
||||
camera.cameraControl.startFocusAndMetering(action)
|
||||
return@OnTouchListener true
|
||||
}
|
||||
else -> return@OnTouchListener false
|
||||
}
|
||||
})
|
||||
} catch(exc: Exception) {
|
||||
Log.e("WUTT", "Use case binding failed", exc)
|
||||
}
|
||||
|
@ -146,7 +159,6 @@ class CameraActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageProxy.close()
|
||||
}
|
||||
.addOnFailureListener {
|
||||
|
|
140
client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
Normal file
140
client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
Normal file
|
@ -0,0 +1,140 @@
|
|||
package org.amnezia.vpn.qt
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
import java.io.*
|
||||
|
||||
import org.amnezia.vpn.R
|
||||
|
||||
|
||||
const val INTENT_ACTION_IMPORT_CONFIG = "org.amnezia.vpn.qt.IMPORT_CONFIG"
|
||||
|
||||
class ImportConfigActivity : Activity() {
|
||||
|
||||
private val STORAGE_PERMISSION_CODE = 42
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_import_config)
|
||||
startReadConfig(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
startReadConfig(intent)
|
||||
}
|
||||
|
||||
private fun startMainActivity(config: String?) {
|
||||
|
||||
if (config == null || config.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val activityIntent = Intent(applicationContext, VPNActivity::class.java)
|
||||
activityIntent.action = INTENT_ACTION_IMPORT_CONFIG
|
||||
activityIntent.addCategory("android.intent.category.DEFAULT")
|
||||
activityIntent.putExtra("CONFIG", config)
|
||||
|
||||
startActivity(activityIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun startReadConfig(intent: Intent?) {
|
||||
val newIntent = intent
|
||||
val newIntentAction: String = newIntent?.action ?: ""
|
||||
|
||||
if (newIntent != null && newIntentAction == Intent.ACTION_VIEW) {
|
||||
readConfig(newIntent, newIntentAction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readConfig(newIntent: Intent, newIntentAction: String) {
|
||||
if (isReadStorageAllowed()) {
|
||||
val configString = processIntent(newIntent, newIntentAction)
|
||||
startMainActivity(configString)
|
||||
} else {
|
||||
requestStoragePermission()
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestStoragePermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
if (requestCode == STORAGE_PERMISSION_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
val configString = processIntent(intent, intent.action!!)
|
||||
|
||||
if (configString != null) {
|
||||
startMainActivity(configString)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIntent(intent: Intent, action: String): String? {
|
||||
val scheme = intent.scheme
|
||||
|
||||
if (scheme == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (action.compareTo(Intent.ACTION_VIEW) == 0) {
|
||||
val resolver = contentResolver
|
||||
|
||||
if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) {
|
||||
val uri = intent.data
|
||||
val name: String? = getContentName(resolver, uri)
|
||||
|
||||
println("Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name)
|
||||
|
||||
val input = resolver.openInputStream(uri!!)
|
||||
|
||||
return input?.bufferedReader()?.use(BufferedReader::readText)
|
||||
} else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) {
|
||||
val uri = intent.data
|
||||
val name = uri!!.lastPathSegment
|
||||
|
||||
println("File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name)
|
||||
|
||||
val input = resolver.openInputStream(uri)
|
||||
|
||||
return input?.bufferedReader()?.use(BufferedReader::readText)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isReadStorageAllowed(): Boolean {
|
||||
val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
return permissionStatus == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? {
|
||||
val cursor = resolver!!.query(uri!!, null, null, null, null)
|
||||
|
||||
cursor.use {
|
||||
cursor!!.moveToFirst()
|
||||
val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||
return if (nameIndex >= 0) {
|
||||
return cursor.getString(nameIndex)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
package org.amnezia.vpn.qt;
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
|
@ -32,11 +34,22 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
private var configString: String? = null
|
||||
private var vpnServiceBinder: IBinder? = null
|
||||
private var isBound = false
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (value && configString != null) {
|
||||
sendImportConfigCommand()
|
||||
}
|
||||
}
|
||||
|
||||
private val TAG = "VPNActivity"
|
||||
private val STORAGE_PERMISSION_CODE = 42
|
||||
|
||||
private val CAMERA_ACTION_CODE = 101
|
||||
private val CREATE_FILE_ACTION_CODE = 102
|
||||
|
||||
private var tmpFileContentToSave: String = ""
|
||||
|
||||
private val delayedCommands: ArrayList<Pair<Int, String>> = ArrayList()
|
||||
|
||||
companion object {
|
||||
private lateinit var instance: VPNActivity
|
||||
|
@ -56,19 +69,27 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
@JvmStatic fun sendToService(actionCode: Int, body: String) {
|
||||
VPNActivity.getInstance().dispatchParcel(actionCode, body)
|
||||
}
|
||||
|
||||
@JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) {
|
||||
VPNActivity.getInstance().saveFile(fileContent, suggestedName)
|
||||
}
|
||||
|
||||
@JvmStatic fun putTextToClipboard(text: String) {
|
||||
VPNActivity.getInstance().putToClipboard(text)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val newIntent = intent
|
||||
val newIntentAction = newIntent.action
|
||||
|
||||
if (newIntent != null && newIntentAction != null) {
|
||||
configString = processIntent(newIntent, newIntentAction)
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
instance = this
|
||||
|
||||
val newIntent = intent
|
||||
val newIntentAction: String? = newIntent.action
|
||||
|
||||
if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.qt.IMPORT_CONFIG") {
|
||||
configString = newIntent.getStringExtra("CONFIG")
|
||||
}
|
||||
}
|
||||
|
||||
private fun startQrCodeActivity() {
|
||||
|
@ -76,6 +97,18 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
startActivityForResult(intent, CAMERA_ACTION_CODE)
|
||||
}
|
||||
|
||||
private fun saveFile(fileContent: String, suggestedName: String) {
|
||||
tmpFileContentToSave = fileContent
|
||||
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "text/*"
|
||||
putExtra(Intent.EXTRA_TITLE, suggestedName)
|
||||
}
|
||||
|
||||
startActivityForResult(intent, CREATE_FILE_ACTION_CODE)
|
||||
}
|
||||
|
||||
override fun getSystemService(name: String): Any? {
|
||||
return if (Build.VERSION.SDK_INT >= 29 && name == "clipboard") {
|
||||
// QT will always attempt to read the clipboard if content is there.
|
||||
|
@ -98,11 +131,22 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
private fun dispatchParcel(actionCode: Int, body: String) {
|
||||
if (!isBound) {
|
||||
Log.d(TAG, "dispatchParcel: not bound")
|
||||
delayedCommands.add(Pair(actionCode, body))
|
||||
return
|
||||
} else {
|
||||
Log.d(TAG, "dispatchParcel: bound")
|
||||
}
|
||||
|
||||
if (delayedCommands.size > 0) {
|
||||
for (command in delayedCommands) {
|
||||
processCommand(command.first, command.second)
|
||||
}
|
||||
|
||||
delayedCommands.clear()
|
||||
}
|
||||
|
||||
processCommand(actionCode, body)
|
||||
}
|
||||
|
||||
private fun processCommand(actionCode: Int, body: String) {
|
||||
val out: Parcel = Parcel.obtain()
|
||||
out.writeByteArray(body.toByteArray())
|
||||
|
||||
|
@ -118,19 +162,15 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
}
|
||||
|
||||
override fun onNewIntent(newIntent: Intent) {
|
||||
intent = newIntent
|
||||
super.onNewIntent(intent)
|
||||
|
||||
setIntent(newIntent)
|
||||
|
||||
val newIntentAction = newIntent.action
|
||||
|
||||
if (newIntent != null && newIntentAction != null && newIntentAction != Intent.ACTION_MAIN) {
|
||||
if (isReadStorageAllowed()) {
|
||||
configString = processIntent(newIntent, newIntentAction)
|
||||
} else {
|
||||
requestStoragePermission()
|
||||
}
|
||||
}
|
||||
|
||||
super.onNewIntent(intent)
|
||||
if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) {
|
||||
configString = newIntent.getStringExtra("CONFIG")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -141,84 +181,6 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun isReadStorageAllowed(): Boolean {
|
||||
val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
return permissionStatus == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun requestStoragePermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
if (requestCode == STORAGE_PERMISSION_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Storage read permission granted")
|
||||
|
||||
if (configString == null) {
|
||||
configString = processIntent(intent, intent.action!!)
|
||||
}
|
||||
|
||||
if (configString != null) {
|
||||
Log.d(TAG, "not empty")
|
||||
sendImportConfigCommand()
|
||||
} else {
|
||||
Log.d(TAG, "empty")
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIntent(intent: Intent, action: String): String? {
|
||||
val scheme = intent.scheme
|
||||
|
||||
if (scheme == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (action.compareTo(Intent.ACTION_VIEW) == 0) {
|
||||
val resolver = contentResolver
|
||||
|
||||
if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) {
|
||||
val uri = intent.data
|
||||
val name: String? = getContentName(resolver, uri)
|
||||
|
||||
Log.d(TAG, "Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name)
|
||||
|
||||
val input = resolver.openInputStream(uri!!)
|
||||
|
||||
return input?.bufferedReader()?.use(BufferedReader::readText)
|
||||
} else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) {
|
||||
val uri = intent.data
|
||||
val name = uri!!.lastPathSegment
|
||||
|
||||
Log.d(TAG, "File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name)
|
||||
|
||||
val input = resolver.openInputStream(uri)
|
||||
|
||||
return input?.bufferedReader()?.use(BufferedReader::readText)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? {
|
||||
val cursor = resolver!!.query(uri!!, null, null, null, null)
|
||||
|
||||
cursor.use {
|
||||
cursor!!.moveToFirst()
|
||||
val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||
return if (nameIndex >= 0) {
|
||||
return cursor.getString(nameIndex)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendImportConfigCommand() {
|
||||
if (configString != null) {
|
||||
val msg: Parcel = Parcel.obtain()
|
||||
|
@ -234,7 +196,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private var connection: ServiceConnection = object : ServiceConnection {
|
||||
private fun createConnection() = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
|
||||
vpnServiceBinder = binder
|
||||
|
||||
|
@ -260,6 +222,8 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private var connection: ServiceConnection = createConnection()
|
||||
|
||||
private fun registerBinder(): Boolean {
|
||||
val binder = VPNClientBinder()
|
||||
val out: Parcel = Parcel.obtain()
|
||||
|
@ -330,13 +294,38 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
val extra = data?.getStringExtra("result") ?: ""
|
||||
onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra)
|
||||
}
|
||||
|
||||
if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) {
|
||||
data?.data?.also { uri ->
|
||||
alterDocument(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) {
|
||||
onBackPressed()
|
||||
return true
|
||||
private fun alterDocument(uri: Uri) {
|
||||
try {
|
||||
applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
|
||||
FileOutputStream(fd.fileDescriptor).use { fos ->
|
||||
fos.write(tmpFileContentToSave.toByteArray())
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
tmpFileContentToSave = ""
|
||||
}
|
||||
|
||||
private fun putToClipboard(text: String) {
|
||||
this.runOnUiThread {
|
||||
val clipboard = applicationContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager?
|
||||
|
||||
if (clipboard != null) {
|
||||
val clip: ClipData = ClipData.newPlainText("", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "amnezia_application.h"
|
||||
#include "defines.h"
|
||||
#include "migrations.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
|
@ -16,6 +17,9 @@
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Migrations migrationsManager;
|
||||
migrationsManager.doMigrations();
|
||||
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
|
||||
|
|
86
client/migrations.cpp
Normal file
86
client/migrations.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "migrations.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "defines.h"
|
||||
|
||||
Migrations::Migrations(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
QString version(APP_MAJOR_VERSION);
|
||||
|
||||
QStringList versionDigits = version.split(".");
|
||||
|
||||
if (versionDigits.size() >= 3) {
|
||||
currentMajor = versionDigits[0].toInt();
|
||||
currentMinor = versionDigits[1].toInt();
|
||||
currentMicro = versionDigits[2].toInt();
|
||||
}
|
||||
|
||||
if (versionDigits.size() == 4) {
|
||||
currentPatch = versionDigits[3].toInt();
|
||||
}
|
||||
}
|
||||
|
||||
void Migrations::doMigrations()
|
||||
{
|
||||
if (currentMajor == 3) {
|
||||
migrateV3();
|
||||
}
|
||||
}
|
||||
|
||||
void Migrations::migrateV3()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
qDebug() << "Migration to V3 on Android...";
|
||||
|
||||
QString packageName = "org.amnezia.vpn";
|
||||
|
||||
QDir dir(".");
|
||||
QString currentDir = dir.absolutePath();
|
||||
|
||||
int packageNameIndex = currentDir.indexOf(packageName);
|
||||
|
||||
if (packageNameIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString rootLocation = currentDir.left(packageNameIndex + packageName.size());
|
||||
|
||||
if (rootLocation.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString location = rootLocation + "/files/.config/AmneziaVPN.ORG/AmneziaVPN.conf";
|
||||
|
||||
QFile oldConfig(location);
|
||||
|
||||
if (oldConfig.exists()) {
|
||||
QString newConfigPath = rootLocation + "/files/settings";
|
||||
|
||||
QDir newConfigDir(newConfigPath);
|
||||
|
||||
newConfigPath += "/AmneziaVPN.ORG";
|
||||
|
||||
bool mkPathRes = newConfigDir.mkpath(newConfigPath);
|
||||
|
||||
if (!mkPathRes) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile newConfigFile(newConfigPath + "/AmneziaVPN.conf");
|
||||
|
||||
if (!newConfigFile.exists()) {
|
||||
bool cpResult = QFile::copy(oldConfig.fileName(), newConfigFile.fileName());
|
||||
if (cpResult) {
|
||||
oldConfig.remove();
|
||||
QDir oldConfigDir(rootLocation + "/files/.config");
|
||||
oldConfigDir.rmdir("AmneziaVPN.ORG");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
24
client/migrations.h
Normal file
24
client/migrations.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef MIGRATIONS_H
|
||||
#define MIGRATIONS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class Migrations : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Migrations(QObject *parent = nullptr);
|
||||
|
||||
void doMigrations();
|
||||
|
||||
private:
|
||||
void migrateV3();
|
||||
|
||||
private:
|
||||
int currentMajor = 0;
|
||||
int currentMinor = 0;
|
||||
int currentMicro = 0;
|
||||
int currentPatch = 0;
|
||||
};
|
||||
|
||||
#endif // MIGRATIONS_H
|
|
@ -15,6 +15,7 @@
|
|||
#include "private/qandroidextras_p.h"
|
||||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "androidvpnactivity.h"
|
||||
#include "androidutils.h"
|
||||
|
||||
namespace {
|
||||
|
@ -54,6 +55,10 @@ AndroidController::AndroidController() : QObject()
|
|||
|
||||
isConnected = doc.object()["connected"].toBool();
|
||||
|
||||
if (isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
emit initialized(
|
||||
true, isConnected,
|
||||
time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime());
|
||||
|
@ -66,9 +71,11 @@ AndroidController::AndroidController() : QObject()
|
|||
Q_UNUSED(parcelBody);
|
||||
qDebug() << "Transact: connected";
|
||||
|
||||
isConnected = true;
|
||||
if (!isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
emit scheduleStatusCheckSignal();
|
||||
isConnected = true;
|
||||
|
||||
emit connectionStateChanged(VpnProtocol::Connected);
|
||||
}, Qt::QueuedConnection);
|
||||
|
@ -85,7 +92,6 @@ AndroidController::AndroidController() : QObject()
|
|||
connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this,
|
||||
[this](const QString& parcelBody) {
|
||||
qDebug() << "Transact: update";
|
||||
|
||||
auto doc = QJsonDocument::fromJson(parcelBody.toUtf8());
|
||||
|
||||
QString rx = doc.object()["rx_bytes"].toString();
|
||||
|
@ -203,12 +209,7 @@ void AndroidController::setNotificationText(const QString& title,
|
|||
}
|
||||
|
||||
void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) {
|
||||
QJsonObject rootObject;
|
||||
rootObject["data"] = configContent;
|
||||
rootObject["suggestedName"] = suggestedName;
|
||||
QJsonDocument doc(rootObject);
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_SHARE_CONFIG, doc.toJson());
|
||||
AndroidVPNActivity::saveFileAs(configContent, suggestedName);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -248,7 +249,7 @@ void AndroidController::cleanupBackendLogs() {
|
|||
}
|
||||
|
||||
void AndroidController::importConfig(const QString& data){
|
||||
m_startPageLogic->importConnectionFromCode(data);
|
||||
m_startPageLogic->selectConfigFormat(data);
|
||||
}
|
||||
|
||||
const QJsonObject &AndroidController::vpnConfig() const
|
||||
|
@ -266,6 +267,11 @@ void AndroidController::startQrReaderActivity()
|
|||
AndroidVPNActivity::instance()->startQrCodeReader();
|
||||
}
|
||||
|
||||
void AndroidController::copyTextToClipboard(QString text)
|
||||
{
|
||||
AndroidVPNActivity::instance()->copyTextToClipboard(text);
|
||||
}
|
||||
|
||||
void AndroidController::scheduleStatusCheckSlot()
|
||||
{
|
||||
QTimer::singleShot(1000, [this]() {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "protocols/vpnprotocol.h"
|
||||
#include "androidvpnactivity.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
|
@ -44,6 +43,7 @@ public:
|
|||
void setVpnConfig(const QJsonObject &newVpnConfig);
|
||||
|
||||
void startQrReaderActivity();
|
||||
void copyTextToClipboard(QString text);
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(VpnProtocol::VpnConnectionState state);
|
||||
|
|
|
@ -57,6 +57,22 @@ void AndroidVPNActivity::startQrCodeReader()
|
|||
QJniObject::callStaticMethod<void>(CLASSNAME, "startQrCodeReader", "()V");
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) {
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME,
|
||||
"saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(fileContent).object<jstring>(),
|
||||
QJniObject::fromString(suggestedFilename).object<jstring>());
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::copyTextToClipboard(QString text)
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME,
|
||||
"putTextToClipboard", "(Ljava/lang/String;)V",
|
||||
QJniObject::fromString(text).object<jstring>());
|
||||
}
|
||||
|
||||
// static
|
||||
AndroidVPNActivity* AndroidVPNActivity::instance() {
|
||||
if (s_instance == nullptr) {
|
||||
|
@ -70,9 +86,9 @@ AndroidVPNActivity* AndroidVPNActivity::instance() {
|
|||
void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) {
|
||||
int messageType = (int)type;
|
||||
|
||||
QJniEnvironment env;
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME, "sendToService", "(ILjava/lang/String;)V",
|
||||
CLASSNAME,
|
||||
"sendToService", "(ILjava/lang/String;)V",
|
||||
static_cast<int>(messageType),
|
||||
QJniObject::fromString(data).object<jstring>());
|
||||
}
|
||||
|
|
|
@ -75,6 +75,8 @@ public:
|
|||
static void sendToService(ServiceAction type, const QString& data);
|
||||
static void connectService();
|
||||
static void startQrCodeReader();
|
||||
static void saveFileAs(QString fileContent, QString suggestedFilename);
|
||||
static void copyTextToClipboard(QString text);
|
||||
|
||||
signals:
|
||||
void serviceConnected();
|
||||
|
|
|
@ -45,7 +45,6 @@ void AdvancedServerSettingsLogic::onPushButtonClearServerClicked()
|
|||
}
|
||||
|
||||
ErrorCode e = m_serverController->removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex));
|
||||
m_serverController->disconnectFromHost(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex));
|
||||
if (e) {
|
||||
emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" +
|
||||
tr("Error message: ") + errorString(e) + "\n" +
|
||||
|
|
|
@ -13,7 +13,7 @@ GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent):
|
|||
void GeneralSettingsLogic::onUpdatePage()
|
||||
{
|
||||
uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex();
|
||||
set_existsAnyServer(uiLogic()->m_selectedServerIndex >= 0);
|
||||
set_existsAnyServer(m_settings->serversCount() > 0);
|
||||
uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex());
|
||||
|
||||
set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex()));
|
||||
|
|
|
@ -31,6 +31,7 @@ void ServerContainersLogic::onUpdatePage()
|
|||
p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex);
|
||||
|
||||
set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex));
|
||||
uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
emit updatePage();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ void ServerListLogic::onServerListPushbuttonDefaultClicked(int index)
|
|||
void ServerListLogic::onServerListPushbuttonSettingsClicked(int index)
|
||||
{
|
||||
uiLogic()->m_selectedServerIndex = index;
|
||||
uiLogic()->m_installCredentials = m_settings->serverCredentials(index);
|
||||
uiLogic()->goToPage(Page::ServerSettings);
|
||||
}
|
||||
|
||||
|
|
|
@ -181,14 +181,7 @@ void StartPageLogic::onPushButtonImportOpenFile()
|
|||
file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
auto configFormat = checkConfigFormat(QString(data));
|
||||
if (configFormat == ConfigTypes::OpenVpn) {
|
||||
importConnectionFromOpenVpnConfig(QString(data));
|
||||
} else if (configFormat == ConfigTypes::WireGuard) {
|
||||
importConnectionFromWireguardConfig(QString(data));
|
||||
} else {
|
||||
importConnectionFromCode(QString(data));
|
||||
}
|
||||
selectConfigFormat(QString(data));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
@ -198,6 +191,18 @@ void StartPageLogic::startQrDecoder()
|
|||
}
|
||||
#endif
|
||||
|
||||
void StartPageLogic::selectConfigFormat(QString configData)
|
||||
{
|
||||
auto configFormat = checkConfigFormat(configData);
|
||||
if (configFormat == ConfigTypes::OpenVpn) {
|
||||
importConnectionFromOpenVpnConfig(configData);
|
||||
} else if (configFormat == ConfigTypes::WireGuard) {
|
||||
importConnectionFromWireguardConfig(configData);
|
||||
} else {
|
||||
importConnectionFromCode(configData);
|
||||
}
|
||||
}
|
||||
|
||||
bool StartPageLogic::importConnection(const QJsonObject &profile)
|
||||
{
|
||||
ServerCredentials credentials;
|
||||
|
|
|
@ -35,6 +35,8 @@ public:
|
|||
Q_INVOKABLE void startQrDecoder();
|
||||
#endif
|
||||
|
||||
void selectConfigFormat(QString configData);
|
||||
|
||||
bool importConnection(const QJsonObject &profile);
|
||||
bool importConnectionFromCode(QString code);
|
||||
bool importConnectionFromQr(const QByteArray &data);
|
||||
|
|
|
@ -61,8 +61,12 @@ Window {
|
|||
|
||||
function close_page() {
|
||||
if (pageLoader.depth <= 1) {
|
||||
if (GC.isMobile()) {
|
||||
root.close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
pageLoader.currentItem.deactivated()
|
||||
pageLoader.pop()
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ void UiLogic::initalizeUiLogic()
|
|||
connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) {
|
||||
if (connected) {
|
||||
pageLogic<VpnLogic>()->onConnectionStateChanged(VpnProtocol::Connected);
|
||||
m_vpnConnection->restoreConnection();
|
||||
}
|
||||
});
|
||||
if (!AndroidController::instance()->initialize(pageLogic<StartPageLogic>())) {
|
||||
|
@ -234,9 +235,10 @@ void UiLogic::keyPressEvent(Qt::Key key)
|
|||
m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex()));
|
||||
break;
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Back:
|
||||
if (currentPage() == Page::Vpn) break;
|
||||
if (currentPage() == Page::ServerConfiguringProgress) break;
|
||||
case Qt::Key_Back:
|
||||
|
||||
// if (currentPage() == Page::Start && pagesStack.size() < 2) break;
|
||||
// if (currentPage() == Page::Sites &&
|
||||
// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) {
|
||||
|
@ -253,10 +255,16 @@ void UiLogic::keyPressEvent(Qt::Key key)
|
|||
|
||||
void UiLogic::onCloseWindow()
|
||||
{
|
||||
if (m_settings->serversCount() == 0) qApp->quit();
|
||||
else {
|
||||
hide();
|
||||
#ifdef Q_OS_ANDROID
|
||||
qApp->quit();
|
||||
#else
|
||||
if (m_settings->serversCount() == 0)
|
||||
{
|
||||
qApp->quit();
|
||||
} else {
|
||||
emit hide();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString UiLogic::containerName(int container)
|
||||
|
@ -344,8 +352,6 @@ void UiLogic::installServer(QPair<DockerContainer, QJsonObject> &container)
|
|||
errorCode = pageLogic<ServerConfiguringProgressLogic>()->doInstallAction(installAction, pageFunc, progressBarFunc,
|
||||
noButton, waitInfoFunc,
|
||||
busyInfoFunc, cancelButtonFunc);
|
||||
m_serverController->disconnectFromHost(m_installCredentials);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!isServerCreated) {
|
||||
QJsonObject server;
|
||||
|
@ -476,7 +482,11 @@ void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &da
|
|||
|
||||
void UiLogic::copyToClipboard(const QString &text)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidController::instance()->copyTextToClipboard(text);
|
||||
#else
|
||||
qApp->clipboard()->setText(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) {
|
||||
|
@ -532,7 +542,6 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool createNewServer, bool &
|
|||
|
||||
QMap<DockerContainer, QJsonObject> installedContainers;
|
||||
ErrorCode errorCode = m_serverController->getAlreadyInstalledContainers(credentials, installedContainers);
|
||||
m_serverController->disconnectFromHost(credentials);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "../../platforms/android/android_controller.h"
|
||||
#include "protocols/android_vpnprotocol.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
|
@ -353,10 +352,8 @@ void VpnConnection::connectToVpn(int serverIndex,
|
|||
}
|
||||
m_vpnProtocol->prepare();
|
||||
#elif defined Q_OS_ANDROID
|
||||
Proto proto = ContainerProps::defaultProtocol(container);
|
||||
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
||||
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
|
||||
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
|
||||
androidVpnProtocol = createDefaultAndroidVpnProtocol(container);
|
||||
createAndroidConnections(container);
|
||||
|
||||
m_vpnProtocol.reset(androidVpnProtocol);
|
||||
#elif defined Q_OS_IOS
|
||||
|
@ -373,16 +370,52 @@ void VpnConnection::connectToVpn(int serverIndex,
|
|||
m_vpnProtocol.reset(iosVpnProtocol);
|
||||
#endif
|
||||
|
||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState)));
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
|
||||
m_serverController->disconnectFromHost(credentials);
|
||||
createProtocolConnections();
|
||||
|
||||
e = m_vpnProtocol.data()->start();
|
||||
if (e) emit VpnProtocol::Error;
|
||||
}
|
||||
|
||||
void VpnConnection::createProtocolConnections() {
|
||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState)));
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void VpnConnection::restoreConnection() {
|
||||
createAndroidConnections();
|
||||
|
||||
m_vpnProtocol.reset(androidVpnProtocol);
|
||||
|
||||
createProtocolConnections();
|
||||
}
|
||||
|
||||
void VpnConnection::createAndroidConnections()
|
||||
{
|
||||
int serverIndex = m_settings->defaultServerIndex();
|
||||
DockerContainer container = m_settings->defaultContainer(serverIndex);
|
||||
|
||||
createAndroidConnections(container);
|
||||
}
|
||||
|
||||
void VpnConnection::createAndroidConnections(DockerContainer container)
|
||||
{
|
||||
androidVpnProtocol = createDefaultAndroidVpnProtocol(container);
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
|
||||
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
|
||||
}
|
||||
|
||||
AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container)
|
||||
{
|
||||
Proto proto = ContainerProps::defaultProtocol(container);
|
||||
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
||||
|
||||
return androidVpnProtocol;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString VpnConnection::bytesPerSecToText(quint64 bytes)
|
||||
{
|
||||
double mbps = bytes * 8 / 1e6;
|
||||
|
@ -401,8 +434,6 @@ void VpnConnection::disconnectFromVpn()
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
if (!m_vpnProtocol.data()) {
|
||||
emit connectionStateChanged(VpnProtocol::Disconnected);
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
@ -415,11 +446,8 @@ void VpnConnection::disconnectFromVpn()
|
|||
|
||||
VpnProtocol::VpnConnectionState VpnConnection::connectionState()
|
||||
{
|
||||
|
||||
|
||||
if (!m_vpnProtocol) return VpnProtocol::Disconnected;
|
||||
return m_vpnProtocol->connectionState();
|
||||
|
||||
}
|
||||
|
||||
bool VpnConnection::isConnected() const
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
#include "core/ipcclient.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "protocols/android_vpnprotocol.h"
|
||||
#endif
|
||||
|
||||
class VpnConfigurator;
|
||||
class ServerController;
|
||||
|
||||
|
@ -61,6 +65,10 @@ public:
|
|||
const QString &remoteAddress() const;
|
||||
void addSitesRoutes(const QString &gw, Settings::RouteMode mode);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnection();
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void connectToVpn(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig);
|
||||
|
@ -101,6 +109,15 @@ private:
|
|||
#ifdef Q_OS_IOS
|
||||
IOSVpnProtocol * iosVpnProtocol{nullptr};
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidVpnProtocol* androidVpnProtocol = nullptr;
|
||||
|
||||
AndroidVpnProtocol* createDefaultAndroidVpnProtocol(DockerContainer container);
|
||||
void createAndroidConnections();
|
||||
void createAndroidConnections(DockerContainer container);
|
||||
#endif
|
||||
|
||||
void createProtocolConnections();
|
||||
};
|
||||
|
||||
#endif // VPNCONNECTION_H
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue