python实现Android实时投屏操控

这篇具有很好参考价值的文章主要介绍了python实现Android实时投屏操控。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

scrcpy-client

        python中有一个scrcpy-client库,可以实现Android设备的实时投屏和操控。它和scrcpy实现Android投屏是一样的,都是把一个scrcpy-server.jar文件通过adb推送到Android设备,并利用adb指令执行scrcpy-server.jar开启投屏和操控服务端,电脑端通过python创建客户端来接收视频流数据和发送控制流数据。视频流数据中就是Android实时屏幕数据,控制流数据就是我们在电脑端对Android设备做的操控动作。在scrcpy-client库中作者提供了一个使用PySide6搭建的投屏控制UI界面,可以完成单台Android设备的投屏控制,我们可以自行制作投屏控制界面,完成多台Android设备的投屏控制。

安装指令:pip3 install scrcpy-client

直接使用

        安装好scrcpy-client库后,我们可以通过直接使用作者提供的ui界面来投屏Android设备。

import scrcpy_ui


scrcpy_ui.main()

确保我们的电脑上通过USB连接了一台Android设备,并且Android设备打开了USB调试功能,已允许电脑调式。这时我们就可以通过执行上面的代码,得到Android设备的投屏UI界面,如下图所示:

scrcpy投屏python,Android自动化测试,android,python,测试工具

在这个界面中我们可以使用鼠标在投屏界面点击、滑动,控制Android设备的屏幕。可以通过设备序列号下拉框切换设备,Flip勾选后可以得到镜像屏幕。下方的HOME按钮点击后回到主屏幕,相当于按设备的home键。BACK按钮点击后会返回上一个界面,相当于按设备上的back键。还支持键盘输入,我们可以通过电脑的键盘让Android产生按键事件。

自定义使用

        如果你觉得作者提供的UI界面不能满足你的需求,我们还可以自定义UI界面来实现更多的操作方式。前提是你要会使用PySide6这种UI框架,你必须知道如何在UI界面中使用动态元素,如何实现鼠标点击、移动事件,鼠标滚轮滚动事件,键盘输入事件。如果你还不会使用UI界面相关的框架,可以先去学习一下。如果你会UI相关的框架,就接着往下看。

创建投屏服务

        使用scrcpy中的Client类建立投屏控制服务,Client类中的实例化方法如下:

class Client:
    def __init__(
        self,
        device: Optional[Union[AdbDevice, str, any]] = None,
        max_width: int = 0,
        bitrate: int = 8000000,
        max_fps: int = 0,
        flip: bool = False,
        block_frame: bool = False,
        stay_awake: bool = False,
        lock_screen_orientation: int = LOCK_SCREEN_ORIENTATION_UNLOCKED,
        connection_timeout: int = 3000,
        encoder_name: Optional[str] = None,
    ):

device:Android设备的设备序列号(使用adb devices指令可以查看到)。

max_width:图像帧的最大宽度,默认使用Android广播信息中的帧宽度。

bitrate:比特率,默认8000000比特。

max_fps:最大帧数,默认不限制帧数。

flip:翻转图像(镜像图像),默认不镜像。

block_frame:返回非空帧,默认不返回,返回非空帧可能会阻塞openCv2的渲染线程。

stay_awake:连接USB时Android设备屏幕保持常亮,默认不保持常亮。

lock_screen_orientation:锁定屏幕方向(禁止自动旋转屏幕),默认不锁定。

connection_timeout:连接投屏控制服务(socket服务)超时时间,设定时间内未能成功连接则初始化失败,默认3000毫秒。

encoder_name:编码器名称,可选OMX.google.h264.encoder、OMX.qcom.video.encoder.avc、 c2.qti.avc.encoder、c2.android.avc.encoder,默认自动选择。

        我们通过实例化Client类来得到一个Android设备的投屏控制服务对象,假如Android设备的序列号为123456789,我们可以通过如下代码创建投屏控制服务实例对象:

import scrcpy


server = scrcpy.Client(device='123456789', bitrate=100000000)

start方法

        start是Client的实例方法用于启动投屏控制服务,start可以接收两个参数,一个是threaded,默认为False,为True时表示在子线程中开启投屏控制服务;另一个是daemon_threaded,默认为False,为True时表示给子线程开启进程守护。threaded和daemon_threaded有一个为True时,都会在子线程中开启投屏控制服务,都为False时表示在主线程中开启投屏控制服务。要同时开启多个投屏控制服务时,就需要在子线程中开启投屏控制服务,自己创建子线程也是可以的。

server.start()

add_listener方法

        add_listener是Client的实例方法用于设置监听字典listeners,listeners字典中有两个元素,第一个元素的键为frame表示图像帧元素,第二个元素为init表示初始化元素。两个元素的值都是一个空列表,用来存放函数的。如果想把Android设备的屏幕图像放到UI界面的某个元素上,就需要在UI框架中写一个能接收图像、显示图像的方法,再把这个方法添加到listeners字典的第一个元素列表中。如果想在建立投屏控制服务时做一些操作,就在UI框架中写一个操作相关的方法,在把这个方法放到listeners字典的第二个元素列表中。

    def on_frame(self, frame):  # 在使用PySide6的UI框架中定义了一个用于显示图像的方法
        app.processEvents()
        if frame is not None:
            ratio = self.max_width / max(self.client.resolution)
            image = QImage(
                frame,
                frame.shape[1],
                frame.shape[0],
                frame.shape[1] * 3,
                QImage.Format_BGR888,
            )
            pix = QPixmap(image)  # 处理图像
            pix.setDevicePixelRatio(1 / ratio)  # 设置图像大小
            self.ui.label.setPixmap(pix)  # 在UI界面显示图像
            self.resize(1, 1)

在初始化UI界面时把显示图像的方法加入到listener字典的第一个元素中(key='frame')。

    def __init__(self):
        super().__init()
        self.server = scrcpy.Client(device='123456789', bitrate=100000000)
        self.server.add_listener(scrcpy.EVENT_FRAME, self.on_frame)

这里只是举例,实际上的方法需要根据你的需求自己写。如果想要把Android设备的图像展示在UI界面的某个元素上,就必须写一个展示图像的方法,再把这个方法添加到listener字典的第一个元素中(key='frame')。

remove_listener方法

        remove_listener方法是Client的实例方法用于移除监听字典listeners中的某个方法,如果我们想不再显示图像时,可以把显示图像的方法从listeners中移除掉。

    def no_display(self):
        self.server.remove_listener(scrcpy.EVENT_FRAME, self.on_frame)

stop方法

        stop方法是Client的实例方法用于结束投屏控制服务,在我们关闭投屏UI界面时需要结束掉投屏控制服务,及时释放内存资源。

    def closeEvent(self, _):
        self.server.stop()

控制方法

        我们已经把Android设备的屏幕投射到了电脑上,现在就需要通过一些控制Android设备的方法来操作Android设备。Client的实例属性中有一个control属性,是通过实例化ControlSender类来得到的,ControlSender类就是专门用来控制Android设备的操作类。

    self.control = ControlSender(self)

所以我们想要控制Android就需要通过投屏控制对象的control属性。

keycode方法

    @inject(const.TYPE_INJECT_KEYCODE)
    def keycode(
        self, keycode: int, action: int = const.ACTION_DOWN, repeat: int = 0
    ) -> bytes:

        keycode方法是ControlSender类的实例方法用于向Android设备发送按键事件。keycode方法可以接收3个参数,第一个参数keycode表示键值(你需要了解adb键值);第二个参数action表示按下还是抬起,默认是按下;第三个参数repeat表示重复操作次数,想重复按几次。

    def click_home(self):
        self.server.control.keycode(scrcpy.KEYCODE_HOME, scrcpy.ACTION_DOWN)
        self.server.control.keycode(scrcpy.KEYCODE_HOME, scrcpy.ACTION_UP)

点击home键,先按下再抬起,完成一次按键。

text方法

    @inject(const.TYPE_INJECT_TEXT)
    def text(self, text: str) -> bytes:

        text方法是ControlSender类的实例方法用于向Android中输入文本,前提是Android设备中的某个输入框被激活了。text方法接收一个参数,就是我们要在Android设备中输入的文本内容。

    def input_text(self, text):
        self.server.control.text(text)

touch方法

    def touch(
        self, x: int, y: int, action: int = const.ACTION_DOWN, touch_id: int = -1
    ) -> bytes:

        touch方法是ControlSender类的实例方法用于Android设备屏幕的多点触控。touch方法可以接收4个参数,前两个参数为触点的x坐标和y坐标;第三个参数action为按下、移动、抬起;第四个参数为触控事件id,默认为-1,你可以设置不同的id来同时执行多个触控事件,达到多点触控的目的。

    def mouse_move(self, evt: QMouseEvent):
        focused_widget = QApplication.focusWidget()
        if focused_widget is not None:
            focused_widget.clearFocus()
        ratio = self.max_width / max(self.one_client.resolution)
        self.server.control.touch(evt.position().x() / ratio, evt.position().y() / ratio, scrcpy.ACTION_MOVE)

scroll方法

    @inject(const.TYPE_INJECT_SCROLL_EVENT)
    def scroll(self, x: int, y: int, h: int, v: int) -> bytes:

        scroll方法是ControlSender类的实例方法用于Android设备屏幕的滚动事件。scroll方法可以接收4个参数,前两个为滚动点的坐标位置;第三个参数为水平滚动距离;第四个参数为垂直滚动距离。

    def on_wheel(self):
        """鼠标滚轮滚动事件"""

        def wheel(evt: QWheelEvent):
            ratio = self.max_width / max(self.one_client.resolution)
            position_x = evt.position().x() / ratio
            position_y = evt.position().y() / ratio
            angle_x = evt.angleDelta().x()
            angle_y = evt.angleDelta().y()
            if angle_y > 0:
                angle_y = 1
            else:
                angle_y = -1
            self.server.control.scroll(position_x, position_y, angle_x, angle_y)

        return wheel

写出这个方法后,我们就可以使用鼠标滚轮来控制Android设备的屏幕上下滚动了。

back_or_turn_screen_on方法

    @inject(const.TYPE_BACK_OR_SCREEN_ON)
    def back_or_turn_screen_on(self, action: int = const.ACTION_DOWN) -> bytes:

        back_or_turn_screen_on方法是ControlSender类的实例方法用于按返回键,并且如果屏幕关闭了还会唤醒屏幕。只接收一个参数action为按下或抬起。

    def click_back(self):
        self.server.control.back_or_turn_screen_on(scrcpy.ACTION_DOWN)
        self.server.control.back_or_turn_screen_on(scrcpy.ACTION_UP)

expand_notification_panel方法

    @inject(const.TYPE_EXPAND_NOTIFICATION_PANEL)
    def expand_notification_panel(self) -> bytes:

        expand_notification_panel方法是ControlSender类的实例方法用于打开Android设备的下拉通知栏。

    def open_notification(self):
        self.server.control.expand_notification_panel()

expand_settings_panel方法

    @inject(const.TYPE_EXPAND_SETTINGS_PANEL)
    def expand_settings_panel(self) -> bytes:

        expand_settings_panel方法是ControlSender类的实例方法用于打开Android设备的下拉菜单栏。

    def open_settings(self):
        self.server.control.expand_settings_panel()

collapse_panels方法

    @inject(const.TYPE_COLLAPSE_PANELS)
    def collapse_panels(self) -> bytes:

        collapse_panels方法是ControlSender类的实例方法用于收起Android设备的下拉通知栏或菜单栏。

    def close_panel(self):
        self.server.control.collapse_panelsl()

get_clipboard方法

    def get_clipboard(self) -> str:

        get_clipboard方法是ControlSender类的实例方法用于获取Android设备粘贴板中的内容。在Android设备上复制的文本,我们可以通过这个方法把文本获取出来。

    def get_android_clipboard(self):
        return self.server.control.get_clipboard()

set_clipboard方法

    @inject(const.TYPE_SET_CLIPBOARD)
    def set_clipboard(self, text: str, paste: bool = False) -> bytes:

        set_clipboard方法是ControlSender类的实例方法用于设置Android设备粘贴板中的内容。set_clipboard方法可以接收两个参数,第一个参数text为要设置到粘贴板中的文本内容;第二个参数paste为粘贴状态,默认为False,当为True时会立即把文本粘贴到输入框中(Android设备的光标在某个输入框中时)。

    def set_android_clipboard(self, text: str, paste=False):
        self.server.control.set_clipboard(text, paste)

set_screen_power_mode方法

    @inject(const.TYPE_SET_SCREEN_POWER_MODE)
    def set_screen_power_mode(self, mode: int = scrcpy.POWER_MODE_NORMAL) -> bytes:

        set_screen_power_mode方法是ControlSender类的实例方法用于Android设备的屏幕电源模式。默认为正常状态表示开启Android设备的屏幕电源,此时Android设备的屏幕为正常状态。还可以设置为关闭状态(scrcpy.POWER_MODE_OFF),此时Android设备的屏幕为关闭状态,但并不是灭屏状态(屏幕电源关了和灭屏是两回事),投屏界面还是能看到屏幕。通过这种方式可以在投屏操控Android设备时减少Android设备的电源消耗。

    def set_screen_power_mode(self, mode=2):
        self.server.control.set_screen_power_mode(mode)

totate_device方法

    @inject(const.TYPE_ROTATE_DEVICE)
    def rotate_device(self) -> bytes:

        totate_device方法是ControlSender类的实例方法用于旋转Android设备的屏幕。

    def totate_screen(self):
        self.server.control.totate_device()

swipe方法

    def swipe(
        self,
        start_x: int,
        start_y: int,
        end_x: int,
        end_y: int,
        move_step_length: int = 5,
        move_steps_delay: float = 0.005,
    ) -> None:

        swipe方法是ControlSender类的实例方法用于滑动Android设备的屏幕。这个方法是对touch方法的封装,相当于一点触控。swipe方法可以接收6个参数,前4个参数为滑动的起始坐标和终止坐标;第5个参数为步长(每次滑动的距离),默认为5个坐标单位;第6个参数为每滑动一步停顿的时间,默认0.005秒。

    def swipe_event(self, start_x: int, start_y: int, end_x: int, end_y: int, step: int, delay: float):
        self.server.control.swipe(start_x, start_y, end_x, end_y, step, delay)

结语

        我们通过在python的UI框架中使用上面这些方法,就能实现Android设备的投屏控制了,这个投屏控制的应用要做成什么样子完全由你自己的需求和审美来决定。如果你想同时操作多台Android可以创建多个投屏控制服务,然后把这些服务放到一个列表或字典中(最好是字典),来实现控制设备的切换,达到单独控制某台设备或同时操作多台设备的目的。

模型(示例)

        我看评论区都想要代码,这里就为大家提供了一个用于参考的模型。可以同时操控多台设备,也可以选择性的操作某台设备。文章来源地址https://www.toymoban.com/news/detail-775898.html

# -*- coding: utf-8 -*-

import sys
import threading
import scrcpy
from PySide6.QtGui import QMouseEvent, QImage, QPixmap, QKeyEvent
from adbutils import adb
from PySide6.QtCore import *
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, \
    QCheckBox, QLabel, QGridLayout, QSpacerItem, QSizePolicy

# 创建QApplication对象
if not QApplication.instance():
    app = QApplication([])
else:
    app = QApplication.instance()


items = [i.serial for i in adb.device_list()]  # 设备列表
client_dict = {}  # 设备scrcpy客服端字典
# 为所有设备建立scrcpy服务
for i in items:
    client_dict[i] = scrcpy.Client(device=i, bitrate=1000000000)


def thread_ui(func, *args):
    """
    开启一个新线程任务\n
    :param func: 要执行的线程函数;
    :param args: 函数中需要传入的参数 Any
    :return:
    """
    t = threading.Thread(target=func, args=args)  # 定义新线程
    t.setDaemon(True)  # 开启线程守护
    t.start()  # 执行线程


class SignThread(QThread):
    """信号线程"""

    def __new__(cls, parent: QWidget, func, *types: type):
        cls.update_date = Signal(*types)  # 定义信号(*types)一个信号中可以有一个或多个类型的数据(int,str,list,...)
        return super().__new__(cls)  # 使用父类__new__方法创建SignThread实例对象

    def __init__(self, parent: QWidget, func, *types: type):
        """
        信号线程初始化\n
        :param parent: 界面UI控件
        :param func: 信号要绑定的方法
        :param types: 信号类型,可以是一个或多个(type,...)
        """
        super().__init__(parent)  # 初始化父类
        self.sign = None
        self.update_date.connect(func)  # 绑定信号与方法

    def send_sign(self, *args):
        """
        使用SonThread发送信号\n
        :param args: 信号的内容
        :return:
        """
        self.sign = args  # 信号元组(type,...)
        self.start()

    def run(self):
        """信号线程执行时执行此函数"""
        self.update_date.emit(*self.sign)  # 发送信号元组(type,...)


class MyWindow(QWidget):
    """UI界面"""

    def __init__(self):
        """UI界面初始化"""
        super().__init__()  # 初始化父级
        self.setWindowTitle('多台手机投屏控制示例(python & scrcpy)')  # 设置窗口标题
        self.max_width = 600  # 设置手机投屏宽度
        self.setStyleSheet("""QLabel {border-width: 3px;border-style: solid;border-color: black;}""")  # 设置Qlabel标签样式
        # 定义元素
        self.check_box = QCheckBox("控制所有设备")  # 定义是否控制所有设备选择框
        self.back_button = QPushButton("BACK")  # 定义返回键
        self.home_button = QPushButton("HOME")  # 定义home键
        self.recent_button = QPushButton("RECENT")  # 定义最近任务键
        self.video = QLabel("设备屏幕信息加载......")  # 定义手机投屏控制标签
        self.video_list = []  # 定义手机投屏标签列表
        for i in items:
            self.video_list.append(QLabel(i))  # 把投屏标签加入列表
        self.main_layout = QHBoxLayout(self)  # 定义主布局容器
        self.frame_layout = QVBoxLayout()  # 定义投屏操控框容器
        self.button_layout = QHBoxLayout()
        self.device_layout = QVBoxLayout()  # 定义投屏容器
        self.list_layout = QGridLayout()  # 定义投屏列表布局容器
        self.spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)  # 弹性空间
        self.device_spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)  # 弹性空间
        self.v_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)  # 弹性空间
        # 页面布局
        self.main_layout.addLayout(self.frame_layout)
        self.main_layout.addLayout(self.device_layout)
        self.main_layout.addItem(self.v_spacer)
        self.frame_layout.addWidget(self.video)
        self.frame_layout.addLayout(self.button_layout)
        self.frame_layout.addWidget(self.check_box)
        self.frame_layout.addItem(self.spacer)
        self.button_layout.addWidget(self.back_button)
        self.button_layout.addWidget(self.home_button)
        self.button_layout.addWidget(self.recent_button)
        self.device_layout.addLayout(self.list_layout)
        self.device_layout.addItem(self.device_spacer)
        # 交互事件
        self.back_button.clicked.connect(self.click_key(scrcpy.KEYCODE_BACK))
        self.home_button.clicked.connect(self.click_key(scrcpy.KEYCODE_HOME))
        self.recent_button.clicked.connect(self.click_key(scrcpy.KEYCODE_APP_SWITCH))
        self.video.mousePressEvent = self.mouse_event(scrcpy.ACTION_DOWN)
        self.video.mouseMoveEvent = self.mouse_event(scrcpy.ACTION_MOVE)
        self.video.mouseReleaseEvent = self.mouse_event(scrcpy.ACTION_UP)
        self.keyPressEvent = self.on_key_event(scrcpy.ACTION_DOWN)
        self.keyReleaseEvent = self.on_key_event(scrcpy.ACTION_UP)
        # 所有设备屏幕有序排布,最多15台设备,可按需修改
        if len(items) > 0:
            self.now_device = items[0]
            self.now_client = client_dict[items[0]]
            self.now_client.add_listener(scrcpy.EVENT_FRAME, self.main_frame)
            for num in range(len(items)):
                self.video_list[num].mousePressEvent = self.switch_video(items[num])
                client = client_dict[items[num]]
                client.add_listener(scrcpy.EVENT_FRAME, self.on_frame(num, client))
                if num < 5:
                    self.list_layout.addWidget(self.video_list[num], 0, num, 1, 1)
                elif num < 10:
                    self.list_layout.addWidget(self.video_list[num], 1, num - 5, 1, 1)
                elif num < 15:
                    self.list_layout.addWidget(self.video_list[num], 2, num - 10, 1, 1)

        self.mouse_thread = SignThread(self, self.mouse_exe, int, int, int)

    def click_key(self, key_value: int):
        """
        按键事件\n
        :param key_value: 键值
        :return:
        """

        def key_event():
            if self.check_box.isChecked():
                for i in client_dict:
                    client_dict[i].control.keycode(key_value, scrcpy.ACTION_DOWN)
                    client_dict[i].control.keycode(key_value, scrcpy.ACTION_UP)
            else:
                self.now_client.control.keycode(key_value, scrcpy.ACTION_DOWN)
                self.now_client.control.keycode(key_value, scrcpy.ACTION_UP)

        return key_event

    def switch_video(self, device):
        """
        切换设备屏幕为主控屏幕\n
        :param device: 设备序列号
        :return:
        """

        def now_video(evt: QMouseEvent):
            app.processEvents()
            self.now_client.remove_listener(scrcpy.EVENT_FRAME, self.main_frame)
            self.now_client = client_dict[device]
            self.now_client.add_listener(scrcpy.EVENT_FRAME, self.main_frame)
            self.now_client.control.keycode(224, scrcpy.ACTION_DOWN)
            self.now_client.control.keycode(224, scrcpy.ACTION_UP)
            bound = self.now_client.resolution
            self.now_client.control.swipe(bound[0] / 2, bound[1] / 2, bound[0] / 2, bound[1] / 2 - 20)
            self.now_client.control.swipe(bound[0] / 2, bound[1] / 2 - 20, bound[0] / 2, bound[1] / 2)
            self.now_device = device

        return now_video

    def main_frame(self, frame):
        """
        监听设备屏幕数据,设置控制窗口图像\n
        :param frame: 屏幕数据
        :return:
        """
        app.processEvents()
        if frame is not None:
            ratio = self.max_width / max(self.now_client.resolution)
            image = QImage(
                frame,
                frame.shape[1],
                frame.shape[0],
                frame.shape[1] * 3,
                QImage.Format_BGR888,
            )
            pix = QPixmap(image)
            pix.setDevicePixelRatio(1 / ratio)
            self.video.setPixmap(pix)

    def on_frame(self, num, client):
        """
        监听设备屏幕数据,设置小窗口图像\n
        :param num: 设备投屏序号
        :param client: scrcpy服务
        :return:
        """

        def client_frame(frame):
            app.processEvents()
            if frame is not None:
                ratio = 300 / max(client.resolution)
                image = QImage(
                    frame,
                    frame.shape[1],
                    frame.shape[0],
                    frame.shape[1] * 3,
                    QImage.Format_BGR888,
                )
                pix = QPixmap(image)
                pix.setDevicePixelRatio(1 / ratio)
                self.video_list[num].setPixmap(pix)

        return client_frame

    def mouse_event(self, action=scrcpy.ACTION_DOWN):
        """
        鼠标事件\n
        :param action: 事件类型
        :return: 对应的事件函数
        """

        def event(evt: QMouseEvent):
            focused_widget = QApplication.focusWidget()
            if focused_widget is not None:
                focused_widget.clearFocus()
            ratio = self.max_width / max(self.now_client.resolution)
            self.mouse_thread.send_sign(evt.position().x() / ratio, evt.position().y() / ratio, action)

        return event

    def mouse_exe(self, x, y, action):
        """
        执行鼠标事件\n
        :param x: x坐标
        :param y: y坐标
        :param action: 事件类型
        :return:
        """
        if self.check_box.isChecked():
            for i in client_dict:
                client_dict[i].control.touch(x, y, action)
        else:
            self.now_client.control.touch(x, y, action)

    def on_key_event(self, action=scrcpy.ACTION_DOWN):
        """
        键盘按键事件\n
        :param action: 事件类型
        :return: 对应的事件函数
        """

        def handler(evt: QKeyEvent):
            code = self.key_code(evt.key())
            if code != -1:
                if self.check_box.isChecked():
                    for i in client_dict:
                        client_dict[i].control.keycode(code, action)
                else:
                    self.now_client.control.keycode(code, action)

        return handler

    @staticmethod
    def key_code(code):
        """
        Map qt keycode ti android keycode

        Args:
            code: qt keycode
            android keycode, -1 if not founded
        """
        if code == -1:
            return -1
        if code == 35:
            return 18
        if code == 42:
            return 17
        if 48 <= code <= 57:
            return code - 48 + 7
        if 65 <= code <= 90:
            return code - 65 + 29
        if 97 <= code <= 122:
            return code - 97 + 29

        hard_code = {
            32: scrcpy.KEYCODE_SPACE,
            16777219: scrcpy.KEYCODE_DEL,
            16777248: scrcpy.KEYCODE_SHIFT_LEFT,
            16777220: scrcpy.KEYCODE_ENTER,
            16777217: scrcpy.KEYCODE_TAB,
            16777249: scrcpy.KEYCODE_CTRL_LEFT,
            16777235: scrcpy.KEYCODE_DPAD_UP,
            16777237: scrcpy.KEYCODE_DPAD_DOWN,
            16777234: scrcpy.KEYCODE_DPAD_LEFT,
            16777236: scrcpy.KEYCODE_DPAD_RIGHT,
        }
        if code in hard_code:
            return hard_code[code]

        print(f"Unknown keycode: {code}")
        return -1

    def closeEvent(self, _):
        """窗口关闭事件"""
        for i in client_dict:
            client_dict[i].stop()  # 关闭scrcpy服务


def main():
    for i in client_dict:
        thread_ui(client_dict[i].start)  # 给每一台设备单独开启一个scrcpy服务线程
    widget = MyWindow()  # 实例化UI线程
    widget.resize(1200, 800)  # 设置窗口大小
    widget.show()  # 展示窗口
    sys.exit(app.exec())  # 持续刷新窗口


if __name__ == '__main__':
    main()

到了这里,关于python实现Android实时投屏操控的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 谁说不可兼得,用scrcpy实现手机免流播放bilibili投屏电脑

    目前的手机大额流量卡都是支持设备免流的,但是如何将这个流量用在其他设备,就相当麻烦。于是我查找了些相关Android投屏技术资料,发现了一个简单的USB投屏工具——scrcpy。 安装说明 Office:https://github.com/Genymobile/scrcpy/releases Windows用户安装建议在github上下载zip免安装版

    2024年02月09日
    浏览(77)
  • 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件

    基于scrcpy的远程调试方案 基于scrcpy的Android群控项目重构 基于scrcpy的Android群控项目重构 进阶版 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件(视频) 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件(博客) 基于scrcpy的

    2024年02月16日
    浏览(69)
  • Scrcpy手机投屏

    Scrcpy投屏(电脑操作手机)@TOC Android设备至少需要5.0以上版本(即API 21) 确保在电脑设备上启动了adb调试 在某些设备上,还需要启动其他选项以使用建买盘和鼠标。链接: 其它选项 adb调试的开启一般是多次点击手机系统的版本号,比如vivoS15pro:设置-系统管理-关于手机-版本

    2024年02月09日
    浏览(56)
  • 【投屏】Scrcpy源码分析一(编译篇)

    Scrcpy源码分析系列 【投屏】Scrcpy源码分析一(编译篇) 【投屏】Scrcpy源码分析二(Client篇-连接阶段) 【投屏】Scrcpy源码分析三(Client篇-投屏阶段) 【投屏】Scrcpy源码分析四(最终章 - Server篇) Scrcpy是一款小巧的Android设备投屏软件。可以跨平台,在Windows、Linux、MacOS上对

    2024年02月12日
    浏览(31)
  • scrcpy 手机投屏与控制

    这是一款手机投屏到电脑的软件, 但是是依靠命令使用的 ( github上的没有UI屏幕控制 ) 下载连接: https://github.com/Genymobile/scrcpy?tab=readme-ov-file 已 windows 系统为例, 我下载的是 2.3.1 版本的, 选择 zip 格式即可. 32/64位根据电脑情况选择即可. 下载完成后, 自行解压到自己想放的目录下

    2024年04月16日
    浏览(31)
  • 基于scrcpy的Android群控项目重构,集成Appium服务执行自动化测试用例

    基于scrcpy的Android群控项目重构 基于scrcpy的Android群控项目重构 进阶版 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件(视频) 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件(博客) 基于scrcpy的Android群控项目重构,集成

    2024年02月16日
    浏览(52)
  • ADB 连接后,使用scrcpy投屏电脑

    将三个ADB文件复制后,放到C:WindowsSystem32下,同时也复制一份放到C:WindowsSysWOW64下 ADB文件: 他这里有提供百度网盘连接下载这几个文件 【adb安装】简单的一批的adb安装,少走弯路_哔哩哔哩_bilibili 然后,cmd命令, 输入adb,出现版本号,出现一大堆的代码说明,说明安装成功

    2024年02月09日
    浏览(37)
  • C++版Android实时投屏软件系统源码,安卓手机投屏软件源码,无需root权限

    QtScrcpy 可以通过 USB / 网络连接Android设备,并进行显示和控制。无需root权限。 同时支持 GNU/Linux ,Windows 和 MacOS 三大主流桌面平台。 完整代码下载地址:C++版Android实时投屏软件系统源码 它专注于: 精致 (仅显示设备屏幕) 性能 (30~60fps) 质量 (1920×1080以上) 低延迟 (35~70ms) 快速启

    2024年02月05日
    浏览(49)
  • 【投屏】Scrcpy源码分析四(最终章 - Server篇)

    Scrcpy源码分析系列 【投屏】Scrcpy源码分析一(编译篇) 【投屏】Scrcpy源码分析二(Client篇-连接阶段) 【投屏】Scrcpy源码分析三(Client篇-投屏阶段) 【投屏】Scrcpy源码分析四(最终章 - Server篇) 在前两篇我们探究了Scrcpy Client的连接和投屏逻辑,本篇我们就要继续探究Serv

    2024年02月06日
    浏览(40)
  • 分享一个开源的windows安卓投屏工具,scrcpy

    安装adb - ADB是一个Android Debug Bridge,用于与Android设备进行通信。如果您已经安装了Android Studio,则可以从其中运行adb。否则,您可以从ADB官方网站下载并手动安装。 安装SDL库 - Scrcpy使用SDL库来呈现Android设备的屏幕。您可以使用系统包管理器来安装SDL库,例如,在Ubuntu上,您可

    2023年04月18日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包