Selaa lähdekoodia

main逻辑拆分

User 5 kuukautta sitten
vanhempi
sitoutus
2ec116c99e

+ 42 - 0
main-js/adb/device-info.js

@@ -0,0 +1,42 @@
+import { ipcMain } from 'electron';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { getCachedAdbPath } from '../config.js';
+
+const execAsync = promisify(exec);
+
+// 获取设备分辨率
+export async function getDeviceResolution(ipPort) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  try {
+    // 使用 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) {
+      return {
+        success: true,
+        width: parseInt(match[1], 10),
+        height: parseInt(match[2], 10)
+      };
+    }
+    // 如果解析失败,返回默认值
+    return { success: true, width: 1280, height: 2400 };
+  } catch (error) {
+    console.error('获取设备分辨率失败:', error);
+    // 返回默认值
+    return { success: true, width: 1280, height: 2400 };
+  }
+}
+
+// 注册 IPC 处理器
+export function registerIpcHandlers() {
+  // IPC 处理程序:获取设备分辨率
+  ipcMain.handle('get-device-resolution', async (event, ipPort) => {
+    return await getDeviceResolution(ipPort);
+  });
+}
+

+ 158 - 0
main-js/adb/device-manager.js

@@ -0,0 +1,158 @@
+import { ipcMain, BrowserWindow } from 'electron';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { getCachedAdbPath, loadConfig } from '../config.js';
+
+const execAsync = promisify(exec);
+
+// 获取主窗口引用(用于实时推送设备发现事件)
+function getMainWindow() {
+  const windows = BrowserWindow.getAllWindows();
+  return windows.length > 0 ? windows[0] : null;
+}
+
+// 获取已连接的 ADB 设备列表
+export async function getADBDevices() {
+  try {
+    const adbPath = getCachedAdbPath();
+    const { stdout } = await execAsync(`${adbPath} devices`);
+    const lines = stdout.split('\n').slice(1);
+    const devices = [];
+    
+    for (const line of lines) {
+      const parts = line.trim().split(/\s+/);
+      if (parts.length >= 2 && parts[0] && parts[1] === 'device') {
+        devices.push({
+          id: parts[0],
+          status: parts[1]
+        });
+      }
+    }
+    return devices;
+  } catch (error) {
+    console.error('获取设备列表失败:', error);
+    return [];
+  }
+}
+
+// 网络扫描:从 192.168.0.1 开始扫描网段,尝试连接设备(实时推送结果)
+export 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
+        });
+        
+        // 连接后稍等片刻,让设备注册到 ADB 服务器
+        await new Promise(resolve => setTimeout(resolve, 500));
+      } catch (error) {
+        // 连接失败是正常的,继续检查设备列表
+      }
+      
+      // 直接使用 adb -s IP:PORT devices 命令检查该设备是否在列表中
+      try {
+        const { stdout } = await execAsync(`${adbPath} -s ${ipPort} devices`, {
+          timeout: 2000,
+          maxBuffer: 1024 * 1024
+        });
+        
+        // 检查输出中是否包含 IP:PORT,如果有则说明设备存在
+        if (stdout.includes(ipPort)) {
+          // 发现设备,实时推送
+          if (!foundDevices.has(ipPort)) {
+            foundDevices.add(ipPort);
+            const device = {
+              id: ipPort,
+              status: 'device'
+            };
+            // 实时发送发现的设备
+            if (event && event.sender) {
+              event.sender.send('device-found', device);
+            } else {
+              const mainWindow = getMainWindow();
+              if (mainWindow) {
+                mainWindow.webContents.send('device-found', device);
+              }
+            }
+            console.log('发现设备:', ipPort);
+          }
+          return ipPort;
+        }
+      } catch (checkError) {
+        // 检查失败,忽略
+      }
+      return null;
+    });
+    
+    await Promise.all(promises);
+  }
+  
+  // 返回所有发现的设备
+  return Array.from(foundDevices).map(ipPort => ({
+    id: ipPort,
+    status: 'device'
+  }));
+}
+
+// 连接 ADB 设备
+export async function connectDevice(ipPort) {
+  try {
+    const adbPath = getCachedAdbPath();
+    await execAsync(`${adbPath} connect ${ipPort}`);
+    return { success: true };
+  } catch (error) {
+    console.error('连接设备失败:', error);
+    return { success: false, error: error.message };
+  }
+}
+
+// 注册 IPC 处理器
+export function registerIpcHandlers() {
+  // 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) => {
+    return await connectDevice(ipPort);
+  });
+}
+

+ 164 - 0
main-js/adb/input.js

@@ -0,0 +1,164 @@
+import { ipcMain } from 'electron';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { getCachedAdbPath } from '../config.js';
+
+const execAsync = promisify(exec);
+
+// 发送 tap 事件到设备
+export async function sendTap(ipPort, x, y) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  if (typeof x !== 'number' || typeof y !== 'number') {
+    return { success: false, error: '坐标必须是数字' };
+  }
+  try {
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input tap ${x} ${y}`;
+    await execAsync(command, {
+      timeout: 5000,
+      maxBuffer: 1024 * 1024
+    });
+    return { success: true };
+  } catch (error) {
+    console.error('Tap 失败:', error.message);
+    return { success: false, error: error.message };
+  }
+}
+
+// 发送 swipe 事件到设备
+export async function sendSwipe(ipPort, x1, y1, x2, y2, duration = 300) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number') {
+    return { success: false, error: '坐标必须是数字' };
+  }
+  try {
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input swipe ${x1} ${y1} ${x2} ${y2} ${duration}`;
+    await execAsync(command, {
+      timeout: 5000,
+      maxBuffer: 1024 * 1024
+    });
+    return { success: true };
+  } catch (error) {
+    console.error('Swipe 失败:', error.message);
+    return { success: false, error: error.message };
+  }
+}
+
+// 转义文字中的特殊字符
+function escapeText(text) {
+  return text
+    .replace(/\\/g, '\\\\')  // 先转义反斜杠
+    .replace(/'/g, "'\\''")   // 转义单引号
+    .replace(/ /g, '%s')      // 空格转换为 %s
+    .replace(/&/g, '\\&')     // 转义 &
+    .replace(/</g, '\\<')     // 转义 <
+    .replace(/>/g, '\\>')     // 转义 >
+    .replace(/\(/g, '\\(')    // 转义 (
+    .replace(/\)/g, '\\)')    // 转义 )
+    .replace(/;/g, '\\;')     // 转义 ;
+    .replace(/\|/g, '\\|')    // 转义 |
+    .replace(/\*/g, '\\*')    // 转义 *
+    .replace(/\?/g, '\\?')    // 转义 ?
+    .replace(/`/g, '\\`')     // 转义 `
+    .replace(/\$/g, '\\$')    // 转义 $
+    .replace(/"/g, '\\"');    // 转义 "
+}
+
+// 发送文字到设备
+export async function sendText(ipPort, text) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  if (typeof text !== 'string') {
+    return { success: false, error: '文字必须是字符串' };
+  }
+  try {
+    const adbPath = getCachedAdbPath();
+    
+    // 对于换行,使用 KEYCODE_ENTER
+    if (text.includes('\n')) {
+      // 如果有换行,分段发送
+      const lines = text.split('\n');
+      for (let i = 0; i < lines.length; i++) {
+        if (lines[i]) {
+          const escapedLine = escapeText(lines[i]);
+          await execAsync(`${adbPath} -s ${ipPort} shell input text "${escapedLine}"`, {
+            timeout: 5000,
+            maxBuffer: 1024 * 1024
+          });
+        }
+        // 发送换行(除了最后一行)
+        if (i < lines.length - 1) {
+          await execAsync(`${adbPath} -s ${ipPort} shell input keyevent KEYCODE_ENTER`, {
+            timeout: 5000,
+            maxBuffer: 1024 * 1024
+          });
+        }
+      }
+    } else {
+      // 没有换行,直接发送
+      const escapedText = escapeText(text);
+      const command = `${adbPath} -s ${ipPort} shell input text "${escapedText}"`;
+      await execAsync(command, {
+        timeout: 5000,
+        maxBuffer: 1024 * 1024
+      });
+    }
+    
+    return { success: true };
+  } catch (error) {
+    console.error('发送文字失败:', error.message);
+    return { success: false, error: error.message };
+  }
+}
+
+// 发送按键事件到设备
+export async function sendKeyEvent(ipPort, keyCode) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  if (typeof keyCode !== 'string') {
+    return { success: false, error: '按键代码必须是字符串' };
+  }
+  try {
+    const adbPath = getCachedAdbPath();
+    const command = `${adbPath} -s ${ipPort} shell input keyevent ${keyCode}`;
+    await execAsync(command, {
+      timeout: 5000,
+      maxBuffer: 1024 * 1024
+    });
+    return { success: true };
+  } catch (error) {
+    console.error('发送按键失败:', error.message);
+    return { success: false, error: error.message };
+  }
+}
+
+// 注册 IPC 处理器
+export function registerIpcHandlers() {
+  // IPC 处理程序:发送 tap 事件到设备
+  ipcMain.handle('send-tap', async (event, ipPort, x, y) => {
+    return await sendTap(ipPort, x, y);
+  });
+
+  // IPC 处理程序:发送 swipe 事件到设备
+  ipcMain.handle('send-swipe', async (event, ipPort, x1, y1, x2, y2, duration = 300) => {
+    return await sendSwipe(ipPort, x1, y1, x2, y2, duration);
+  });
+
+  // IPC 处理程序:发送文字到设备
+  ipcMain.handle('send-text', async (event, ipPort, text) => {
+    return await sendText(ipPort, text);
+  });
+
+  // IPC 处理程序:发送按键事件到设备
+  ipcMain.handle('send-key-event', async (event, ipPort, keyCode) => {
+    return await sendKeyEvent(ipPort, keyCode);
+  });
+}
+

+ 56 - 0
main-js/adb/screenshot.js

@@ -0,0 +1,56 @@
+import { ipcMain } from 'electron';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { getCachedAdbPath } from '../config.js';
+
+const execAsync = promisify(exec);
+
+// 抓取设备截屏(返回 base64 PNG/JPEG)
+export async function captureScreenshot(ipPort, options = {}) {
+  if (!ipPort) {
+    return { success: false, error: '缺少设备 ID' };
+  }
+  try {
+    const adbPath = getCachedAdbPath();
+    
+    // 从选项或默认值获取参数
+    const format = options.format || 'png'; // 'png' 或 'jpeg'
+    const quality = options.quality || 80; // JPEG 质量 1-100
+    const scale = options.scale || 1.0; // 缩放比例 0.1-1.0
+    
+    // 构建 screencap 命令
+    let command = `${adbPath} -s ${ipPort} exec-out screencap`;
+    
+    // 根据格式选择参数
+    if (format === 'jpeg') {
+      // JPEG 格式(更小,延迟更低)
+      command += ` -j ${quality}`;
+    } else {
+      // PNG 格式(默认)
+      command += ' -p';
+    }
+    
+    // 如果缩放比例不是 1.0,需要通过 shell 命令处理
+    // 注意:screencap 本身不支持缩放,需要通过其他方式实现
+    // 这里先实现基本功能,缩放可以在后续优化
+    
+    const { stdout } = await execAsync(command, {
+      encoding: 'buffer',
+      maxBuffer: 25 * 1024 * 1024,
+    });
+    
+    return { success: true, data: stdout.toString('base64') };
+  } catch (error) {
+    console.error('截屏失败:', error);
+    return { success: false, error: error.message };
+  }
+}
+
+// 注册 IPC 处理器
+export function registerIpcHandlers() {
+  // IPC 处理程序:抓取设备截屏(返回 base64 PNG/JPEG)
+  ipcMain.handle('capture-screenshot', async (event, ipPort, options = {}) => {
+    return await captureScreenshot(ipPort, options);
+  });
+}
+

+ 68 - 0
main-js/config.js

@@ -0,0 +1,68 @@
+import { existsSync, readFileSync } from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// 读取配置文件
+export 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;
+export function getCachedAdbPath() {
+  if (!adbPathCache) {
+    adbPathCache = getAdbPath();
+  }
+  return adbPathCache;
+}
+

+ 16 - 0
main-js/security.js

@@ -0,0 +1,16 @@
+import { session } from 'electron';
+
+// 设置内容安全策略(CSP),防止 XSS 攻击
+export function setContentSecurityPolicy(isDev) {
+  session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
+    const csp = isDev
+      ? "default-src 'self'; script-src 'self' 'unsafe-inline' http://localhost:*; style-src 'self' 'unsafe-inline'; connect-src 'self' http://localhost:* ws://localhost:*; img-src 'self' data: https: blob:; font-src 'self' data:; worker-src 'self' blob:;"
+      : "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: https:; font-src 'self' data:;";
+    
+    const responseHeaders = Object.assign({}, details.responseHeaders);
+    responseHeaders['Content-Security-Policy'] = [csp];
+    
+    callback({ responseHeaders });
+  });
+}
+

+ 59 - 0
main-js/window-setup.js

@@ -0,0 +1,59 @@
+import { BrowserWindow, app } from 'electron';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// 创建主窗口,根据环境加载不同内容源
+export function createWindow() {
+  const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
+  
+  const mainWindow = new BrowserWindow({
+    width: 1200,
+    height: 800,
+    webPreferences: {
+      preload: path.join(__dirname, '..', 'preload.cjs'),
+      nodeIntegration: false,
+      contextIsolation: true
+    }
+  });
+
+  // 设置窗口事件处理(禁用关闭确认对话框等)
+  setupWindow(mainWindow);
+
+  if (isDev) {
+    mainWindow.loadURL('http://localhost:5173');
+    mainWindow.webContents.openDevTools();
+  } else {
+    mainWindow.loadFile(path.join(__dirname, '..', 'dist/index.html'));
+  }
+  
+  return mainWindow;
+}
+
+// 设置窗口事件处理(禁用关闭确认对话框等)
+function setupWindow(mainWindow) {
+  // 禁用窗口关闭确认对话框,直接关闭
+  mainWindow.on('close', (event) => {
+    // 不阻止关闭事件,直接关闭窗口
+    // 如果需要清理资源,可以在这里添加
+  });
+  
+  // 禁用 webContents 的 beforeunload 确认对话框
+  mainWindow.webContents.on('will-prevent-unload', (event) => {
+    // 不阻止卸载,直接关闭
+    // 注意:这里不需要调用 event.preventDefault(),因为我们要允许关闭
+  });
+  
+  // 禁用 beforeunload 事件(前端页面可能触发的确认对话框)
+  mainWindow.webContents.setWindowOpenHandler(() => {
+    return { action: 'allow' };
+  });
+  
+  // 禁用所有可能阻止关闭的事件
+  mainWindow.webContents.on('beforeunload', (event) => {
+    // 不阻止卸载
+  });
+}
+

+ 14 - 474
main.js

@@ -1,490 +1,30 @@
-import { app, BrowserWindow, session, ipcMain } from 'electron';
+import { app, BrowserWindow, session } from 'electron';
 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);
+import { setContentSecurityPolicy } from './main-js/security.js';
+import { createWindow } from './main-js/window-setup.js';
+import { registerIpcHandlers as registerDeviceManagerHandlers } from './main-js/adb/device-manager.js';
+import { registerIpcHandlers as registerDeviceInfoHandlers } from './main-js/adb/device-info.js';
+import { registerIpcHandlers as registerScreenshotHandlers } from './main-js/adb/screenshot.js';
+import { registerIpcHandlers as registerInputHandlers } from './main-js/adb/input.js';
 
 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) => {
-    const csp = isDev
-      ? "default-src 'self'; script-src 'self' 'unsafe-inline' http://localhost:*; style-src 'self' 'unsafe-inline'; connect-src 'self' http://localhost:* ws://localhost:*; img-src 'self' data: https: blob:; font-src 'self' data:; worker-src 'self' blob:;"
-      : "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: https:; font-src 'self' data:;";
-    
-    const responseHeaders = Object.assign({}, details.responseHeaders);
-    responseHeaders['Content-Security-Policy'] = [csp];
-    
-    callback({ responseHeaders });
-  });
-}
-
-// 全局主窗口引用,用于发送实时事件
-let mainWindow = null;
-
-// 创建主窗口,根据环境加载不同内容源
-function createWindow() {
-  mainWindow = new BrowserWindow({
-    width: 1200,
-    height: 800,
-    webPreferences: {
-      preload: path.join(__dirname, 'preload.cjs'),
-      nodeIntegration: false,
-      contextIsolation: true
-    }
-  });
-
-  // 禁用窗口关闭确认对话框,直接关闭
-  mainWindow.on('close', (event) => {
-    // 不阻止关闭事件,直接关闭窗口
-    // 如果需要清理资源,可以在这里添加
-  });
-  
-  // 禁用 webContents 的 beforeunload 确认对话框
-  mainWindow.webContents.on('will-prevent-unload', (event) => {
-    // 不阻止卸载,直接关闭
-    // 注意:这里不需要调用 event.preventDefault(),因为我们要允许关闭
-  });
-  
-  // 禁用 beforeunload 事件(前端页面可能触发的确认对话框)
-  mainWindow.webContents.setWindowOpenHandler(() => {
-    return { action: 'allow' };
-  });
-  
-  // 禁用所有可能阻止关闭的事件
-  mainWindow.webContents.on('beforeunload', (event) => {
-    // 不阻止卸载
-  });
-
-  if (isDev) {
-    mainWindow.loadURL('http://localhost:5173');
-    mainWindow.webContents.openDevTools();
-  } else {
-    mainWindow.loadFile(path.join(__dirname, 'dist/index.html'));
-  }
-}
-
-// 获取已连接的 ADB 设备列表
-async function getADBDevices() {
-  try {
-    const adbPath = getCachedAdbPath();
-    const { stdout } = await execAsync(`${adbPath} devices`);
-    const lines = stdout.split('\n').slice(1);
-    const devices = [];
-    
-    for (const line of lines) {
-      const parts = line.trim().split(/\s+/);
-      if (parts.length >= 2 && parts[0] && parts[1] === 'device') {
-        devices.push({
-          id: parts[0],
-          status: parts[1]
-        });
-      }
-    }
-    return devices;
-  } catch (error) {
-    console.error('获取设备列表失败:', error);
-    return [];
-  }
-}
-
-// 网络扫描:从 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
-        });
-        
-        // 连接后稍等片刻,让设备注册到 ADB 服务器
-        await new Promise(resolve => setTimeout(resolve, 500));
-      } catch (error) {
-        // 连接失败是正常的,继续检查设备列表
-      }
-      
-      // 直接使用 adb -s IP:PORT devices 命令检查该设备是否在列表中
-      try {
-        const { stdout } = await execAsync(`${adbPath} -s ${ipPort} devices`, {
-          timeout: 2000,
-          maxBuffer: 1024 * 1024
-        });
-        
-        // 检查输出中是否包含 IP:PORT,如果有则说明设备存在
-        if (stdout.includes(ipPort)) {
-          // 发现设备,实时推送
-          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) {
-        // 检查失败,忽略
-      }
-      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 {
-    const adbPath = getCachedAdbPath();
-    await execAsync(`${adbPath} connect ${ipPort}`);
-    return { success: true };
-  } catch (error) {
-    console.error('连接设备失败:', error);
-    return { success: false, error: error.message };
-  }
-});
-
-// IPC 处理程序:获取设备分辨率
-ipcMain.handle('get-device-resolution', async (event, ipPort) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  try {
-    // 使用 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) {
-      return {
-        success: true,
-        width: parseInt(match[1], 10),
-        height: parseInt(match[2], 10)
-      };
-    }
-    // 如果解析失败,返回默认值
-    return { success: true, width: 1280, height: 2400 };
-  } catch (error) {
-    console.error('获取设备分辨率失败:', error);
-    // 返回默认值
-    return { success: true, width: 1280, height: 2400 };
-  }
-});
-
-// IPC 处理程序:抓取设备截屏(返回 base64 PNG/JPEG)
-ipcMain.handle('capture-screenshot', async (event, ipPort, options = {}) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  try {
-    const adbPath = getCachedAdbPath();
-    
-    // 从选项或默认值获取参数
-    const format = options.format || 'png'; // 'png' 或 'jpeg'
-    const quality = options.quality || 80; // JPEG 质量 1-100
-    const scale = options.scale || 1.0; // 缩放比例 0.1-1.0
-    
-    // 构建 screencap 命令
-    let command = `${adbPath} -s ${ipPort} exec-out screencap`;
-    
-    // 根据格式选择参数
-    if (format === 'jpeg') {
-      // JPEG 格式(更小,延迟更低)
-      command += ` -j ${quality}`;
-    } else {
-      // PNG 格式(默认)
-      command += ' -p';
-    }
-    
-    // 如果缩放比例不是 1.0,需要通过 shell 命令处理
-    // 注意:screencap 本身不支持缩放,需要通过其他方式实现
-    // 这里先实现基本功能,缩放可以在后续优化
-    
-    const { stdout } = await execAsync(command, {
-      encoding: 'buffer',
-      maxBuffer: 25 * 1024 * 1024,
-    });
-    
-    return { success: true, data: stdout.toString('base64') };
-  } catch (error) {
-    console.error('截屏失败:', error);
-    return { success: false, error: error.message };
-  }
-});
-
-// IPC 处理程序:发送 tap 事件到设备
-ipcMain.handle('send-tap', async (event, ipPort, x, y) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  if (typeof x !== 'number' || typeof y !== 'number') {
-    return { success: false, error: '坐标必须是数字' };
-  }
-  try {
-    const adbPath = getCachedAdbPath();
-    const command = `${adbPath} -s ${ipPort} shell input tap ${x} ${y}`;
-    await execAsync(command, {
-      timeout: 5000,
-      maxBuffer: 1024 * 1024
-    });
-    return { success: true };
-  } catch (error) {
-    console.error('Tap 失败:', error.message);
-    return { success: false, error: error.message };
-  }
-});
-
-// IPC 处理程序:发送 swipe 事件到设备
-ipcMain.handle('send-swipe', async (event, ipPort, x1, y1, x2, y2, duration = 300) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number') {
-    return { success: false, error: '坐标必须是数字' };
-  }
-  try {
-    const adbPath = getCachedAdbPath();
-    const command = `${adbPath} -s ${ipPort} shell input swipe ${x1} ${y1} ${x2} ${y2} ${duration}`;
-    await execAsync(command, {
-      timeout: 5000,
-      maxBuffer: 1024 * 1024
-    });
-    return { success: true };
-  } catch (error) {
-    console.error('Swipe 失败:', error.message);
-    return { success: false, error: error.message };
-  }
-});
-
-// IPC 处理程序:发送文字到设备
-ipcMain.handle('send-text', async (event, ipPort, text) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  if (typeof text !== 'string') {
-    return { success: false, error: '文字必须是字符串' };
-  }
-  try {
-    const adbPath = getCachedAdbPath();
-    // ADB input text 需要转义特殊字符
-    // 使用单引号包裹,并转义单引号
-    const escapedText = text
-      .replace(/\\/g, '\\\\')  // 先转义反斜杠
-      .replace(/'/g, "'\\''")   // 转义单引号
-      .replace(/ /g, '%s')      // 空格转换为 %s
-      .replace(/&/g, '\\&')     // 转义 &
-      .replace(/</g, '\\<')     // 转义 <
-      .replace(/>/g, '\\>')     // 转义 >
-      .replace(/\(/g, '\\(')    // 转义 (
-      .replace(/\)/g, '\\)')    // 转义 )
-      .replace(/;/g, '\\;')     // 转义 ;
-      .replace(/\|/g, '\\|')    // 转义 |
-      .replace(/\*/g, '\\*')    // 转义 *
-      .replace(/\?/g, '\\?')    // 转义 ?
-      .replace(/`/g, '\\`')     // 转义 `
-      .replace(/\$/g, '\\$')    // 转义 $
-      .replace(/"/g, '\\"');    // 转义 "
-    
-    // 对于换行,使用 KEYCODE_ENTER
-    if (text.includes('\n')) {
-      // 如果有换行,分段发送
-      const lines = text.split('\n');
-      for (let i = 0; i < lines.length; i++) {
-        if (lines[i]) {
-          const escapedLine = lines[i]
-            .replace(/\\/g, '\\\\')
-            .replace(/'/g, "'\\''")
-            .replace(/ /g, '%s')
-            .replace(/&/g, '\\&')
-            .replace(/</g, '\\<')
-            .replace(/>/g, '\\>')
-            .replace(/\(/g, '\\(')
-            .replace(/\)/g, '\\)')
-            .replace(/;/g, '\\;')
-            .replace(/\|/g, '\\|')
-            .replace(/\*/g, '\\*')
-            .replace(/\?/g, '\\?')
-            .replace(/`/g, '\\`')
-            .replace(/\$/g, '\\$')
-            .replace(/"/g, '\\"');
-          
-          await execAsync(`${adbPath} -s ${ipPort} shell input text "${escapedLine}"`, {
-            timeout: 5000,
-            maxBuffer: 1024 * 1024
-          });
-        }
-        // 发送换行(除了最后一行)
-        if (i < lines.length - 1) {
-          await execAsync(`${adbPath} -s ${ipPort} shell input keyevent KEYCODE_ENTER`, {
-            timeout: 5000,
-            maxBuffer: 1024 * 1024
-          });
-        }
-      }
-    } else {
-      // 没有换行,直接发送
-      const command = `${adbPath} -s ${ipPort} shell input text "${escapedText}"`;
-      await execAsync(command, {
-        timeout: 5000,
-        maxBuffer: 1024 * 1024
-      });
-    }
-    
-    return { success: true };
-  } catch (error) {
-    console.error('发送文字失败:', error.message);
-    return { success: false, error: error.message };
-  }
-});
-
-// IPC 处理程序:发送按键事件到设备
-ipcMain.handle('send-key-event', async (event, ipPort, keyCode) => {
-  if (!ipPort) {
-    return { success: false, error: '缺少设备 ID' };
-  }
-  if (typeof keyCode !== 'string') {
-    return { success: false, error: '按键代码必须是字符串' };
-  }
-  try {
-    const adbPath = getCachedAdbPath();
-    const command = `${adbPath} -s ${ipPort} shell input keyevent ${keyCode}`;
-    await execAsync(command, {
-      timeout: 5000,
-      maxBuffer: 1024 * 1024
-    });
-    return { success: true };
-  } catch (error) {
-    console.error('发送按键失败:', error.message);
-    return { success: false, error: error.message };
-  }
-});
 
 // 应用启动逻辑:设置 CSP、创建窗口、监听激活事件
 app.whenReady().then(() => {
-  setContentSecurityPolicy();
+  setContentSecurityPolicy(isDev);
   createWindow();
 
+  // 注册所有 ADB 相关的 IPC 处理器
+  registerDeviceManagerHandlers();
+  registerDeviceInfoHandlers();
+  registerScreenshotHandlers();
+  registerInputHandlers();
+
   app.on('activate', () => {
     if (BrowserWindow.getAllWindows().length === 0) {
       createWindow();

+ 2 - 0
src/App.jsx

@@ -1,9 +1,11 @@
 import './App.css';
 import Home from './pages/Home.jsx';
+import Chat from './pages/Chat/Chat.jsx';
 function App() {
   return (
     <div className="app">
       <Home />
+      <Chat />
     </div>
   );
 }

+ 3 - 0
src/pages/Chat/Chat.css

@@ -0,0 +1,3 @@
+.Chat-container {
+  padding: 20px;
+}

+ 7 - 0
src/pages/Chat/Chat.js

@@ -0,0 +1,7 @@
+export function ChatLogic() {
+  // business logic placeholder
+
+  return {
+    // expose data or methods here
+  };
+}

+ 14 - 0
src/pages/Chat/Chat.jsx

@@ -0,0 +1,14 @@
+import './Chat.css';
+import { ChatLogic } from './Chat.js';
+
+function Chat() {
+  const logic = ChatLogic();
+
+  return (
+    <div className="Chat-container">
+      <h1>Chat</h1>
+    </div>
+  );
+}
+
+export default Chat;