feat: 支持 USB 设备转 Wi-Fi ADB (#25)
This commit is contained in:
@@ -9,6 +9,7 @@ from typing import Optional
|
||||
from PyQt5.QtCore import QCoreApplication, QObject, QThread, pyqtSignal, pyqtSlot
|
||||
|
||||
from .android_device_service import (
|
||||
AndroidDeviceConversionCancelled,
|
||||
AndroidDeviceSearchError,
|
||||
AndroidDeviceService,
|
||||
)
|
||||
@@ -130,6 +131,49 @@ class AndroidDeviceSearchWorker(QObject):
|
||||
self.completed.emit()
|
||||
|
||||
|
||||
class AndroidWifiConversionWorker(QObject):
|
||||
"""在后台引导一台 USB 设备切换到 Wi-Fi ADB。"""
|
||||
|
||||
progressChanged = pyqtSignal(str)
|
||||
succeeded = pyqtSignal(object)
|
||||
failed = pyqtSignal(str)
|
||||
completed = pyqtSignal()
|
||||
|
||||
def __init__(self, service: AndroidDeviceService, usb_serial: str):
|
||||
super().__init__()
|
||||
self._service = service
|
||||
self._usb_serial = usb_serial
|
||||
self._cancelled = False
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""停止轮询和后续命令;正在执行的 ADB 命令等待自身超时。"""
|
||||
|
||||
self._cancelled = True
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self) -> None:
|
||||
try:
|
||||
try:
|
||||
result = self._service.convert_usb_to_wifi(
|
||||
self._usb_serial,
|
||||
lambda: self._cancelled,
|
||||
self.progressChanged.emit,
|
||||
)
|
||||
except AndroidDeviceConversionCancelled:
|
||||
return
|
||||
except AndroidDeviceSearchError as exc:
|
||||
if not self._cancelled:
|
||||
self.failed.emit(str(exc))
|
||||
except Exception as exc:
|
||||
if not self._cancelled:
|
||||
self.failed.emit(str(exc) or "无法将 USB 设备转为 Wi-Fi")
|
||||
else:
|
||||
if not self._cancelled:
|
||||
self.succeeded.emit(result)
|
||||
finally:
|
||||
self.completed.emit()
|
||||
|
||||
|
||||
class AndroidDeviceSettingWorker(QObject):
|
||||
"""在后台保存或删除当前使用的 Android 设备号。"""
|
||||
|
||||
@@ -183,6 +227,7 @@ class SettingsPageEventBinder(QObject):
|
||||
|
||||
searchRequested = pyqtSignal()
|
||||
connectRequested = pyqtSignal(str)
|
||||
convertWifiRequested = pyqtSignal(str)
|
||||
currentDeviceSaveRequested = pyqtSignal(str, str)
|
||||
saveRequested = pyqtSignal(str, str)
|
||||
deleteRequested = pyqtSignal(str)
|
||||
@@ -204,6 +249,11 @@ class SettingsPageEventBinder(QObject):
|
||||
self._search_busy = False
|
||||
self._search_thread: Optional[QThread] = None
|
||||
self._search_worker: Optional[AndroidDeviceSearchWorker] = None
|
||||
self._wifi_conversion_busy = False
|
||||
self._wifi_conversion_thread: Optional[QThread] = None
|
||||
self._wifi_conversion_worker: Optional[
|
||||
AndroidWifiConversionWorker
|
||||
] = None
|
||||
self._android_setting_busy = False
|
||||
self._android_setting_thread: Optional[QThread] = None
|
||||
self._android_setting_worker: Optional[AndroidDeviceSettingWorker] = None
|
||||
@@ -240,6 +290,8 @@ class SettingsPageEventBinder(QObject):
|
||||
page.searchButton.clicked.connect(self._request_search)
|
||||
self.searchRequested.connect(self._start_search)
|
||||
page.connectButton.clicked.connect(self._request_connect)
|
||||
page.convertWifiButton.clicked.connect(self._request_convert_wifi)
|
||||
self.convertWifiRequested.connect(self._start_convert_wifi)
|
||||
page.saveButton.clicked.connect(self._request_save)
|
||||
self.saveRequested.connect(self._start_save_android_device)
|
||||
page.deleteButton.clicked.connect(self._request_delete)
|
||||
@@ -261,6 +313,7 @@ class SettingsPageEventBinder(QObject):
|
||||
self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
@@ -278,6 +331,7 @@ class SettingsPageEventBinder(QObject):
|
||||
self._closing
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
@@ -323,6 +377,7 @@ class SettingsPageEventBinder(QObject):
|
||||
self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
@@ -335,6 +390,7 @@ class SettingsPageEventBinder(QObject):
|
||||
or self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
@@ -361,7 +417,13 @@ class SettingsPageEventBinder(QObject):
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_connect(self) -> None:
|
||||
if self._busy:
|
||||
if (
|
||||
self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
|
||||
address = self._page.addressInput.text().strip()
|
||||
@@ -373,9 +435,76 @@ class SettingsPageEventBinder(QObject):
|
||||
return
|
||||
self.connectRequested.emit(address)
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_convert_wifi(self) -> None:
|
||||
if (
|
||||
self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
device = self._page.deviceTableModel.device_for_serial(serial)
|
||||
if device is None or device.status != "device":
|
||||
self._page.deviceStatusLabel.setText(
|
||||
"转换失败:请先勾选一台已连接的 USB 设备"
|
||||
)
|
||||
self._page.deviceTable.setFocus()
|
||||
return
|
||||
if device.connection_type != "USB":
|
||||
self._page.deviceStatusLabel.setText(
|
||||
"转换失败:当前设备已经是 Wi-Fi 连接"
|
||||
)
|
||||
return
|
||||
self.convertWifiRequested.emit(serial)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _start_convert_wifi(self, usb_serial: str) -> None:
|
||||
if (
|
||||
self._closing
|
||||
or self._busy
|
||||
or self._current_device_busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
|
||||
self._wifi_conversion_busy = True
|
||||
self._sync_button_state()
|
||||
self._page.deviceStatusLabel.setText("正在准备 USB 转 Wi-Fi…")
|
||||
|
||||
thread = QThread(self)
|
||||
worker = AndroidWifiConversionWorker(
|
||||
self._android_device_service,
|
||||
usb_serial,
|
||||
)
|
||||
worker.moveToThread(thread)
|
||||
|
||||
thread.started.connect(worker.run)
|
||||
worker.progressChanged.connect(self._on_wifi_conversion_progress)
|
||||
worker.succeeded.connect(self._on_wifi_conversion_succeeded)
|
||||
worker.failed.connect(self._on_wifi_conversion_failed)
|
||||
worker.completed.connect(thread.quit)
|
||||
worker.completed.connect(worker.deleteLater)
|
||||
thread.finished.connect(self._on_wifi_conversion_finished)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
|
||||
self._wifi_conversion_thread = thread
|
||||
self._wifi_conversion_worker = worker
|
||||
thread.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_save(self) -> None:
|
||||
if self._busy or self._search_busy or self._android_setting_busy:
|
||||
if (
|
||||
self._busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
@@ -394,7 +523,12 @@ class SettingsPageEventBinder(QObject):
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_delete(self) -> None:
|
||||
if self._busy or self._search_busy or self._android_setting_busy:
|
||||
if (
|
||||
self._busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
|
||||
serial = self._saved_android_serial
|
||||
@@ -414,6 +548,7 @@ class SettingsPageEventBinder(QObject):
|
||||
self._closing
|
||||
or self._busy
|
||||
or self._search_busy
|
||||
or self._wifi_conversion_busy
|
||||
or self._android_setting_busy
|
||||
):
|
||||
return
|
||||
@@ -450,16 +585,19 @@ class SettingsPageEventBinder(QObject):
|
||||
@pyqtSlot()
|
||||
def _sync_button_state(self, *_args) -> None:
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
selected_device = self._page.deviceTableModel.device_for_serial(serial)
|
||||
has_address = bool(self._page.addressInput.text().strip())
|
||||
self._page.currentDeviceSaveButton.setEnabled(
|
||||
not self._busy
|
||||
and not self._current_device_busy
|
||||
and not self._search_busy
|
||||
and not self._wifi_conversion_busy
|
||||
and not self._android_setting_busy
|
||||
)
|
||||
device_commands_enabled = (
|
||||
not self._busy
|
||||
and not self._search_busy
|
||||
and not self._wifi_conversion_busy
|
||||
and not self._android_setting_busy
|
||||
)
|
||||
self._page.searchButton.setEnabled(
|
||||
@@ -471,6 +609,13 @@ class SettingsPageEventBinder(QObject):
|
||||
self._page.saveButton.setEnabled(
|
||||
device_commands_enabled and bool(serial)
|
||||
)
|
||||
self._page.convertWifiButton.setEnabled(
|
||||
device_commands_enabled
|
||||
and not self._current_device_busy
|
||||
and selected_device is not None
|
||||
and selected_device.status == "device"
|
||||
and selected_device.connection_type == "USB"
|
||||
)
|
||||
self._page.deleteButton.setEnabled(
|
||||
device_commands_enabled and bool(self._saved_android_serial)
|
||||
)
|
||||
@@ -576,6 +721,49 @@ class SettingsPageEventBinder(QObject):
|
||||
if not self._closing:
|
||||
self._sync_button_state()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_wifi_conversion_progress(self, message: str) -> None:
|
||||
if not self._closing:
|
||||
self._page.deviceStatusLabel.setText(message)
|
||||
|
||||
@pyqtSlot(object)
|
||||
def _on_wifi_conversion_succeeded(self, result) -> None:
|
||||
if self._closing:
|
||||
return
|
||||
|
||||
rows = [
|
||||
AndroidDeviceRow(
|
||||
serial=device.serial,
|
||||
connection_type=device.connection_type,
|
||||
model=device.model,
|
||||
android_version=device.android_version,
|
||||
status=device.status,
|
||||
)
|
||||
for device in result.devices
|
||||
]
|
||||
self._page.set_android_devices(rows)
|
||||
self._page.deviceTableModel.set_checked_serial(result.wifi_serial)
|
||||
self._page.deviceStatusLabel.setText(
|
||||
f"Wi-Fi 设备 {result.wifi_serial} 已连接并勾选;"
|
||||
"请确认后点击保存,仅在可信网络使用"
|
||||
)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_wifi_conversion_failed(self, message: str) -> None:
|
||||
if self._closing:
|
||||
return
|
||||
self._page.deviceStatusLabel.setText(
|
||||
f"转换失败:{message};设备列表和已保存配置未更改,可重试"
|
||||
)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_wifi_conversion_finished(self) -> None:
|
||||
self._wifi_conversion_worker = None
|
||||
self._wifi_conversion_thread = None
|
||||
self._wifi_conversion_busy = False
|
||||
if not self._closing:
|
||||
self._sync_button_state()
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def _on_android_device_setting_succeeded(
|
||||
self, action: str, serial: str
|
||||
@@ -695,6 +883,33 @@ class SettingsPageEventBinder(QObject):
|
||||
search_thread.quit()
|
||||
search_thread.wait(6000)
|
||||
|
||||
conversion_worker = self._wifi_conversion_worker
|
||||
conversion_thread = self._wifi_conversion_thread
|
||||
if conversion_worker is not None:
|
||||
conversion_worker.cancel()
|
||||
for signal, slot in (
|
||||
(
|
||||
conversion_worker.progressChanged,
|
||||
self._on_wifi_conversion_progress,
|
||||
),
|
||||
(
|
||||
conversion_worker.succeeded,
|
||||
self._on_wifi_conversion_succeeded,
|
||||
),
|
||||
(
|
||||
conversion_worker.failed,
|
||||
self._on_wifi_conversion_failed,
|
||||
),
|
||||
):
|
||||
try:
|
||||
signal.disconnect(slot)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if conversion_thread is not None and conversion_thread.isRunning():
|
||||
conversion_thread.quit()
|
||||
conversion_thread.wait(6000)
|
||||
|
||||
setting_worker = self._android_setting_worker
|
||||
setting_thread = self._android_setting_thread
|
||||
if setting_worker is not None:
|
||||
|
||||
Reference in New Issue
Block a user