device-manager.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { ipcMain, BrowserWindow } from 'electron';
  2. import { exec } from 'child_process';
  3. import { promisify } from 'util';
  4. import path from 'path';
  5. import { fileURLToPath } from 'url';
  6. import { getCachedAdbPath, loadConfig } from '../config.js';
  7. const __filename = fileURLToPath(import.meta.url);
  8. const __dirname = path.dirname(__filename);
  9. const execAsync = promisify(exec);
  10. // 获取主窗口引用(用于实时推送设备发现事件)
  11. function getMainWindow() {
  12. const windows = BrowserWindow.getAllWindows();
  13. return windows.length > 0 ? windows[0] : null;
  14. }
  15. // 获取 ADB 设备列表(每次都重新扫描网络)
  16. export async function getADBDevices() {
  17. try {
  18. // 每次都重新扫描网络设备,不记录已连接的设备
  19. return await scanNetworkDevices(null);
  20. } catch (error) {
  21. return [];
  22. }
  23. }
  24. // 网络扫描:按顺序扫描 192.168.0、192.168.1、192.168.2 三个网段,尝试连接设备(实时推送结果)
  25. export async function scanNetworkDevices(event) {
  26. const adbPath = getCachedAdbPath();
  27. const port = 5555;
  28. const maxConcurrent = 50; // 限制并发数,避免过载
  29. const connectTimeout = 1500; // 连接超时时间(毫秒)
  30. const foundDevices = new Set(); // 使用 Set 避免重复
  31. // 定义要扫描的网段(按顺序:0.0 -> 1.0 -> 2.0)
  32. const networkSegments = ['192.168.0', '192.168.1', '192.168.2'];
  33. // 推送设备发现的辅助函数
  34. const pushDevice = (ipPort) => {
  35. if (!foundDevices.has(ipPort)) {
  36. foundDevices.add(ipPort);
  37. const device = {
  38. id: ipPort,
  39. status: 'device'
  40. };
  41. // 实时发送发现的设备
  42. if (event && event.sender) {
  43. event.sender.send('device-found', device);
  44. } else {
  45. const mainWindow = getMainWindow();
  46. if (mainWindow) {
  47. mainWindow.webContents.send('device-found', device);
  48. }
  49. }
  50. }
  51. };
  52. // 扫描单个 IP 地址的函数
  53. const scanIP = async (ip) => {
  54. const ipPort = `${ip}:${port}`;
  55. try {
  56. // 先尝试连接设备
  57. await execAsync(`${adbPath} connect ${ipPort}`, {
  58. timeout: connectTimeout,
  59. maxBuffer: 1024 * 1024
  60. });
  61. // 连接后稍等片刻,让设备注册到 ADB 服务器
  62. await new Promise(resolve => setTimeout(resolve, 500));
  63. } catch (error) {
  64. // 连接失败是正常的,继续检查设备列表
  65. }
  66. // 调用 adb devices 命令获取所有设备列表,检查是否包含端口 5555 的设备
  67. try {
  68. const { stdout } = await execAsync(`${adbPath} devices`, {
  69. timeout: 2000,
  70. maxBuffer: 1024 * 1024
  71. });
  72. // 检查输出中是否包含该 IP:PORT(匹配端口 5555)
  73. // 使用正则表达式匹配 IP:5555 格式
  74. const devicePattern = new RegExp(`${ip.replace(/\./g, '\\.')}:${port}\\s+device`, 'i');
  75. if (devicePattern.test(stdout)) {
  76. // 发现设备,立即推送显示(不等待整批完成)
  77. pushDevice(ipPort);
  78. return ipPort;
  79. }
  80. // 如果没有匹配到,说明没有设备
  81. } catch (checkError) {
  82. // 检查失败,忽略
  83. }
  84. return null;
  85. };
  86. // 按顺序扫描每个网段
  87. for (const baseIP of networkSegments) {
  88. // 生成当前网段的 IP 地址列表
  89. const ipList = [];
  90. for (let i = 1; i <= 255; i++) {
  91. ipList.push(`${baseIP}.${i}`);
  92. }
  93. // 分批并发扫描当前网段,扫描到一个显示一个
  94. for (let i = 0; i < ipList.length; i += maxConcurrent) {
  95. const batch = ipList.slice(i, i + maxConcurrent);
  96. const promises = batch.map(ip => scanIP(ip));
  97. // 并发执行当前批次,但不等待所有完成才推送
  98. // 每个 promise 一旦发现设备就会立即推送
  99. await Promise.all(promises);
  100. }
  101. }
  102. // 返回所有发现的设备
  103. return Array.from(foundDevices).map(ipPort => ({
  104. id: ipPort,
  105. status: 'device'
  106. }));
  107. }
  108. // 连接 ADB 设备
  109. export async function connectDevice(ipPort) {
  110. try {
  111. const adbPath = getCachedAdbPath();
  112. // 尝试连接设备
  113. try {
  114. await execAsync(`${adbPath} connect ${ipPort}`, {
  115. timeout: 3000,
  116. maxBuffer: 1024 * 1024
  117. });
  118. } catch (connectError) {
  119. // 连接命令失败
  120. return { success: false, error: connectError.message || '连接失败' };
  121. }
  122. // 连接后稍等片刻,让设备注册到 ADB 服务器
  123. await new Promise(resolve => setTimeout(resolve, 1000));
  124. // 验证设备是否真的连接成功(检查设备列表)
  125. try {
  126. const { stdout } = await execAsync(`${adbPath} devices`, {
  127. timeout: 2000,
  128. maxBuffer: 1024 * 1024
  129. });
  130. // 检查输出中是否包含该设备且状态为 'device'
  131. // 匹配格式:IP:PORT device
  132. const escapedIpPort = ipPort.replace(/\./g, '\\.').replace(/:/g, '\\:');
  133. const devicePattern = new RegExp(`${escapedIpPort}\\s+device`, 'i');
  134. if (devicePattern.test(stdout)) {
  135. // 设备确实连接成功
  136. return { success: true };
  137. } else {
  138. // 设备不在列表中,连接失败
  139. return {
  140. success: false,
  141. error: `无法连接到设备 ${ipPort},设备未出现在ADB设备列表中`
  142. };
  143. }
  144. } catch (checkError) {
  145. // 检查设备列表失败,但连接命令已执行,返回不确定状态
  146. return {
  147. success: false,
  148. error: `无法验证设备连接状态:${checkError.message || '未知错误'}`
  149. };
  150. }
  151. } catch (error) {
  152. return { success: false, error: error.message || '连接失败' };
  153. }
  154. }
  155. // 注册 IPC 处理器
  156. export function registerIpcHandlers() {
  157. // IPC 处理程序:获取 ADB 路径配置(返回实际使用的 ADB 路径)
  158. ipcMain.handle('get-adb-path-config', async () => {
  159. // 返回实际使用的 ADB 路径(项目目录下的 adb-tools/adb.exe)
  160. const adbPath = getCachedAdbPath();
  161. // 如果是项目目录下的 adb,返回其目录路径;否则返回 null(表示使用系统路径)
  162. const projectRoot = path.join(__dirname, '..', '..');
  163. const localAdbPath = path.join(projectRoot, 'adb-tools', 'adb.exe');
  164. if (adbPath === localAdbPath || adbPath.includes('adb-tools')) {
  165. return path.join(projectRoot, 'adb-tools');
  166. }
  167. // 向后兼容:如果配置文件存在,返回配置的路径
  168. const config = loadConfig();
  169. if (config && config['adb-path']) {
  170. return config['adb-path'];
  171. }
  172. return null;
  173. });
  174. // IPC 处理程序:获取 ADB 设备列表
  175. ipcMain.handle('get-adb-devices', async () => {
  176. return await getADBDevices();
  177. });
  178. // IPC 处理程序:扫描网络设备(支持实时推送)
  179. ipcMain.handle('scan-adb-devices', async (event) => {
  180. try {
  181. const devices = await scanNetworkDevices(event);
  182. return devices;
  183. } catch (error) {
  184. return [];
  185. }
  186. });
  187. // IPC 处理程序:连接 ADB 设备
  188. ipcMain.handle('connect-adb-device', async (event, ipPort) => {
  189. return await connectDevice(ipPort);
  190. });
  191. }