diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml
index ede6353ef..b3c655917 100644
--- a/flutter/android/app/src/main/AndroidManifest.xml
+++ b/flutter/android/app/src/main/AndroidManifest.xml
@@ -11,22 +11,25 @@
-
+
+
+ android:supportsRtl="true">
+ android:enabled="true"
+ android:exported="true">
+
+
@@ -53,8 +56,6 @@
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
-
-
@@ -62,6 +63,11 @@
+
+
-
+
\ No newline at end of file
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
index 328701567..71bbba754 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
@@ -1,21 +1,45 @@
package com.carriez.flutter_hbb
+import android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.util.Log
import android.widget.Toast
+import com.hjq.permissions.XXPermissions
+import io.flutter.embedding.android.FlutterActivity
+
+const val DEBUG_BOOT_COMPLETED = "com.carriez.flutter_hbb.DEBUG_BOOT_COMPLETED"
class BootReceiver : BroadcastReceiver() {
+ private val logTag = "tagBootReceiver"
+
override fun onReceive(context: Context, intent: Intent) {
- if ("android.intent.action.BOOT_COMPLETED" == intent.action){
- val it = Intent(context,MainService::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Log.d(logTag, "onReceive ${intent.action}")
+
+ if (Intent.ACTION_BOOT_COMPLETED == intent.action || DEBUG_BOOT_COMPLETED == intent.action) {
+ // check SharedPreferences config
+ val prefs = context.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
+ if (!prefs.getBoolean(KEY_START_ON_BOOT_OPT, false)) {
+ Log.d(logTag, "KEY_START_ON_BOOT_OPT is false")
+ return
}
- Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show();
+ // check pre-permission
+ if (!XXPermissions.isGranted(context, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, SYSTEM_ALERT_WINDOW)){
+ Log.d(logTag, "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS or SYSTEM_ALERT_WINDOW is not granted")
+ return
+ }
+
+ val it = Intent(context, MainService::class.java).apply {
+ action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
+ putExtra(EXT_INIT_FROM_BOOT, true)
+ }
+ Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(it)
- }else{
+ } else {
context.startService(it)
}
}
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
index fd340f7ed..52a5ff75e 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
@@ -7,35 +7,29 @@ package com.carriez.flutter_hbb
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/
-import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
-import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.IBinder
-import android.provider.Settings
import android.util.Log
import android.view.WindowManager
-import androidx.annotation.RequiresApi
+import com.hjq.permissions.XXPermissions
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
-const val MEDIA_REQUEST_CODE = 42
class MainActivity : FlutterActivity() {
companion object {
- lateinit var flutterMethodChannel: MethodChannel
+ var flutterMethodChannel: MethodChannel? = null
}
private val channelTag = "mChannel"
private val logTag = "mMainActivity"
- private var mediaProjectionResultIntent: Intent? = null
private var mainService: MainService? = null
- @RequiresApi(Build.VERSION_CODES.M)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
if (MainService.isReady) {
@@ -46,169 +40,32 @@ class MainActivity : FlutterActivity() {
flutterMethodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
channelTag
- ).apply {
- // make sure result is set, otherwise flutter will await forever
- setMethodCallHandler { call, result ->
- when (call.method) {
- "init_service" -> {
- Intent(activity, MainService::class.java).also {
- bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
- }
- if (MainService.isReady) {
- result.success(false)
- return@setMethodCallHandler
- }
- getMediaProjection()
- result.success(true)
- }
- "start_capture" -> {
- mainService?.let {
- result.success(it.startCapture())
- } ?: let {
- result.success(false)
- }
- }
- "stop_service" -> {
- Log.d(logTag, "Stop service")
- mainService?.let {
- it.destroy()
- result.success(true)
- } ?: let {
- result.success(false)
- }
- }
- "check_permission" -> {
- if (call.arguments is String) {
- result.success(checkPermission(context, call.arguments as String))
- } else {
- result.success(false)
- }
- }
- "request_permission" -> {
- if (call.arguments is String) {
- requestPermission(context, call.arguments as String)
- result.success(true)
- } else {
- result.success(false)
- }
- }
- "check_video_permission" -> {
- mainService?.let {
- result.success(it.checkMediaPermission())
- } ?: let {
- result.success(false)
- }
- }
- "check_service" -> {
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "input", "value" to InputService.isOpen.toString())
- )
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "media", "value" to MainService.isReady.toString())
- )
- result.success(true)
- }
- "init_input" -> {
- initInput()
- result.success(true)
- }
- "stop_input" -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- InputService.ctx?.disableSelf()
- }
- InputService.ctx = null
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "input", "value" to InputService.isOpen.toString())
- )
- result.success(true)
- }
- "cancel_notification" -> {
- try {
- val id = call.arguments as Int
- mainService?.cancelNotification(id)
- } finally {
- result.success(true)
- }
- }
- "enable_soft_keyboard" -> {
- // https://blog.csdn.net/hanye2020/article/details/105553780
- try {
- if (call.arguments as Boolean) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
- }
- } finally {
- result.success(true)
- }
- }
- else -> {
- result.error("-1", "No such method", null)
- }
- }
- }
- }
- }
-
- private fun getMediaProjection() {
- val mMediaProjectionManager =
- getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
- val mIntent = mMediaProjectionManager.createScreenCaptureIntent()
- startActivityForResult(mIntent, MEDIA_REQUEST_CODE)
- }
-
- private fun initService() {
- if (mediaProjectionResultIntent == null) {
- Log.w(logTag, "initService fail,mediaProjectionResultIntent is null")
- return
- }
- Log.d(logTag, "Init service")
- val serviceIntent = Intent(this, MainService::class.java)
- serviceIntent.action = INIT_SERVICE
- serviceIntent.putExtra(EXTRA_MP_DATA, mediaProjectionResultIntent)
-
- launchMainService(serviceIntent)
- }
-
- private fun launchMainService(intent: Intent) {
- // TEST api < O
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(intent)
- } else {
- startService(intent)
- }
- }
-
- private fun initInput() {
- val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
- if (intent.resolveActivity(packageManager) != null) {
- startActivity(intent)
- }
+ )
+ initFlutterChannel(flutterMethodChannel!!)
}
override fun onResume() {
super.onResume()
val inputPer = InputService.isOpen
activity.runOnUiThread {
- flutterMethodChannel.invokeMethod(
+ flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to inputPer.toString())
)
}
}
+ private fun requestMediaProjection() {
+ val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
+ action = ACT_REQUEST_MEDIA_PROJECTION
+ }
+ startActivityForResult(intent, REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION)
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == MEDIA_REQUEST_CODE) {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mediaProjectionResultIntent = data
- initService()
- } else {
- flutterMethodChannel.invokeMethod("on_media_projection_canceled", null)
- }
+ if (requestCode == REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION && resultCode == RES_FAILED) {
+ flutterMethodChannel?.invokeMethod("on_media_projection_canceled", null)
}
}
@@ -232,4 +89,138 @@ class MainActivity : FlutterActivity() {
mainService = null
}
}
+
+ private fun initFlutterChannel(flutterMethodChannel: MethodChannel) {
+ flutterMethodChannel.setMethodCallHandler { call, result ->
+ // make sure result will be invoked, otherwise flutter will await forever
+ when (call.method) {
+ "init_service" -> {
+ Intent(activity, MainService::class.java).also {
+ bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+ if (MainService.isReady) {
+ result.success(false)
+ return@setMethodCallHandler
+ }
+ requestMediaProjection()
+ result.success(true)
+ }
+ "start_capture" -> {
+ mainService?.let {
+ result.success(it.startCapture())
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "stop_service" -> {
+ Log.d(logTag, "Stop service")
+ mainService?.let {
+ it.destroy()
+ result.success(true)
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "check_permission" -> {
+ if (call.arguments is String) {
+ result.success(XXPermissions.isGranted(context, call.arguments as String))
+ } else {
+ result.success(false)
+ }
+ }
+ "request_permission" -> {
+ if (call.arguments is String) {
+ requestPermission(context, call.arguments as String)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ START_ACTION -> {
+ if (call.arguments is String) {
+ startAction(context, call.arguments as String)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ "check_video_permission" -> {
+ mainService?.let {
+ result.success(it.checkMediaPermission())
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "check_service" -> {
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "input", "value" to InputService.isOpen.toString())
+ )
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "media", "value" to MainService.isReady.toString())
+ )
+ result.success(true)
+ }
+ "stop_input" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ InputService.ctx?.disableSelf()
+ }
+ InputService.ctx = null
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "input", "value" to InputService.isOpen.toString())
+ )
+ result.success(true)
+ }
+ "cancel_notification" -> {
+ if (call.arguments is Int) {
+ val id = call.arguments as Int
+ mainService?.cancelNotification(id)
+ } else {
+ result.success(true)
+ }
+ }
+ "enable_soft_keyboard" -> {
+ // https://blog.csdn.net/hanye2020/article/details/105553780
+ if (call.arguments as Boolean) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+ }
+ result.success(true)
+
+ }
+ GET_START_ON_BOOT_OPT -> {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
+ }
+ SET_START_ON_BOOT_OPT -> {
+ if (call.arguments is Boolean) {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ val edit = prefs.edit()
+ edit.putBoolean(KEY_START_ON_BOOT_OPT, call.arguments as Boolean)
+ edit.apply()
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ SYNC_APP_DIR_CONFIG_PATH -> {
+ if (call.arguments is String) {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ val edit = prefs.edit()
+ edit.putString(KEY_APP_DIR_CONFIG_PATH, call.arguments as String)
+ edit.apply()
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ else -> {
+ result.error("-1", "No such method", null)
+ }
+ }
+ }
+ }
}
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
index cf8e12e92..fa7440c8d 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
@@ -35,6 +35,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
+import io.flutter.embedding.android.FlutterActivity
import java.util.concurrent.Executors
import kotlin.concurrent.thread
import org.json.JSONException
@@ -43,10 +44,6 @@ import java.nio.ByteBuffer
import kotlin.math.max
import kotlin.math.min
-const val EXTRA_MP_DATA = "mp_intent"
-const val INIT_SERVICE = "init_service"
-const val ACTION_LOGIN_REQ_NOTIFY = "ACTION_LOGIN_REQ_NOTIFY"
-const val EXTRA_LOGIN_REQ_NOTIFY = "EXTRA_LOGIN_REQ_NOTIFY"
const val DEFAULT_NOTIFY_TITLE = "RustDesk"
const val DEFAULT_NOTIFY_TEXT = "Service is running"
@@ -147,7 +144,11 @@ class MainService : Service() {
// jvm call rust
private external fun init(ctx: Context)
- private external fun startServer()
+
+ /// When app start on boot, app_dir will not be passed from flutter
+ /// so pass a app_dir here to rust server
+ private external fun startServer(app_dir: String)
+ private external fun startService()
private external fun onVideoFrameUpdate(buf: ByteBuffer)
private external fun onAudioFrameUpdate(buf: ByteBuffer)
private external fun translateLocale(localeName: String, input: String): String
@@ -195,6 +196,7 @@ class MainService : Service() {
override fun onCreate() {
super.onCreate()
+ Log.d(logTag,"MainService onCreate")
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
@@ -202,7 +204,13 @@ class MainService : Service() {
}
updateScreenInfo(resources.configuration.orientation)
initNotification()
- startServer()
+
+ // keep the config dir same with flutter
+ val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
+ val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
+ startServer(configPath)
+
+ createForegroundNotification()
}
override fun onDestroy() {
@@ -277,22 +285,30 @@ class MainService : Service() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- Log.d("whichService", "this service:${Thread.currentThread()}")
+ Log.d("whichService", "this service: ${Thread.currentThread()}")
super.onStartCommand(intent, flags, startId)
- if (intent?.action == INIT_SERVICE) {
- Log.d(logTag, "service starting:${startId}:${Thread.currentThread()}")
+ if (intent?.action == ACT_INIT_MEDIA_PROJECTION_AND_SERVICE) {
createForegroundNotification()
- val mMediaProjectionManager =
+
+ if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
+ startService()
+ }
+ Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
+ val mediaProjectionManager =
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
- intent.getParcelableExtra(EXTRA_MP_DATA)?.let {
+
+ intent.getParcelableExtra(EXT_MEDIA_PROJECTION_RES_INTENT)?.let {
mediaProjection =
- mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
+ mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
checkMediaPermission()
init(this)
_isReady = true
+ } ?: let {
+ Log.d(logTag, "getParcelableExtra intent null, invoke requestMediaProjection")
+ requestMediaProjection()
}
}
- return START_NOT_STICKY // don't use sticky (auto restart),the new service (from auto restart) will lose control
+ return START_NOT_STICKY // don't use sticky (auto restart), the new service (from auto restart) will lose control
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -300,6 +316,14 @@ class MainService : Service() {
updateScreenInfo(newConfig.orientation)
}
+ private fun requestMediaProjection() {
+ val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
+ action = ACT_REQUEST_MEDIA_PROJECTION
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ startActivity(intent)
+ }
+
@SuppressLint("WrongConstant")
private fun createSurface(): Surface? {
return if (useVP9) {
@@ -400,13 +424,13 @@ class MainService : Service() {
fun checkMediaPermission(): Boolean {
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "media", "value" to isReady.toString())
)
}
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
@@ -653,8 +677,8 @@ class MainService : Service() {
@SuppressLint("UnspecifiedImmutableFlag")
private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent {
val intent = Intent(this, MainService::class.java).apply {
- action = ACTION_LOGIN_REQ_NOTIFY
- putExtra(EXTRA_LOGIN_REQ_NOTIFY, res)
+ action = ACT_LOGIN_REQ_NOTIFY
+ putExtra(EXT_LOGIN_REQ_NOTIFY, res)
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE)
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt
new file mode 100644
index 000000000..3beb7ec6b
--- /dev/null
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt
@@ -0,0 +1,54 @@
+package com.carriez.flutter_hbb
+
+import android.app.Activity
+import android.content.Intent
+import android.media.projection.MediaProjectionManager
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+
+class PermissionRequestTransparentActivity: Activity() {
+ private val logTag = "permissionRequest"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.d(logTag, "onCreate PermissionRequestTransparentActivity: intent.action: ${intent.action}")
+
+ when (intent.action) {
+ ACT_REQUEST_MEDIA_PROJECTION -> {
+ val mediaProjectionManager =
+ getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
+ val intent = mediaProjectionManager.createScreenCaptureIntent()
+ startActivityForResult(intent, REQ_REQUEST_MEDIA_PROJECTION)
+ }
+ else -> finish()
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == REQ_REQUEST_MEDIA_PROJECTION) {
+ if (resultCode == RESULT_OK && data != null) {
+ launchService(data)
+ } else {
+ setResult(RES_FAILED)
+ }
+ }
+
+ finish()
+ }
+
+ private fun launchService(mediaProjectionResultIntent: Intent) {
+ Log.d(logTag, "Launch MainService")
+ val serviceIntent = Intent(this, MainService::class.java)
+ serviceIntent.action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
+ serviceIntent.putExtra(EXT_MEDIA_PROJECTION_RES_INTENT, mediaProjectionResultIntent)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(serviceIntent)
+ } else {
+ startService(serviceIntent)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
index 4bf244a06..f8ef07fd1 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
@@ -1,5 +1,6 @@
package com.carriez.flutter_hbb
+import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
@@ -12,8 +13,8 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
-import android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
-import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+import android.provider.Settings
+import android.provider.Settings.*
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService
import com.hjq.permissions.Permission
@@ -22,6 +23,31 @@ import java.nio.ByteBuffer
import java.util.*
+// intent action, extra
+const val ACT_REQUEST_MEDIA_PROJECTION = "REQUEST_MEDIA_PROJECTION"
+const val ACT_INIT_MEDIA_PROJECTION_AND_SERVICE = "INIT_MEDIA_PROJECTION_AND_SERVICE"
+const val ACT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
+const val EXT_INIT_FROM_BOOT = "EXT_INIT_FROM_BOOT"
+const val EXT_MEDIA_PROJECTION_RES_INTENT = "MEDIA_PROJECTION_RES_INTENT"
+const val EXT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
+
+// Activity requestCode
+const val REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION = 101
+const val REQ_REQUEST_MEDIA_PROJECTION = 201
+
+// Activity responseCode
+const val RES_FAILED = -100
+
+// Flutter channel
+const val START_ACTION = "start_action"
+const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt"
+const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt"
+const val SYNC_APP_DIR_CONFIG_PATH = "sync_app_dir"
+
+const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES"
+const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT"
+const val KEY_APP_DIR_CONFIG_PATH = "KEY_APP_DIR_CONFIG_PATH"
+
@SuppressLint("ConstantLocale")
val LOCAL_NAME = Locale.getDefault().toString()
val SCREEN_INFO = Info(0, 0, 1, 200)
@@ -30,61 +56,13 @@ data class Info(
var width: Int, var height: Int, var scale: Int, var dpi: Int
)
-@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-fun testVP9Support(): Boolean {
- return true
- val res = MediaCodecList(MediaCodecList.ALL_CODECS)
- .findEncoderForFormat(
- MediaFormat.createVideoFormat(
- MediaFormat.MIMETYPE_VIDEO_VP9,
- SCREEN_INFO.width,
- SCREEN_INFO.width
- )
- )
- return res != null
-}
-
-@RequiresApi(Build.VERSION_CODES.M)
fun requestPermission(context: Context, type: String) {
- val permission = when (type) {
- "ignore_battery_optimizations" -> {
- try {
- context.startActivity(Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
- data = Uri.parse("package:" + context.packageName)
- })
- } catch (e:Exception) {
- e.printStackTrace()
- }
- return
- }
- "application_details_settings" -> {
- try {
- context.startActivity(Intent().apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- action = "android.settings.APPLICATION_DETAILS_SETTINGS"
- data = Uri.parse("package:" + context.packageName)
- })
- } catch (e:Exception) {
- e.printStackTrace()
- }
- return
- }
- "audio" -> {
- Permission.RECORD_AUDIO
- }
- "file" -> {
- Permission.MANAGE_EXTERNAL_STORAGE
- }
- else -> {
- return
- }
- }
XXPermissions.with(context)
- .permission(permission)
+ .permission(type)
.request { _, all ->
if (all) {
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_android_permission_result",
mapOf("type" to type, "result" to all)
)
@@ -93,24 +71,18 @@ fun requestPermission(context: Context, type: String) {
}
}
-@RequiresApi(Build.VERSION_CODES.M)
-fun checkPermission(context: Context, type: String): Boolean {
- val permission = when (type) {
- "ignore_battery_optimizations" -> {
- val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
- return pw.isIgnoringBatteryOptimizations(context.packageName)
- }
- "audio" -> {
- Permission.RECORD_AUDIO
- }
- "file" -> {
- Permission.MANAGE_EXTERNAL_STORAGE
- }
- else -> {
- return false
- }
+fun startAction(context: Context, action: String) {
+ try {
+ context.startActivity(Intent(action).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // don't pass package name when launch ACTION_ACCESSIBILITY_SETTINGS
+ if (ACTION_ACCESSIBILITY_SETTINGS != action) {
+ data = Uri.parse("package:" + context.packageName)
+ }
+ })
+ } catch (e: Exception) {
+ e.printStackTrace()
}
- return XXPermissions.isGranted(context, permission)
}
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
diff --git a/flutter/android/app/src/main/res/values/styles.xml b/flutter/android/app/src/main/res/values/styles.xml
index d74aa35c2..146267c91 100644
--- a/flutter/android/app/src/main/res/values/styles.xml
+++ b/flutter/android/app/src/main/res/values/styles.xml
@@ -15,4 +15,12 @@
+
diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart
index 023fe7511..e0306a3a7 100644
--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -910,21 +910,14 @@ class AccessibilityListener extends StatelessWidget {
}
}
-class PermissionManager {
+class AndroidPermissionManager {
static Completer? _completer;
static Timer? _timer;
static var _current = "";
- static final permissions = [
- "audio",
- "file",
- "ignore_battery_optimizations",
- "application_details_settings"
- ];
-
static bool isWaitingFile() {
if (_completer != null) {
- return !_completer!.isCompleted && _current == "file";
+ return !_completer!.isCompleted && _current == kManageExternalStorage;
}
return false;
}
@@ -933,31 +926,33 @@ class PermissionManager {
if (isDesktop) {
return Future.value(true);
}
- if (!permissions.contains(type)) {
- return Future.error("Wrong permission!$type");
- }
return gFFI.invokeMethod("check_permission", type);
}
+ // startActivity goto Android Setting's page to request permission manually by user
+ static void startAction(String action) {
+ gFFI.invokeMethod(AndroidChannel.kStartAction, action);
+ }
+
+ /// We use XXPermissions to request permissions,
+ /// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java
static Future request(String type) {
if (isDesktop) {
return Future.value(true);
}
- if (!permissions.contains(type)) {
- return Future.error("Wrong permission!$type");
- }
gFFI.invokeMethod("request_permission", type);
- if (type == "ignore_battery_optimizations") {
- return Future.value(false);
+
+ // clear last task
+ if (_completer?.isCompleted == false) {
+ _completer?.complete(false);
}
+ _timer?.cancel();
+
_current = type;
_completer = Completer();
- gFFI.invokeMethod("request_permission", type);
- // timeout
- _timer?.cancel();
- _timer = Timer(Duration(seconds: 60), () {
+ _timer = Timer(Duration(seconds: 120), () {
if (_completer == null) return;
if (!_completer!.isCompleted) {
_completer!.complete(false);
@@ -1487,8 +1482,8 @@ connect(BuildContext context, String id,
}
} else {
if (isFileTransfer) {
- if (!await PermissionManager.check("file")) {
- if (!await PermissionManager.request("file")) {
+ if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
+ if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
return;
}
}
diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart
index 3c414cd85..95e4d17e7 100644
--- a/flutter/lib/consts.dart
+++ b/flutter/lib/consts.dart
@@ -59,11 +59,12 @@ const double kDesktopFileTransferMaximumWidth = 300;
const double kDesktopFileTransferRowHeight = 30.0;
const double kDesktopFileTransferHeaderHeight = 25.0;
-EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && Platform.isLinux
- ? stateGlobal.fullscreen || stateGlobal.maximize
- ? EdgeInsets.zero
- : EdgeInsets.all(5.0)
- : EdgeInsets.zero;
+EdgeInsets get kDragToResizeAreaPadding =>
+ !kUseCompatibleUiMode && Platform.isLinux
+ ? stateGlobal.fullscreen || stateGlobal.maximize
+ ? EdgeInsets.zero
+ : EdgeInsets.all(5.0)
+ : EdgeInsets.zero;
// https://en.wikipedia.org/wiki/Non-breaking_space
const int $nbsp = 0x00A0;
@@ -136,6 +137,25 @@ const kRemoteAudioDualWay = 'dual-way';
const kIgnoreDpi = true;
+/// Android constants
+const kActionApplicationDetailsSettings =
+ "android.settings.APPLICATION_DETAILS_SETTINGS";
+const kActionAccessibilitySettings = "android.settings.ACCESSIBILITY_SETTINGS";
+
+const kRecordAudio = "android.permission.RECORD_AUDIO";
+const kManageExternalStorage = "android.permission.MANAGE_EXTERNAL_STORAGE";
+const kRequestIgnoreBatteryOptimizations =
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+const kSystemAlertWindow = "android.permission.SYSTEM_ALERT_WINDOW";
+
+/// Android channel invoke type key
+class AndroidChannel {
+ static final kStartAction = "start_action";
+ static final kGetStartOnBootOpt = "get_start_on_boot_opt";
+ static final kSetStartOnBootOpt = "set_start_on_boot_opt";
+ static final kSyncAppDirConfigPath = "sync_app_dir";
+}
+
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
/// see [LogicalKeyboardKey.keyLabel]
const Map logicalKeyMap = {
diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart
index baf7193b3..6d3f863e7 100644
--- a/flutter/lib/main.dart
+++ b/flutter/lib/main.dart
@@ -153,6 +153,7 @@ void runMainApp(bool startService) async {
void runMobileApp() async {
await initEnv(kAppTypeMain);
if (isAndroid) androidChannelInit();
+ platformFFI.syncAndroidServiceAppDirConfigPath();
runApp(App());
}
diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart
index abccdf683..bab2911f1 100644
--- a/flutter/lib/mobile/pages/server_page.dart
+++ b/flutter/lib/mobile/pages/server_page.dart
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
+import '../../consts.dart';
import '../../models/platform_model.dart';
import '../../models/server_model.dart';
import 'home_page.dart';
@@ -40,14 +41,14 @@ class ServerPage extends StatefulWidget implements PageShape {
value: "setTemporaryPasswordLength",
enabled:
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
- child: Text(translate("Set temporary password length")),
+ child: Text(translate("One-time password length")),
),
const PopupMenuDivider(),
PopupMenuItem(
padding: const EdgeInsets.symmetric(horizontal: 0.0),
value: kUseTemporaryPassword,
child: ListTile(
- title: Text(translate("Use temporary password")),
+ title: Text(translate("Use one-time password")),
trailing: Icon(
Icons.check,
color: gFFI.serverModel.verificationMethod ==
@@ -150,10 +151,11 @@ class _ServerPageState extends State {
}
void checkService() async {
- gFFI.invokeMethod("check_service"); // jvm
- // for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page
- if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
- PermissionManager.complete("file", await PermissionManager.check("file"));
+ gFFI.invokeMethod("check_service");
+ // for Android 10/11, request MANAGE_EXTERNAL_STORAGE permission from system setting page
+ if (AndroidPermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
+ AndroidPermissionManager.complete(kManageExternalStorage,
+ await AndroidPermissionManager.check(kManageExternalStorage));
debugPrint("file permission finished");
}
}
@@ -567,7 +569,7 @@ void androidChannelInit() {
{
var type = arguments["type"] as String;
var result = arguments["result"] as bool;
- PermissionManager.complete(type, result);
+ AndroidPermissionManager.complete(type, result);
break;
}
case "on_media_projection_canceled":
diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart
index c5f3b6935..e07f8f59f 100644
--- a/flutter/lib/mobile/pages/settings_page.dart
+++ b/flutter/lib/mobile/pages/settings_page.dart
@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
+import '../../consts.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart';
@@ -31,18 +32,20 @@ class SettingsPage extends StatefulWidget implements PageShape {
}
const url = 'https://rustdesk.com/';
-final _hasIgnoreBattery = androidVersion >= 26;
-var _ignoreBatteryOpt = false;
-var _enableAbr = false;
-var _denyLANDiscovery = false;
-var _onlyWhiteList = false;
-var _enableDirectIPAccess = false;
-var _enableRecordSession = false;
-var _autoRecordIncomingSession = false;
-var _localIP = "";
-var _directAccessPort = "";
class _SettingsState extends State with WidgetsBindingObserver {
+ final _hasIgnoreBattery = androidVersion >= 26;
+ var _ignoreBatteryOpt = false;
+ var _enableStartOnBoot = false;
+ var _enableAbr = false;
+ var _denyLANDiscovery = false;
+ var _onlyWhiteList = false;
+ var _enableDirectIPAccess = false;
+ var _enableRecordSession = false;
+ var _autoRecordIncomingSession = false;
+ var _localIP = "";
+ var _directAccessPort = "";
+
@override
void initState() {
super.initState();
@@ -50,11 +53,34 @@ class _SettingsState extends State with WidgetsBindingObserver {
() async {
var update = false;
+
if (_hasIgnoreBattery) {
- update = await updateIgnoreBatteryStatus();
+ if (await checkAndUpdateIgnoreBatteryStatus()) {
+ update = true;
+ }
}
- final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N";
+ if (await checkAndUpdateStartOnBoot()) {
+ update = true;
+ }
+
+ // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
+ var enableStartOnBoot =
+ await gFFI.invokeMethod(AndroidChannel.kGetStartOnBootOpt);
+ if (enableStartOnBoot) {
+ if (!await canStartOnBoot()) {
+ enableStartOnBoot = false;
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
+ }
+ }
+
+ if (enableStartOnBoot != _enableStartOnBoot) {
+ update = true;
+ _enableStartOnBoot = enableStartOnBoot;
+ }
+
+ final enableAbrRes = option2bool(
+ "enable-abr", await bind.mainGetOption(key: "enable-abr"));
if (enableAbrRes != _enableAbr) {
update = true;
_enableAbr = enableAbrRes;
@@ -125,15 +151,18 @@ class _SettingsState extends State with WidgetsBindingObserver {
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
() async {
- if (await updateIgnoreBatteryStatus()) {
+ final ibs = await checkAndUpdateIgnoreBatteryStatus();
+ final sob = await checkAndUpdateStartOnBoot();
+ if (ibs || sob) {
setState(() {});
}
}();
}
}
- Future updateIgnoreBatteryStatus() async {
- final res = await PermissionManager.check("ignore_battery_optimizations");
+ Future checkAndUpdateIgnoreBatteryStatus() async {
+ final res = await AndroidPermissionManager.check(
+ kRequestIgnoreBatteryOptimizations);
if (_ignoreBatteryOpt != res) {
_ignoreBatteryOpt = res;
return true;
@@ -142,6 +171,18 @@ class _SettingsState extends State with WidgetsBindingObserver {
}
}
+ Future checkAndUpdateStartOnBoot() async {
+ if (!await canStartOnBoot() && _enableStartOnBoot) {
+ _enableStartOnBoot = false;
+ debugPrint(
+ "checkAndUpdateStartOnBoot and set _enableStartOnBoot -> false");
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
@override
Widget build(BuildContext context) {
Provider.of(context);
@@ -265,7 +306,8 @@ class _SettingsState extends State with WidgetsBindingObserver {
]),
onToggle: (v) async {
if (v) {
- PermissionManager.request("ignore_battery_optimizations");
+ await AndroidPermissionManager.request(
+ kRequestIgnoreBatteryOptimizations);
} else {
final res = await gFFI.dialogManager
.show((setState, close) => CustomAlertDialog(
@@ -282,11 +324,44 @@ class _SettingsState extends State with WidgetsBindingObserver {
],
));
if (res == true) {
- PermissionManager.request("application_details_settings");
+ AndroidPermissionManager.startAction(
+ kActionApplicationDetailsSettings);
}
}
}));
}
+ enhancementsTiles.add(SettingsTile.switchTile(
+ initialValue: _enableStartOnBoot,
+ title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+ Text("${translate('Start on Boot')} (beta)"),
+ Text(
+ '* ${translate('Start the screen sharing service on boot, requires special permissions')}',
+ style: Theme.of(context).textTheme.bodySmall),
+ ]),
+ onToggle: (toValue) async {
+ if (toValue) {
+ // 1. request kIgnoreBatteryOptimizations
+ if (!await AndroidPermissionManager.check(
+ kRequestIgnoreBatteryOptimizations)) {
+ if (!await AndroidPermissionManager.request(
+ kRequestIgnoreBatteryOptimizations)) {
+ return;
+ }
+ }
+
+ // 2. request kSystemAlertWindow
+ if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
+ if (!await AndroidPermissionManager.request(kSystemAlertWindow)) {
+ return;
+ }
+ }
+
+ // (Optional) 3. request input permission
+ }
+ setState(() => _enableStartOnBoot = toValue);
+
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
+ }));
return SettingsList(
sections: [
@@ -387,6 +462,17 @@ class _SettingsState extends State with WidgetsBindingObserver {
],
);
}
+
+ Future canStartOnBoot() async {
+ // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
+ if (_hasIgnoreBattery && !_ignoreBatteryOpt) {
+ return false;
+ }
+ if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
+ return false;
+ }
+ return true;
+ }
}
void showServerSettings(OverlayDialogManager dialogManager) async {
diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart
index 13f5b4587..28dc8085e 100644
--- a/flutter/lib/models/native_model.dart
+++ b/flutter/lib/models/native_model.dart
@@ -30,7 +30,7 @@ typedef F4Dart = int Function(Pointer);
typedef F5 = Void Function(Pointer);
typedef F5Dart = void Function(Pointer);
typedef HandleEvent = Future Function(Map evt);
-// pub fn session_register_texture(id: *const char, ptr: usize)
+// pub fn session_register_texture(id: *const char, ptr: usize)
typedef F6 = Void Function(Pointer, Uint64);
typedef F6Dart = void Function(Pointer, int);
@@ -56,7 +56,6 @@ class PlatformFFI {
F4Dart? _session_get_rgba_size;
F5Dart? _session_next_rgba;
F6Dart? _session_register_texture;
-
static get localeName => Platform.localeName;
@@ -162,7 +161,8 @@ class PlatformFFI {
dylib.lookupFunction("session_get_rgba_size");
_session_next_rgba =
dylib.lookupFunction("session_next_rgba");
- _session_register_texture = dylib.lookupFunction("session_register_texture");
+ _session_register_texture =
+ dylib.lookupFunction("session_register_texture");
try {
// SYSTEM user failed
_dir = (await getApplicationDocumentsDirectory()).path;
@@ -301,4 +301,8 @@ class PlatformFFI {
if (!isAndroid) return Future(() => false);
return await _toAndroidChannel.invokeMethod(method, arguments);
}
+
+ void syncAndroidServiceAppDirConfigPath() {
+ invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir);
+ }
}
diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart
index b2043f3c2..7ee23ec40 100644
--- a/flutter/lib/models/server_model.dart
+++ b/flutter/lib/models/server_model.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
+import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
@@ -154,7 +155,8 @@ class ServerModel with ChangeNotifier {
/// file true by default (if permission on)
checkAndroidPermission() async {
// audio
- if (androidVersion < 30 || !await PermissionManager.check("audio")) {
+ if (androidVersion < 30 ||
+ !await AndroidPermissionManager.check(kRecordAudio)) {
_audioOk = false;
bind.mainSetOption(key: "enable-audio", value: "N");
} else {
@@ -163,7 +165,7 @@ class ServerModel with ChangeNotifier {
}
// file
- if (!await PermissionManager.check("file")) {
+ if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
_fileOk = false;
bind.mainSetOption(key: "enable-file-transfer", value: "N");
} else {
@@ -229,10 +231,10 @@ class ServerModel with ChangeNotifier {
}
toggleAudio() async {
- if (!_audioOk && !await PermissionManager.check("audio")) {
- final res = await PermissionManager.request("audio");
+ if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) {
+ final res = await AndroidPermissionManager.request(kRecordAudio);
if (!res) {
- // TODO handle fail
+ showToast(translate('Failed'));
return;
}
}
@@ -243,10 +245,12 @@ class ServerModel with ChangeNotifier {
}
toggleFile() async {
- if (!_fileOk && !await PermissionManager.check("file")) {
- final res = await PermissionManager.request("file");
+ if (!_fileOk &&
+ !await AndroidPermissionManager.check(kManageExternalStorage)) {
+ final res =
+ await AndroidPermissionManager.request(kManageExternalStorage);
if (!res) {
- // TODO handle fail
+ showToast(translate('Failed'));
return;
}
}
@@ -344,10 +348,6 @@ class ServerModel with ChangeNotifier {
}
}
- Future initInput() async {
- await parent.target?.invokeMethod("init_input");
- }
-
Future setPermanentPassword(String newPW) async {
await bind.mainSetPermanentPassword(password: newPW);
await Future.delayed(Duration(milliseconds: 500));
@@ -561,7 +561,8 @@ class ServerModel with ChangeNotifier {
}
Future closeAll() async {
- await Future.wait(_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
+ await Future.wait(
+ _clients.map((client) => bind.cmCloseConnection(connId: client.id)));
_clients.clear();
tabController.state.value.tabs.clear();
}
@@ -684,7 +685,7 @@ String getLoginDialogTag(int id) {
showInputWarnAlert(FFI ffi) {
ffi.dialogManager.show((setState, close) {
submit() {
- ffi.serverModel.initInput();
+ AndroidPermissionManager.startAction(kActionAccessibilitySettings);
close();
}
diff --git a/res/lang.py b/res/lang.py
index 481d65553..aa5f99f83 100644
--- a/res/lang.py
+++ b/res/lang.py
@@ -36,11 +36,11 @@ def main():
def expand():
for fn in glob.glob('./src/lang/*'):
lang = os.path.basename(fn)[:-3]
- if lang in ['en','cn']: continue
+ if lang in ['en','template']: continue
print(lang)
dict = get_lang(lang)
fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8')
- for line in open('./src/lang/cn.rs', encoding='utf8'):
+ for line in open('./src/lang/template.rs', encoding='utf8'):
line_strip = line.strip()
if line_strip.startswith('("'):
k, v = line_split(line_strip)
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index e49ba65f7..e5b24fa53 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -1361,7 +1361,7 @@ pub fn send_url_scheme(_url: String) {
#[cfg(target_os = "android")]
pub mod server_side {
- use hbb_common::log;
+ use hbb_common::{log, config};
use jni::{
objects::{JClass, JString},
sys::jstring,
@@ -1374,11 +1374,25 @@ pub mod server_side {
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
env: JNIEnv,
_class: JClass,
+ app_dir: JString,
) {
- log::debug!("startServer from java");
+ log::debug!("startServer from jvm");
+ if let Ok(app_dir) = env.get_string(app_dir) {
+ *config::APP_DIR.write().unwrap() = app_dir.into();
+ }
std::thread::spawn(move || start_server(true));
}
+ #[no_mangle]
+ pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
+ env: JNIEnv,
+ _class: JClass,
+ ) {
+ log::debug!("startService from jvm");
+ config::Config::set_option("stop-service".into(), "".into());
+ crate::rendezvous_mediator::RendezvousMediator::restart();
+ }
+
#[no_mangle]
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
env: JNIEnv,
diff --git a/src/lang/ca.rs b/src/lang/ca.rs
index aa33ae6e5..53ec69b5f 100644
--- a/src/lang/ca.rs
+++ b/src/lang/ca.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Connexió no disponible"),
("Legacy mode", "Mode heretat"),
("Map mode", "Mode mapa"),
diff --git a/src/lang/cn.rs b/src/lang/cn.rs
index f975e343f..4c037234b 100644
--- a/src/lang/cn.rs
+++ b/src/lang/cn.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
+ ("Start on Boot", "开机自启动"),
+ ("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"),
("Connection not allowed", "对方不允许连接"),
("Legacy mode", "传统模式"),
("Map mode", "1:1 传输"),
diff --git a/src/lang/cs.rs b/src/lang/cs.rs
index cfe69924c..25a494eef 100644
--- a/src/lang/cs.rs
+++ b/src/lang/cs.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
- ("No transfers in progress", ""),
("Codec", ""),
("Resolution", ""),
+ ("No transfers in progress", ""),
].iter().cloned().collect();
}
diff --git a/src/lang/da.rs b/src/lang/da.rs
index 19310357b..8fd6f9be1 100644
--- a/src/lang/da.rs
+++ b/src/lang/da.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
("Ignore Battery Optimizations", "Ignorer betteri optimeringer"),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Forbindelse ikke tilladt"),
("Legacy mode", "Bagudkompatibilitetstilstand"),
("Map mode", ""),
diff --git a/src/lang/de.rs b/src/lang/de.rs
index 3d95832ec..754d7b9ef 100644
--- a/src/lang/de.rs
+++ b/src/lang/de.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Verbindung abgelehnt"),
("Legacy mode", "Kompatibilitätsmodus"),
("Map mode", "Kartenmodus"),
@@ -457,5 +459,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Codec", "Codec"),
("Resolution", "Auflösung"),
("No transfers in progress", "Keine Übertragungen im Gange"),
- ].iter().cloned().collect();
+ ].iter().cloned().collect();
}
diff --git a/src/lang/eo.rs b/src/lang/eo.rs
index 9b7912cff..dfee4fb87 100644
--- a/src/lang/eo.rs
+++ b/src/lang/eo.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/es.rs b/src/lang/es.rs
index a95d39776..dc28cdae0 100644
--- a/src/lang/es.rs
+++ b/src/lang/es.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Conexión no disponible"),
("Legacy mode", "Modo heredado"),
("Map mode", "Modo mapa"),
diff --git a/src/lang/fa.rs b/src/lang/fa.rs
index f76567ee5..824bd039c 100644
--- a/src/lang/fa.rs
+++ b/src/lang/fa.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "اتصال مجاز نیست"),
("Legacy mode", "legacy حالت"),
("Map mode", "map حالت"),
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", "توقف تماس صوتی"),
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
("Reconnect", "اتصال مجدد"),
- ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
("Codec", "کدک"),
("Resolution", "وضوح"),
+ ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
].iter().cloned().collect();
}
diff --git a/src/lang/fr.rs b/src/lang/fr.rs
index 0e45827f7..28f1dd9d1 100644
--- a/src/lang/fr.rs
+++ b/src/lang/fr.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Connexion non autorisée"),
("Legacy mode", "Mode hérité"),
("Map mode", ""),
diff --git a/src/lang/gr.rs b/src/lang/gr.rs
index fca98f228..55a3c9bb7 100644
--- a/src/lang/gr.rs
+++ b/src/lang/gr.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Η σύνδεση απορρίφθηκε"),
("Legacy mode", "Λειτουργία συμβατότητας"),
("Map mode", "Map mode"),
diff --git a/src/lang/hu.rs b/src/lang/hu.rs
index 437cf445a..f47d522db 100644
--- a/src/lang/hu.rs
+++ b/src/lang/hu.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "A csatlakozás nem engedélyezett"),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/id.rs b/src/lang/id.rs
index 84892a7f8..7d02e154d 100644
--- a/src/lang/id.rs
+++ b/src/lang/id.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Legacy mode", "Mode lama"),
("Map mode", "Mode peta"),
diff --git a/src/lang/it.rs b/src/lang/it.rs
index 101685c4a..8aedc04f6 100644
--- a/src/lang/it.rs
+++ b/src/lang/it.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
("android_open_battery_optimizations_tip", "Se si desidera disabilitare questa funzione, andare nelle impostazioni dell'applicazione RustDesk, aprire la sezione [Batteria] e deselezionare [Senza restrizioni]."),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Connessione non consentita"),
("Legacy mode", "Modalità legacy"),
("Map mode", "Modalità mappa"),
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", "Interrompi la chiamata vocale"),
("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."),
("Reconnect", "Riconnetti"),
- ("No transfers in progress", "Nessun trasferimento in corso"),
("Codec", "Codec"),
("Resolution", "Risoluzione"),
+ ("No transfers in progress", "Nessun trasferimento in corso"),
].iter().cloned().collect();
}
diff --git a/src/lang/ja.rs b/src/lang/ja.rs
index c19b607ca..d097a8b61 100644
--- a/src/lang/ja.rs
+++ b/src/lang/ja.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "接続が許可されていません"),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/ko.rs b/src/lang/ko.rs
index 97574e67d..8ca881f16 100644
--- a/src/lang/ko.rs
+++ b/src/lang/ko.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "연결이 허용되지 않음"),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/kz.rs b/src/lang/kz.rs
index 54a51b439..a9acdce65 100644
--- a/src/lang/kz.rs
+++ b/src/lang/kz.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Қосылу рұқсат етілмеген"),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/nl.rs b/src/lang/nl.rs
index f38c14791..cf3eb430c 100644
--- a/src/lang/nl.rs
+++ b/src/lang/nl.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Verbinding niet toegestaan"),
("Legacy mode", "Verouderde modus"),
("Map mode", "Map mode"),
diff --git a/src/lang/pl.rs b/src/lang/pl.rs
index 13027a682..a0808f5bf 100644
--- a/src/lang/pl.rs
+++ b/src/lang/pl.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
("android_open_battery_optimizations_tip", "Jeśli chcesz wyłączyć tę funkcję, przejdź do następnej strony ustawień aplikacji RustDesk, znajdź i wprowadź [Bateria], odznacz [Bez ograniczeń]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Połączenie niedozwolone"),
("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"),
("Map mode", "Tryb mapowania"),
@@ -456,9 +458,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reconnect", "Połącz ponownie"),
("Codec", "Kodek"),
("Resolution", "Rozdzielczość"),
- ("Use temporary password", "Użyj hasła tymczasowego"),
- ("Set temporary password length", "Ustaw długość hasła tymczasowego"),
- ("Key", "Klucz"),
("No transfers in progress", ""),
].iter().cloned().collect();
}
diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs
index 923bbab05..b62bd5a31 100644
--- a/src/lang/pt_PT.rs
+++ b/src/lang/pt_PT.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Ligação não autorizada"),
("Legacy mode", ""),
("Map mode", ""),
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
- ("No transfers in progress", ""),
("Codec", ""),
("Resolution", ""),
+ ("No transfers in progress", ""),
].iter().cloned().collect();
}
diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs
index aa491f951..546ef2a3c 100644
--- a/src/lang/ptbr.rs
+++ b/src/lang/ptbr.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Conexão não permitida"),
("Legacy mode", "Modo legado"),
("Map mode", "Modo mapa"),
diff --git a/src/lang/ro.rs b/src/lang/ro.rs
index e992b19d8..af9389a29 100644
--- a/src/lang/ro.rs
+++ b/src/lang/ro.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Conexiune neautoriztă"),
("Legacy mode", "Mod legacy"),
("Map mode", "Mod hartă"),
diff --git a/src/lang/ru.rs b/src/lang/ru.rs
index 3bfb5357d..b9af4ce98 100644
--- a/src/lang/ru.rs
+++ b/src/lang/ru.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Подключение не разрешено"),
("Legacy mode", "Устаревший режим"),
("Map mode", "Режим сопоставления"),
diff --git a/src/lang/sk.rs b/src/lang/sk.rs
index 6468b7eef..8a6b765be 100644
--- a/src/lang/sk.rs
+++ b/src/lang/sk.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/sl.rs b/src/lang/sl.rs
index d128e7322..5721d01f4 100755
--- a/src/lang/sl.rs
+++ b/src/lang/sl.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
("Ignore Battery Optimizations", "Prezri optimizacije baterije"),
("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Povezava ni dovoljena"),
("Legacy mode", "Stari način"),
("Map mode", "Način preslikave"),
diff --git a/src/lang/sq.rs b/src/lang/sq.rs
index 29c5cbbf8..1c488d470 100644
--- a/src/lang/sq.rs
+++ b/src/lang/sq.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"),
("android_open_battery_optimizations_tip", "Nëse dëshironi ta çaktivizoni këtë veçori, ju lutemi shkoni te faqja tjetër e cilësimeve të aplikacionit RustDesk, gjeni dhe shtypni [Batteri], hiqni zgjedhjen [Te pakufizuara]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Lidhja nuk lejohet"),
("Legacy mode", "Modaliteti i trashëgimisë"),
("Map mode", "Modaliteti i hartës"),
diff --git a/src/lang/sr.rs b/src/lang/sr.rs
index 63173dc11..249c0b599 100644
--- a/src/lang/sr.rs
+++ b/src/lang/sr.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"),
("android_open_battery_optimizations_tip", "Ako želite da onemogućite ovu funkciju, molimo idite na sledeću stranicu za podešavanje RustDesk aplikacije, pronađite i uđite u [Battery], isključite [Unrestricted]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Konekcija nije dozvoljena"),
("Legacy mode", "Zastareli mod"),
("Map mode", "Mod mapiranja"),
diff --git a/src/lang/sv.rs b/src/lang/sv.rs
index 1a00ece43..90ec8c1cf 100644
--- a/src/lang/sv.rs
+++ b/src/lang/sv.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
("Ignore Battery Optimizations", "Ignorera batterioptimering"),
("android_open_battery_optimizations_tip", "Om du vill stänga av denna funktion, gå till nästa RustDesk programs inställningar, hitta [Batteri], Checka ur [Obegränsad]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Anslutning ej tillåten"),
("Legacy mode", "Legacy mode"),
("Map mode", "Kartläge"),
diff --git a/src/lang/template.rs b/src/lang/template.rs
index 2c83f9474..6563d6056 100644
--- a/src/lang/template.rs
+++ b/src/lang/template.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/th.rs b/src/lang/th.rs
index 6fcf02ed2..316622395 100644
--- a/src/lang/th.rs
+++ b/src/lang/th.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
("Legacy mode", ""),
("Map mode", ""),
diff --git a/src/lang/tr.rs b/src/lang/tr.rs
index d35d288d6..7359bf064 100644
--- a/src/lang/tr.rs
+++ b/src/lang/tr.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
("android_open_battery_optimizations_tip", ""),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Legacy mode", "Eski mod"),
("Map mode", "Haritalama modu"),
diff --git a/src/lang/tw.rs b/src/lang/tw.rs
index 20a2998ec..70533c482 100644
--- a/src/lang/tw.rs
+++ b/src/lang/tw.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持RustDesk後台服務"),
("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "對方不允許連接"),
("Legacy mode", "傳統模式"),
("Map mode", "1:1傳輸"),
diff --git a/src/lang/ua.rs b/src/lang/ua.rs
index 4c4b5d4bc..6b54c83c3 100644
--- a/src/lang/ua.rs
+++ b/src/lang/ua.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"),
("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Підключення не дозволено"),
("Legacy mode", "Застарілий режим"),
("Map mode", "Режим карти"),
diff --git a/src/lang/vn.rs b/src/lang/vn.rs
index 32cd084cb..a379b3185 100644
--- a/src/lang/vn.rs
+++ b/src/lang/vn.rs
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
+ ("Start on Boot", ""),
+ ("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Kết nối không đuợc phép"),
("Legacy mode", ""),
("Map mode", ""),