Merge pull request #3425 from Heap-Hop/android_start_on_boot

Android start on boot
This commit is contained in:
RustDesk 2023-03-01 11:06:24 +08:00 committed by GitHub
commit b10e76f67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 602 additions and 337 deletions

View File

@ -11,22 +11,25 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />--> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="RustDesk" android:label="RustDesk"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true">
android:requestLegacyExternalStorage="true">
<receiver <receiver
android:name=".BootReceiver" android:name=".BootReceiver"
android:enabled="false" android:enabled="true"
android:exported="false"> android:exported="true">
<intent-filter android:priority="1000"> <intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" />
<!--ACTION_BOOT_COMPLETED for debug test on no root device-->
<action android:name="com.carriez.flutter_hbb.DEBUG_BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
@ -53,8 +56,6 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -62,6 +63,11 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".PermissionRequestTransparentActivity"
android:excludeFromRecents="true"
android:theme="@style/Transparent" />
<service <service
android:name=".MainService" android:name=".MainService"
android:enabled="true" android:enabled="true"
@ -75,4 +81,4 @@
android:value="2" /> android:value="2" />
</application> </application>
</manifest> </manifest>

View File

@ -1,21 +1,45 @@
package com.carriez.flutter_hbb 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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import android.widget.Toast 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() { class BootReceiver : BroadcastReceiver() {
private val logTag = "tagBootReceiver"
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if ("android.intent.action.BOOT_COMPLETED" == intent.action){ Log.d(logTag, "onReceive ${intent.action}")
val it = Intent(context,MainService::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(it) context.startForegroundService(it)
}else{ } else {
context.startService(it) context.startService(it)
} }
} }

View File

@ -7,35 +7,29 @@ package com.carriez.flutter_hbb
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/ */
import android.app.Activity
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.media.projection.MediaProjectionManager
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.RequiresApi import com.hjq.permissions.XXPermissions
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
const val MEDIA_REQUEST_CODE = 42
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
companion object { companion object {
lateinit var flutterMethodChannel: MethodChannel var flutterMethodChannel: MethodChannel? = null
} }
private val channelTag = "mChannel" private val channelTag = "mChannel"
private val logTag = "mMainActivity" private val logTag = "mMainActivity"
private var mediaProjectionResultIntent: Intent? = null
private var mainService: MainService? = null private var mainService: MainService? = null
@RequiresApi(Build.VERSION_CODES.M)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
if (MainService.isReady) { if (MainService.isReady) {
@ -46,169 +40,32 @@ class MainActivity : FlutterActivity() {
flutterMethodChannel = MethodChannel( flutterMethodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, flutterEngine.dartExecutor.binaryMessenger,
channelTag channelTag
).apply { )
// make sure result is set, otherwise flutter will await forever initFlutterChannel(flutterMethodChannel!!)
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)
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val inputPer = InputService.isOpen val inputPer = InputService.isOpen
activity.runOnUiThread { activity.runOnUiThread {
flutterMethodChannel.invokeMethod( flutterMethodChannel?.invokeMethod(
"on_state_changed", "on_state_changed",
mapOf("name" to "input", "value" to inputPer.toString()) 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MEDIA_REQUEST_CODE) { if (requestCode == REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION && resultCode == RES_FAILED) {
if (resultCode == Activity.RESULT_OK && data != null) { flutterMethodChannel?.invokeMethod("on_media_projection_canceled", null)
mediaProjectionResultIntent = data
initService()
} else {
flutterMethodChannel.invokeMethod("on_media_projection_canceled", null)
}
} }
} }
@ -232,4 +89,138 @@ class MainActivity : FlutterActivity() {
mainService = null 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)
}
}
}
}
} }

View File

@ -35,6 +35,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterActivity
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.concurrent.thread import kotlin.concurrent.thread
import org.json.JSONException import org.json.JSONException
@ -43,10 +44,6 @@ import java.nio.ByteBuffer
import kotlin.math.max import kotlin.math.max
import kotlin.math.min 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_TITLE = "RustDesk"
const val DEFAULT_NOTIFY_TEXT = "Service is running" const val DEFAULT_NOTIFY_TEXT = "Service is running"
@ -147,7 +144,11 @@ class MainService : Service() {
// jvm call rust // jvm call rust
private external fun init(ctx: Context) 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 onVideoFrameUpdate(buf: ByteBuffer)
private external fun onAudioFrameUpdate(buf: ByteBuffer) private external fun onAudioFrameUpdate(buf: ByteBuffer)
private external fun translateLocale(localeName: String, input: String): String private external fun translateLocale(localeName: String, input: String): String
@ -195,6 +196,7 @@ class MainService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.d(logTag,"MainService onCreate")
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply { HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
start() start()
serviceLooper = looper serviceLooper = looper
@ -202,7 +204,13 @@ class MainService : Service() {
} }
updateScreenInfo(resources.configuration.orientation) updateScreenInfo(resources.configuration.orientation)
initNotification() 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() { override fun onDestroy() {
@ -277,22 +285,30 @@ class MainService : Service() {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 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) super.onStartCommand(intent, flags, startId)
if (intent?.action == INIT_SERVICE) { if (intent?.action == ACT_INIT_MEDIA_PROJECTION_AND_SERVICE) {
Log.d(logTag, "service starting:${startId}:${Thread.currentThread()}")
createForegroundNotification() 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 getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
intent.getParcelableExtra<Intent>(EXTRA_MP_DATA)?.let {
intent.getParcelableExtra<Intent>(EXT_MEDIA_PROJECTION_RES_INTENT)?.let {
mediaProjection = mediaProjection =
mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it) mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
checkMediaPermission() checkMediaPermission()
init(this) init(this)
_isReady = true _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) { override fun onConfigurationChanged(newConfig: Configuration) {
@ -300,6 +316,14 @@ class MainService : Service() {
updateScreenInfo(newConfig.orientation) 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") @SuppressLint("WrongConstant")
private fun createSurface(): Surface? { private fun createSurface(): Surface? {
return if (useVP9) { return if (useVP9) {
@ -400,13 +424,13 @@ class MainService : Service() {
fun checkMediaPermission(): Boolean { fun checkMediaPermission(): Boolean {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod( MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed", "on_state_changed",
mapOf("name" to "media", "value" to isReady.toString()) mapOf("name" to "media", "value" to isReady.toString())
) )
} }
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod( MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed", "on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString()) mapOf("name" to "input", "value" to InputService.isOpen.toString())
) )
@ -653,8 +677,8 @@ class MainService : Service() {
@SuppressLint("UnspecifiedImmutableFlag") @SuppressLint("UnspecifiedImmutableFlag")
private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent { private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent {
val intent = Intent(this, MainService::class.java).apply { val intent = Intent(this, MainService::class.java).apply {
action = ACTION_LOGIN_REQ_NOTIFY action = ACT_LOGIN_REQ_NOTIFY
putExtra(EXTRA_LOGIN_REQ_NOTIFY, res) putExtra(EXT_LOGIN_REQ_NOTIFY, res)
} }
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE) PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE)

View File

@ -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)
}
}
}

View File

@ -1,5 +1,6 @@
package com.carriez.flutter_hbb package com.carriez.flutter_hbb
import android.Manifest.permission.*
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -12,8 +13,8 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS import android.provider.Settings
import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS import android.provider.Settings.*
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
@ -22,6 +23,31 @@ import java.nio.ByteBuffer
import java.util.* 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") @SuppressLint("ConstantLocale")
val LOCAL_NAME = Locale.getDefault().toString() val LOCAL_NAME = Locale.getDefault().toString()
val SCREEN_INFO = Info(0, 0, 1, 200) 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 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) { 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) XXPermissions.with(context)
.permission(permission) .permission(type)
.request { _, all -> .request { _, all ->
if (all) { if (all) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod( MainActivity.flutterMethodChannel?.invokeMethod(
"on_android_permission_result", "on_android_permission_result",
mapOf("type" to type, "result" to all) mapOf("type" to type, "result" to all)
) )
@ -93,24 +71,18 @@ fun requestPermission(context: Context, type: String) {
} }
} }
@RequiresApi(Build.VERSION_CODES.M) fun startAction(context: Context, action: String) {
fun checkPermission(context: Context, type: String): Boolean { try {
val permission = when (type) { context.startActivity(Intent(action).apply {
"ignore_battery_optimizations" -> { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager // don't pass package name when launch ACTION_ACCESSIBILITY_SETTINGS
return pw.isIgnoringBatteryOptimizations(context.packageName) if (ACTION_ACCESSIBILITY_SETTINGS != action) {
} data = Uri.parse("package:" + context.packageName)
"audio" -> { }
Permission.RECORD_AUDIO })
} } catch (e: Exception) {
"file" -> { e.printStackTrace()
Permission.MANAGE_EXTERNAL_STORAGE
}
else -> {
return false
}
} }
return XXPermissions.isGranted(context, permission)
} }
class AudioReader(val bufSize: Int, private val maxFrames: Int) { class AudioReader(val bufSize: Int, private val maxFrames: Int) {

View File

@ -15,4 +15,12 @@
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
<style name="Transparent" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources> </resources>

View File

@ -910,21 +910,14 @@ class AccessibilityListener extends StatelessWidget {
} }
} }
class PermissionManager { class AndroidPermissionManager {
static Completer<bool>? _completer; static Completer<bool>? _completer;
static Timer? _timer; static Timer? _timer;
static var _current = ""; static var _current = "";
static final permissions = [
"audio",
"file",
"ignore_battery_optimizations",
"application_details_settings"
];
static bool isWaitingFile() { static bool isWaitingFile() {
if (_completer != null) { if (_completer != null) {
return !_completer!.isCompleted && _current == "file"; return !_completer!.isCompleted && _current == kManageExternalStorage;
} }
return false; return false;
} }
@ -933,31 +926,33 @@ class PermissionManager {
if (isDesktop) { if (isDesktop) {
return Future.value(true); return Future.value(true);
} }
if (!permissions.contains(type)) {
return Future.error("Wrong permission!$type");
}
return gFFI.invokeMethod("check_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<bool> request(String type) { static Future<bool> request(String type) {
if (isDesktop) { if (isDesktop) {
return Future.value(true); return Future.value(true);
} }
if (!permissions.contains(type)) {
return Future.error("Wrong permission!$type");
}
gFFI.invokeMethod("request_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; _current = type;
_completer = Completer<bool>(); _completer = Completer<bool>();
gFFI.invokeMethod("request_permission", type);
// timeout _timer = Timer(Duration(seconds: 120), () {
_timer?.cancel();
_timer = Timer(Duration(seconds: 60), () {
if (_completer == null) return; if (_completer == null) return;
if (!_completer!.isCompleted) { if (!_completer!.isCompleted) {
_completer!.complete(false); _completer!.complete(false);
@ -1487,8 +1482,8 @@ connect(BuildContext context, String id,
} }
} else { } else {
if (isFileTransfer) { if (isFileTransfer) {
if (!await PermissionManager.check("file")) { if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
if (!await PermissionManager.request("file")) { if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
return; return;
} }
} }

View File

@ -59,11 +59,12 @@ const double kDesktopFileTransferMaximumWidth = 300;
const double kDesktopFileTransferRowHeight = 30.0; const double kDesktopFileTransferRowHeight = 30.0;
const double kDesktopFileTransferHeaderHeight = 25.0; const double kDesktopFileTransferHeaderHeight = 25.0;
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && Platform.isLinux EdgeInsets get kDragToResizeAreaPadding =>
? stateGlobal.fullscreen || stateGlobal.maximize !kUseCompatibleUiMode && Platform.isLinux
? EdgeInsets.zero ? stateGlobal.fullscreen || stateGlobal.maximize
: EdgeInsets.all(5.0) ? EdgeInsets.zero
: EdgeInsets.zero; : EdgeInsets.all(5.0)
: EdgeInsets.zero;
// https://en.wikipedia.org/wiki/Non-breaking_space // https://en.wikipedia.org/wiki/Non-breaking_space
const int $nbsp = 0x00A0; const int $nbsp = 0x00A0;
@ -136,6 +137,25 @@ const kRemoteAudioDualWay = 'dual-way';
const kIgnoreDpi = true; 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 /// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
/// see [LogicalKeyboardKey.keyLabel] /// see [LogicalKeyboardKey.keyLabel]
const Map<int, String> logicalKeyMap = <int, String>{ const Map<int, String> logicalKeyMap = <int, String>{

View File

@ -153,6 +153,7 @@ void runMainApp(bool startService) async {
void runMobileApp() async { void runMobileApp() async {
await initEnv(kAppTypeMain); await initEnv(kAppTypeMain);
if (isAndroid) androidChannelInit(); if (isAndroid) androidChannelInit();
platformFFI.syncAndroidServiceAppDirConfigPath();
runApp(App()); runApp(App());
} }

View File

@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/dialog.dart'; import '../../common/widgets/dialog.dart';
import '../../consts.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../models/server_model.dart'; import '../../models/server_model.dart';
import 'home_page.dart'; import 'home_page.dart';
@ -40,14 +41,14 @@ class ServerPage extends StatefulWidget implements PageShape {
value: "setTemporaryPasswordLength", value: "setTemporaryPasswordLength",
enabled: enabled:
gFFI.serverModel.verificationMethod != kUsePermanentPassword, gFFI.serverModel.verificationMethod != kUsePermanentPassword,
child: Text(translate("Set temporary password length")), child: Text(translate("One-time password length")),
), ),
const PopupMenuDivider(), const PopupMenuDivider(),
PopupMenuItem( PopupMenuItem(
padding: const EdgeInsets.symmetric(horizontal: 0.0), padding: const EdgeInsets.symmetric(horizontal: 0.0),
value: kUseTemporaryPassword, value: kUseTemporaryPassword,
child: ListTile( child: ListTile(
title: Text(translate("Use temporary password")), title: Text(translate("Use one-time password")),
trailing: Icon( trailing: Icon(
Icons.check, Icons.check,
color: gFFI.serverModel.verificationMethod == color: gFFI.serverModel.verificationMethod ==
@ -150,10 +151,11 @@ class _ServerPageState extends State<ServerPage> {
} }
void checkService() async { void checkService() async {
gFFI.invokeMethod("check_service"); // jvm gFFI.invokeMethod("check_service");
// for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page // for Android 10/11, request MANAGE_EXTERNAL_STORAGE permission from system setting page
if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) { if (AndroidPermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
PermissionManager.complete("file", await PermissionManager.check("file")); AndroidPermissionManager.complete(kManageExternalStorage,
await AndroidPermissionManager.check(kManageExternalStorage));
debugPrint("file permission finished"); debugPrint("file permission finished");
} }
} }
@ -567,7 +569,7 @@ void androidChannelInit() {
{ {
var type = arguments["type"] as String; var type = arguments["type"] as String;
var result = arguments["result"] as bool; var result = arguments["result"] as bool;
PermissionManager.complete(type, result); AndroidPermissionManager.complete(type, result);
break; break;
} }
case "on_media_projection_canceled": case "on_media_projection_canceled":

View File

@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/dialog.dart'; import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart'; import '../../common/widgets/login.dart';
import '../../consts.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../widgets/dialog.dart'; import '../widgets/dialog.dart';
@ -31,18 +32,20 @@ class SettingsPage extends StatefulWidget implements PageShape {
} }
const url = 'https://rustdesk.com/'; 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<SettingsPage> with WidgetsBindingObserver { class _SettingsState extends State<SettingsPage> 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 @override
void initState() { void initState() {
super.initState(); super.initState();
@ -50,11 +53,34 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
() async { () async {
var update = false; var update = false;
if (_hasIgnoreBattery) { 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) { if (enableAbrRes != _enableAbr) {
update = true; update = true;
_enableAbr = enableAbrRes; _enableAbr = enableAbrRes;
@ -125,15 +151,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
() async { () async {
if (await updateIgnoreBatteryStatus()) { final ibs = await checkAndUpdateIgnoreBatteryStatus();
final sob = await checkAndUpdateStartOnBoot();
if (ibs || sob) {
setState(() {}); setState(() {});
} }
}(); }();
} }
} }
Future<bool> updateIgnoreBatteryStatus() async { Future<bool> checkAndUpdateIgnoreBatteryStatus() async {
final res = await PermissionManager.check("ignore_battery_optimizations"); final res = await AndroidPermissionManager.check(
kRequestIgnoreBatteryOptimizations);
if (_ignoreBatteryOpt != res) { if (_ignoreBatteryOpt != res) {
_ignoreBatteryOpt = res; _ignoreBatteryOpt = res;
return true; return true;
@ -142,6 +171,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
} }
} }
Future<bool> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
@ -265,7 +306,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
]), ]),
onToggle: (v) async { onToggle: (v) async {
if (v) { if (v) {
PermissionManager.request("ignore_battery_optimizations"); await AndroidPermissionManager.request(
kRequestIgnoreBatteryOptimizations);
} else { } else {
final res = await gFFI.dialogManager final res = await gFFI.dialogManager
.show<bool>((setState, close) => CustomAlertDialog( .show<bool>((setState, close) => CustomAlertDialog(
@ -282,11 +324,44 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
], ],
)); ));
if (res == true) { 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( return SettingsList(
sections: [ sections: [
@ -387,6 +462,17 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
], ],
); );
} }
Future<bool> 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 { void showServerSettings(OverlayDialogManager dialogManager) async {

View File

@ -30,7 +30,7 @@ typedef F4Dart = int Function(Pointer<Utf8>);
typedef F5 = Void Function(Pointer<Utf8>); typedef F5 = Void Function(Pointer<Utf8>);
typedef F5Dart = void Function(Pointer<Utf8>); typedef F5Dart = void Function(Pointer<Utf8>);
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt); typedef HandleEvent = Future<void> Function(Map<String, dynamic> 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<Utf8>, Uint64); typedef F6 = Void Function(Pointer<Utf8>, Uint64);
typedef F6Dart = void Function(Pointer<Utf8>, int); typedef F6Dart = void Function(Pointer<Utf8>, int);
@ -56,7 +56,6 @@ class PlatformFFI {
F4Dart? _session_get_rgba_size; F4Dart? _session_get_rgba_size;
F5Dart? _session_next_rgba; F5Dart? _session_next_rgba;
F6Dart? _session_register_texture; F6Dart? _session_register_texture;
static get localeName => Platform.localeName; static get localeName => Platform.localeName;
@ -162,7 +161,8 @@ class PlatformFFI {
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size"); dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
_session_next_rgba = _session_next_rgba =
dylib.lookupFunction<F5, F5Dart>("session_next_rgba"); dylib.lookupFunction<F5, F5Dart>("session_next_rgba");
_session_register_texture = dylib.lookupFunction<F6, F6Dart>("session_register_texture"); _session_register_texture =
dylib.lookupFunction<F6, F6Dart>("session_register_texture");
try { try {
// SYSTEM user failed // SYSTEM user failed
_dir = (await getApplicationDocumentsDirectory()).path; _dir = (await getApplicationDocumentsDirectory()).path;
@ -301,4 +301,8 @@ class PlatformFFI {
if (!isAndroid) return Future<bool>(() => false); if (!isAndroid) return Future<bool>(() => false);
return await _toAndroidChannel.invokeMethod(method, arguments); return await _toAndroidChannel.invokeMethod(method, arguments);
} }
void syncAndroidServiceAppDirConfigPath() {
invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir);
}
} }

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -154,7 +155,8 @@ class ServerModel with ChangeNotifier {
/// file true by default (if permission on) /// file true by default (if permission on)
checkAndroidPermission() async { checkAndroidPermission() async {
// audio // audio
if (androidVersion < 30 || !await PermissionManager.check("audio")) { if (androidVersion < 30 ||
!await AndroidPermissionManager.check(kRecordAudio)) {
_audioOk = false; _audioOk = false;
bind.mainSetOption(key: "enable-audio", value: "N"); bind.mainSetOption(key: "enable-audio", value: "N");
} else { } else {
@ -163,7 +165,7 @@ class ServerModel with ChangeNotifier {
} }
// file // file
if (!await PermissionManager.check("file")) { if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
_fileOk = false; _fileOk = false;
bind.mainSetOption(key: "enable-file-transfer", value: "N"); bind.mainSetOption(key: "enable-file-transfer", value: "N");
} else { } else {
@ -229,10 +231,10 @@ class ServerModel with ChangeNotifier {
} }
toggleAudio() async { toggleAudio() async {
if (!_audioOk && !await PermissionManager.check("audio")) { if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) {
final res = await PermissionManager.request("audio"); final res = await AndroidPermissionManager.request(kRecordAudio);
if (!res) { if (!res) {
// TODO handle fail showToast(translate('Failed'));
return; return;
} }
} }
@ -243,10 +245,12 @@ class ServerModel with ChangeNotifier {
} }
toggleFile() async { toggleFile() async {
if (!_fileOk && !await PermissionManager.check("file")) { if (!_fileOk &&
final res = await PermissionManager.request("file"); !await AndroidPermissionManager.check(kManageExternalStorage)) {
final res =
await AndroidPermissionManager.request(kManageExternalStorage);
if (!res) { if (!res) {
// TODO handle fail showToast(translate('Failed'));
return; return;
} }
} }
@ -344,10 +348,6 @@ class ServerModel with ChangeNotifier {
} }
} }
Future<void> initInput() async {
await parent.target?.invokeMethod("init_input");
}
Future<bool> setPermanentPassword(String newPW) async { Future<bool> setPermanentPassword(String newPW) async {
await bind.mainSetPermanentPassword(password: newPW); await bind.mainSetPermanentPassword(password: newPW);
await Future.delayed(Duration(milliseconds: 500)); await Future.delayed(Duration(milliseconds: 500));
@ -561,7 +561,8 @@ class ServerModel with ChangeNotifier {
} }
Future<void> closeAll() async { Future<void> 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(); _clients.clear();
tabController.state.value.tabs.clear(); tabController.state.value.tabs.clear();
} }
@ -684,7 +685,7 @@ String getLoginDialogTag(int id) {
showInputWarnAlert(FFI ffi) { showInputWarnAlert(FFI ffi) {
ffi.dialogManager.show((setState, close) { ffi.dialogManager.show((setState, close) {
submit() { submit() {
ffi.serverModel.initInput(); AndroidPermissionManager.startAction(kActionAccessibilitySettings);
close(); close();
} }

View File

@ -36,11 +36,11 @@ def main():
def expand(): def expand():
for fn in glob.glob('./src/lang/*'): for fn in glob.glob('./src/lang/*'):
lang = os.path.basename(fn)[:-3] lang = os.path.basename(fn)[:-3]
if lang in ['en','cn']: continue if lang in ['en','template']: continue
print(lang) print(lang)
dict = get_lang(lang) dict = get_lang(lang)
fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8') 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() line_strip = line.strip()
if line_strip.startswith('("'): if line_strip.startswith('("'):
k, v = line_split(line_strip) k, v = line_split(line_strip)

View File

@ -1361,7 +1361,7 @@ pub fn send_url_scheme(_url: String) {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use hbb_common::log; use hbb_common::{log, config};
use jni::{ use jni::{
objects::{JClass, JString}, objects::{JClass, JString},
sys::jstring, sys::jstring,
@ -1374,11 +1374,25 @@ pub mod server_side {
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer( pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
env: JNIEnv, env: JNIEnv,
_class: JClass, _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)); 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] #[no_mangle]
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale( pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
env: JNIEnv, env: JNIEnv,

View File

@ -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"), ("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"), ("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Connexió no disponible"), ("Connection not allowed", "Connexió no disponible"),
("Legacy mode", "Mode heretat"), ("Legacy mode", "Mode heretat"),
("Map mode", "Mode mapa"), ("Map mode", "Mode mapa"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持 RustDesk 后台服务"), ("Keep RustDesk background service", "保持 RustDesk 后台服务"),
("Ignore Battery Optimizations", "忽略电池优化"), ("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"), ("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Start on Boot", "开机自启动"),
("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"),
("Connection not allowed", "对方不允许连接"), ("Connection not allowed", "对方不允许连接"),
("Legacy mode", "传统模式"), ("Legacy mode", "传统模式"),
("Map mode", "11 传输"), ("Map mode", "11 传输"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""), ("Connection not allowed", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),
@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", ""), ("Stop voice call", ""),
("relay_hint_tip", ""), ("relay_hint_tip", ""),
("Reconnect", ""), ("Reconnect", ""),
("No transfers in progress", ""),
("Codec", ""), ("Codec", ""),
("Resolution", ""), ("Resolution", ""),
("No transfers in progress", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"), ("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
("Ignore Battery Optimizations", "Ignorer betteri optimeringer"), ("Ignore Battery Optimizations", "Ignorer betteri optimeringer"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Forbindelse ikke tilladt"), ("Connection not allowed", "Forbindelse ikke tilladt"),
("Legacy mode", "Bagudkompatibilitetstilstand"), ("Legacy mode", "Bagudkompatibilitetstilstand"),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"), ("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"), ("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"), ("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"), ("Connection not allowed", "Verbindung abgelehnt"),
("Legacy mode", "Kompatibilitätsmodus"), ("Legacy mode", "Kompatibilitätsmodus"),
("Map mode", "Kartenmodus"), ("Map mode", "Kartenmodus"),
@ -457,5 +459,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Codec", "Codec"), ("Codec", "Codec"),
("Resolution", "Auflösung"), ("Resolution", "Auflösung"),
("No transfers in progress", "Keine Übertragungen im Gange"), ("No transfers in progress", "Keine Übertragungen im Gange"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""), ("Connection not allowed", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -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"), ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), ("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]"), ("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"), ("Connection not allowed", "Conexión no disponible"),
("Legacy mode", "Modo heredado"), ("Legacy mode", "Modo heredado"),
("Map mode", "Modo mapa"), ("Map mode", "Modo mapa"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"), ("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"), ("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"), ("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "اتصال مجاز نیست"), ("Connection not allowed", "اتصال مجاز نیست"),
("Legacy mode", "legacy حالت"), ("Legacy mode", "legacy حالت"),
("Map mode", "map حالت"), ("Map mode", "map حالت"),
@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", "توقف تماس صوتی"), ("Stop voice call", "توقف تماس صوتی"),
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر می‌خواهید فوراً از سرور رله استفاده کنید، می‌توانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"), ("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر می‌خواهید فوراً از سرور رله استفاده کنید، می‌توانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
("Reconnect", "اتصال مجدد"), ("Reconnect", "اتصال مجدد"),
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
("Codec", "کدک"), ("Codec", "کدک"),
("Resolution", "وضوح"), ("Resolution", "وضوح"),
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"), ("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de 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"), ("Connection not allowed", "Connexion non autorisée"),
("Legacy mode", "Mode hérité"), ("Legacy mode", "Mode hérité"),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"), ("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"), ("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"), ("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Η σύνδεση απορρίφθηκε"), ("Connection not allowed", "Η σύνδεση απορρίφθηκε"),
("Legacy mode", "Λειτουργία συμβατότητας"), ("Legacy mode", "Λειτουργία συμβατότητας"),
("Map mode", "Map mode"), ("Map mode", "Map mode"),

View File

@ -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"), ("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"), ("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."), ("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"), ("Connection not allowed", "A csatlakozás nem engedélyezett"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -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"), ("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"), ("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Koneksi tidak dijinkan"), ("Connection not allowed", "Koneksi tidak dijinkan"),
("Legacy mode", "Mode lama"), ("Legacy mode", "Mode lama"),
("Map mode", "Mode peta"), ("Map mode", "Mode peta"),

View File

@ -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"), ("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"), ("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]."), ("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"), ("Connection not allowed", "Connessione non consentita"),
("Legacy mode", "Modalità legacy"), ("Legacy mode", "Modalità legacy"),
("Map mode", "Modalità mappa"), ("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"), ("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."), ("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"), ("Reconnect", "Riconnetti"),
("No transfers in progress", "Nessun trasferimento in corso"),
("Codec", "Codec"), ("Codec", "Codec"),
("Resolution", "Risoluzione"), ("Resolution", "Risoluzione"),
("No transfers in progress", "Nessun trasferimento in corso"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"), ("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"), ("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"), ("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "接続が許可されていません"), ("Connection not allowed", "接続が許可されていません"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"), ("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
("Ignore Battery Optimizations", "배터리 최적화 무시하기"), ("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."), ("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "연결이 허용되지 않음"), ("Connection not allowed", "연결이 허용되지 않음"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"), ("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"), ("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"), ("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Қосылу рұқсат етілмеген"), ("Connection not allowed", "Қосылу рұқсат етілмеген"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"), ("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"), ("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"), ("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"), ("Connection not allowed", "Verbinding niet toegestaan"),
("Legacy mode", "Verouderde modus"), ("Legacy mode", "Verouderde modus"),
("Map mode", "Map mode"), ("Map mode", "Map mode"),

View File

@ -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"), ("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"), ("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ń]"), ("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"), ("Connection not allowed", "Połączenie niedozwolone"),
("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"), ("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"),
("Map mode", "Tryb mapowania"), ("Map mode", "Tryb mapowania"),
@ -456,9 +458,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reconnect", "Połącz ponownie"), ("Reconnect", "Połącz ponownie"),
("Codec", "Kodek"), ("Codec", "Kodek"),
("Resolution", "Rozdzielczość"), ("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", ""), ("No transfers in progress", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"), ("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
("android_open_battery_optimizations_tip", ""), ("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"), ("Connection not allowed", "Ligação não autorizada"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),
@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop voice call", ""), ("Stop voice call", ""),
("relay_hint_tip", ""), ("relay_hint_tip", ""),
("Reconnect", ""), ("Reconnect", ""),
("No transfers in progress", ""),
("Codec", ""), ("Codec", ""),
("Resolution", ""), ("Resolution", ""),
("No transfers in progress", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"), ("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
("android_open_battery_optimizations_tip", "Abrir 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"), ("Connection not allowed", "Conexão não permitida"),
("Legacy mode", "Modo legado"), ("Legacy mode", "Modo legado"),
("Map mode", "Modo mapa"), ("Map mode", "Modo mapa"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"), ("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"), ("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]."), ("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ă"), ("Connection not allowed", "Conexiune neautoriztă"),
("Legacy mode", "Mod legacy"), ("Legacy mode", "Mod legacy"),
("Map mode", "Mod hartă"), ("Map mode", "Mod hartă"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Держать в фоне службу RustDesk"), ("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"), ("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"), ("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Подключение не разрешено"), ("Connection not allowed", "Подключение не разрешено"),
("Legacy mode", "Устаревший режим"), ("Legacy mode", "Устаревший режим"),
("Map mode", "Режим сопоставления"), ("Map mode", "Режим сопоставления"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""), ("Connection not allowed", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"), ("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
("Ignore Battery Optimizations", "Prezri optimizacije baterije"), ("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«"), ("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"), ("Connection not allowed", "Povezava ni dovoljena"),
("Legacy mode", "Stari način"), ("Legacy mode", "Stari način"),
("Map mode", "Način preslikave"), ("Map mode", "Način preslikave"),

View File

@ -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"), ("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"), ("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]"), ("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"), ("Connection not allowed", "Lidhja nuk lejohet"),
("Legacy mode", "Modaliteti i trashëgimisë"), ("Legacy mode", "Modaliteti i trashëgimisë"),
("Map mode", "Modaliteti i hartës"), ("Map mode", "Modaliteti i hartës"),

View File

@ -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"), ("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"), ("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]"), ("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"), ("Connection not allowed", "Konekcija nije dozvoljena"),
("Legacy mode", "Zastareli mod"), ("Legacy mode", "Zastareli mod"),
("Map mode", "Mod mapiranja"), ("Map mode", "Mod mapiranja"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"), ("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
("Ignore Battery Optimizations", "Ignorera batterioptimering"), ("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]"), ("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"), ("Connection not allowed", "Anslutning ej tillåten"),
("Legacy mode", "Legacy mode"), ("Legacy mode", "Legacy mode"),
("Map mode", "Kartläge"), ("Map mode", "Kartläge"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""), ("Connection not allowed", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"), ("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"), ("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),

View File

@ -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"), ("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"), ("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
("android_open_battery_optimizations_tip", ""), ("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"), ("Connection not allowed", "bağlantıya izin verilmedi"),
("Legacy mode", "Eski mod"), ("Legacy mode", "Eski mod"),
("Map mode", "Haritalama modu"), ("Map mode", "Haritalama modu"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "保持RustDesk後台服務"), ("Keep RustDesk background service", "保持RustDesk後台服務"),
("Ignore Battery Optimizations", "忽略電池優化"), ("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"), ("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "對方不允許連接"), ("Connection not allowed", "對方不允許連接"),
("Legacy mode", "傳統模式"), ("Legacy mode", "傳統模式"),
("Map mode", "11傳輸"), ("Map mode", "11傳輸"),

View File

@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"), ("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"), ("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"),
("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"), ("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Підключення не дозволено"), ("Connection not allowed", "Підключення не дозволено"),
("Legacy mode", "Застарілий режим"), ("Legacy mode", "Застарілий режим"),
("Map mode", "Режим карти"), ("Map mode", "Режим карти"),

View File

@ -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"), ("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"), ("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ế]"), ("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"), ("Connection not allowed", "Kết nối không đuợc phép"),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),