From d262adf54df2a28c734b3db1a5d17bbccd983db9 Mon Sep 17 00:00:00 2001 From: viarotel Date: Fri, 27 Dec 2024 22:58:46 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=F0=9F=93=B8=20Support=20viewing=20real?= =?UTF-8?q?-time=20images=20and=20power=20information=20of=20the=20device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/exposes/adb/helpers/battery/index.js | 91 ++++++++++++++++ electron/exposes/adb/index.js | 26 ++++- electron/helpers/index.js | 17 +++ package.json | 18 ++-- src/locales/languages/en-US.json | 7 ++ src/locales/languages/ru-RU.json | 7 ++ src/locales/languages/zh-CN.json | 7 ++ src/locales/languages/zh-TW.json | 7 ++ .../device/components/DevicePopover/index.vue | 100 ++++++++++++++++-- 9 files changed, 261 insertions(+), 19 deletions(-) create mode 100644 electron/exposes/adb/helpers/battery/index.js diff --git a/electron/exposes/adb/helpers/battery/index.js b/electron/exposes/adb/helpers/battery/index.js new file mode 100644 index 00000000..8e5f1f3a --- /dev/null +++ b/electron/exposes/adb/helpers/battery/index.js @@ -0,0 +1,91 @@ +import { camelCase } from 'lodash-es' +/** + * Parse ADB battery dump data into a structured object + * + * @param {string} dumpData - Raw battery dump data from ADB + * @returns {Object} Parsed and normalized battery data + * + * // Example usage: + * const dumpData = fs.readFileSync('battery-dump.txt', 'utf8'); + * const batteryInfo = parseBatteryDump(dumpData); + * console.log(batteryInfo); + * + */ +export function parseBatteryDump(dumpData) { + // Helper to convert string values to appropriate types + const parseValue = (value) => { + value = value.trim() + + // Handle booleans + if (value.toLowerCase() === 'true') + return true + if (value.toLowerCase() === 'false') + return false + + // Handle numbers + if (!Number.isNaN(Number(value)) && value !== '') { + return Number(value) + } + + return value + } + + const result = { + raw: {}, + computed: {}, + } + + // Split into lines and process each line + const lines = dumpData.split('\n').filter(line => line.trim()) + + lines.forEach((line) => { + if (line.includes('Battery Service state:')) { + return + } + + // Parse key-value pairs + const separatorIndex = line.indexOf(':') + if (separatorIndex === -1) + return + + const key = line.substring(0, separatorIndex).trim() + const value = line.substring(separatorIndex + 1).trim() + + // Skip empty key/values + if (!key || !value) + return + + // Convert key to camelCase + const camelKey = camelCase(key) + + // Add to appropriate section + result.raw[camelKey] = parseValue(value) + }) + + // Add computed values + result.computed = { + // Convert temperatures to actual degrees + temperatureCelsius: result.raw.temperature ? result.raw.temperature / 10 : null, + + // Battery percentage normalized to 0-100 + batteryPercentage: result.raw.level || 0, + + // Charging state as string + isCharging: !!(result.raw.usbPowered || result.raw.acPowered + || result.raw.wirelessPowered || result.raw.dockPowered), + + // Voltage in V instead of mV + voltageV: result.raw.voltage ? result.raw.voltage / 1000 : null, + + // Power source type + powerSource: result.raw.acPowered + ? 'AC' + : result.raw.usbPowered + ? 'USB' + : result.raw.wirelessPowered + ? 'Wireless' + : result.raw.dockPowered ? 'Dock' : 'Battery', + } + + return result +} diff --git a/electron/exposes/adb/index.js b/electron/exposes/adb/index.js index 3ec92827..a560c4ba 100644 --- a/electron/exposes/adb/index.js +++ b/electron/exposes/adb/index.js @@ -9,6 +9,8 @@ import { Adb } from '@devicefarmer/adbkit' import dayjs from 'dayjs' import { uniq } from 'lodash-es' import adbConnectionMonitor from './helpers/adbConnectionMonitor/index.js' +import { streamToBase64 } from '$electron/helpers/index.js' +import { parseBatteryDump } from './helpers/battery/index.js' const exec = util.promisify(_exec) @@ -124,6 +126,8 @@ const getDeviceIP = async (id) => { const tcpip = async (id, port = 5555) => client.getDevice(id).tcpip(port) const screencap = async (deviceId, options = {}) => { + const { returnBase64 = false } = options + let fileStream = null try { const device = client.getDevice(deviceId) @@ -138,6 +142,11 @@ const screencap = async (deviceId, options = {}) => { return false } + if (returnBase64) { + const base64 = await streamToBase64(fileStream) + return base64 + } + const fileName = `Screencap-${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.png` const savePath = options.savePath || path.resolve('../', fileName) @@ -278,10 +287,24 @@ async function connectCode(password, options = {}) { pair, connect, }, - ...options + ...options, }) } +async function battery(id) { + try { + const res = await deviceShell(id, 'dumpsys battery') + + const value = parseBatteryDump(res) + + return value + } + catch (error) { + console.warn(error?.message || error) + return {} + } +} + function init() { const bin = appStore.get('common.adbPath') || adbPath @@ -312,4 +335,5 @@ export default { watch, readdir, connectCode, + battery, } diff --git a/electron/helpers/index.js b/electron/helpers/index.js index 30393a90..d2f0df4c 100644 --- a/electron/helpers/index.js +++ b/electron/helpers/index.js @@ -1,4 +1,5 @@ import { join, resolve } from 'node:path' +import { Buffer } from 'node:buffer' import { contextBridge } from 'electron' import { cloneDeep } from 'lodash-es' @@ -68,3 +69,19 @@ export function loadPage(win, prefix = '') { win.loadFile(join(process.env.DIST, prefix, 'index.html')) } } + +export function streamToBase64(stream) { + return new Promise((resolve, reject) => { + const chunks = [] + stream.on('data', (chunk) => { + chunks.push(chunk) + }) + stream.on('end', () => { + const buffer = Buffer.concat(chunks) + resolve(buffer.toString('base64')) + }) + stream.on('error', (error) => { + reject(error) + }) + }) +} diff --git a/package.json b/package.json index 6fc1f460..1599e3f0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@viarotel-org/unocss-preset-shades": "0.8.2", "@vitejs/plugin-vue": "5.0.4", "@vueuse/core": "10.9.0", - "bonjour-service": "^1.3.0", + "bonjour-service": "1.3.0", "dayjs": "1.11.11", "electron": "33.0.2", "electron-builder": "25.1.8", @@ -43,7 +43,7 @@ "electron-log": "5.2.0", "electron-store": "9.0.0", "electron-updater": "6.3.9", - "element-plus": "2.9.0", + "element-plus": "2.9.1", "eslint": "9.13.0", "fix-path": "4.0.0", "fs-extra": "11.2.0", @@ -52,17 +52,17 @@ "nanoid": "5.0.7", "pinia": "2.1.7", "pinia-plugin-persistedstate": "3.2.1", - "pinyin-pro": "^3.26.0", + "pinyin-pro": "3.26.0", "postcss": "8.4.38", "postcss-nested": "6.0.1", "postcss-scss": "4.0.9", - "qrcode": "^1.5.4", - "rimraf": "^6.0.1", - "simple-git": "^3.27.0", + "qrcode": "1.5.4", + "rimraf": "6.0.1", + "simple-git": "3.27.0", "unocss": "0.62.3", "unplugin-auto-import": "0.18.3", "unplugin-vue-components": "0.27.4", - "unplugin-vue-router": "^0.10.9", + "unplugin-vue-router": "0.10.9", "vite": "5.1.5", "vite-plugin-electron": "0.28.8", "vite-plugin-electron-renderer": "0.14.6", @@ -70,8 +70,8 @@ "vue": "3.4.21", "vue-command": "35.2.1", "vue-i18n": "9.13.1", - "vue-router": "^4.5.0", - "vue-screen": "^2.4.0", + "vue-router": "4.5.0", + "vue-screen": "2.4.2", "which": "4.0.0" } } diff --git a/src/locales/languages/en-US.json b/src/locales/languages/en-US.json index 7c6cab39..1cf1bb2b 100644 --- a/src/locales/languages/en-US.json +++ b/src/locales/languages/en-US.json @@ -1,4 +1,6 @@ { + "common.yes": "Yes", + "common.no": "No", "common.cancel": "Cancel", "common.confirm": "Confirm", "common.restart": "Restart", @@ -67,6 +69,11 @@ "device.status.connected": "Connected", "device.status.offline": "Offline", "device.status.unauthorized": "Unauthorized", + "device.battery": "Device Battery", + "device.isCharging": "Charging Status", + "device.temperature": "Device Temperature", + "device.powerSource": "Power Source", + "device.voltage": "Device Voltage", "device.task.name": "Scheduled Task", "device.task.tips": " Note: Please ensure that your computer stays awake, otherwise scheduled tasks will not be executed properly.", diff --git a/src/locales/languages/ru-RU.json b/src/locales/languages/ru-RU.json index 380d6f81..fe10b0ac 100644 --- a/src/locales/languages/ru-RU.json +++ b/src/locales/languages/ru-RU.json @@ -1,4 +1,6 @@ { + "common.yes": "Да", + "common.no": "Нет", "common.cancel": "Отмена", "common.confirm": "Подтвердить", "common.restart": "Перезапустить", @@ -67,6 +69,11 @@ "device.status.connected": "Подключено", "device.status.offline": "Не в сети", "device.status.unauthorized": "Не авторизован", + "device.battery": "Уровень заряда устройства", + "device.isCharging": "Состояние зарядки", + "device.temperature": "Температура устройства", + "device.powerSource": "Источник питания", + "device.voltage": "Напряжение устройства", "device.task.name": "Запланированная задача", "device.task.tips": "Примечание: Пожалуйста, убедитесь, что ваш компьютер не переходит в спящий режим, иначе запланированные задачи не будут выполнены правильно.", diff --git a/src/locales/languages/zh-CN.json b/src/locales/languages/zh-CN.json index 3ca055df..cd2a9de7 100644 --- a/src/locales/languages/zh-CN.json +++ b/src/locales/languages/zh-CN.json @@ -1,4 +1,6 @@ { + "common.yes": "是", + "common.no": "否", "common.cancel": "取消", "common.confirm": "确认", "common.restart": "重启", @@ -67,6 +69,11 @@ "device.status.offline": "已离线", "device.status.unauthorized": "未授权", "device.status.connected": "已连接", + "device.battery": "设备电量", + "device.isCharging": "充电状态", + "device.temperature": "设备温度", + "device.powerSource": "驱动来源", + "device.voltage": "设备电压", "device.task.name": "计划任务", "device.task.tips": " 注意:请确保你的计算机保持唤醒状态,否则计划任务将无法被正常执行。", diff --git a/src/locales/languages/zh-TW.json b/src/locales/languages/zh-TW.json index 10c277ec..dac576e2 100644 --- a/src/locales/languages/zh-TW.json +++ b/src/locales/languages/zh-TW.json @@ -1,4 +1,6 @@ { + "common.yes": "是", + "common.no": "否", "common.cancel": "取消", "common.confirm": "確認", "common.restart": "重新啟動", @@ -67,6 +69,11 @@ "device.status.connected": "已連接", "device.status.offline": "已離線", "device.status.unauthorized": "未授權", + "device.battery": "設備電量", + "device.isCharging": "充電狀態", + "device.temperature": "設備溫度", + "device.powerSource": "驅動來源", + "device.voltage": "設備電壓", "device.task.name": "計劃任務", "device.task.tips": "注意:請確保您的電腦保持唤醒状态,否則計劃任務將無法正常執行。", diff --git a/src/pages/device/components/DevicePopover/index.vue b/src/pages/device/components/DevicePopover/index.vue index f1a6c7d4..719b2ba4 100644 --- a/src/pages/device/components/DevicePopover/index.vue +++ b/src/pages/device/components/DevicePopover/index.vue @@ -2,31 +2,113 @@ - - - {{ device.id }} - - +
+
+ +
+ +
+ + + {{ deviceInfo.id }} + + + + +
+
-