From d70b0cdd4fcd9925e005719e2a6e638e5bfb4884 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 13 May 2024 20:24:50 +0800 Subject: [PATCH 01/10] Not require both max-width and max-height of mediacodec larger than (#8036) screen width and screen height * Only use hardware codec, when api < 29, judge with codec name prefix. Signed-off-by: 21pages --- .../com/carriez/flutter_hbb/MainActivity.kt | 11 +++++++-- libs/scrap/src/android/ffi.rs | 2 ++ libs/scrap/src/common/hwcodec.rs | 23 ++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index ab51b20d13..076fac38c7 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -264,8 +264,12 @@ class MainActivity : FlutterActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { hw = codec.isHardwareAccelerated } else { - if (listOf("OMX.google.", "OMX.SEC.", "c2.android").any { codec.name.startsWith(it) }) { + // https://chromium.googlesource.com/external/webrtc/+/HEAD/sdk/android/src/java/org/webrtc/MediaCodecUtils.java#29 + // https://chromium.googlesource.com/external/webrtc/+/master/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java#229 + if (listOf("OMX.google.", "OMX.SEC.", "c2.android").any { codec.name.startsWith(it, true) }) { hw = false + } else if (listOf("c2.qti", "OMX.qcom.video", "OMX.Exynos", "OMX.hisi", "OMX.MTK", "OMX.Intel", "OMX.Nvidia").any { codec.name.startsWith(it, true) }) { + hw = true } } codecObject.put("hw", hw) @@ -280,7 +284,8 @@ class MainActivity : FlutterActivity() { val caps = codec.getCapabilitiesForType(mime_type) var usable = true; if (codec.isEncoder) { - if (!caps.videoCapabilities.isSizeSupported(w,h) || !caps.videoCapabilities.isSizeSupported(h,w)) { + // Encoder‘s max_height and max_width are interchangeable + if (!caps.videoCapabilities.isSizeSupported(w,h) && !caps.videoCapabilities.isSizeSupported(h,w)) { usable = false } } @@ -309,6 +314,8 @@ class MainActivity : FlutterActivity() { } val result = JSONObject() result.put("version", Build.VERSION.SDK_INT) + result.put("w", w) + result.put("h", h) result.put("codecs", codecArray) FFI.setCodecInfo(result.toString()) } diff --git a/libs/scrap/src/android/ffi.rs b/libs/scrap/src/android/ffi.rs index a604166a7e..02f73667a0 100644 --- a/libs/scrap/src/android/ffi.rs +++ b/libs/scrap/src/android/ffi.rs @@ -178,6 +178,8 @@ pub struct MediaCodecInfo { #[derive(Debug, Deserialize, Clone)] pub struct MediaCodecInfos { pub version: usize, + pub w: usize, + pub h: usize, pub codecs: Vec, } diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index a1dbeae188..83569150b7 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -416,6 +416,7 @@ fn get_config() -> ResultType { #[cfg(target_os = "android")] { let info = crate::android::ffi::get_codec_info(); + log::info!("all codec info: {info:?}"); struct T { name_prefix: &'static str, data_format: DataFormat, @@ -440,16 +441,26 @@ fn get_config() -> ResultType { c.is_encoder && c.mime_type.as_str() == get_mime_type(t.data_format) && c.nv12 + && c.hw == Some(true) //only use hardware codec }) .collect(); log::debug!("available {:?} encoders: {codecs:?}", t.data_format); + let screen_wh = std::cmp::max(info.w, info.h); let mut best = None; - if let Some(c) = codecs.iter().find(|c| c.hw == Some(true)) { - best = Some(c.name.clone()); - } else if let Some(c) = codecs.iter().find(|c| c.hw == None) { - best = Some(c.name.clone()); - } else if let Some(c) = codecs.first() { - best = Some(c.name.clone()); + if let Some(codec) = codecs + .iter() + .find(|c| c.max_width >= screen_wh && c.max_height >= screen_wh) + { + best = Some(codec.name.clone()); + } else { + // find the max resolution + let mut max_area = 0; + for codec in codecs.iter() { + if codec.max_width * codec.max_height > max_area { + best = Some(codec.name.clone()); + max_area = codec.max_width * codec.max_height; + } + } } if let Some(best) = best { e.push(CodecInfo { From 0500bf070e719728bfe07966ba32efd206082c75 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 May 2024 09:20:27 +0800 Subject: [PATCH 02/10] refact: android audio input, voice call (#8037) Signed-off-by: fufesou --- .../carriez/flutter_hbb/AudioRecordHandle.kt | 200 ++++++++++++++++++ .../com/carriez/flutter_hbb/MainActivity.kt | 49 +++++ .../com/carriez/flutter_hbb/MainService.kt | 157 ++++++-------- flutter/lib/mobile/pages/remote_page.dart | 96 ++++++++- flutter/lib/mobile/pages/server_page.dart | 125 ++++++++--- flutter/lib/models/chat_model.dart | 6 + flutter/lib/models/server_model.dart | 57 +++-- src/flutter.rs | 11 +- src/lang/ar.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + src/ui_cm_interface.rs | 15 +- 51 files changed, 610 insertions(+), 148 deletions(-) create mode 100644 flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt new file mode 100644 index 0000000000..122a5a92b9 --- /dev/null +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt @@ -0,0 +1,200 @@ +package com.carriez.flutter_hbb + +import ffi.FFI + +import android.Manifest +import android.content.Context +import android.media.* +import android.content.pm.PackageManager +import android.media.projection.MediaProjection +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import android.os.Build +import android.util.Log +import kotlin.concurrent.thread + +const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_FLOAT // ENCODING_OPUS need API 30 +const val AUDIO_SAMPLE_RATE = 48000 +const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO + +class AudioRecordHandle(private var context: Context, private var isVideoStart: ()->Boolean, private var isAudioStart: ()->Boolean) { + private val logTag = "LOG_AUDIO_RECORD_HANDLE" + + private var audioRecorder: AudioRecord? = null + private var audioReader: AudioReader? = null + private var minBufferSize = 0 + private var audioRecordStat = false + private var audioThread: Thread? = null + + @RequiresApi(Build.VERSION_CODES.M) + fun createAudioRecorder(inVoiceCall: Boolean, mediaProjection: MediaProjection?): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return false + } + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.RECORD_AUDIO + ) != PackageManager.PERMISSION_GRANTED + ) { + Log.d(logTag, "createAudioRecorder failed, no RECORD_AUDIO permission") + return false + } + + var builder = AudioRecord.Builder() + .setAudioFormat( + AudioFormat.Builder() + .setEncoding(AUDIO_ENCODING) + .setSampleRate(AUDIO_SAMPLE_RATE) + .setChannelMask(AUDIO_CHANNEL_MASK).build() + ); + if (inVoiceCall) { + builder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) + } else { + mediaProjection?.let { + var apcc = AudioPlaybackCaptureConfiguration.Builder(it) + .addMatchingUsage(AudioAttributes.USAGE_MEDIA) + .addMatchingUsage(AudioAttributes.USAGE_ALARM) + .addMatchingUsage(AudioAttributes.USAGE_GAME) + .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build(); + builder.setAudioPlaybackCaptureConfig(apcc); + } ?: let { + Log.d(logTag, "createAudioRecorder failed, mediaProjection null") + return false + } + } + audioRecorder = builder.build() + Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize") + return true + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun checkAudioReader() { + if (audioReader != null && minBufferSize != 0) { + return + } + // read f32 to byte , length * 4 + minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize( + AUDIO_SAMPLE_RATE, + AUDIO_CHANNEL_MASK, + AUDIO_ENCODING + ) + if (minBufferSize == 0) { + Log.d(logTag, "get min buffer size fail!") + return + } + audioReader = AudioReader(minBufferSize, 4) + Log.d(logTag, "init audioData len:$minBufferSize") + } + + @RequiresApi(Build.VERSION_CODES.M) + fun startAudioRecorder() { + checkAudioReader() + if (audioReader != null && audioRecorder != null && minBufferSize != 0) { + try { + FFI.setFrameRawEnable("audio", true) + audioRecorder!!.startRecording() + audioRecordStat = true + audioThread = thread { + while (audioRecordStat) { + audioReader!!.readSync(audioRecorder!!)?.let { + FFI.onAudioFrameUpdate(it) + } + } + // let's release here rather than onDestroy to avoid threading issue + audioRecorder?.release() + audioRecorder = null + minBufferSize = 0 + FFI.setFrameRawEnable("audio", false) + Log.d(logTag, "Exit audio thread") + } + } catch (e: Exception) { + Log.d(logTag, "startAudioRecorder fail:$e") + } + } else { + Log.d(logTag, "startAudioRecorder fail") + } + } + + fun onVoiceCallStarted(mediaProjection: MediaProjection?): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return false + } + if (isVideoStart() || isAudioStart()) { + if (!switchToVoiceCall(mediaProjection)) { + return false + } + } else { + if (!switchToVoiceCall(mediaProjection)) { + return false + } + } + return true + } + + fun onVoiceCallClosed(mediaProjection: MediaProjection?): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return false + } + if (isVideoStart()) { + switchOutVoiceCall(mediaProjection) + } + tryReleaseAudio() + return true + } + + @RequiresApi(Build.VERSION_CODES.M) + fun switchToVoiceCall(mediaProjection: MediaProjection?): Boolean { + audioRecorder?.let { + if (it.getAudioSource() == MediaRecorder.AudioSource.VOICE_COMMUNICATION) { + return true + } + } + audioRecordStat = false + audioThread?.join() + audioThread = null + + if (!createAudioRecorder(true, mediaProjection)) { + Log.e(logTag, "createAudioRecorder fail") + return false + } + startAudioRecorder() + return true + } + + @RequiresApi(Build.VERSION_CODES.M) + fun switchOutVoiceCall(mediaProjection: MediaProjection?): Boolean { + audioRecorder?.let { + if (it.getAudioSource() != MediaRecorder.AudioSource.VOICE_COMMUNICATION) { + return true + } + } + audioRecordStat = false + audioThread?.join() + + if (!createAudioRecorder(false, mediaProjection)) { + Log.e(logTag, "createAudioRecorder fail") + return false + } + startAudioRecorder() + return true + } + + fun tryReleaseAudio() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return + } + if (isAudioStart() || isVideoStart()) { + return + } + audioRecordStat = false + audioThread?.join() + audioThread = null + } + + fun destroy() { + Log.d(logTag, "destroy audio record handle") + + audioRecordStat = false + audioThread?.join() + } +} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index 076fac38c7..230d8ec71c 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -42,6 +42,9 @@ class MainActivity : FlutterActivity() { private val logTag = "mMainActivity" private var mainService: MainService? = null + private var isAudioStart = false + private val audioRecordHandle = AudioRecordHandle(this, { false }, { isAudioStart }) + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) if (MainService.isReady) { @@ -230,6 +233,12 @@ class MainActivity : FlutterActivity() { result.success(false) } } + "on_voice_call_started" -> { + onVoiceCallStarted() + } + "on_voice_call_closed" -> { + onVoiceCallClosed() + } else -> { result.error("-1", "No such method", null) } @@ -319,4 +328,44 @@ class MainActivity : FlutterActivity() { result.put("codecs", codecArray) FFI.setCodecInfo(result.toString()) } + + private fun onVoiceCallStarted() { + var ok = false + mainService?.let { + ok = it.onVoiceCallStarted() + } ?: let { + isAudioStart = true + ok = audioRecordHandle.onVoiceCallStarted(null) + } + if (!ok) { + // Rarely happens, So we just add log and msgbox here. + Log.e(logTag, "onVoiceCallStarted fail") + flutterMethodChannel?.invokeMethod("msgbox", mapOf( + "type" to "custom-nook-nocancel-hasclose-error", + "title" to "Voice call", + "text" to "Failed to start voice call.")) + } else { + Log.d(logTag, "onVoiceCallStarted success") + } + } + + private fun onVoiceCallClosed() { + var ok = false + mainService?.let { + ok = it.onVoiceCallClosed() + } ?: let { + isAudioStart = false + ok = audioRecordHandle.onVoiceCallClosed(null) + } + if (!ok) { + // Rarely happens, So we just add log and msgbox here. + Log.e(logTag, "onVoiceCallClosed fail") + flutterMethodChannel?.invokeMethod("msgbox", mapOf( + "type" to "custom-nook-nocancel-hasclose-error", + "title" to "Voice call", + "text" to "Failed to stop voice call.")) + } else { + Log.d(logTag, "onVoiceCallClosed success") + } + } } diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt index 2261df5aff..e704f84a08 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt @@ -58,11 +58,6 @@ const val MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_VP9 const val VIDEO_KEY_BIT_RATE = 1024_000 const val VIDEO_KEY_FRAME_RATE = 30 -// audio const -const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_FLOAT // ENCODING_OPUS need API 30 -const val AUDIO_SAMPLE_RATE = 48000 -const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO - class MainService : Service() { @Keep @@ -138,6 +133,39 @@ class MainService : Service() { e.printStackTrace() } } + "update_voice_call_state" -> { + try { + val jsonObject = JSONObject(arg1) + val id = jsonObject["id"] as Int + val username = jsonObject["name"] as String + val peerId = jsonObject["peer_id"] as String + val inVoiceCall = jsonObject["in_voice_call"] as Boolean + val incomingVoiceCall = jsonObject["incoming_voice_call"] as Boolean + if (!inVoiceCall) { + if (incomingVoiceCall) { + voiceCallRequestNotification(id, "Voice Call Request", username, peerId) + } else { + if (!audioRecordHandle.switchOutVoiceCall(mediaProjection)) { + Log.e(logTag, "switchOutVoiceCall fail") + MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf( + "type" to "custom-nook-nocancel-hasclose-error", + "title" to "Voice call", + "text" to "Failed to switch out voice call.")) + } + } + } else { + if (!audioRecordHandle.switchToVoiceCall(mediaProjection)) { + Log.e(logTag, "switchToVoiceCall fail") + MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf( + "type" to "custom-nook-nocancel-hasclose-error", + "title" to "Voice call", + "text" to "Failed to switch to voice call.")) + } + } + } catch (e: JSONException) { + e.printStackTrace() + } + } "stop_capture" -> { Log.d(logTag, "from rust:stop_capture") stopCapture() @@ -161,10 +189,13 @@ class MainService : Service() { companion object { private var _isReady = false // media permission ready status private var _isStart = false // screen capture start status + private var _isAudioStart = false // audio capture start status val isReady: Boolean get() = _isReady val isStart: Boolean get() = _isStart + val isAudioStart: Boolean + get() = _isAudioStart } private val logTag = "LOG_SERVICE" @@ -182,10 +213,7 @@ class MainService : Service() { private var virtualDisplay: VirtualDisplay? = null // audio - private var audioRecorder: AudioRecord? = null - private var audioReader: AudioReader? = null - private var minBufferSize = 0 - private var audioRecordStat = false + private val audioRecordHandle = AudioRecordHandle(this, { isStart }, { isAudioStart }) // notification private lateinit var notificationManager: NotificationManager @@ -349,6 +377,14 @@ class MainService : Service() { } } + fun onVoiceCallStarted(): Boolean { + return audioRecordHandle.onVoiceCallStarted(mediaProjection) + } + + fun onVoiceCallClosed(): Boolean { + return audioRecordHandle.onVoiceCallClosed(mediaProjection) + } + fun startCapture(): Boolean { if (isStart) { return true @@ -369,12 +405,16 @@ class MainService : Service() { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - startAudioRecorder() + if (!audioRecordHandle.createAudioRecorder(false, mediaProjection)) { + Log.d(logTag, "createAudioRecorder fail") + } else { + Log.d(logTag, "audio recorder start") + audioRecordHandle.startAudioRecorder() + } } checkMediaPermission() _isStart = true FFI.setFrameRawEnable("video",true) - FFI.setFrameRawEnable("audio",true) return true } @@ -382,7 +422,6 @@ class MainService : Service() { fun stopCapture() { Log.d(logTag, "Stop Capture") FFI.setFrameRawEnable("video",false) - FFI.setFrameRawEnable("audio",false) _isStart = false // release video if (reuseVirtualDisplay) { @@ -411,12 +450,14 @@ class MainService : Service() { surface?.release() // release audio - audioRecordStat = false + _isAudioStart = false + audioRecordHandle.tryReleaseAudio() } fun destroy() { Log.d(logTag, "destroy service") _isReady = false + _isAudioStart = false stopCapture() @@ -514,7 +555,6 @@ class MainService : Service() { } } - private fun createMediaCodec() { Log.d(logTag, "MediaFormat.MIMETYPE_VIDEO_VP9 :$MIME_TYPE") videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE) @@ -534,80 +574,6 @@ class MainService : Service() { } } - @RequiresApi(Build.VERSION_CODES.M) - private fun startAudioRecorder() { - checkAudioRecorder() - if (audioReader != null && audioRecorder != null && minBufferSize != 0) { - try { - audioRecorder!!.startRecording() - audioRecordStat = true - thread { - while (audioRecordStat) { - audioReader!!.readSync(audioRecorder!!)?.let { - FFI.onAudioFrameUpdate(it) - } - } - // let's release here rather than onDestroy to avoid threading issue - audioRecorder?.release() - audioRecorder = null - minBufferSize = 0 - Log.d(logTag, "Exit audio thread") - } - } catch (e: Exception) { - Log.d(logTag, "startAudioRecorder fail:$e") - } - } else { - Log.d(logTag, "startAudioRecorder fail") - } - } - - @RequiresApi(Build.VERSION_CODES.M) - private fun checkAudioRecorder() { - if (audioRecorder != null && audioRecorder != null && minBufferSize != 0) { - return - } - // read f32 to byte , length * 4 - minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize( - AUDIO_SAMPLE_RATE, - AUDIO_CHANNEL_MASK, - AUDIO_ENCODING - ) - if (minBufferSize == 0) { - Log.d(logTag, "get min buffer size fail!") - return - } - audioReader = AudioReader(minBufferSize, 4) - Log.d(logTag, "init audioData len:$minBufferSize") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - mediaProjection?.let { - val apcc = AudioPlaybackCaptureConfiguration.Builder(it) - .addMatchingUsage(AudioAttributes.USAGE_MEDIA) - .addMatchingUsage(AudioAttributes.USAGE_ALARM) - .addMatchingUsage(AudioAttributes.USAGE_GAME) - .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build() - if (ActivityCompat.checkSelfPermission( - this, - Manifest.permission.RECORD_AUDIO - ) != PackageManager.PERMISSION_GRANTED - ) { - return - } - audioRecorder = AudioRecord.Builder() - .setAudioFormat( - AudioFormat.Builder() - .setEncoding(AUDIO_ENCODING) - .setSampleRate(AUDIO_SAMPLE_RATE) - .setChannelMask(AUDIO_CHANNEL_MASK).build() - ) - .setAudioPlaybackCaptureConfig(apcc) - .setBufferSizeInBytes(minBufferSize).build() - Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize") - return - } - } - Log.d(logTag, "createAudioRecorder fail") - } - private fun initNotification() { notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -692,6 +658,21 @@ class MainService : Service() { notificationManager.notify(getClientNotifyID(clientID), notification) } + private fun voiceCallRequestNotification( + clientID: Int, + type: String, + username: String, + peerId: String + ) { + val notification = notificationBuilder + .setOngoing(false) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setContentTitle(translate("Do you accept?")) + .setContentText("$type:$username-$peerId") + .build() + notificationManager.notify(getClientNotifyID(clientID), notification) + } + private fun getClientNotifyID(clientID: Int): Int { return clientID + NOTIFY_ID_OFFSET } diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 7cdb7471ce..a5c00d8a9b 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -8,6 +8,7 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -79,7 +80,7 @@ class _RemotePageState extends State { initSharedStates(widget.id); gFFI.chatModel .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID)); - + gFFI.chatModel.voiceCallStatus.value = VoiceCallStatus.notStarted; _blockableOverlayState.applyFfi(gFFI); } @@ -102,6 +103,11 @@ class _RemotePageState extends State { } await keyboardSubscription.cancel(); removeSharedStates(widget.id); + if (isAndroid) { + // Only one client is considered here for now. + // TODO: take into account the case where there are multiple clients + gFFI.invokeMethod("on_voice_call_closed"); + } } // to-do: It should be better to use transparent color instead of the bgColor. @@ -369,9 +375,7 @@ class _RemotePageState extends State { onPressed: () { clientClose(sessionId, gFFI.dialogManager); }, - ) - ] + - [ + ), IconButton( color: Colors.white, icon: Icon(Icons.tv), @@ -416,11 +420,9 @@ class _RemotePageState extends State { IconButton( color: Colors.white, icon: Icon(Icons.message), - onPressed: () { - gFFI.chatModel.changeCurrentKey(MessageKey( - widget.id, ChatModel.clientModeID)); - gFFI.chatModel.toggleChatOverlay(); - }, + onPressed: () => isAndroid + ? showChatOptions(widget.id) + : onPressedTextChat(widget.id), ) ]) + [ @@ -538,6 +540,82 @@ class _RemotePageState extends State { }(); } + onPressedTextChat(String id) { + gFFI.chatModel.changeCurrentKey(MessageKey(id, ChatModel.clientModeID)); + gFFI.chatModel.toggleChatOverlay(); + } + + showChatOptions(String id) async { + onPressVoiceCall() => bind.sessionRequestVoiceCall(sessionId: sessionId); + onPressEndVoiceCall() => bind.sessionCloseVoiceCall(sessionId: sessionId); + + makeTextMenu(String label, String svg, VoidCallback onPressed, + {ColorFilter? colorFilter, TextStyle? labelStyle}) => + TTextMenu( + child: Text(translate(label), style: labelStyle), + trailingIcon: Transform.scale( + scale: (isDesktop || isWebDesktop) ? 0.8 : 1, + child: IconButton( + onPressed: onPressed, + icon: SvgPicture.asset( + svg, + colorFilter: colorFilter ?? + ColorFilter.mode(MyTheme.accent, BlendMode.srcIn), + ), + ), + ), + onPressed: onPressed, + ); + + final isInVoice = [ + VoiceCallStatus.waitingForResponse, + VoiceCallStatus.connected + ].contains(gFFI.chatModel.voiceCallStatus.value); + final menus = [ + makeTextMenu( + 'Text chat', 'assets/chat.svg', () => onPressedTextChat(widget.id)), + isInVoice + ? makeTextMenu( + 'End voice call', 'assets/call_wait.svg', onPressEndVoiceCall, + colorFilter: ColorFilter.mode(Colors.redAccent, BlendMode.srcIn), + labelStyle: TextStyle(color: Colors.redAccent)) + : makeTextMenu( + 'Voice call', 'assets/call_wait.svg', onPressVoiceCall), + ]; + getChild(TTextMenu menu) { + if (menu.trailingIcon != null) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + menu.child, + menu.trailingIcon!, + ]); + } else { + return menu.child; + } + } + + final menuItems = menus + .asMap() + .entries + .map((e) => PopupMenuItem(child: getChild(e.value), value: e.key)) + .toList(); + Future.delayed(Duration.zero, () async { + final size = MediaQuery.of(context).size; + final x = 120.0; + final y = size.height; + var index = await showMenu( + context: context, + position: RelativeRect.fromLTRB(x, y, x, y), + items: menuItems, + elevation: 8, + ); + if (index != null && index < menus.length) { + menus[index].onPressed.call(); + } + }); + } + /// aka changeTouchMode BottomAppBar getGestureHelp() { return BottomAppBar( diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index a7bf12148b..7b011f5f81 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -637,40 +637,94 @@ class ConnectionManager extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium, ).marginOnly(bottom: 5), client.authorized - ? Container( - alignment: Alignment.centerRight, - child: ElevatedButton.icon( - style: ButtonStyle( - backgroundColor: - MaterialStatePropertyAll(Colors.red)), - icon: const Icon(Icons.close), - onPressed: () { - bind.cmCloseConnection(connId: client.id); - gFFI.invokeMethod( - "cancel_notification", client.id); - }, - label: Text(translate("Disconnect")))) - : Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - child: Text(translate("Dismiss")), - onPressed: () { - serverModel.sendLoginResponse( - client, false); - }).marginOnly(right: 15), - if (serverModel.approveMode != 'password') - ElevatedButton.icon( - icon: const Icon(Icons.check), - label: Text(translate("Accept")), - onPressed: () { - serverModel.sendLoginResponse( - client, true); - }), - ]), + ? _buildDisconnectButton(client) + : _buildNewConnectionHint(serverModel, client), + if (client.incomingVoiceCall && !client.inVoiceCall) + ..._buildNewVoiceCallHint(context, serverModel, client), ]))) .toList()); } + + Widget _buildDisconnectButton(Client client) { + final disconnectButton = ElevatedButton.icon( + style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(Colors.red)), + icon: const Icon(Icons.close), + onPressed: () { + bind.cmCloseConnection(connId: client.id); + gFFI.invokeMethod("cancel_notification", client.id); + }, + label: Text(translate("Disconnect")), + ); + final buttons = [disconnectButton]; + if (client.inVoiceCall) { + buttons.insert( + 0, + ElevatedButton.icon( + style: ButtonStyle( + backgroundColor: MaterialStatePropertyAll(Colors.red)), + icon: const Icon(Icons.phone), + label: Text(translate("Stop")), + onPressed: () { + bind.cmCloseVoiceCall(id: client.id); + gFFI.invokeMethod("cancel_notification", client.id); + }, + ), + ); + } + + if (buttons.length == 1) { + return Container( + alignment: Alignment.centerRight, + child: disconnectButton, + ); + } else { + return Row( + children: buttons, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + ); + } + } + + Widget _buildNewConnectionHint(ServerModel serverModel, Client client) { + return Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + TextButton( + child: Text(translate("Dismiss")), + onPressed: () { + serverModel.sendLoginResponse(client, false); + }).marginOnly(right: 15), + if (serverModel.approveMode != 'password') + ElevatedButton.icon( + icon: const Icon(Icons.check), + label: Text(translate("Accept")), + onPressed: () { + serverModel.sendLoginResponse(client, true); + }), + ]); + } + + List _buildNewVoiceCallHint( + BuildContext context, ServerModel serverModel, Client client) { + return [ + Text( + translate("android_new_voice_call_tip"), + style: Theme.of(context).textTheme.bodyMedium, + ).marginOnly(bottom: 5), + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + TextButton( + child: Text(translate("Dismiss")), + onPressed: () { + serverModel.handleVoiceCall(client, false); + }).marginOnly(right: 15), + if (serverModel.approveMode != 'password') + ElevatedButton.icon( + icon: const Icon(Icons.check), + label: Text(translate("Accept")), + onPressed: () { + serverModel.handleVoiceCall(client, true); + }), + ]) + ]; + } } class PaddingCard extends StatelessWidget { @@ -787,6 +841,15 @@ void androidChannelInit() { gFFI.serverModel.stopService(); break; } + case "msgbox": + { + var type = arguments["type"] as String; + var title = arguments["title"] as String; + var text = arguments["text"] as String; + var link = (arguments["link"] ?? '') as String; + msgBox(gFFI.sessionId, type, title, text, link, gFFI.dialogManager); + break; + } } } catch (e) { debugPrintStack(label: "MethodCallHandler err:$e"); diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 5f5a1d707c..2891241045 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -527,10 +527,16 @@ class ChatModel with ChangeNotifier { void onVoiceCallStarted() { _voiceCallStatus.value = VoiceCallStatus.connected; + if (isAndroid) { + parent.target?.invokeMethod("on_voice_call_started"); + } } void onVoiceCallClosed(String reason) { _voiceCallStatus.value = VoiceCallStatus.notStarted; + if (isAndroid) { + parent.target?.invokeMethod("on_voice_call_closed"); + } } void onVoiceCallIncoming() { diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 82cc8367ad..5fc669756c 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -550,37 +550,60 @@ class ServerModel with ChangeNotifier { } void showLoginDialog(Client client) { + showClientDialog( + client, + client.isFileTransfer ? "File Connection" : "Screen Connection", + 'Do you accept?', + 'android_new_connection_tip', + () => sendLoginResponse(client, false), + () => sendLoginResponse(client, true), + ); + } + + handleVoiceCall(Client client, bool accept) { + parent.target?.invokeMethod("cancel_notification", client.id); + bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept); + } + + showVoiceCallDialog(Client client) { + showClientDialog( + client, + 'Voice call', + 'Do you accept?', + 'android_new_voice_call_tip', + () => handleVoiceCall(client, false), + () => handleVoiceCall(client, true), + ); + } + + showClientDialog(Client client, String title, String contentTitle, + String content, VoidCallback onCancel, VoidCallback onSubmit) { parent.target?.dialogManager.show((setState, close, context) { cancel() { - sendLoginResponse(client, false); + onCancel(); close(); } submit() { - sendLoginResponse(client, true); + onSubmit(); close(); } return CustomAlertDialog( title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(translate( - client.isFileTransfer ? "File Connection" : "Screen Connection")), - IconButton( - onPressed: () { - close(); - }, - icon: const Icon(Icons.close)) + Text(translate(title)), + IconButton(onPressed: close, icon: const Icon(Icons.close)) ]), content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(translate("Do you accept?")), + Text(translate(contentTitle)), ClientInfo(client), Text( - translate("android_new_connection_tip"), + translate(content), style: Theme.of(globalKey.currentContext!).textTheme.bodyMedium, ), ], @@ -676,10 +699,14 @@ class ServerModel with ChangeNotifier { _clients[index].inVoiceCall = client.inVoiceCall; _clients[index].incomingVoiceCall = client.incomingVoiceCall; if (client.incomingVoiceCall) { - // Has incoming phone call, let's set the window on top. - Future.delayed(Duration.zero, () { - windowOnTop(null); - }); + if (isAndroid) { + showVoiceCallDialog(client); + } else { + // Has incoming phone call, let's set the window on top. + Future.delayed(Duration.zero, () { + windowOnTop(null); + }); + } } notifyListeners(); } diff --git a/src/flutter.rs b/src/flutter.rs index a0600d4564..29b0265614 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -1239,11 +1239,11 @@ pub mod connection_manager { fn add_connection(&self, client: &crate::ui_cm_interface::Client) { let client_json = serde_json::to_string(&client).unwrap_or("".into()); // send to Android service, active notification no matter UI is shown or not. - #[cfg(any(target_os = "android"))] + #[cfg(target_os = "android")] if let Err(e) = call_main_service_set_by_name("add_connection", Some(&client_json), None) { - log::debug!("call_service_set_by_name fail,{}", e); + log::debug!("call_main_service_set_by_name fail,{}", e); } // send to UI, refresh widget self.push_event("add_connection", &[("client", &client_json)]); @@ -1277,6 +1277,13 @@ pub mod connection_manager { fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) { let client_json = serde_json::to_string(&client).unwrap_or("".into()); + // send to Android service, active notification no matter UI is shown or not. + #[cfg(target_os = "android")] + if let Err(e) = + call_main_service_set_by_name("update_voice_call_state", Some(&client_json), None) + { + log::debug!("call_main_service_set_by_name fail,{}", e); + } self.push_event("update_voice_call_state", &[("client", &client_json)]); } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 00bd3099be..617b0c571f 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index e1fa0ddf16..80b602a390 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 76ae6f4d60..8b6ea652d5 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 9e0c42055d..61f6c35167 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "清除 Wayland 的屏幕选择"), ("clear_Wayland_screen_selection_tip", "清除 Wayland 的屏幕选择后,您可以重新选择分享的屏幕。"), ("confirm_clear_Wayland_screen_selection_tip", "是否确认清除 Wayland 的分享屏幕选择?"), + ("android_new_voice_call_tip", "收到新的语音呼叫请求。如果您接受,音频将切换为语音通信。"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 45872431a0..be9a10c57c 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Vymazat výběr obrazovky Wayland"), ("clear_Wayland_screen_selection_tip", "Po vymazání výběru obrazovky můžete znovu vybrat obrazovku, kterou chcete sdílet."), ("confirm_clear_Wayland_screen_selection_tip", "Opravdu chcete vymazat výběr obrazovky Wayland?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 099264b1e0..5035670729 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 2f15c64389..62b7202008 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Wayland-Bildschirmauswahl löschen"), ("clear_Wayland_screen_selection_tip", "Nachdem Sie die Bildschirmauswahl gelöscht haben, können Sie den freizugebenden Bildschirm erneut auswählen."), ("confirm_clear_Wayland_screen_selection_tip", "Sind Sie sicher, dass Sie die Auswahl des Wayland-Bildschirms löschen möchten?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 0e196ac3cb..ac08f3efbf 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 239e859522..25438ee656 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -227,5 +227,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_audio_input_device_tip", "No audio input device found."), ("clear_Wayland_screen_selection_tip", "After clearing the screen selection, you can reselect the screen to share."), ("confirm_clear_Wayland_screen_selection_tip", "Are you sure to clear the Wayland screen selection?"), + ("android_new_voice_call_tip", "A new voice call request was received. If you accept, the audio will switch to voice communication."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 2f26a586f9..fc85b75b3d 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 40b7251c5f..6ad42f992a 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 5abe567153..09535549a2 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 2785383b77..84be3cc246 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index b67838f938..05d03f43bd 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 9102a7c41d..881a2c1582 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index d2425c5868..9f91e226ad 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 993ff961e3..28020432e9 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index f9b7e2d2ba..6bcba2f768 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 57a9c470d1..e15a0edc7a 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Annulla selezione schermata Wayland"), ("clear_Wayland_screen_selection_tip", "Dopo aver annullato la selezione schermo, è possibile selezionare nuovamente lo schermo da condividere."), ("confirm_clear_Wayland_screen_selection_tip", "Sei sicuro di voler annullare la selezione schermo Wayland?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 4bf5aa6df2..f90f485c48 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index e3c9779d86..e67ae4552d 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 0ac4fc72d0..022fc3d796 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 77c79297c5..ae154bc6ac 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 0fe154f104..d1858e6051 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Notīrīt Wayland ekrāna atlasi"), ("clear_Wayland_screen_selection_tip", "Pēc ekrāna atlases notīrīšanas varat atkārtoti atlasīt ekrānu, ko kopīgot."), ("confirm_clear_Wayland_screen_selection_tip", "Vai tiešām notīrīt Wayland ekrāna atlasi?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 5a4bcb48d5..ce52a9aa6c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 6a0b2cd473..47b3d755e9 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index ada223be8c..a2a054a951 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index ca5b1becaf..e5e6c252b9 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 764e490474..53624c288f 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 7811bf1b7c..73eea0c6c7 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index ef8cb033ce..c14286e786 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Отменить выбор экрана Wayland"), ("clear_Wayland_screen_selection_tip", "После отмены можно заново выбрать экран для демонстрации."), ("confirm_clear_Wayland_screen_selection_tip", "Отменить выбор экрана Wayland?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 51f28e5309..6a62326e18 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Vyčistiť výber obrazovky Wayland"), ("clear_Wayland_screen_selection_tip", "Po vymazaní výberu obrazovky môžete znova vybrať obrazovku, ktorú chcete zdieľať."), ("confirm_clear_Wayland_screen_selection_tip", "Určite ste si istý, že chcete vyčistiť výber obrazovky Wayland?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 48f0028de0..352548e0e5 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 5497c6a213..acb149fa8e 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index e7303e0bf2..901010120d 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index d1b86e4b34..400a003156 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index e40d12145c..8166187179 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 130fb432dc..dca9cd8bc2 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 970aeb7d90..5d5d2388e3 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 86cc8bb564..8f97ede994 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "清除 Wayland 的螢幕選擇"), ("clear_Wayland_screen_selection_tip", "清除 Wayland 的螢幕選擇後,您可以重新選擇分享的螢幕。"), ("confirm_clear_Wayland_screen_selection_tip", "是否確認清除 Wayland 的分享螢幕選擇?"), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 9df9229d26..5e203d4d5c 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 2354f46e34..ea10f50ff0 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -612,5 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", ""), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), + ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 7b09640f54..da483b33a5 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -221,7 +221,7 @@ impl ConnectionManager { self.ui_handler.show_elevation(show); } - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] fn voice_call_started(&self, id: i32) { if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { client.incoming_voice_call = false; @@ -230,7 +230,7 @@ impl ConnectionManager { } } - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] fn voice_call_incoming(&self, id: i32) { if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { client.incoming_voice_call = true; @@ -239,7 +239,7 @@ impl ConnectionManager { } } - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] fn voice_call_closed(&self, id: i32, _reason: &str) { if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { client.incoming_voice_call = false; @@ -656,6 +656,15 @@ pub async fn start_listen( Some(Data::Close) => { break; } + Some(Data::StartVoiceCall) => { + cm.voice_call_started(current_id); + } + Some(Data::VoiceCallIncoming) => { + cm.voice_call_incoming(current_id); + } + Some(Data::CloseVoiceCall(reason)) => { + cm.voice_call_closed(current_id, reason.as_str()); + } None => { break; } From 53647fd58e774253775376165ee523daa8e719a1 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 May 2024 15:35:34 +0800 Subject: [PATCH 03/10] fix: mobile chat icon (#8041) Signed-off-by: fufesou --- flutter/lib/mobile/pages/remote_page.dart | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index a5c00d8a9b..281cf74197 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -419,7 +419,11 @@ class _RemotePageState extends State { : [ IconButton( color: Colors.white, - icon: Icon(Icons.message), + icon: isAndroid + ? SvgPicture.asset('assets/chat.svg', + colorFilter: ColorFilter.mode( + Colors.white, BlendMode.srcIn)) + : Icon(Icons.message), onPressed: () => isAndroid ? showChatOptions(widget.id) : onPressedTextChat(widget.id), @@ -549,19 +553,15 @@ class _RemotePageState extends State { onPressVoiceCall() => bind.sessionRequestVoiceCall(sessionId: sessionId); onPressEndVoiceCall() => bind.sessionCloseVoiceCall(sessionId: sessionId); - makeTextMenu(String label, String svg, VoidCallback onPressed, - {ColorFilter? colorFilter, TextStyle? labelStyle}) => + makeTextMenu(String label, Widget icon, VoidCallback onPressed, + {TextStyle? labelStyle}) => TTextMenu( child: Text(translate(label), style: labelStyle), trailingIcon: Transform.scale( scale: (isDesktop || isWebDesktop) ? 0.8 : 1, child: IconButton( onPressed: onPressed, - icon: SvgPicture.asset( - svg, - colorFilter: colorFilter ?? - ColorFilter.mode(MyTheme.accent, BlendMode.srcIn), - ), + icon: icon, ), ), onPressed: onPressed, @@ -572,15 +572,25 @@ class _RemotePageState extends State { VoiceCallStatus.connected ].contains(gFFI.chatModel.voiceCallStatus.value); final menus = [ - makeTextMenu( - 'Text chat', 'assets/chat.svg', () => onPressedTextChat(widget.id)), + makeTextMenu('Text chat', Icon(Icons.message, color: MyTheme.accent), + () => onPressedTextChat(widget.id)), isInVoice ? makeTextMenu( - 'End voice call', 'assets/call_wait.svg', onPressEndVoiceCall, - colorFilter: ColorFilter.mode(Colors.redAccent, BlendMode.srcIn), + 'End voice call', + SvgPicture.asset( + 'assets/call_wait.svg', + colorFilter: + ColorFilter.mode(Colors.redAccent, BlendMode.srcIn), + ), + onPressEndVoiceCall, labelStyle: TextStyle(color: Colors.redAccent)) : makeTextMenu( - 'Voice call', 'assets/call_wait.svg', onPressVoiceCall), + 'Voice call', + SvgPicture.asset( + 'assets/call_wait.svg', + colorFilter: ColorFilter.mode(MyTheme.accent, BlendMode.srcIn), + ), + onPressVoiceCall), ]; getChild(TTextMenu menu) { if (menu.trailingIcon != null) { From 3bb1c22f49948482e9941e38c1f949d1c89d8467 Mon Sep 17 00:00:00 2001 From: jxdv Date: Tue, 14 May 2024 23:11:46 +0000 Subject: [PATCH 04/10] update cs.rs (#8054) --- src/lang/cs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/cs.rs b/src/lang/cs.rs index be9a10c57c..783dcfeb74 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -612,6 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Vymazat výběr obrazovky Wayland"), ("clear_Wayland_screen_selection_tip", "Po vymazání výběru obrazovky můžete znovu vybrat obrazovku, kterou chcete sdílet."), ("confirm_clear_Wayland_screen_selection_tip", "Opravdu chcete vymazat výběr obrazovky Wayland?"), - ("android_new_voice_call_tip", ""), + ("android_new_voice_call_tip", "Byl přijat nový požadavek na hlasové volání. Pokud hovor přijmete, přepne se zvuk na hlasovou komunikaci."), ].iter().cloned().collect(); } From c5b781fb02232f164bc2d93c0dabf766a1f9cf3e Mon Sep 17 00:00:00 2001 From: jxdv Date: Tue, 14 May 2024 23:12:14 +0000 Subject: [PATCH 05/10] update sk.rs (#8053) --- src/lang/sk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 6a62326e18..2bd403dbb3 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -612,6 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Vyčistiť výber obrazovky Wayland"), ("clear_Wayland_screen_selection_tip", "Po vymazaní výberu obrazovky môžete znova vybrať obrazovku, ktorú chcete zdieľať."), ("confirm_clear_Wayland_screen_selection_tip", "Určite ste si istý, že chcete vyčistiť výber obrazovky Wayland?"), - ("android_new_voice_call_tip", ""), + ("android_new_voice_call_tip", "Bola prijatá nová žiadosť o hlasový hovor. Ak ho prijmete, zvuk sa prepne na hlasovú komunikáciu."), ].iter().cloned().collect(); } From da23e26a70a563883e4945629f6653551dd060b4 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Wed, 15 May 2024 01:12:33 +0200 Subject: [PATCH 06/10] Update Italian language (#8052) --- src/lang/it.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index e15a0edc7a..44c8dc9732 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -612,6 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Annulla selezione schermata Wayland"), ("clear_Wayland_screen_selection_tip", "Dopo aver annullato la selezione schermo, è possibile selezionare nuovamente lo schermo da condividere."), ("confirm_clear_Wayland_screen_selection_tip", "Sei sicuro di voler annullare la selezione schermo Wayland?"), - ("android_new_voice_call_tip", ""), + ("android_new_voice_call_tip", "È stata ricevuta una nuova richiesta di chiamata vocale. Se accetti, l'audio passerà alla comunicazione vocale."), ].iter().cloned().collect(); } From e8003510ef44281686d0705baa054d28b504f642 Mon Sep 17 00:00:00 2001 From: flusheDData <116861809+flusheDData@users.noreply.github.com> Date: Wed, 15 May 2024 01:15:26 +0200 Subject: [PATCH 07/10] Update es.rs (#8047) New terms added Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> --- src/lang/es.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 6ad42f992a..1951e581ec 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -605,13 +605,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_need_privacy_mode_no_physical_displays_tip", "No hay pantallas físicas, no es necesario usar el modo privado."), ("Follow remote cursor", "Seguir cursor remoto"), ("Follow remote window focus", "Seguir ventana remota activa"), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), - ("Incoming", ""), - ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), + ("default_proxy_tip", "El protocolo y puerto predeterminados es Socks5 y 1080"), + ("no_audio_input_device_tip", "No se ha encontrado ningún dispositivo de entrada de autio."), + ("Incoming", "Entrante"), + ("Outgoing", "Saliente"), + ("Clear Wayland screen selection", "Borrar la selección de pantalla Wayland"), + ("clear_Wayland_screen_selection_tip", "Tras borrar la selección de pantalla, puedes volver a seleccionarla para compartir."), + ("confirm_clear_Wayland_screen_selection_tip", "¿Seguro que deseas borrar la selección de pantalla Wayland?"), ("android_new_voice_call_tip", ""), ].iter().cloned().collect(); } From 4e5dcd827baaac3b6ee308dc6e4c32f3d7776b42 Mon Sep 17 00:00:00 2001 From: Mikhail Samodurov <20065717+easymoney322@users.noreply.github.com> Date: Wed, 15 May 2024 04:38:31 +0500 Subject: [PATCH 08/10] CI: Remove unused apt package (#8048) * Update bridge.yml Removed apt instructions for metapackages clang and llvm in favor of specific version package * Replaced apt instructions to use specific llvm and libclang version --- .github/workflows/bridge.yml | 2 -- .github/workflows/flutter-build.yml | 4 ---- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/bridge.yml b/.github/workflows/bridge.yml index d73dc7bfd6..fbaf459a4d 100644 --- a/.github/workflows/bridge.yml +++ b/.github/workflows/bridge.yml @@ -38,10 +38,8 @@ jobs: git \ g++ \ libclang-10-dev \ - libclang-dev \ libgtk-3-dev \ llvm-10-dev \ - llvm-dev \ nasm \ ninja-build \ pkg-config \ diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 0a3677b7ec..75cedac648 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -783,7 +783,6 @@ jobs: libasound2-dev \ libc6-dev \ libclang-10-dev \ - libclang-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ @@ -797,7 +796,6 @@ jobs: libxdo-dev \ libxfixes-dev \ llvm-10-dev \ - llvm-dev \ nasm \ ninja-build \ openjdk-11-jdk-headless \ @@ -1073,7 +1071,6 @@ jobs: libappindicator3-dev \ libasound2-dev \ libclang-10-dev \ - libclang-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ @@ -1087,7 +1084,6 @@ jobs: libxdo-dev \ libxfixes-dev \ llvm-10-dev \ - llvm-dev \ nasm \ ninja-build \ pkg-config \ From e01b1ed04d09e02f206712cb36e2dd85a59ba481 Mon Sep 17 00:00:00 2001 From: flusheDData <116861809+flusheDData@users.noreply.github.com> Date: Wed, 15 May 2024 03:42:07 +0200 Subject: [PATCH 09/10] Android new call tip (#8057) * Update es.rs New terms added * Update es.rs New tip added --------- Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> --- src/lang/es.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 1951e581ec..4f713ec5f5 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -612,6 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Borrar la selección de pantalla Wayland"), ("clear_Wayland_screen_selection_tip", "Tras borrar la selección de pantalla, puedes volver a seleccionarla para compartir."), ("confirm_clear_Wayland_screen_selection_tip", "¿Seguro que deseas borrar la selección de pantalla Wayland?"), - ("android_new_voice_call_tip", ""), + ("android_new_voice_call_tip", "Se ha recibido una nueva solicitud de llamada de voz. Si aceptas el audio cambiará a comunicación de voz."), ].iter().cloned().collect(); } From 42428261d7c4b2e42b62ed40e0767bb10a8431d3 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Wed, 15 May 2024 11:20:03 +0200 Subject: [PATCH 10/10] Update de.rs (#8063) --- src/lang/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 62b7202008..9e299b12e9 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -612,6 +612,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear Wayland screen selection", "Wayland-Bildschirmauswahl löschen"), ("clear_Wayland_screen_selection_tip", "Nachdem Sie die Bildschirmauswahl gelöscht haben, können Sie den freizugebenden Bildschirm erneut auswählen."), ("confirm_clear_Wayland_screen_selection_tip", "Sind Sie sicher, dass Sie die Auswahl des Wayland-Bildschirms löschen möchten?"), - ("android_new_voice_call_tip", ""), + ("android_new_voice_call_tip", "Eine neue Sprachanrufanfrage wurde empfangen. Wenn Sie die Anfrage annehmen, wird der Ton auf Sprachkommunikation umgeschaltet."), ].iter().cloned().collect(); }