mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-29 16:49:10 +08:00
Merge pull request #3425 from Heap-Hop/android_start_on_boot
Android start on boot
This commit is contained in:
commit
b10e76f67b
@ -11,22 +11,25 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<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
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="RustDesk"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:supportsRtl="true">
|
||||
|
||||
<receiver
|
||||
android:name=".BootReceiver"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="1000">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<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>
|
||||
</receiver>
|
||||
|
||||
@ -53,8 +56,6 @@
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@ -62,6 +63,11 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PermissionRequestTransparentActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/Transparent" />
|
||||
|
||||
<service
|
||||
android:name=".MainService"
|
||||
android:enabled="true"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Intent>(EXTRA_MP_DATA)?.let {
|
||||
|
||||
intent.getParcelableExtra<Intent>(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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
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)
|
||||
}
|
||||
"audio" -> {
|
||||
Permission.RECORD_AUDIO
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
"file" -> {
|
||||
Permission.MANAGE_EXTERNAL_STORAGE
|
||||
}
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return XXPermissions.isGranted(context, permission)
|
||||
}
|
||||
|
||||
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
|
||||
|
@ -15,4 +15,12 @@
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</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>
|
||||
|
@ -910,21 +910,14 @@ class AccessibilityListener extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionManager {
|
||||
class AndroidPermissionManager {
|
||||
static Completer<bool>? _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<bool> 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<bool>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ const double kDesktopFileTransferMaximumWidth = 300;
|
||||
const double kDesktopFileTransferRowHeight = 30.0;
|
||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||
|
||||
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && Platform.isLinux
|
||||
EdgeInsets get kDragToResizeAreaPadding =>
|
||||
!kUseCompatibleUiMode && Platform.isLinux
|
||||
? stateGlobal.fullscreen || stateGlobal.maximize
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(5.0)
|
||||
@ -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<int, String> logicalKeyMap = <int, String>{
|
||||
|
@ -153,6 +153,7 @@ void runMainApp(bool startService) async {
|
||||
void runMobileApp() async {
|
||||
await initEnv(kAppTypeMain);
|
||||
if (isAndroid) androidChannelInit();
|
||||
platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||
runApp(App());
|
||||
}
|
||||
|
||||
|
@ -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<ServerPage> {
|
||||
}
|
||||
|
||||
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":
|
||||
|
@ -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<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
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -50,11 +53,34 @@ class _SettingsState extends State<SettingsPage> 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<SettingsPage> 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<bool> updateIgnoreBatteryStatus() async {
|
||||
final res = await PermissionManager.check("ignore_battery_optimizations");
|
||||
Future<bool> checkAndUpdateIgnoreBatteryStatus() async {
|
||||
final res = await AndroidPermissionManager.check(
|
||||
kRequestIgnoreBatteryOptimizations);
|
||||
if (_ignoreBatteryOpt != res) {
|
||||
_ignoreBatteryOpt = res;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
Provider.of<FfiModel>(context);
|
||||
@ -265,7 +306,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
]),
|
||||
onToggle: (v) async {
|
||||
if (v) {
|
||||
PermissionManager.request("ignore_battery_optimizations");
|
||||
await AndroidPermissionManager.request(
|
||||
kRequestIgnoreBatteryOptimizations);
|
||||
} else {
|
||||
final res = await gFFI.dialogManager
|
||||
.show<bool>((setState, close) => CustomAlertDialog(
|
||||
@ -282,11 +324,44 @@ class _SettingsState extends State<SettingsPage> 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<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 {
|
||||
|
@ -57,7 +57,6 @@ class PlatformFFI {
|
||||
F5Dart? _session_next_rgba;
|
||||
F6Dart? _session_register_texture;
|
||||
|
||||
|
||||
static get localeName => Platform.localeName;
|
||||
|
||||
static get isMain => instance._appType == kAppTypeMain;
|
||||
@ -162,7 +161,8 @@ class PlatformFFI {
|
||||
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
|
||||
_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 {
|
||||
// SYSTEM user failed
|
||||
_dir = (await getApplicationDocumentsDirectory()).path;
|
||||
@ -301,4 +301,8 @@ class PlatformFFI {
|
||||
if (!isAndroid) return Future<bool>(() => false);
|
||||
return await _toAndroidChannel.invokeMethod(method, arguments);
|
||||
}
|
||||
|
||||
void syncAndroidServiceAppDirConfigPath() {
|
||||
invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir);
|
||||
}
|
||||
}
|
||||
|
@ -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<void> initInput() async {
|
||||
await parent.target?.invokeMethod("init_input");
|
||||
}
|
||||
|
||||
Future<bool> setPermanentPassword(String newPW) async {
|
||||
await bind.mainSetPermanentPassword(password: newPW);
|
||||
await Future.delayed(Duration(milliseconds: 500));
|
||||
@ -561,7 +561,8 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
|
@ -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 传输"),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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ă"),
|
||||
|
@ -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", "Режим сопоставления"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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"),
|
||||
|
@ -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"),
|
||||
|
@ -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"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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傳輸"),
|
||||
|
@ -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", "Режим карти"),
|
||||
|
@ -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", ""),
|
||||
|
Loading…
Reference in New Issue
Block a user