diff --git a/scrcpy_android/.idea/deploymentTargetDropDown.xml b/scrcpy_android/.idea/deploymentTargetDropDown.xml
index 23521723..ea49ab6c 100644
--- a/scrcpy_android/.idea/deploymentTargetDropDown.xml
+++ b/scrcpy_android/.idea/deploymentTargetDropDown.xml
@@ -1,6 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -12,6 +23,6 @@
-
+
\ No newline at end of file
diff --git a/scrcpy_android/app/build.gradle b/scrcpy_android/app/build.gradle
index 72cfa356..5e77169b 100644
--- a/scrcpy_android/app/build.gradle
+++ b/scrcpy_android/app/build.gradle
@@ -11,8 +11,8 @@ android {
applicationId "top.saymzx.scrcpy.android"
minSdk 23
targetSdk 33
- versionCode 1211
- versionName "12.1.1"
+ versionCode 1250
+ versionName "12.5.0"
resConfigs "zh"
resConfigs "xhdpi"
ndk {
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/Adb.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/Adb.kt
index f00b2bd1..2036973b 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/Adb.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/Adb.kt
@@ -105,6 +105,8 @@ class Adb(host: String, port: Int, keyPair: AdbKeyPair) {
fun tcpForward(port: Int, isNeedSource: Boolean): AdbStream = open("tcp:$port", isNeedSource)
+ fun localSocketForward(socketName: String, isNeedSource: Boolean): AdbStream = open("localabstract:$socketName", isNeedSource)
+
// 读取线程
private fun readAdb() {
try {
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/AdbWriter.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/AdbWriter.kt
index 85e3c5a9..04875134 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/AdbWriter.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/adb/AdbWriter.kt
@@ -108,14 +108,11 @@ class AdbWriter(sink: Sink) : AutoCloseable {
bufferedSink.close()
}
- companion object {
-
- private fun payloadChecksum(payload: ByteArray): Int {
- var checksum = 0
- for (byte in payload) {
- checksum += byte.toUByte().toInt()
- }
- return checksum
+ private fun payloadChecksum(payload: ByteArray): Int {
+ var checksum = 0
+ for (byte in payload) {
+ checksum += byte.toUByte().toInt()
}
+ return checksum
}
}
\ No newline at end of file
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/MainActivity.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/MainActivity.kt
index 75eea530..689edcd4 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/MainActivity.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/MainActivity.kt
@@ -1,22 +1,27 @@
package top.saymzx.scrcpy.android
-import android.annotation.SuppressLint
-import android.app.*
-import android.content.*
-import android.content.Intent.*
-import android.media.*
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Intent
import android.net.Uri
-import android.os.*
+import android.os.Bundle
import android.provider.Settings
-import android.view.*
-import android.view.KeyEvent.*
-import android.view.MotionEvent.*
-import android.widget.*
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ArrayAdapter
+import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import top.saymzx.scrcpy.adb.Adb
+import top.saymzx.scrcpy.adb.AdbKeyPair
import top.saymzx.scrcpy.android.databinding.ActivityMainBinding
import top.saymzx.scrcpy.android.databinding.AddDeviceBinding
+import top.saymzx.scrcpy.android.databinding.EditPortBinding
import top.saymzx.scrcpy.android.entity.Scrcpy
import top.saymzx.scrcpy.android.entity.defaultAudioCodec
import top.saymzx.scrcpy.android.entity.defaultFps
@@ -26,10 +31,7 @@ import top.saymzx.scrcpy.android.entity.defaultSetResolution
import top.saymzx.scrcpy.android.entity.defaultVideoBit
import top.saymzx.scrcpy.android.entity.defaultVideoCodec
import top.saymzx.scrcpy.android.helper.AppData
-import java.io.*
-import java.util.*
-@SuppressLint("StaticFieldLeak")
lateinit var appData: AppData
class MainActivity : Activity(), ViewModelStoreOwner {
@@ -56,6 +58,33 @@ class MainActivity : Activity(), ViewModelStoreOwner {
this, ShowAppActivity::class.java
), 1
)
+ else readMode()
+ }
+
+ // 其他页面回调
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ // ShowApp页面回调
+ if (requestCode == 1) {
+ readMode()
+ }
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ // 选择模式
+ private fun readMode() {
+ if (appData.settings.getInt("appMode", 1) == 1) {
+ asMaster()
+ } else {
+ asSlave()
+ }
+ }
+
+ // 作为控制端
+ private fun asMaster() {
+ // 检查权限
+ checkPermission()
+ // 启动默认设备
+ startDefault()
// 设置设备列表适配器
setDevicesList()
// 添加按钮监听
@@ -68,42 +97,20 @@ class MainActivity : Activity(), ViewModelStoreOwner {
checkUpdate()
}
- override fun onResume() {
- // 检查权限
- if (checkPermission()) {
- // 仅在第一次启动默认设备
- if (!appData.isShowDefultDevice) {
- appData.isShowDefultDevice = true
- // 启动默认设备
- val defalueDevice = appData.settings.getString("DefaultDevice", "")
- if (defalueDevice != "") {
- for (i in appData.devices) {
- if (i.name == defalueDevice) {
- if (i.status == -1) {
- i.scrcpy = Scrcpy(i)
- i.scrcpy!!.start()
- }
- break
- }
+ // 启动默认设备
+ private fun startDefault() {
+ val defalueDevice = appData.settings.getString("DefaultDevice", "")
+ if (defalueDevice != "") {
+ for (i in appData.devices) {
+ if (i.name == defalueDevice) {
+ if (i.status == -1) {
+ i.scrcpy = Scrcpy(i)
+ i.scrcpy!!.start()
}
+ break
}
}
}
- super.onResume()
- }
-
- // 其他页面回调
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- // ShowApp页面回调
- if (requestCode == 1) {
- if (resultCode == 1) {
- appData.settings.edit().apply {
- putBoolean("FirstUse", false)
- apply()
- }
- }
- }
- super.onActivityResult(requestCode, resultCode, data)
}
// 检查权限
@@ -236,6 +243,73 @@ class MainActivity : Activity(), ViewModelStoreOwner {
}
}
+ // 作为被控端
+ private fun asSlave() {
+ // 防止多次调用
+ if (mainActivity.addDevice.visibility == View.GONE) return
+ // 取消画面
+ mainActivity.addDevice.visibility = View.GONE
+ mainActivity.set.visibility = View.GONE
+ // 要求用户输入ADB端口
+ if (!appData.settings.getBoolean("isSetSlaveAdbPort", false)) setSlaveAdbPort()
+ else slaveBack()
+ }
+
+ // 弹窗输入adb端口
+ private fun setSlaveAdbPort() {
+ val builder: AlertDialog.Builder = AlertDialog.Builder(this)
+ builder.setCancelable(false)
+ val editPortDialog = builder.create()
+ editPortDialog.setCanceledOnTouchOutside(false)
+ editPortDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ val editPortBinding = EditPortBinding.inflate(LayoutInflater.from(this))
+ editPortDialog.setView(editPortBinding.root)
+ // 设置监听
+ editPortBinding.editPortOk.setOnClickListener {
+ appData.settings.edit().apply {
+ putBoolean("isSetSlaveAdbPort", true)
+ putInt(
+ "slaveAdbPort",
+ editPortBinding.editPortPort.text.toString().toInt()
+ )
+ apply()
+ }
+ editPortDialog.cancel()
+ slaveBack()
+ }
+ editPortDialog.show()
+ }
+
+ // 恢复
+ private fun slaveBack() {
+ appData.mainScope.launch {
+ withContext(Dispatchers.IO) {
+ try {
+ val adb = Adb(
+ "127.0.0.1",
+ appData.settings.getInt("slaveAdbPort", 5555),
+ AdbKeyPair.read(appData.privateKey, appData.publicKey)
+ )
+ adb.runAdbCmd(
+ "ps aux | grep scrcpy | grep -v grep | awk '{print \$2}' | xargs kill -9", false
+ )
+ adb.runAdbCmd("wm size reset", false)
+ adb.close()
+ withContext(Dispatchers.Main) {
+ Toast.makeText(appData.main, "恢复程序执行完毕,将自动退出", Toast.LENGTH_LONG).show()
+ delay(1000)
+ finishAndRemoveTask()
+ Runtime.getRuntime().exit(0)
+ }
+ } catch (_: Exception) {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(appData.main, "连接失败", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ }
+ }
+
// ViewModel
override fun getViewModelStore(): ViewModelStore {
if (VIEWMODEL_STORE == null) {
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/SetActivity.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/SetActivity.kt
index df4a13f9..d43286f3 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/SetActivity.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/SetActivity.kt
@@ -1,6 +1,5 @@
package top.saymzx.scrcpy.android
-import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
@@ -23,9 +22,7 @@ import top.saymzx.scrcpy.android.entity.defaultSetResolution
import top.saymzx.scrcpy.android.entity.defaultVideoBit
import top.saymzx.scrcpy.android.entity.defaultVideoCodec
import java.io.File
-import java.io.FileNotFoundException
import java.io.FileOutputStream
-import java.io.IOException
class SetActivity : Activity() {
private lateinit var setActivity: ActivitySetBinding
@@ -346,7 +343,6 @@ class SetActivity : Activity() {
Toast.makeText(this, "请不要选择Download或其他隐私位置", Toast.LENGTH_LONG).show()
}
- @SuppressLint("Range")
override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?
) {
@@ -430,10 +426,7 @@ class SetActivity : Activity() {
}
}
}
- } catch (e: FileNotFoundException) {
- e.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
+ } catch (_: Exception) {
}
}
@@ -461,10 +454,7 @@ class SetActivity : Activity() {
}
}
}
- } catch (e: FileNotFoundException) {
- e.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
+ } catch (_: Exception) {
}
return ""
}
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/ShowAppActivity.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/ShowAppActivity.kt
index 20029022..a289d1e5 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/ShowAppActivity.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/ShowAppActivity.kt
@@ -1,10 +1,14 @@
package top.saymzx.scrcpy.android
import android.app.Activity
+import android.app.AlertDialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import android.view.LayoutInflater
+import android.widget.Toast
import top.saymzx.scrcpy.android.databinding.ActivityShowAppBinding
+import top.saymzx.scrcpy.android.databinding.ModeSelectBinding
class ShowAppActivity : Activity() {
@@ -14,6 +18,14 @@ class ShowAppActivity : Activity() {
showAppActivity = ActivityShowAppBinding.inflate(layoutInflater)
setContentView(showAppActivity.root)
appData.publicTools.setStatusAndNavBar(this)
+ // 设置隐私用户政策
+ setUserPriListener()
+ // 设置同意按钮
+ setAgreeListener()
+ }
+
+ // 设置隐私用户政策
+ private fun setUserPriListener() {
// 设置隐私政策链接
showAppActivity.showAppPrivacy.setOnClickListener {
try {
@@ -36,10 +48,43 @@ class ShowAppActivity : Activity() {
} catch (_: Exception) {
}
}
- // 设置下一步按钮
+ }
+
+ // 设置同意按钮
+ private lateinit var modeSelectDialog: AlertDialog
+ private fun setAgreeListener() {
showAppActivity.showAppAgree.setOnClickListener {
- setResult(1)
- finish()
+ // 弹窗选择模式
+ val builder: AlertDialog.Builder = AlertDialog.Builder(this)
+ builder.setCancelable(false)
+ modeSelectDialog = builder.create()
+ modeSelectDialog.setCanceledOnTouchOutside(false)
+ modeSelectDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ val modeSelectBinding = ModeSelectBinding.inflate(LayoutInflater.from(this))
+ modeSelectDialog.setView(modeSelectBinding.root)
+ modeSelectBinding.modeSelectMaster.setOnClickListener {
+ saveSet(1)
+ }
+ modeSelectBinding.modeSelectSlave.setOnClickListener {
+ saveSet(0)
+ }
+ modeSelectDialog.show()
}
}
+
+ // 保存设置
+ private fun saveSet(mode: Int) {
+ appData.settings.edit().apply {
+ putInt("appMode", mode)
+ putBoolean("FirstUse", false)
+ apply()
+ }
+ modeSelectDialog.cancel()
+ finish()
+ }
+
+ // 禁止返回上一级
+ override fun onBackPressed() {
+ Toast.makeText(this, "请先同意用户及隐私协议", Toast.LENGTH_SHORT).show()
+ }
}
\ No newline at end of file
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/FloatVideo.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/FloatVideo.kt
index 30440c56..2b7a82b6 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/FloatVideo.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/FloatVideo.kt
@@ -16,9 +16,9 @@ import top.saymzx.scrcpy.android.databinding.FloatNavBinding
import top.saymzx.scrcpy.android.databinding.FloatVideoBinding
import java.nio.ByteBuffer
import java.util.*
+import kotlin.math.abs
import kotlin.math.sqrt
-
@SuppressLint("ClickableViewAccessibility", "InternalInsetResource", "DiscouragedApi")
class FloatVideo(
private val device: Device,
@@ -488,16 +488,31 @@ class FloatVideo(
// 设置悬浮窗大小拖动按钮监听控制
private fun setSetSizeListener() {
+ var width = 0f
+ val maxCal = appData.publicTools.dp2px(30f)
floatVideo.floatVideoSetSize.setOnTouchListener { _, event ->
setFocus(true)
- if (event.actionMasked == MotionEvent.ACTION_MOVE) {
- // 计算新大小(等比缩放)
- val tmpWidth = event.rawX - floatVideoParams.x
- val tmpHeight = tmpWidth * remoteVideoHeight / remoteVideoWidth
- // 最小300个像素
- if (tmpWidth < 300 || tmpHeight < 300) return@setOnTouchListener true
- calculateFloatSize(tmpWidth.toInt(), tmpHeight.toInt())
- update(true)
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ width = event.rawX - floatVideoParams.x
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ // 计算新大小(等比缩放)
+ val tmpWidth = event.rawX - floatVideoParams.x
+ val tmpHeight = tmpWidth * remoteVideoHeight / remoteVideoWidth
+ // 最小300个像素
+ if (tmpWidth < 300 || tmpHeight < 300) return@setOnTouchListener true
+ calculateFloatSize(tmpWidth.toInt(), tmpHeight.toInt())
+ if (abs(tmpWidth - width) > maxCal) {
+ width = tmpWidth
+ update(true)
+ } else update(false)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ update(true)
+ }
}
return@setOnTouchListener true
}
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/Scrcpy.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/Scrcpy.kt
index 29a2409c..a8c3f03f 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/Scrcpy.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/entity/Scrcpy.kt
@@ -22,6 +22,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import okhttp3.internal.notify
+import okhttp3.internal.wait
+import okio.Buffer
import top.saymzx.scrcpy.adb.Adb
import top.saymzx.scrcpy.adb.AdbKeyPair
import top.saymzx.scrcpy.adb.AdbStream
@@ -31,7 +34,6 @@ import top.saymzx.scrcpy.android.appData
import java.net.Inet4Address
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
-import java.util.concurrent.LinkedBlockingQueue
class Scrcpy(private val device: Device) {
@@ -132,7 +134,7 @@ class Scrcpy(private val device: Device) {
stop("投屏停止", e)
}
}.start()
- // 控制报文
+ // 控制
Thread {
android.os.Process.setThreadPriority(THREAD_PRIORITY_LOWEST)
try {
@@ -141,7 +143,6 @@ class Scrcpy(private val device: Device) {
stop("投屏停止", e)
}
}.start()
- // 控制报文
Thread {
android.os.Process.setThreadPriority(THREAD_PRIORITY_MORE_FAVORABLE)
try {
@@ -154,10 +155,13 @@ class Scrcpy(private val device: Device) {
}
// 停止投屏
+ private val stopSetStatus = Object()
fun stop(scrcpyError: String, e: Exception? = null) {
// 防止多次调用
- if (device.status == -1) return
- device.status = -1
+ synchronized(stopSetStatus) {
+ if (device.status == -1) return
+ device.status = -1
+ }
appData.isFocus = false
appData.mainScope.launch {
withContext(Dispatchers.Main) {
@@ -289,14 +293,14 @@ class Scrcpy(private val device: Device) {
if (device.status == -1) return@withContext
try {
if (connect == 0) {
- videoStream = adb.tcpForward(6006, true)
+ videoStream = adb.localSocketForward("scrcpy_android", true)
connect = 1
}
if (connect == 1) {
- audioStream = adb.tcpForward(6006, true)
+ audioStream = adb.localSocketForward("scrcpy_android", true)
connect = 2
}
- controlStream = adb.tcpForward(6006, true)
+ controlStream = adb.localSocketForward("scrcpy_android", true)
break
} catch (_: Exception) {
Log.i("Scrcpy", "连接失败,再次尝试")
@@ -317,8 +321,10 @@ class Scrcpy(private val device: Device) {
// 显示悬浮窗
floatVideo =
FloatVideo(device, remoteVideoWidth, remoteVideoHeight) {
- hasControls = true
- controls.offer(it)
+ controls.write(it)
+ synchronized(controls) {
+ controls.notify()
+ }
}
}
floatVideo.show()
@@ -353,8 +359,12 @@ class Scrcpy(private val device: Device) {
// 启动解码器
videoDecodec.start()
// 解析首帧,解决开始黑屏问题
- pushCodec(videoDecodec, csd0)
- pushCodec(videoDecodec, csd1)
+ var inIndex = videoDecodec.dequeueInputBuffer(-1)
+ videoDecodec.getInputBuffer(inIndex)!!.put(csd0)
+ videoDecodec.queueInputBuffer(inIndex, 0, csd0.size, 0, 0)
+ inIndex = videoDecodec.dequeueInputBuffer(-1)
+ videoDecodec.getInputBuffer(inIndex)!!.put(csd1)
+ videoDecodec.queueInputBuffer(inIndex, 0, csd1.size, 0, 0)
}
// 音频解码器
@@ -397,7 +407,7 @@ class Scrcpy(private val device: Device) {
// 初始化音频播放器
private fun setAudioTrack() {
val audioDecodecBuild = AudioTrack.Builder()
- val sampleRate = 48000
+ val sampleRate = 44100
val minBufferSize = AudioTrack.getMinBufferSize(
sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT
)
@@ -430,13 +440,18 @@ class Scrcpy(private val device: Device) {
// 开始解码
while (device.status == 1) {
val buffer = readFrame(stream)
- pushCodec(codec, buffer)
- // 连续4个空包检测是否熄屏了
- if (buffer.size < 150) {
- zeroFrameNum++
- if (zeroFrameNum > 4) {
- zeroFrameNum = 0
- checkScreenOff()
+ val inIndex = codec.dequeueInputBuffer(-1)
+ if (inIndex >= 0) {
+ codec.getInputBuffer(inIndex)!!.put(buffer)
+ // 提交解码器解码
+ codec.queueInputBuffer(inIndex, 0, buffer.size, 0, 0)
+ // 连续4个空包检测是否熄屏了
+ if (buffer.size < 150) {
+ zeroFrameNum++
+ if (zeroFrameNum > 4) {
+ zeroFrameNum = 0
+ checkScreenOff()
+ }
}
}
}
@@ -448,7 +463,7 @@ class Scrcpy(private val device: Device) {
val bufferInfo = MediaCodec.BufferInfo()
while (device.status == 1) {
// 找到已完成的输出缓冲区
- outIndex = videoDecodec.dequeueOutputBuffer(bufferInfo, 0)
+ outIndex = videoDecodec.dequeueOutputBuffer(bufferInfo, -1)
if (outIndex >= 0) {
// 是否需要检查旋转(仍需要检查,因为可能是90°和270°的旋转)
if (checkRotationNotification) {
@@ -462,9 +477,6 @@ class Scrcpy(private val device: Device) {
}
}
videoDecodec.releaseOutputBuffer(outIndex, true)
- } else if (outIndex != MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- Thread.sleep(4)
- continue
}
}
}
@@ -473,7 +485,13 @@ class Scrcpy(private val device: Device) {
private fun decodeAudioOutput() {
var outIndex: Int
val bufferInfo = MediaCodec.BufferInfo()
+ var loopNum = 0
while (device.status == 1) {
+ loopNum++
+ if (loopNum > 200) {
+ loopNum = 0
+ checkClipBoard()
+ }
// 找到已完成的输出缓冲区
outIndex = audioDecodec.dequeueOutputBuffer(bufferInfo, 0)
if (outIndex >= 0) {
@@ -534,41 +552,16 @@ class Scrcpy(private val device: Device) {
}
// 控制报文输出
- private val controls = LinkedBlockingQueue()
- private var hasControls = false
+ private val controls = Buffer()
private fun setControlOutput() {
- var loopNum = 0
while (device.status == 1) {
- loopNum++
- if (loopNum > 200) {
- loopNum = 0
- checkClipBoard()
- }
- if (!hasControls) {
- Thread.sleep(4)
- continue
+ if (!controls.request(1)) synchronized(controls) {
+ controls.wait()
}
- val buffer = controls.poll()
- if (buffer == null) {
- hasControls = false
- continue
- }
- controlStream.write(buffer)
+ controlStream.write(controls.readByteArray())
}
}
- // 向解码器输入
- private fun pushCodec(codec: MediaCodec, buffer: ByteArray) {
- var inIndex: Int
- // 找到一个空的输入缓冲区
- do {
- inIndex = codec.dequeueInputBuffer(0)
- } while (inIndex <= 0)
- codec.getInputBuffer(inIndex)!!.put(buffer)
- // 提交解码器解码
- codec.queueInputBuffer(inIndex, 0, buffer.size, 0, 0)
- }
-
// 防止被控端熄屏
private var isScreenOning = false
private fun checkScreenOff() {
@@ -579,7 +572,8 @@ class Scrcpy(private val device: Device) {
if (!runAdbCmd("dumpsys deviceidle | grep mScreenOn", true).contains("mScreenOn=true")) {
isScreenOning = true
runAdbCmd("input keyevent 26", false)
- delay(500)
+ delay(1000)
+ setPowerOff()
isScreenOning = false
}
} catch (_: Exception) {
@@ -591,8 +585,10 @@ class Scrcpy(private val device: Device) {
// 被控端熄屏
private fun setPowerOff() {
- hasControls = true
- controls.offer(byteArrayOf(10, 0))
+ controls.write(byteArrayOf(10, 0))
+ synchronized(controls) {
+ controls.notify()
+ }
}
// 同步本机剪切板至被控端
@@ -617,8 +613,10 @@ class Scrcpy(private val device: Device) {
byteBuffer.putInt(textByteArray.size)
byteBuffer.put(textByteArray)
byteBuffer.flip()
- hasControls = true
- controls.offer(byteBuffer.array())
+ controls.write(byteBuffer)
+ synchronized(controls) {
+ controls.notify()
+ }
}
// 从socket流中解析数据
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/AppData.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/AppData.kt
index d09e406c..5de1e50f 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/AppData.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/AppData.kt
@@ -15,16 +15,11 @@ import top.saymzx.scrcpy.android.MainActivity
import top.saymzx.scrcpy.android.entity.Device
import java.io.File
-@SuppressLint("Range")
class AppData : ViewModel() {
// 是否初始化
var isInit = false
- // 是否显示默认设备
- var isShowDefultDevice = false
-
- @SuppressLint("StaticFieldLeak")
lateinit var main: MainActivity
// 是否处于专注模式
@@ -37,10 +32,10 @@ class AppData : ViewModel() {
// 公共工具库
val publicTools = PublicTools()
- // 数据库管理
+ // 数据库工具
lateinit var dbHelper: DbHelper
- // 网络管理
+ // 网络工具
val netHelper = NetHelper()
// 设备列表管理
@@ -98,6 +93,7 @@ class AppData : ViewModel() {
}
// 读取数据库设备列表
+ @SuppressLint("Range")
private fun readDeviceList() {
val cursor = dbHelper.readableDatabase.query("DevicesDb", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/DeviceListAdapter.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/DeviceListAdapter.kt
index dde008bc..76d7aa86 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/DeviceListAdapter.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/DeviceListAdapter.kt
@@ -1,6 +1,5 @@
package top.saymzx.scrcpy.android.helper
-import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.ContentValues
import android.view.View
@@ -226,7 +225,6 @@ class DeviceListAdapter : BaseAdapter() {
}
//新建数据
- @SuppressLint("NotifyDataSetChanged")
fun newDevice(
name: String,
address: String,
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/NetHelper.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/NetHelper.kt
index afb64483..8d9f1c34 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/NetHelper.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/NetHelper.kt
@@ -12,7 +12,6 @@ import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import top.saymzx.scrcpy.android.appData
-
class NetHelper {
private val okhttpClient = OkHttpClient()
diff --git a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/PublicTools.kt b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/PublicTools.kt
index fb3708fa..53c2dc59 100644
--- a/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/PublicTools.kt
+++ b/scrcpy_android/app/src/main/java/top/saymzx/scrcpy/android/helper/PublicTools.kt
@@ -57,7 +57,7 @@ class PublicTools {
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setCancelable(false)
val loadingDialog = builder.create()
- loadingDialog.setCanceledOnTouchOutside(false)
+ loadingDialog.setCanceledOnTouchOutside(isCanCancel)
loadingDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
val loadingBinding = LoadingBinding.inflate(LayoutInflater.from(context))
loadingDialog.setView(loadingBinding.root)
diff --git a/scrcpy_android/app/src/main/res/layout/activity_main.xml b/scrcpy_android/app/src/main/res/layout/activity_main.xml
index c23ca2c2..70cbdc57 100644
--- a/scrcpy_android/app/src/main/res/layout/activity_main.xml
+++ b/scrcpy_android/app/src/main/res/layout/activity_main.xml
@@ -13,7 +13,7 @@
android:id="@+id/set"
android:layout_width="36dp"
android:layout_height="36dp"
- android:layout_marginTop="12dp"
+ android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:src="@drawable/set"
app:layout_constraintEnd_toEndOf="parent"
@@ -24,12 +24,12 @@
android:id="@+id/devices_list"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_marginStart="30dp"
- android:layout_marginTop="60dp"
- android:layout_marginEnd="30dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginTop="64dp"
+ android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:divider="#00000000"
- android:dividerHeight="10dp"
+ android:dividerHeight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/scrcpy_back/app/src/main/res/layout/edit_port.xml b/scrcpy_android/app/src/main/res/layout/edit_port.xml
similarity index 82%
rename from scrcpy_back/app/src/main/res/layout/edit_port.xml
rename to scrcpy_android/app/src/main/res/layout/edit_port.xml
index 648960ca..0c55816c 100644
--- a/scrcpy_back/app/src/main/res/layout/edit_port.xml
+++ b/scrcpy_android/app/src/main/res/layout/edit_port.xml
@@ -8,7 +8,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background"
- android:backgroundTint="@color/alertBackground"
+ android:backgroundTint="@color/cardBackground"
android:orientation="vertical">
+ android:textColor="@color/onCardBackground"
+ android:textColorHint="@color/onCardBackgroundSecond" />
diff --git a/scrcpy_android/app/src/main/res/layout/mode_select.xml b/scrcpy_android/app/src/main/res/layout/mode_select.xml
new file mode 100644
index 00000000..cc0133c8
--- /dev/null
+++ b/scrcpy_android/app/src/main/res/layout/mode_select.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scrcpy_android/app/src/main/res/values/size.xml b/scrcpy_android/app/src/main/res/values/size.xml
index 240bd3b8..746b6fdb 100644
--- a/scrcpy_android/app/src/main/res/values/size.xml
+++ b/scrcpy_android/app/src/main/res/values/size.xml
@@ -4,11 +4,11 @@
24sp
18sp
- 24dp
+ 20dp
34dp
10dp
9dp
- 6dp
+ 8dp
200dp
300dp
80dp
diff --git a/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
index 671c372f..d707c613 100644
--- a/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
+++ b/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
@@ -17,7 +17,7 @@
public final class AudioCapture {
- public static final int SAMPLE_RATE = 48000;
+ public static final int SAMPLE_RATE = 44100;
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
public static final int CHANNELS = 2;
public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT;
diff --git a/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
index b77b7ffc..aadcc1fc 100644
--- a/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
+++ b/scrcpy_android/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
@@ -1,16 +1,14 @@
package com.genymobile.scrcpy;
+import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import java.io.Closeable;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable {
@@ -19,20 +17,20 @@ public final class DesktopConnection implements Closeable {
private static final String SOCKET_NAME_PREFIX = "scrcpy";
- private final Socket videoSocket;
+ private final LocalSocket videoSocket;
private final FileDescriptor videoFd;
- private final Socket audioSocket;
+ private final LocalSocket audioSocket;
private final FileDescriptor audioFd;
- private final Socket controlSocket;
+ private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final OutputStream controlOutputStream;
private final ControlMessageReader reader = new ControlMessageReader();
private final DeviceMessageWriter writer = new DeviceMessageWriter();
- private DesktopConnection(Socket videoSocket, Socket audioSocket, Socket controlSocket) throws IOException {
+ private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket;
this.controlSocket = controlSocket;
this.audioSocket = audioSocket;
@@ -43,8 +41,8 @@ private DesktopConnection(Socket videoSocket, Socket audioSocket, Socket control
controlInputStream = null;
controlOutputStream = null;
}
- videoFd = videoSocket != null ? ((FileOutputStream) videoSocket.getOutputStream()).getFD() : null;
- audioFd = audioSocket != null ? ((FileOutputStream) audioSocket.getOutputStream()).getFD() : null;
+ videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null;
+ audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null;
}
private static LocalSocket connect(String abstractName) throws IOException {
@@ -64,13 +62,13 @@ private static String getSocketName(int scid) {
public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte)
throws IOException {
- String socketName = getSocketName(scid);
+// String socketName = getSocketName(scid);
- Socket videoSocket = null;
- Socket audioSocket = null;
- Socket controlSocket = null;
+ LocalSocket videoSocket = null;
+ LocalSocket audioSocket = null;
+ LocalSocket controlSocket = null;
try {
- try (ServerSocket localServerSocket = new ServerSocket(6006)) {
+ try (LocalServerSocket localServerSocket = new LocalServerSocket("scrcpy_android")) {
if (video) {
videoSocket = localServerSocket.accept();
if (sendDummyByte) {
@@ -112,7 +110,7 @@ public static DesktopConnection open(int scid, boolean tunnelForward, boolean vi
return new DesktopConnection(videoSocket, audioSocket, controlSocket);
}
- private Socket getFirstSocket() {
+ private LocalSocket getFirstSocket() {
if (videoSocket != null) {
return videoSocket;
}
@@ -148,7 +146,7 @@ public void sendDeviceMeta(String deviceName) throws IOException {
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
// byte[] are always 0-initialized in java, no need to set '\0' explicitly
- FileDescriptor fd = ((FileOutputStream) getFirstSocket().getOutputStream()).getFD();
+ FileDescriptor fd = getFirstSocket().getFileDescriptor();
IO.writeFully(fd, buffer, 0, buffer.length);
}
@@ -172,4 +170,4 @@ public ControlMessage receiveControlMessage() throws IOException {
public void sendDeviceMessage(DeviceMessage msg) throws IOException {
writer.writeTo(msg, controlOutputStream);
}
-}
+}
\ No newline at end of file
diff --git a/scrcpy_back/.idea/.name b/scrcpy_back/.idea/.name
deleted file mode 100644
index 748ec48c..00000000
--- a/scrcpy_back/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-scrcpy-back
\ No newline at end of file
diff --git a/scrcpy_back/.idea/codeStyles/Project.xml b/scrcpy_back/.idea/codeStyles/Project.xml
deleted file mode 100644
index 7643783a..00000000
--- a/scrcpy_back/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/codeStyles/codeStyleConfig.xml b/scrcpy_back/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123c..00000000
--- a/scrcpy_back/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/compiler.xml b/scrcpy_back/.idea/compiler.xml
deleted file mode 100644
index b589d56e..00000000
--- a/scrcpy_back/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/gradle.xml b/scrcpy_back/.idea/gradle.xml
deleted file mode 100644
index ae388c2a..00000000
--- a/scrcpy_back/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/kotlinc.xml b/scrcpy_back/.idea/kotlinc.xml
deleted file mode 100644
index 69e86158..00000000
--- a/scrcpy_back/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/misc.xml b/scrcpy_back/.idea/misc.xml
deleted file mode 100644
index 773fe0fb..00000000
--- a/scrcpy_back/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/.idea/vcs.xml b/scrcpy_back/.idea/vcs.xml
deleted file mode 100644
index 6c0b8635..00000000
--- a/scrcpy_back/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/app/.gitignore b/scrcpy_back/app/.gitignore
deleted file mode 100644
index 42afabfd..00000000
--- a/scrcpy_back/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/scrcpy_back/app/build.gradle b/scrcpy_back/app/build.gradle
deleted file mode 100644
index 1f2355e9..00000000
--- a/scrcpy_back/app/build.gradle
+++ /dev/null
@@ -1,46 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
-}
-
-android {
- namespace 'top.saymzx.scrcpy.back'
- compileSdk 33
-
- defaultConfig {
- applicationId "top.saymzx.scrcpy.back"
- minSdk 24
- targetSdk 33
- versionCode 90
- versionName "9.0"
- resConfigs "zh"
- resConfigs "xhdpi"
- ndk {
- abiFilter "arm64-v8a"
- }
- }
-
- buildTypes {
- release {
- minifyEnabled true
- shrinkResources true
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- packagingOptions {
- resources.excludes.add("META-INF/*")
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.10.1'
- implementation("dev.mobile:dadb:1.2.6")
-}
\ No newline at end of file
diff --git a/scrcpy_back/app/proguard-rules.pro b/scrcpy_back/app/proguard-rules.pro
deleted file mode 100644
index ec25b711..00000000
--- a/scrcpy_back/app/proguard-rules.pro
+++ /dev/null
@@ -1,22 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
--keep public class top.saymzx.scrcpy.*
\ No newline at end of file
diff --git a/scrcpy_back/app/src/main/AndroidManifest.xml b/scrcpy_back/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 25f3fc96..00000000
--- a/scrcpy_back/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/scrcpy_back/app/src/main/java/top/saymzx/scrcpy/back/MainActivity.kt b/scrcpy_back/app/src/main/java/top/saymzx/scrcpy/back/MainActivity.kt
deleted file mode 100644
index 46db4593..00000000
--- a/scrcpy_back/app/src/main/java/top/saymzx/scrcpy/back/MainActivity.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package top.saymzx.scrcpy.back
-
-import android.app.Activity
-import android.app.AlertDialog
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.os.Build
-import android.os.Bundle
-import android.service.quicksettings.TileService
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.WindowManager
-import android.widget.Button
-import android.widget.EditText
-import android.widget.Toast
-import dadb.AdbKeyPair
-import dadb.Dadb
-import java.io.File
-
-class MainActivity : Activity() {
- lateinit var settings: SharedPreferences
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- // 全屏显示
- window.decorView.systemUiVisibility =
- (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
- // 设置异形屏
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- window.attributes.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
- }
- // 隐藏标题栏
- actionBar?.hide()
- settings = getSharedPreferences("setting", Context.MODE_PRIVATE)
- // 首次打开
- if (settings.getBoolean("FirstUse", true)) {
- val builder: AlertDialog.Builder = AlertDialog.Builder(this)
- builder.setCancelable(false)
- val editPortDialog = builder.create()
- editPortDialog.setCanceledOnTouchOutside(false)
- editPortDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
- val editPort = LayoutInflater.from(this).inflate(R.layout.edit_port, null, false)
- editPortDialog.setView(editPort)
- // 设置监听
- editPort.findViewById