yichael 5 mesi fa
parent
commit
758154c6aa
4 ha cambiato i file con 261 aggiunte e 0 eliminazioni
  1. 101 0
      main.js
  2. 2 0
      preload.cjs
  3. 154 0
      src/pages/ScreenShot/InputEvent.js
  4. 4 0
      src/pages/ScreenShot/ScreenShot.jsx

+ 101 - 0
main.js

@@ -168,6 +168,107 @@ ipcMain.handle('send-swipe', async (event, ipPort, x1, y1, x2, y2, duration = 30
   }
 });
 
+// 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 {
+    // 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(`adb -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`, {
+            timeout: 5000,
+            maxBuffer: 1024 * 1024
+          });
+        }
+      }
+    } else {
+      // 没有换行,直接发送
+      const command = `adb -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 command = `adb -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();

+ 2 - 0
preload.cjs

@@ -8,5 +8,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
   captureScreenshot: (ipPort) => ipcRenderer.invoke('capture-screenshot', ipPort),
   sendTap: (ipPort, x, y) => ipcRenderer.invoke('send-tap', ipPort, x, y),
   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),
 });
 

+ 154 - 0
src/pages/ScreenShot/InputEvent.js

@@ -0,0 +1,154 @@
+import { useEffect, useRef, useCallback } from 'react';
+
+// 键盘输入事件处理:将电脑键盘输入转换为真机输入
+export function useInputEvents(currentDevice, isInputModeActive = true) {
+  const inputBufferRef = useRef('');
+  const isComposingRef = useRef(false);
+
+  // 发送文字到设备
+  const sendTextToDevice = useCallback(async (text) => {
+    if (!currentDevice || !text || !window.electronAPI || !window.electronAPI.sendText) {
+      return;
+    }
+
+    try {
+      const result = await window.electronAPI.sendText(currentDevice, text);
+      if (!result?.success) {
+        console.error('发送文字失败:', result?.error);
+      }
+    } catch (err) {
+      console.error('发送文字异常:', err);
+    }
+  }, [currentDevice]);
+
+  // 处理键盘输入
+  const handleKeyDown = useCallback((e) => {
+    // 如果输入模式未激活,不处理
+    if (!isInputModeActive || !currentDevice) {
+      return;
+    }
+
+    // 忽略系统快捷键(如 Ctrl+C, Ctrl+V 等)
+    if (e.ctrlKey || e.metaKey || e.altKey) {
+      // 但允许 Ctrl+V 粘贴
+      if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !e.shiftKey && !e.altKey) {
+        e.preventDefault();
+        // 从剪贴板获取内容
+        navigator.clipboard.readText().then(text => {
+          if (text) {
+            sendTextToDevice(text);
+          }
+        }).catch(err => {
+          console.error('读取剪贴板失败:', err);
+        });
+      }
+      return;
+    }
+
+    // 处理特殊键
+    switch (e.key) {
+      case 'Enter':
+        e.preventDefault();
+        sendTextToDevice('\n');
+        break;
+      case 'Backspace':
+        e.preventDefault();
+        // 发送删除键事件
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_DEL');
+        }
+        break;
+      case 'Delete':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_FORWARD_DEL');
+        }
+        break;
+      case 'ArrowLeft':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_DPAD_LEFT');
+        }
+        break;
+      case 'ArrowRight':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_DPAD_RIGHT');
+        }
+        break;
+      case 'ArrowUp':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_DPAD_UP');
+        }
+        break;
+      case 'ArrowDown':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_DPAD_DOWN');
+        }
+        break;
+      case 'Tab':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_TAB');
+        }
+        break;
+      case 'Escape':
+        e.preventDefault();
+        if (window.electronAPI && window.electronAPI.sendKeyEvent) {
+          window.electronAPI.sendKeyEvent(currentDevice, 'KEYCODE_BACK');
+        }
+        break;
+      default:
+        // 普通字符,在 keypress 事件中处理
+        break;
+    }
+  }, [currentDevice, isInputModeActive, sendTextToDevice]);
+
+  // 处理字符输入(keypress 事件可以获取实际字符)
+  const handleKeyPress = useCallback((e) => {
+    if (!isInputModeActive || !currentDevice) {
+      return;
+    }
+
+    // 忽略系统快捷键
+    if (e.ctrlKey || e.metaKey || e.altKey) {
+      return;
+    }
+
+    // 获取输入的字符
+    const char = e.key;
+    
+    // 忽略控制字符(除了换行)
+    if (char.length === 1 && char.charCodeAt(0) < 32 && char !== '\n') {
+      return;
+    }
+
+    e.preventDefault();
+    
+    // 发送单个字符
+    sendTextToDevice(char);
+  }, [currentDevice, isInputModeActive, sendTextToDevice]);
+
+  // 监听全局键盘事件
+  useEffect(() => {
+    if (!isInputModeActive || !currentDevice) {
+      return;
+    }
+
+    // 添加键盘事件监听
+    window.addEventListener('keydown', handleKeyDown);
+    window.addEventListener('keypress', handleKeyPress);
+
+    return () => {
+      window.removeEventListener('keydown', handleKeyDown);
+      window.removeEventListener('keypress', handleKeyPress);
+    };
+  }, [isInputModeActive, currentDevice, handleKeyDown, handleKeyPress]);
+
+  return {
+    // 可以返回一些控制函数,如果需要的话
+  };
+}
+

+ 4 - 0
src/pages/ScreenShot/ScreenShot.jsx

@@ -1,12 +1,16 @@
 import './ScreenShot.css';
 import { ScreenShotLogic } from './ScreenShot.js';
 import { useTouchEvents } from './TouchEvent.js';
+import { useInputEvents } from './InputEvent.js';
 import { useRef } from 'react';
 
 function ScreenShot() {
   const { imageSrc, notifyImageLoaded, currentDevice } = ScreenShotLogic();
   const imageRef = useRef(null);
   const { handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave } = useTouchEvents(currentDevice, imageRef);
+  
+  // 启用键盘输入(当设备连接时自动启用)
+  useInputEvents(currentDevice, !!currentDevice);
 
   return (
     <div className="ScreenShot-container">