diff --git a/control/App.vue b/control/App.vue
index 844676f..75db695 100644
--- a/control/App.vue
+++ b/control/App.vue
@@ -28,7 +28,7 @@
icon="ArrowDown"
@click="switchDevice"
>
- {{ deviceInfo.$remark || deviceInfo.$name }}
+ {{ deviceName }}
@@ -79,6 +79,8 @@ const deviceInfo = ref({})
const deviceList = ref([])
+const deviceName = computed(() => deviceStore.getLabel(deviceInfo.value, ({ deviceName }) => deviceName))
+
function handleClose() {
window.electron.ipcRenderer.send('hide-active-window')
}
@@ -88,7 +90,17 @@ async function switchDevice(e) {
const data = await deviceStore.getList()
- window.electron.ipcRenderer.send('open-control-device-menu', data)
+ const options = data.map((item) => {
+ return {
+ label: deviceStore.getLabel(item, ({ deviceName }) => deviceName),
+ value: item,
+ }
+ })
+
+ window.electron.ipcRenderer.send('open-system-menu', {
+ channel: 'device-change',
+ options,
+ })
}
onMounted(() => {
diff --git a/control/electron/events/devices/index.js b/control/electron/events/devices/index.js
deleted file mode 100644
index 092418e..0000000
--- a/control/electron/events/devices/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { BrowserWindow, ipcMain, Menu } from 'electron'
-import { openControlWindow } from '$control/electron/helpers/index.js'
-
-export default function (controlWindow) {
- ipcMain.on('open-control-device-menu', (event, deviceList) => {
- const template = deviceList.map((item) => {
- let label = item.$remark || item.$name
-
- if (item.$wifi) {
- label += ` (WIFI)`
- }
-
- return {
- label,
- click: () => {
- openControlWindow(controlWindow, item)
- },
- }
- })
-
- const menu = Menu.buildFromTemplate(template)
- menu.popup(BrowserWindow.fromWebContents(event.sender))
- })
-}
diff --git a/control/electron/events/index.js b/control/electron/events/index.js
index 63255b2..98db7c1 100644
--- a/control/electron/events/index.js
+++ b/control/electron/events/index.js
@@ -1,4 +1 @@
-export { default as devices } from './devices/index.js'
-export { default as gnirehtet } from './gnirehtet/index.js'
-export { default as rotation } from './rotation/index.js'
-export { default as volume } from './volume/index.js'
+export { default as menu } from './menu/index.js'
diff --git a/control/electron/events/gnirehtet/index.js b/control/electron/events/menu/index.js
similarity index 61%
rename from control/electron/events/gnirehtet/index.js
rename to control/electron/events/menu/index.js
index d77e9e9..fc32f11 100644
--- a/control/electron/events/gnirehtet/index.js
+++ b/control/electron/events/menu/index.js
@@ -1,16 +1,16 @@
import { BrowserWindow, ipcMain, Menu } from 'electron'
export default function (controlWindow) {
- ipcMain.on('open-device-gnirehtet-menu', openDeviceGnirehtetMenu)
+ ipcMain.on('open-system-menu', openSystemMenu)
- function openDeviceGnirehtetMenu(event, args = {}) {
- const { options = [] } = args
+ function openSystemMenu(event, args = {}) {
+ const { options = [], channel = 'system-menu-click' } = args
const template = options.map((item) => {
return {
label: item.label,
click() {
- controlWindow.webContents.send(item.value)
+ controlWindow.webContents.send(channel, item.value)
},
}
})
diff --git a/control/electron/events/rotation/index.js b/control/electron/events/rotation/index.js
deleted file mode 100644
index 857e6db..0000000
--- a/control/electron/events/rotation/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { BrowserWindow, ipcMain, Menu } from 'electron'
-
-export default function (controlWindow) {
- ipcMain.on('open-device-rotation-menu', openDeviceRotationMenu)
-
- function openDeviceRotationMenu(event, args = {}) {
- const { options = [] } = args
-
- const template = options.map((item) => {
- return {
- label: item.label,
- click: () => {
- controlWindow.webContents.send(
- 'execute-device-rotation-shell',
- item.value,
- )
- },
- }
- })
-
- const menu = Menu.buildFromTemplate(template)
- menu.popup(BrowserWindow.fromWebContents(event.sender))
- }
-}
diff --git a/control/electron/events/volume/index.js b/control/electron/events/volume/index.js
deleted file mode 100644
index 8cb325f..0000000
--- a/control/electron/events/volume/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { BrowserWindow, ipcMain, Menu } from 'electron'
-
-export default function (controlWindow) {
- ipcMain.on('open-device-volume-menu', openDeviceVolumeMenu)
-
- function openDeviceVolumeMenu(event, args = {}) {
- const { options = [] } = args
-
- const template = options.map((item) => {
- return {
- label: item.label,
- click() {
- controlWindow.webContents.send('execute-device-volume-shell', item.value)
- },
- }
- })
-
- const menu = Menu.buildFromTemplate(template)
- menu.popup(BrowserWindow.fromWebContents(event.sender))
- }
-}
diff --git a/control/electron/main.js b/control/electron/main.js
index 6d10b98..a2814dd 100644
--- a/control/electron/main.js
+++ b/control/electron/main.js
@@ -1,7 +1,7 @@
import { BrowserWindow, ipcMain } from 'electron'
import { initControlWindow, openControlWindow } from './helpers/index.js'
-import { devices, gnirehtet, rotation, volume } from './events/index.js'
+import { menu } from './events/index.js'
function onControlMounted(controlWindow) {
ipcMain.on('language-change', (event, data) => {
@@ -12,10 +12,7 @@ function onControlMounted(controlWindow) {
controlWindow.webContents.send('theme-change', data)
})
- rotation(controlWindow)
- devices(controlWindow)
- volume(controlWindow)
- gnirehtet(controlWindow)
+ menu(controlWindow)
}
export default (mainWindow) => {
diff --git a/electron/exposes/scrcpy/helper.js b/electron/exposes/scrcpy/helper.js
new file mode 100644
index 0000000..71a2a0a
--- /dev/null
+++ b/electron/exposes/scrcpy/helper.js
@@ -0,0 +1,43 @@
+/**
+ * Parse scrcpy app list output into structured data
+ * @param {string} rawText - Raw text output from scrcpy --list-apps command
+ * @returns {Array<{
+ * name: string,
+ * packageName: string,
+ * isSystemApp: boolean
+ * }>} Array of parsed app objects
+ */
+export function parseScrcpyAppList(rawText) {
+ try {
+ // Split by lines and filter out non-app lines
+ const lines = rawText.split('\n').filter((line) => {
+ const trimmed = line.trim()
+ return trimmed.startsWith('*') || trimmed.startsWith('-')
+ })
+
+ return lines.map((line) => {
+ // Remove leading * or - and trim
+ const cleanLine = line.trim().replace(/^[*\-]\s+/, '')
+
+ // Extract app name and package name using a more precise regex
+ // Matches any characters up to the last [ followed by package name and ]
+ const match = cleanLine.match(/^([^[]+)\[([^\]]+)\]$/)
+
+ if (!match) {
+ return null
+ }
+
+ const [, name, packageName] = match
+
+ return {
+ name: name.trim(),
+ packageName: packageName.trim(),
+ isSystemApp: line.trim().startsWith('*'),
+ }
+ }).filter(item => item !== null)
+ }
+ catch (error) {
+ console.error('Error parsing scrcpy app list:', error)
+ return []
+ }
+}
diff --git a/electron/exposes/scrcpy/index.js b/electron/exposes/scrcpy/index.js
index 18aea9d..8988db9 100644
--- a/electron/exposes/scrcpy/index.js
+++ b/electron/exposes/scrcpy/index.js
@@ -5,11 +5,13 @@ import appStore from '$electron/helpers/store.js'
import { replaceIP, sleep } from '$renderer/utils/index.js'
import commandHelper from '$renderer/utils/command/index.js'
+import { parseScrcpyAppList } from './helper.js'
+
let adbkit
const exec = util.promisify(_exec)
-async function shell(command, { stdout, stderr, ...options } = {}) {
+async function shell(command, { stdout, stderr, signal, ...options } = {}) {
const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('common.adbPath') || adbPath
const args = command.split(' ')
@@ -21,28 +23,35 @@ async function shell(command, { stdout, stderr, ...options } = {}) {
...options,
})
- scrcpyProcess.stdout.on('data', (data) => {
- const stringData = data.toString()
-
- if (stdout) {
- stdout(stringData, scrcpyProcess)
- }
- })
-
const stderrList = []
- scrcpyProcess.stderr.on('data', (data) => {
- const stringData = data.toString()
-
- stderrList.push(stringData)
-
- console.error('scrcpyProcess.stderr.data:', stringData)
-
- if (stderr) {
- stderr(stringData, scrcpyProcess)
- }
- })
return new Promise((resolve, reject) => {
+ scrcpyProcess.stdout.on('data', (data) => {
+ const stringData = data.toString()
+
+ if (stdout) {
+ stdout(stringData, scrcpyProcess)
+ }
+
+ const matchList = stringData.match(signal)
+
+ if (matchList) {
+ resolve(matchList, stringData, scrcpyProcess)
+ }
+ })
+
+ scrcpyProcess.stderr.on('data', (data) => {
+ const stringData = data.toString()
+
+ stderrList.push(stringData)
+
+ console.error('scrcpyProcess.stderr.data:', stringData)
+
+ if (stderr) {
+ stderr(stringData, scrcpyProcess)
+ }
+ })
+
scrcpyProcess.on('close', (code) => {
if (code === 0) {
resolve()
@@ -184,6 +193,31 @@ async function helper(
)
}
+async function getAppList(serial) {
+ const res = await execShell(`--serial="${serial}" --list-apps`)
+
+ const stdout = res.stdout
+ const value = parseScrcpyAppList(stdout)
+
+ return value
+}
+
+async function startApp(serial, args = {}) {
+ let { commands, packageName, ...options } = args
+
+ commands += ` --new-display --start-app=${packageName}`
+
+ const res = await mirror(serial, { ...options, args: commands, signal: /display id: (\d+)/i })
+
+ const displayId = res?.[1]
+
+ if (!displayId) {
+ throw new Error('The display ID was not obtained.')
+ }
+
+ return displayId
+}
+
export default (options = {}) => {
adbkit = options.adbkit
@@ -195,5 +229,7 @@ export default (options = {}) => {
record,
mirrorGroup,
helper,
+ getAppList,
+ startApp,
}
}
diff --git a/src/components/Device/components/ControlBar/ApplicationStart/index.vue b/src/components/Device/components/ControlBar/ApplicationStart/index.vue
new file mode 100644
index 0000000..af9e212
--- /dev/null
+++ b/src/components/Device/components/ControlBar/ApplicationStart/index.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+ {{ $t(item.label) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Device/components/ControlBar/Gnirehtet/index.vue b/src/components/Device/components/ControlBar/Gnirehtet/index.vue
index 62b45f8..d55d78f 100644
--- a/src/components/Device/components/ControlBar/Gnirehtet/index.vue
+++ b/src/components/Device/components/ControlBar/Gnirehtet/index.vue
@@ -55,8 +55,10 @@ export default {
return false
}
+ const channel = 'stop-device-gnirehtet'
+
window.electron.ipcRenderer.once(
- 'stop-device-gnirehtet',
+ channel,
(event, data) => {
this.handleStop()
},
@@ -65,11 +67,11 @@ export default {
const options = [
{
label: window.t('device.control.gnirehtet.stop'),
- value: 'stop-device-gnirehtet',
},
]
- window.electron.ipcRenderer.send('open-device-gnirehtet-menu', {
+ window.electron.ipcRenderer.send('open-system-menu', {
+ channel,
options,
})
},
diff --git a/src/components/Device/components/ControlBar/Rotation/index.vue b/src/components/Device/components/ControlBar/Rotation/index.vue
index 4ce9471..23b2198 100644
--- a/src/components/Device/components/ControlBar/Rotation/index.vue
+++ b/src/components/Device/components/ControlBar/Rotation/index.vue
@@ -73,8 +73,10 @@ export default {
return false
}
+ const channel = 'rotationScreen'
+
window.electron.ipcRenderer.once(
- 'execute-device-rotation-shell',
+ channel,
(event, data) => {
this.handleCommand(data)
},
@@ -82,7 +84,8 @@ export default {
const options = toRaw(this.options)
- window.electron.ipcRenderer.send('open-device-rotation-menu', {
+ window.electron.ipcRenderer.send('open-system-menu', {
+ channel,
options,
})
},
@@ -97,6 +100,8 @@ export default {
await sleep(500)
}
+ console.log('command', command)
+
this.$adb.deviceShell(this.device.id, command)
this.loading = false
diff --git a/src/components/Device/components/ControlBar/Volume/index.vue b/src/components/Device/components/ControlBar/Volume/index.vue
index b94e41b..76c4633 100644
--- a/src/components/Device/components/ControlBar/Volume/index.vue
+++ b/src/components/Device/components/ControlBar/Volume/index.vue
@@ -67,16 +67,20 @@ export default {
return false
}
+ const channel = 'changeVolume'
+
window.electron.ipcRenderer.once(
- 'execute-device-volume-shell',
+ channel,
(event, data) => {
+ console.log('data')
this.handleCommand(data)
},
)
const options = toRaw(this.options)
- window.electron.ipcRenderer.send('open-device-volume-menu', {
+ window.electron.ipcRenderer.send('open-system-menu', {
+ channel,
options,
})
},
diff --git a/src/components/Device/components/ControlBar/index.vue b/src/components/Device/components/ControlBar/index.vue
index 8eeed6a..16d5e63 100644
--- a/src/components/Device/components/ControlBar/index.vue
+++ b/src/components/Device/components/ControlBar/index.vue
@@ -64,10 +64,11 @@