scrcpy-client
? ? ? ? python中有一個scrcpy-client庫,可以實現(xiàn)Android設備的實時投屏和操控。它和scrcpy實現(xiàn)Android投屏是一樣的,都是把一個scrcpy-server.jar文件通過adb推送到Android設備,并利用adb指令執(zhí)行scrcpy-server.jar開啟投屏和操控服務端,電腦端通過python創(chuàng)建客戶端來接收視頻流數(shù)據(jù)和發(fā)送控制流數(shù)據(jù)。視頻流數(shù)據(jù)中就是Android實時屏幕數(shù)據(jù),控制流數(shù)據(jù)就是我們在電腦端對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調試功能,已允許電腦調式。這時我們就可以通過執(zhí)行上面的代碼,得到Android設備的投屏UI界面,如下圖所示:
在這個界面中我們可以使用鼠標在投屏界面點擊、滑動,控制Android設備的屏幕??梢酝ㄟ^設備序列號下拉框切換設備,F(xiàn)lip勾選后可以得到鏡像屏幕。下方的HOME按鈕點擊后回到主屏幕,相當于按設備的home鍵。BACK按鈕點擊后會返回上一個界面,相當于按設備上的back鍵。還支持鍵盤輸入,我們可以通過電腦的鍵盤讓Android產(chǎn)生按鍵事件。
自定義使用
? ? ? ? 如果你覺得作者提供的UI界面不能滿足你的需求,我們還可以自定義UI界面來實現(xiàn)更多的操作方式。前提是你要會使用PySide6這種UI框架,你必須知道如何在UI界面中使用動態(tài)元素,如何實現(xiàn)鼠標點擊、移動事件,鼠標滾輪滾動事件,鍵盤輸入事件。如果你還不會使用UI界面相關的框架,可以先去學習一下。如果你會UI相關的框架,就接著往下看。
創(chuàng)建投屏服務
? ? ? ? 使用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:最大幀數(shù),默認不限制幀數(shù)。
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,我們可以通過如下代碼創(chuàng)建投屏控制服務實例對象:
import scrcpy
server = scrcpy.Client(device='123456789', bitrate=100000000)
start方法
? ? ? ? start是Client的實例方法用于啟動投屏控制服務,start可以接收兩個參數(shù),一個是threaded,默認為False,為True時表示在子線程中開啟投屏控制服務;另一個是daemon_threaded,默認為False,為True時表示給子線程開啟進程守護。threaded和daemon_threaded有一個為True時,都會在子線程中開啟投屏控制服務,都為False時表示在主線程中開啟投屏控制服務。要同時開啟多個投屏控制服務時,就需要在子線程中開啟投屏控制服務,自己創(chuàng)建子線程也是可以的。
server.start()
add_listener方法
????????add_listener是Client的實例方法用于設置監(jiān)聽字典listeners,listeners字典中有兩個元素,第一個元素的鍵為frame表示圖像幀元素,第二個元素為init表示初始化元素。兩個元素的值都是一個空列表,用來存放函數(shù)的。如果想把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)
這里只是舉例,實際上的方法需要根據(jù)你的需求自己寫。如果想要把Android設備的圖像展示在UI界面的某個元素上,就必須寫一個展示圖像的方法,再把這個方法添加到listener字典的第一個元素中(key='frame')。
remove_listener方法
????????remove_listener方法是Client的實例方法用于移除監(jiān)聽字典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()
控制方法
? ? ? ? 我們已經(jīng)把Android設備的屏幕投射到了電腦上,現(xiàn)在就需要通過一些控制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設備發(fā)送按鍵事件。keycode方法可以接收3個參數(shù),第一個參數(shù)keycode表示鍵值(你需要了解adb鍵值);第二個參數(shù)action表示按下還是抬起,默認是按下;第三個參數(shù)repeat表示重復操作次數(shù),想重復按幾次。
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方法接收一個參數(shù),就是我們要在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個參數(shù),前兩個參數(shù)為觸點的x坐標和y坐標;第三個參數(shù)action為按下、移動、抬起;第四個參數(shù)為觸控事件id,默認為-1,你可以設置不同的id來同時執(zhí)行多個觸控事件,達到多點觸控的目的。
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個參數(shù),前兩個為滾動點的坐標位置;第三個參數(shù)為水平滾動距離;第四個參數(shù)為垂直滾動距離。
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類的實例方法用于按返回鍵,并且如果屏幕關閉了還會喚醒屏幕。只接收一個參數(shù)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方法可以接收兩個參數(shù),第一個參數(shù)text為要設置到粘貼板中的文本內容;第二個參數(shù)paste為粘貼狀態(tài),默認為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設備的屏幕電源模式。默認為正常狀態(tài)表示開啟Android設備的屏幕電源,此時Android設備的屏幕為正常狀態(tài)。還可以設置為關閉狀態(tài)(scrcpy.POWER_MODE_OFF),此時Android設備的屏幕為關閉狀態(tài),但并不是滅屏狀態(tài)(屏幕電源關了和滅屏是兩回事),投屏界面還是能看到屏幕。通過這種方式可以在投屏操控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個參數(shù),前4個參數(shù)為滑動的起始坐標和終止坐標;第5個參數(shù)為步長(每次滑動的距離),默認為5個坐標單位;第6個參數(shù)為每滑動一步停頓的時間,默認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框架中使用上面這些方法,就能實現(xiàn)Android設備的投屏控制了,這個投屏控制的應用要做成什么樣子完全由你自己的需求和審美來決定。如果你想同時操作多臺Android可以創(chuàng)建多個投屏控制服務,然后把這些服務放到一個列表或字典中(最好是字典),來實現(xiàn)控制設備的切換,達到單獨控制某臺設備或同時操作多臺設備的目的。文章來源:http://www.zghlxwxcb.cn/news/detail-775898.html
模型(示例)
? ? ? ? 我看評論區(qū)都想要代碼,這里就為大家提供了一個用于參考的模型??梢酝瑫r操控多臺設備,也可以選擇性的操作某臺設備。文章來源地址http://www.zghlxwxcb.cn/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
# 創(chuàng)建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: 要執(zhí)行的線程函數(shù);
:param args: 函數(shù)中需要傳入的參數(shù) Any
:return:
"""
t = threading.Thread(target=func, args=args) # 定義新線程
t.setDaemon(True) # 開啟線程守護
t.start() # 執(zhí)行線程
class SignThread(QThread):
"""信號線程"""
def __new__(cls, parent: QWidget, func, *types: type):
cls.update_date = Signal(*types) # 定義信號(*types)一個信號中可以有一個或多個類型的數(shù)據(jù)(int,str,list,...)
return super().__new__(cls) # 使用父類__new__方法創(chuàng)建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發(fā)送信號\n
:param args: 信號的內容
:return:
"""
self.sign = args # 信號元組(type,...)
self.start()
def run(self):
"""信號線程執(zhí)行時執(zhí)行此函數(shù)"""
self.update_date.emit(*self.sign) # 發(fā)送信號元組(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):
"""
監(jiān)聽設備屏幕數(shù)據(jù),設置控制窗口圖像\n
:param frame: 屏幕數(shù)據(jù)
: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):
"""
監(jiān)聽設備屏幕數(shù)據(jù),設置小窗口圖像\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: 對應的事件函數(shù)
"""
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):
"""
執(zhí)行鼠標事件\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: 對應的事件函數(shù)
"""
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()) # 持續(xù)刷新窗口
if __name__ == '__main__':
main()
到了這里,關于python實現(xiàn)Android實時投屏操控的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!