Explorar o código

激活同时添加ip

yichael hai 1 semana
pai
achega
8856f2a7a9

+ 0 - 0
bat-tool/getip.bat


+ 14 - 0
bat-tool/getip/getip.bat

@@ -0,0 +1,14 @@
+@echo off
+chcp 65001 >nul
+title 获取设备 IP
+cd /d "%~dp0"
+node getip.js
+if errorlevel 1 (
+    echo.
+    pause
+    exit /b 1
+) else (
+    echo.
+    pause
+    exit /b 0
+)

+ 91 - 0
bat-tool/getip/getip.js

@@ -0,0 +1,91 @@
+#!/usr/bin/env node
+const { execSync } = require('child_process')
+const path = require('path')
+
+// 项目根:bat-tool/getip -> 上两级;打包时若在 app.asar 内则用 app.asar.unpacked
+let PROJECT_ROOT = path.resolve(__dirname, '..', '..')
+if (PROJECT_ROOT.includes('app.asar') && !PROJECT_ROOT.includes('app.asar.unpacked')) {
+  PROJECT_ROOT = PROJECT_ROOT.replace('app.asar', 'app.asar.unpacked')
+}
+
+function getAdbPath() {
+  const configPath = path.join(PROJECT_ROOT, 'configs', 'config.js')
+  const config = require(configPath)
+  return config.adbPath?.path
+    ? path.resolve(PROJECT_ROOT, config.adbPath.path)
+    : path.join(PROJECT_ROOT, 'lib', 'scrcpy-adb', 'adb.exe')
+}
+
+function getConnectedDeviceIds(adbPath) {
+  const out = execSync(`"${adbPath}" devices`, { encoding: 'utf-8' })
+  return out
+    .split('\n')
+    .filter((line) => line.trim() && !line.startsWith('List') && line.includes('\tdevice'))
+    .map((line) => line.trim().split('\t')[0])
+    .filter((id) => id && !id.includes(':'))
+}
+
+function parseInetFromOutput(out, skipLoopback = false) {
+  const re = /inet\s+(\d+\.\d+\.\d+\.\d+)/g
+  const ips = []
+  let m
+  while ((m = re.exec(out || '')) !== null) ips.push(m[1])
+  if (ips.length === 0) return null
+  if (skipLoopback) {
+    const nonLoop = ips.find((ip) => ip !== '127.0.0.1')
+    return nonLoop || null
+  }
+  return ips[0]
+}
+
+function adbShellCmd(adbPath, deviceId, shellCmd) {
+  const id = deviceId.indexOf(' ') >= 0 ? `"${deviceId}"` : deviceId
+  return `"${adbPath}" -s ${id} shell ${shellCmd}`
+}
+
+function getDeviceIp(adbPath, deviceId) {
+  const run = (shellCmd) => {
+    try {
+      return execSync(adbShellCmd(adbPath, deviceId, shellCmd), { encoding: 'utf-8' })
+    } catch (e) {
+      return ''
+    }
+  }
+  let out = run('ip -4 addr show wlan0')
+  let ip = parseInetFromOutput(out)
+  if (ip) return ip
+  out = run('ip -4 addr show eth0')
+  ip = parseInetFromOutput(out)
+  if (ip) return ip
+  out = run('ip route get 1.1.1.1')
+  let m = out.match(/\bsrc\s+(\d+\.\d+\.\d+\.\d+)\b/)
+  if (m) return m[1]
+  m = out.match(/\bfrom\s+(\d+\.\d+\.\d+\.\d+)\b/)
+  if (m) return m[1]
+  for (const prop of ['dhcp.wlan0.ipaddress', 'net.wlan0.ipaddress', 'dhcp.eth0.ipaddress']) {
+    out = run('getprop ' + prop)
+    ip = (out || '').trim()
+    if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) return ip
+  }
+  out = run('ip -4 addr show')
+  return parseInetFromOutput(out, true)
+}
+
+function run() {
+  const adbPath = getAdbPath()
+  const devices = getConnectedDeviceIds(adbPath)
+  if (devices.length === 0) {
+    process.stderr.write('No devices found. Please connect a device via USB.\n')
+    process.exit(1)
+  }
+  const deviceId = devices[0]
+  const ip = getDeviceIp(adbPath, deviceId)
+  if (ip) {
+    process.stdout.write(ip + '\n')
+    process.exit(0)
+  }
+  process.stderr.write('Could not get device IP. Check WiFi on device.\n')
+  process.exit(1)
+}
+
+run()

+ 60 - 1
nodejs/enable-wirless-connect.js

@@ -39,7 +39,61 @@ function enableTcpipPort(adbPath, deviceId, port) {
   return execSync(`"${adbPath}" -s ${deviceId} tcpip ${port}`, { encoding: 'utf-8' }).trim()
   return execSync(`"${adbPath}" -s ${deviceId} tcpip ${port}`, { encoding: 'utf-8' }).trim()
 }
 }
 
 
-/** 主流程:取 USB 设备,依次执行「开启无线调试」「激活 5555 端口」 */
+/** 从 adb shell 输出中匹配 inet 后的 IPv4(支持 inet 192.168.1.1/24 格式),可排除回环 */
+function parseInetFromOutput(out, skipLoopback = false) {
+  const re = /inet\s+(\d+\.\d+\.\d+\.\d+)/g
+  const ips = []
+  let m
+  while ((m = re.exec(out || '')) !== null) ips.push(m[1])
+  if (ips.length === 0) return null
+  if (skipLoopback) {
+    const nonLoop = ips.find((ip) => ip !== '127.0.0.1')
+    return nonLoop || null
+  }
+  return ips[0]
+}
+
+/** 安全拼 adb -s 参数(deviceId 含空格时需引号) */
+function adbShellCmd(adbPath, deviceId, shellCmd) {
+  const id = deviceId.indexOf(' ') >= 0 ? `"${deviceId}"` : deviceId
+  return `"${adbPath}" -s ${id} shell ${shellCmd}`
+}
+
+/** 获取设备当前 WiFi IP:多接口 + 多命令兼容不同机型 */
+function getDeviceIp(adbPath, deviceId) {
+  const run = (shellCmd) => {
+    try {
+      return execSync(adbShellCmd(adbPath, deviceId, shellCmd), { encoding: 'utf-8' })
+    } catch (e) {
+      return ''
+    }
+  }
+  // 1) wlan0 inet(最常见)
+  let out = run('ip -4 addr show wlan0')
+  let ip = parseInetFromOutput(out)
+  if (ip) return ip
+  // 2) eth0(部分机型/平板)
+  out = run('ip -4 addr show eth0')
+  ip = parseInetFromOutput(out)
+  if (ip) return ip
+  // 3) ip route get:src 或 from
+  out = run('ip route get 1.1.1.1')
+  let m = out.match(/\bsrc\s+(\d+\.\d+\.\d+\.\d+)\b/)
+  if (m) return m[1]
+  m = out.match(/\bfrom\s+(\d+\.\d+\.\d+\.\d+)\b/)
+  if (m) return m[1]
+  // 4) getprop
+  for (const prop of ['dhcp.wlan0.ipaddress', 'net.wlan0.ipaddress', 'dhcp.eth0.ipaddress']) {
+    out = run('getprop ' + prop)
+    ip = (out || '').trim()
+    if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) return ip
+  }
+  // 5) ip -4 addr show 全量,取第一个非回环 IP(避免 lo 的 127.0.0.1)
+  out = run('ip -4 addr show')
+  return parseInetFromOutput(out, true)
+}
+
+/** 主流程:取 USB 设备,先取 IP(与 getip.js 一致,此时 USB 稳定),再开无线与 tcpip,最后输出 */
 function run() {
 function run() {
   const adbPath = getAdbPath()
   const adbPath = getAdbPath()
   const devices = getConnectedDeviceIds(adbPath)
   const devices = getConnectedDeviceIds(adbPath)
@@ -48,9 +102,14 @@ function run() {
     process.exit(1)
     process.exit(1)
   }
   }
   const deviceId = devices[0]
   const deviceId = devices[0]
+  // 先取 IP(在 tcpip 之前,USB 稳定时取,与 bat-tool/getip 行为一致)
+  const ip = getDeviceIp(adbPath, deviceId)
   enableWirelessSetting(adbPath, deviceId)
   enableWirelessSetting(adbPath, deviceId)
   const tcpipOut = enableTcpipPort(adbPath, deviceId, TCPIP_PORT)
   const tcpipOut = enableTcpipPort(adbPath, deviceId, TCPIP_PORT)
   process.stdout.write(tcpipOut + '\n')
   process.stdout.write(tcpipOut + '\n')
+  if (ip) {
+    process.stdout.write('DEVICE_IP:' + ip + '\n')
+  }
   process.exit(0)
   process.exit(0)
 }
 }
 
 

+ 37 - 6
package.json

@@ -7,12 +7,43 @@
   "build": {
   "build": {
     "appId": "com.electron.android-remote-controller",
     "appId": "com.electron.android-remote-controller",
     "productName": "AndroidRemoteController",
     "productName": "AndroidRemoteController",
-    "directories": { "output": "dist" },
-    "files": ["electron/**", "configs/**", "nodejs/**", "python/**", "lib/**"],
-    "asarUnpack": ["nodejs/**", "configs/**", "lib/**", "python/**"],
-    "extraFiles": [{ "from": "dist", "to": "dist", "filter": ["index.html", "assets/**"] }],
+    "directories": {
+      "output": "dist"
+    },
+    "files": [
+      "electron/**",
+      "configs/**",
+      "nodejs/**",
+      "python/**",
+      "lib/**"
+    ],
+    "asarUnpack": [
+      "nodejs/**",
+      "configs/**",
+      "lib/**",
+      "python/**"
+    ],
+    "extraFiles": [
+      {
+        "from": "dist",
+        "to": "dist",
+        "filter": [
+          "index.html",
+          "assets/**"
+        ]
+      }
+    ],
     "afterPack": "package/afterPack.js",
     "afterPack": "package/afterPack.js",
-    "win": { "target": [{ "target": "dir", "arch": ["arm64"] }] }
+    "win": {
+      "target": [
+        {
+          "target": "dir",
+          "arch": [
+            "arm64"
+          ]
+        }
+      ]
+    }
   },
   },
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
@@ -26,7 +57,7 @@
   "dependencies": {
   "dependencies": {
     "react": "^18.2.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-dom": "^18.2.0",
-    "react-router-dom": "^6.20.0"
+    "react-router-dom": "^6.30.3"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@vitejs/plugin-react": "^5.1.2",
     "@vitejs/plugin-react": "^5.1.2",

+ 3 - 1
src/index.jsx

@@ -5,14 +5,16 @@ import React from 'react'
 import ReactDOM from 'react-dom/client'
 import ReactDOM from 'react-dom/client'
 import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'
 import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'
 import Home from './page/home.jsx'
 import Home from './page/home.jsx'
+import VisualCode from './page/visual_code/visual_code.jsx'
 
 
-// 打包后为 file:// 协议,BrowserRouter 的 pathname 是文件路径,路由不匹配会白屏;用 HashRouter 以 #/ 做路由
+// 打包后为 file:// 协议,用 HashRouter 以 #/ 做路由
 function App() {
 function App() {
   return (
   return (
     <HashRouter>
     <HashRouter>
       <Routes>
       <Routes>
         <Route path="/" element={<Navigate to="/page" replace />} />
         <Route path="/" element={<Navigate to="/page" replace />} />
         <Route path="/page" element={<Home />} />
         <Route path="/page" element={<Home />} />
+        <Route path="/page/visual-code" element={<VisualCode />} />
       </Routes>
       </Routes>
     </HashRouter>
     </HashRouter>
   )
   )

+ 28 - 11
src/page/device/device.js

@@ -59,26 +59,43 @@ class DeviceClass {
         }
         }
     }
     }
 
 
-    /** 执行开启无线连接脚本并根据结果提示 */
+    /**
+     * 执行开启无线连接脚本并根据结果提示。
+     * 失败判断:1) 脚本 exitCode !== 0  2) 或 stdout/stderr 含 "No devices"(无 USB 设备)
+     * 成功:exitCode === 0,并解析 DEVICE_IP 展示、可自动加设备。
+     */
     async onEnableWirlessConnect() {
     async onEnableWirlessConnect() {
         const result = await window.electronAPI.runNodejsScript('enable-wirless-connect')
         const result = await window.electronAPI.runNodejsScript('enable-wirless-connect')
-        const noDevice = (result.stderr || '').includes('No devices') || (result.stdout || '').includes('No devices')
-        if (result.exitCode === 0) {
-            hintView.setContent('开启手机无线连接成功')
+        const out = (result.stderr || '') + (result.stdout || '')
+        const noDevice = out.includes('No devices')
+
+        if (noDevice) {
+            hintView.setContent('未检测到设备,请先用 USB 连接手机')
             hintView.show()
             hintView.show()
             return
             return
         }
         }
-        if (noDevice) {
-            hintView.setContent('未检测到设备,请先用 USB 连接手机')
+
+        // 无线启动失败,仅提示不进行下一步(不展示具体错误信息)
+        if (result.exitCode !== 0) {
+            hintView.setContent('开启无线连接失败')
             hintView.show()
             hintView.show()
             return
             return
         }
         }
-        const raw = (result.stderr || result.stdout || '').trim().split('\n')[0] || '开启无线连接失败'
-        const msg = (raw === 'error: closed' || raw.includes('closed'))
-            ? 'ADB 连接已断开,请检查 USB 连接后重试'
-            : raw
-        hintView.setContent(msg)
+
+        const stdout = (result.stdout || '').trim()
+        const ipLine = stdout.split(/\r?\n/).find((line) => line.trim().startsWith('DEVICE_IP:'))
+        const deviceIp = ipLine ? ipLine.replace(/^DEVICE_IP:/, '').trim() : null
+        hintView.setContent('开启手机无线连接成功')
         hintView.show()
         hintView.show()
+        if (deviceIp) {
+            const ipList = await window.electronAPI.runNodejsScript('json-parser', 'read', 'device_list.json')
+            const jsonData = JSON.parse(ipList.stdout)
+            const devices = jsonData.data.devices
+            if (!devices.includes(deviceIp)) {
+                this.setSelectedDevice(prev => [...(prev || []), deviceIp])
+                await this.addDevice(deviceIp, this.currentDeviceList || [])
+            }
+        }
     }
     }
     
     
     async onAddDevice() {
     async onAddDevice() {

+ 4 - 1
src/page/home.jsx

@@ -1,4 +1,5 @@
 import React, { useEffect, useState } from 'react'
 import React, { useEffect, useState } from 'react'
+import { Link, useNavigate } from 'react-router-dom'
 import './home.scss'
 import './home.scss'
 //弹窗
 //弹窗
 import Alert from './public/alert-view/alert-view.jsx'
 import Alert from './public/alert-view/alert-view.jsx'
@@ -21,12 +22,14 @@ function Home() {
   const [showDevice, setShowDevice] = useState(true)
   const [showDevice, setShowDevice] = useState(true)
   const [showAiChat, setShowAiChat] = useState(true)
   const [showAiChat, setShowAiChat] = useState(true)
   const [showProcess, setShowProcess] = useState(true)
   const [showProcess, setShowProcess] = useState(true)
+  const navigate = useNavigate()
 
 
   useEffect(() => {
   useEffect(() => {
     hintView.setShowCallback(setShowHint)
     hintView.setShowCallback(setShowHint)
     alertView.setShowCallback(setShowAlert)
     alertView.setShowCallback(setShowAlert)
     comfirmView.setShowCallback(setShowComfirm)
     comfirmView.setShowCallback(setShowComfirm)
-  }, [])
+    // navigate('/page/visual-code')
+  }, [navigate])
   
   
   return (
   return (
     <div className="home-container">
     <div className="home-container">

+ 0 - 0
src/page/visual_code/code_canvas/code-canvas.js


+ 16 - 0
src/page/visual_code/code_canvas/code-canvas.jsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import './code-canvas.scss'
+
+function CodeCanvas({ show }) {
+  if (!show) {
+    return null
+  }
+
+  return (
+    <div className="code-canvas-container">
+
+    </div>
+  )
+}
+
+export default CodeCanvas

+ 5 - 0
src/page/visual_code/code_canvas/code-canvas.scss

@@ -0,0 +1,5 @@
+.code-canvas-container {
+  width: 100%;
+  height: 100%;
+  background-color: #000000f1;
+}

+ 0 - 0
src/page/visual_code/side_bar/side-bar.js


+ 16 - 0
src/page/visual_code/side_bar/side-bar.jsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import './side-bar.scss'
+
+function SideBar({ show }) {
+  if (!show) {
+    return null
+  }
+
+  return (
+    <div className="side-bar-container">
+   
+    </div>
+  )
+}
+
+export default SideBar

+ 6 - 0
src/page/visual_code/side_bar/side-bar.scss

@@ -0,0 +1,6 @@
+.side-bar-container {
+  width: 100%;
+  height: 100%;
+  background-color: #ff0000ef;
+
+}

+ 0 - 0
src/page/visual_code/top_bar/top-bar.js


+ 16 - 0
src/page/visual_code/top_bar/top-bar.jsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import './top-bar.scss'
+
+function TopBar({ show }) {
+  if (!show) {
+    return null
+  }
+
+  return (
+    <div className="top-bar-container">
+
+    </div>
+  )
+}
+
+export default TopBar

+ 5 - 0
src/page/visual_code/top_bar/top-bar.scss

@@ -0,0 +1,5 @@
+.top-bar-container {
+  width: 100%;
+  height: 100%;
+  background-color: #4dff00;
+}

+ 60 - 0
src/page/visual_code/visual_code.js

@@ -0,0 +1,60 @@
+import alertView from './public/alert-view/alert-view.js'
+import hintView from './public/hint-view/hint-view.js'
+import comfirmView from './public/comfirm-view/comfirm-view.js'
+
+function closeAlert(setShowAlert) {
+  setShowAlert(false)
+}
+
+function toggleAlert(showAlert, setShowAlert) {
+  setShowAlert(!showAlert)
+}
+
+export function createHandleClose(setShowAlert) {
+  return () => {
+    closeAlert(setShowAlert)
+  }
+}
+
+export function createHandleToggle(showAlert, setShowAlert) {
+  return () => {
+    toggleAlert(showAlert, setShowAlert)
+  }
+}
+
+export function createAlertProps(showAlert, setShowAlert) {
+  return {
+    show: showAlert,
+    onClose: createHandleClose(setShowAlert),
+    title: alertView._title,
+    content: alertView._content
+  }
+}
+
+export function createComfirmViewProps(showComfirm, setShowComfirm) {
+  return {
+    show: showComfirm,
+    onClose: () => {
+      comfirmView.hide()
+      if (comfirmView._onCancel) {
+        comfirmView._onCancel()
+      }
+    },
+    onConfirm: () => {
+      if (comfirmView._onConfirm) {
+        comfirmView._onConfirm()
+      }
+    },
+    title: comfirmView._title,
+    content: comfirmView._content
+  }
+}
+
+export function createHintViewProps(showHint, setShowHint) {
+  return {
+    show: showHint,
+    onClose: createHandleClose(setShowHint),
+    title: hintView._title,
+    content: hintView._content
+  }
+}

+ 28 - 0
src/page/visual_code/visual_code.jsx

@@ -0,0 +1,28 @@
+import React from 'react'
+import { Link } from 'react-router-dom'
+import './visual_code.scss'
+import TopBar from './top_bar/top-bar.jsx'
+import SideBar from './side_bar/side-bar.jsx'
+import CodeCanvas from './code_canvas/code-canvas.jsx'
+
+function VisualCode() {
+  return (
+    <div className="visual-code-container">
+      <div className="visual-code-header">
+      </div>
+      <div className="top-bar">
+        <TopBar show />
+      </div>
+      <div className="visual-code-content">
+        <div className="side-bar">
+          <SideBar show />
+        </div>
+        <div className="code-canvas">
+          <CodeCanvas show />
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default VisualCode

+ 29 - 0
src/page/visual_code/visual_code.scss

@@ -0,0 +1,29 @@
+.visual-code-container {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+
+  .top-bar {
+    width: 100%;
+    height: 10%;
+    min-height: 40px;
+  }
+  .visual-code-content {
+    width: 100%;
+    flex: 1;
+    display: flex;
+    flex-direction: row;
+    min-height: 0;
+
+    .side-bar {
+      width: 20%;
+      height: 100%;
+    }
+    .code-canvas {
+      width: 80%;
+      height: 100%;
+    }
+  }
+}
+  

+ 1 - 3
static/device_list.json

@@ -1,5 +1,3 @@
 {
 {
-  "devices": [
-    "192.168.2.5"
-  ]
+  "devices": []
 }
 }

+ 1 - 1
static/scrcpy-pid.json

@@ -1 +1 @@
-{"pid":9468}
+{"pid":5068}