User 5 ay önce
ebeveyn
işleme
78d4c53e74
7 değiştirilmiş dosya ile 277 ekleme ve 44 silme
  1. 6 5
      README.md
  2. 3 0
      config.js
  3. 181 11
      main.js
  4. 0 4
      package-lock.json
  5. 9 0
      preload.cjs
  6. 25 12
      src/pages/0-TEST-CONNECT.BAT
  7. 53 12
      src/pages/Devices/Devices.js

+ 6 - 5
README.md

@@ -1,14 +1,15 @@
 **一、框架:Electron + React + Vite
 
-**二、开发命令
+**二、测试连接:adb connect 192.168.0.15:5555
+
+**三、开发命令
 - `npm run dev`:只启动 Vite 前端开发服务器。
 - `npm run electron`:直接启动 Electron(加载本地文件)。
 - `npm run electron:dev`:同时启动 Vite 开发服务器并在准备好后打开 Electron(推荐开发时使用)。
 - `npm run build`:构建生产版本。
 - `npm run preview`:预览构建后的应用。
 
-
-**三、将页面设置为子页面(条件渲染)
+**四、将页面设置为子页面(条件渲染)
 
 **步骤:**
 
@@ -25,7 +26,7 @@
 **3. 修改 `src/App.jsx`**:
    - 删除 Devices 的 import 和组件使用
 
-**、响应式适配方案
+**、响应式适配方案
 
 **推荐组合:媒体查询 + 相对单位 + Flexbox**
 
@@ -35,7 +36,7 @@
 - **关键尺寸**:`vw` 或 `%`
 - **布局控制**:Flexbox + 媒体查询断点
 
-**、媒体查询器**
+**、媒体查询器**
 
 - **桌面端适配**:
   - 最小值 = 1024px × 20% = 204.8px ≈ 200px(1024 是 PC 的最小尺寸)

+ 3 - 0
config.js

@@ -0,0 +1,3 @@
+{
+	"adb-path": "C:\\Users\\GIGABYTE\\AppData\\Local\\Android\\Sdk\\platform-tools"
+}

+ 181 - 11
main.js

@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
 import path from 'path';
 import { exec } from 'child_process';
 import { promisify } from 'util';
+import { existsSync, readFileSync } from 'fs';
 
 const execAsync = promisify(exec);
 
@@ -10,6 +11,67 @@ const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
 
+// 读取配置文件
+function loadConfig() {
+  try {
+    const configPath = path.join(__dirname, 'config.js');
+    if (existsSync(configPath)) {
+      const configContent = readFileSync(configPath, 'utf-8');
+      // 解析 JSON(移除可能的注释和尾随逗号)
+      const jsonContent = configContent.replace(/\/\/.*$/gm, '').replace(/,(\s*[}\]])/g, '$1');
+      return JSON.parse(jsonContent);
+    }
+  } catch (error) {
+    console.warn('Failed to load config.js:', error.message);
+  }
+  return null;
+}
+
+// 查找 ADB 可执行文件路径
+function getAdbPath() {
+  // 首先尝试从配置文件读取
+  const config = loadConfig();
+  if (config && config['adb-path']) {
+    const configAdbPath = path.join(config['adb-path'], 'adb.exe');
+    if (existsSync(configAdbPath)) {
+      console.log('Using ADB path from config.js:', configAdbPath);
+      return configAdbPath;
+    }
+    // 如果配置的路径不存在,尝试直接使用配置的路径(可能已经是完整路径)
+    if (existsSync(config['adb-path'])) {
+      console.log('Using ADB path from config.js:', config['adb-path']);
+      return config['adb-path'];
+    }
+  }
+
+  // 如果配置文件没有或路径不存在,使用常见 ADB 安装位置(按优先级排序)
+  const possiblePaths = [
+    path.join(process.env.LOCALAPPDATA || '', 'Android', 'Sdk', 'platform-tools', 'adb.exe'),
+    path.join(process.env.USERPROFILE || '', 'AppData', 'Local', 'Android', 'Sdk', 'platform-tools', 'adb.exe'),
+    path.join(process.env.ProgramFiles || '', 'Android', 'android-sdk', 'platform-tools', 'adb.exe'),
+    path.join(process.env['ProgramFiles(x86)'] || '', 'Android', 'android-sdk', 'platform-tools', 'adb.exe'),
+  ];
+
+  // 检查常见位置
+  for (const adbPath of possiblePaths) {
+    if (existsSync(adbPath)) {
+      return adbPath;
+    }
+  }
+
+  // 如果都找不到,尝试使用 PATH 中的 adb
+  return 'adb';
+}
+
+// 缓存 ADB 路径
+let adbPathCache = null;
+function getCachedAdbPath() {
+  if (!adbPathCache) {
+    adbPathCache = getAdbPath();
+  }
+  return adbPathCache;
+}
+
 // 设置内容安全策略(CSP),防止 XSS 攻击
 function setContentSecurityPolicy() {
   session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
@@ -24,9 +86,12 @@ function setContentSecurityPolicy() {
   });
 }
 
+// 全局主窗口引用,用于发送实时事件
+let mainWindow = null;
+
 // 创建主窗口,根据环境加载不同内容源
 function createWindow() {
-  const mainWindow = new BrowserWindow({
+  mainWindow = new BrowserWindow({
     width: 1200,
     height: 800,
     webPreferences: {
@@ -47,7 +112,8 @@ function createWindow() {
 // 获取已连接的 ADB 设备列表
 async function getADBDevices() {
   try {
-    const { stdout } = await execAsync('adb devices');
+    const adbPath = getCachedAdbPath();
+    const { stdout } = await execAsync(`${adbPath} devices`);
     const lines = stdout.split('\n').slice(1);
     const devices = [];
     
@@ -67,15 +133,113 @@ async function getADBDevices() {
   }
 }
 
+// 网络扫描:从 192.168.0.1 开始扫描网段,尝试连接设备(实时推送结果)
+async function scanNetworkDevices(event) {
+  const adbPath = getCachedAdbPath();
+  const baseIP = '192.168.0';
+  const port = 5555;
+  const maxConcurrent = 20; // 限制并发数,避免过载
+  const connectTimeout = 1500; // 连接超时时间(毫秒)
+  
+  // 生成 IP 地址列表
+  const ipList = [];
+  for (let i = 1; i <= 255; i++) {
+    ipList.push(`${baseIP}.${i}`);
+  }
+  
+  const foundDevices = new Set(); // 使用 Set 避免重复
+  
+  // 分批并发扫描
+  for (let i = 0; i < ipList.length; i += maxConcurrent) {
+    const batch = ipList.slice(i, i + maxConcurrent);
+    const promises = batch.map(async (ip) => {
+      const ipPort = `${ip}:${port}`;
+      try {
+        // 尝试连接设备(超时时间短,避免等待太久)
+        await execAsync(`${adbPath} connect ${ipPort}`, {
+          timeout: connectTimeout,
+          maxBuffer: 1024 * 1024
+        });
+        
+        // 连接后稍等片刻,检查设备是否真的连接成功
+        await new Promise(resolve => setTimeout(resolve, 300));
+        
+        // 检查设备是否在列表中
+        try {
+          const { stdout } = await execAsync(`${adbPath} devices`, {
+            timeout: 2000,
+            maxBuffer: 1024 * 1024
+          });
+          
+          const lines = stdout.split('\n').slice(1);
+          for (const line of lines) {
+            const parts = line.trim().split(/\s+/);
+            if (parts.length >= 2 && parts[0] === ipPort && parts[1] === 'device') {
+              // 发现设备,实时推送
+              if (!foundDevices.has(ipPort)) {
+                foundDevices.add(ipPort);
+                const device = {
+                  id: ipPort,
+                  status: 'device'
+                };
+                // 实时发送发现的设备
+                if (event && event.sender) {
+                  event.sender.send('device-found', device);
+                } else if (mainWindow) {
+                  mainWindow.webContents.send('device-found', device);
+                }
+                console.log('发现设备:', ipPort);
+              }
+              return ipPort;
+            }
+          }
+        } catch (checkError) {
+          // 检查失败,忽略
+        }
+      } catch (error) {
+        // 连接失败是正常的,忽略错误
+        return null;
+      }
+      return null;
+    });
+    
+    await Promise.all(promises);
+  }
+  
+  // 返回所有发现的设备
+  return Array.from(foundDevices).map(ipPort => ({
+    id: ipPort,
+    status: 'device'
+  }));
+}
+
+// IPC 处理程序:获取 ADB 路径配置
+ipcMain.handle('get-adb-path-config', async () => {
+  const config = loadConfig();
+  return config ? config['adb-path'] : null;
+});
+
 // IPC 处理程序:获取 ADB 设备列表
 ipcMain.handle('get-adb-devices', async () => {
   return await getADBDevices();
 });
 
+// IPC 处理程序:扫描网络设备(支持实时推送)
+ipcMain.handle('scan-adb-devices', async (event) => {
+  try {
+    const devices = await scanNetworkDevices(event);
+    return devices;
+  } catch (error) {
+    console.error('网络扫描失败:', error);
+    return [];
+  }
+});
+
 // IPC 处理程序:连接 ADB 设备
 ipcMain.handle('connect-adb-device', async (event, ipPort) => {
   try {
-    await execAsync(`adb connect ${ipPort}`);
+    const adbPath = getCachedAdbPath();
+    await execAsync(`${adbPath} connect ${ipPort}`);
     return { success: true };
   } catch (error) {
     console.error('连接设备失败:', error);
@@ -90,7 +254,8 @@ ipcMain.handle('get-device-resolution', async (event, ipPort) => {
   }
   try {
     // 使用 wm size 命令获取设备分辨率
-    const { stdout } = await execAsync(`adb -s ${ipPort} shell wm size`);
+    const adbPath = getCachedAdbPath();
+    const { stdout } = await execAsync(`${adbPath} -s ${ipPort} shell wm size`);
     // 输出格式通常是: "Physical size: 1080x2400" 或 "1080x2400"
     const match = stdout.match(/(\d+)x(\d+)/);
     if (match) {
@@ -115,7 +280,8 @@ ipcMain.handle('capture-screenshot', async (event, ipPort) => {
     return { success: false, error: '缺少设备 ID' };
   }
   try {
-    const { stdout } = await execAsync(`adb -s ${ipPort} exec-out screencap -p`, {
+    const adbPath = getCachedAdbPath();
+    const { stdout } = await execAsync(`${adbPath} -s ${ipPort} exec-out screencap -p`, {
       encoding: 'buffer',
       maxBuffer: 25 * 1024 * 1024,
     });
@@ -135,7 +301,8 @@ ipcMain.handle('send-tap', async (event, ipPort, x, y) => {
     return { success: false, error: '坐标必须是数字' };
   }
   try {
-    const command = `adb -s ${ipPort} shell input tap ${x} ${y}`;
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input tap ${x} ${y}`;
     await execAsync(command, {
       timeout: 5000,
       maxBuffer: 1024 * 1024
@@ -156,7 +323,8 @@ ipcMain.handle('send-swipe', async (event, ipPort, x1, y1, x2, y2, duration = 30
     return { success: false, error: '坐标必须是数字' };
   }
   try {
-    const command = `adb -s ${ipPort} shell input swipe ${x1} ${y1} ${x2} ${y2} ${duration}`;
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input swipe ${x1} ${y1} ${x2} ${y2} ${duration}`;
     await execAsync(command, {
       timeout: 5000,
       maxBuffer: 1024 * 1024
@@ -177,6 +345,7 @@ ipcMain.handle('send-text', async (event, ipPort, text) => {
     return { success: false, error: '文字必须是字符串' };
   }
   try {
+    const adbPath = getCachedAdbPath();
     // ADB input text 需要转义特殊字符
     // 使用单引号包裹,并转义单引号
     const escapedText = text
@@ -219,14 +388,14 @@ ipcMain.handle('send-text', async (event, ipPort, text) => {
             .replace(/\$/g, '\\$')
             .replace(/"/g, '\\"');
           
-          await execAsync(`adb -s ${ipPort} shell input text "${escapedLine}"`, {
+          await execAsync(`${adbPath} -s ${ipPort} shell input text "${escapedLine}"`, {
             timeout: 5000,
             maxBuffer: 1024 * 1024
           });
         }
         // 发送换行(除了最后一行)
         if (i < lines.length - 1) {
-          await execAsync(`adb -s ${ipPort} shell input keyevent KEYCODE_ENTER`, {
+          await execAsync(`${adbPath} -s ${ipPort} shell input keyevent KEYCODE_ENTER`, {
             timeout: 5000,
             maxBuffer: 1024 * 1024
           });
@@ -234,7 +403,7 @@ ipcMain.handle('send-text', async (event, ipPort, text) => {
       }
     } else {
       // 没有换行,直接发送
-      const command = `adb -s ${ipPort} shell input text "${escapedText}"`;
+      const command = `${adbPath} -s ${ipPort} shell input text "${escapedText}"`;
       await execAsync(command, {
         timeout: 5000,
         maxBuffer: 1024 * 1024
@@ -257,7 +426,8 @@ ipcMain.handle('send-key-event', async (event, ipPort, keyCode) => {
     return { success: false, error: '按键代码必须是字符串' };
   }
   try {
-    const command = `adb -s ${ipPort} shell input keyevent ${keyCode}`;
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input keyevent ${keyCode}`;
     await execAsync(command, {
       timeout: 5000,
       maxBuffer: 1024 * 1024

+ 0 - 4
package-lock.json

@@ -52,7 +52,6 @@
       "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@babel/code-frame": "^7.27.1",
         "@babel/generator": "^7.28.5",
@@ -1377,7 +1376,6 @@
         }
       ],
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "baseline-browser-mapping": "^2.9.0",
         "caniuse-lite": "^1.0.30001759",
@@ -2729,7 +2727,6 @@
       "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
       "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "loose-envify": "^1.1.0"
       },
@@ -3143,7 +3140,6 @@
       "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "esbuild": "^0.21.3",
         "postcss": "^8.4.43",

+ 9 - 0
preload.cjs

@@ -1,6 +1,7 @@
 const { contextBridge, ipcRenderer } = require('electron');
 
 contextBridge.exposeInMainWorld('electronAPI', {
+  getAdbPathConfig: () => ipcRenderer.invoke('get-adb-path-config'),
   scanADBDevices: () => ipcRenderer.invoke('scan-adb-devices'),
   getADBDevices: () => ipcRenderer.invoke('get-adb-devices'),
   connectADBDevice: (ipPort) => ipcRenderer.invoke('connect-adb-device', ipPort),
@@ -10,5 +11,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
   sendSwipe: (ipPort, x1, y1, x2, y2, duration) => ipcRenderer.invoke('send-swipe', ipPort, x1, y1, x2, y2, duration),
   sendText: (ipPort, text) => ipcRenderer.invoke('send-text', ipPort, text),
   sendKeyEvent: (ipPort, keyCode) => ipcRenderer.invoke('send-key-event', ipPort, keyCode),
+  // 监听设备发现事件
+  onDeviceFound: (callback) => {
+    ipcRenderer.on('device-found', (event, device) => callback(device));
+  },
+  // 移除设备发现事件监听器
+  removeDeviceFoundListener: () => {
+    ipcRenderer.removeAllListeners('device-found');
+  },
 });
 

+ 25 - 12
src/pages/0-TEST-CONNECT.BAT

@@ -1,9 +1,22 @@
 @echo off
 chcp 65001 >nul
 echo ========================================
-echo Test ADB Connection 192.168.2.5:5555
+echo Test ADB Connection
 echo ========================================
 echo.
+echo Please enter the last two digits of the IP address:
+set /p IP_LAST_TWO="Last two digits (e.g., 0.15): "
+if "%IP_LAST_TWO%"=="" (
+    echo Error: IP address cannot be empty
+    pause
+    exit /b 1
+)
+set DEVICE_IP=192.168.%IP_LAST_TWO%
+set DEVICE_PORT=5555
+set DEVICE_ADDRESS=%DEVICE_IP%:%DEVICE_PORT%
+echo.
+echo Connecting to %DEVICE_ADDRESS%...
+echo.
 
 echo [1/5] Check if ADB is available...
 adb version >nul 2>&1
@@ -16,10 +29,10 @@ if errorlevel 1 (
 echo ADB is available
 echo.
 
-echo [2/5] Test network connectivity (ping 192.168.2.5)...
-ping -n 2 192.168.2.5 >nul 2>&1
+echo [2/5] Test network connectivity (ping %DEVICE_IP%)...
+ping -n 2 %DEVICE_IP% >nul 2>&1
 if errorlevel 1 (
-    echo Cannot ping 192.168.2.5
+    echo Cannot ping %DEVICE_IP%
     echo   Possible reasons: Phone and computer not on same network, or phone is off
     echo.
 ) else (
@@ -27,22 +40,22 @@ if errorlevel 1 (
     echo.
 )
 
-echo [3/5] Check if port 5555 is open...
-powershell -Command "Test-NetConnection -ComputerName 192.168.2.5 -Port 5555 -InformationLevel Quiet" >nul 2>&1
+echo [3/5] Check if port %DEVICE_PORT% is open...
+powershell -Command "Test-NetConnection -ComputerName %DEVICE_IP% -Port %DEVICE_PORT% -InformationLevel Quiet" >nul 2>&1
 if errorlevel 1 (
-    echo Port 5555 may not be open or blocked by firewall
+    echo Port %DEVICE_PORT% may not be open or blocked by firewall
     echo.
 ) else (
-    echo Port 5555 is accessible
+    echo Port %DEVICE_PORT% is accessible
     echo.
 )
 
 echo [4/5] Disconnect old connection (if any)...
-adb disconnect 192.168.2.5:5555 >nul 2>&1
+adb disconnect %DEVICE_ADDRESS% >nul 2>&1
 timeout /t 1 /nobreak >nul
 
-echo Attempting to connect 192.168.2.5:5555...
-adb connect 192.168.2.5:5555
+echo Attempting to connect %DEVICE_ADDRESS%...
+adb connect %DEVICE_ADDRESS%
 set CONNECT_RESULT=%ERRORLEVEL%
 
 echo.
@@ -66,7 +79,7 @@ if %CONNECT_RESULT% equ 0 (
     echo 3. Enable "Wireless debugging" or "Network ADB debugging"
     echo    - Settings ^> Developer options ^> Wireless debugging
     echo    - Or: Settings ^> Developer options ^> Network ADB debugging
-    echo    - Confirm port is 5555
+    echo    - Confirm port is %DEVICE_PORT%
     echo.
     echo 4. If phone doesn't have "Wireless debugging" option, try:
     echo    a) Connect phone to computer via USB

+ 53 - 12
src/pages/Devices/Devices.js

@@ -13,7 +13,7 @@ export function DevicesLogic() {
   // 防止重复扫描标记
   const hasScanned = useRef(false);
 
-  // 扫描设备列表(只获取已连接设备,不自动连接
+  // 扫描设备列表(网络扫描 + 获取已连接设备,实时显示
   const scanDevices = async () => {
     if (!window.electronAPI || !window.electronAPI.getADBDevices) {
       console.warn('Electron API 不可用');
@@ -22,23 +22,64 @@ export function DevicesLogic() {
     }
 
     setLoading(true);
+    // 清空现有设备列表,准备重新扫描
+    setDevices([]);
+    
+    // 设置实时监听设备发现事件
+    const handleDeviceFound = (device) => {
+      const deviceId = device?.id || device;
+      if (deviceId && /^\d+\.\d+\.\d+\.\d+:\d+$/.test(deviceId)) {
+        setDevices(prev => {
+          // 避免重复添加
+          if (prev.includes(deviceId)) {
+            return prev;
+          }
+          console.log('实时发现设备:', deviceId);
+          return [...prev, deviceId];
+        });
+      }
+    };
+    
+    // 注册事件监听器
+    if (window.electronAPI.onDeviceFound) {
+      window.electronAPI.onDeviceFound(handleDeviceFound);
+    }
+    
     try {
-      const deviceList = await window.electronAPI.getADBDevices();
-      // 过滤出 IP:端口格式的设备
-      const ipPortList = (deviceList || [])
+      // 先获取当前已连接的设备
+      const currentDevices = await window.electronAPI.getADBDevices();
+      const currentIPPorts = currentDevices
         .map(device => device?.id || device)
-        .filter(Boolean)
-        .filter(id => {
-          const ipPortPattern = /^\d+\.\d+\.\d+\.\d+:\d+$/;
-          return ipPortPattern.test(id);
-        });
-      console.log('devices:', ipPortList);
-      setDevices(ipPortList);
+        .filter(id => id && /^\d+\.\d+\.\d+\.\d+:\d+$/.test(id));
+      
+      if (currentIPPorts.length > 0) {
+        setDevices(currentIPPorts);
+      }
+      
+      // 然后进行网络扫描(会实时推送发现的设备)
+      console.log('开始网络扫描...');
+      await window.electronAPI.scanADBDevices();
+      console.log('网络扫描完成');
+      
+      // 扫描完成后,再次获取所有设备,确保没有遗漏
+      const finalDevices = await window.electronAPI.getADBDevices();
+      const finalIPPorts = finalDevices
+        .map(device => device?.id || device)
+        .filter(id => id && /^\d+\.\d+\.\d+\.\d+:\d+$/.test(id));
+      
+      // 合并并去重
+      setDevices(prev => {
+        const allDevices = new Set([...prev, ...finalIPPorts]);
+        return Array.from(allDevices);
+      });
     } catch (err) {
       console.error('扫描失败:', err);
-      setDevices([]);
     } finally {
       setLoading(false);
+      // 移除事件监听器
+      if (window.electronAPI.removeDeviceFoundListener) {
+        window.electronAPI.removeDeviceFoundListener();
+      }
     }
   };