mirror of
https://github.com/AkiChase/scrcpy-mask
synced 2025-04-18 18:18:03 +08:00
324 lines
7.3 KiB
Vue
324 lines
7.3 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
Ref,
|
|
computed,
|
|
h,
|
|
nextTick,
|
|
onActivated,
|
|
onMounted,
|
|
onUnmounted,
|
|
ref,
|
|
} from "vue";
|
|
import {
|
|
Device,
|
|
adbDevices,
|
|
pushServerFile,
|
|
forwardServerPort,
|
|
startScrcpyServer,
|
|
} from "../invoke";
|
|
import {
|
|
NH4,
|
|
NInputNumber,
|
|
NButton,
|
|
NDataTable,
|
|
NDropdown,
|
|
NEmpty,
|
|
NTooltip,
|
|
NFlex,
|
|
NIcon,
|
|
NSpin,
|
|
DataTableColumns,
|
|
DropdownOption,
|
|
useDialog,
|
|
} from "naive-ui";
|
|
import { CloseCircle, InformationCircle } from "@vicons/ionicons5";
|
|
import { Refresh } from "@vicons/ionicons5";
|
|
import { UnlistenFn, listen } from "@tauri-apps/api/event";
|
|
import { shutdown } from "../frontcommand/scrcpyMaskCmd";
|
|
import { useGlobalStore } from "../store/global";
|
|
|
|
const dialog = useDialog();
|
|
const store = useGlobalStore();
|
|
|
|
const port = ref(27183);
|
|
|
|
//#region listener
|
|
const deviceWaitForMetadataTask: ((
|
|
deviceName: string
|
|
) => void)[] = [];
|
|
|
|
let unlisten: UnlistenFn | undefined;
|
|
onMounted(async () => {
|
|
unlisten = await listen("device-reply", (event) => {
|
|
try {
|
|
let payload = JSON.parse(event.payload as string);
|
|
switch (payload.type) {
|
|
case "MetaData":
|
|
let task = deviceWaitForMetadataTask.shift();
|
|
task?.(payload.deviceName);
|
|
break;
|
|
case "ClipboardChanged":
|
|
console.log("剪切板变动", payload.clipboard);
|
|
break;
|
|
case "ClipboardSetAck":
|
|
console.log("剪切板设置成功", payload.sequence);
|
|
break;
|
|
default:
|
|
console.log("Unknown reply", payload);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
});
|
|
});
|
|
|
|
onActivated(async () => {
|
|
await refreshDevices();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (unlisten !== undefined) unlisten();
|
|
});
|
|
//#endregion
|
|
|
|
//#region table
|
|
const devices: Ref<Device[]> = ref([]);
|
|
const availableDevice = computed(() => {
|
|
return devices.value.filter((d) => {
|
|
return store.controledDevice?.device.id !== d.id;
|
|
});
|
|
});
|
|
const tableCols: DataTableColumns = [
|
|
{
|
|
title: "ID",
|
|
key: "id",
|
|
},
|
|
{
|
|
title: "Status",
|
|
key: "status",
|
|
},
|
|
];
|
|
|
|
// record last operated row index
|
|
let rowIndex = -1;
|
|
|
|
// table row contextmenu and click event handler
|
|
const tableRowProps = (_: any, index: number) => {
|
|
return {
|
|
onContextmenu: (e: MouseEvent) => {
|
|
e.preventDefault();
|
|
showMenu.value = false;
|
|
rowIndex = index;
|
|
nextTick().then(() => {
|
|
showMenu.value = true;
|
|
menuX.value = e.clientX;
|
|
menuY.value = e.clientY;
|
|
});
|
|
},
|
|
onclick: (e: MouseEvent) => {
|
|
e.preventDefault();
|
|
showMenu.value = false;
|
|
rowIndex = index;
|
|
nextTick().then(() => {
|
|
showMenu.value = true;
|
|
menuX.value = e.clientX;
|
|
menuY.value = e.clientY;
|
|
});
|
|
},
|
|
};
|
|
};
|
|
//#endregion
|
|
|
|
//#region controled device
|
|
|
|
async function shutdownSC() {
|
|
dialog.warning({
|
|
title: "Warning",
|
|
content: "确定关闭Scrcpy控制服务?",
|
|
positiveText: "确定",
|
|
negativeText: "取消",
|
|
onPositiveClick: async () => {
|
|
await shutdown();
|
|
store.controledDevice = null;
|
|
},
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region menu
|
|
const menuX = ref(0);
|
|
const menuY = ref(0);
|
|
const showMenu = ref(false);
|
|
const menuOptions: DropdownOption[] = [
|
|
{
|
|
label: () => h("span", "控制此设备"),
|
|
key: "control",
|
|
},
|
|
];
|
|
|
|
function onMenuClickoutside() {
|
|
showMenu.value = false;
|
|
}
|
|
|
|
async function onMenuSelect(key: string) {
|
|
showMenu.value = false;
|
|
store.showLoading();
|
|
switch (key) {
|
|
case "control":
|
|
if (!port.value) {
|
|
port.value = 27183;
|
|
}
|
|
let device = devices.value[rowIndex];
|
|
|
|
let scid = (
|
|
"00000000" + Math.floor(Math.random() * 100000).toString(16)
|
|
).slice(-8);
|
|
|
|
await pushServerFile(device.id);
|
|
await forwardServerPort(device.id, scid, port.value);
|
|
await startScrcpyServer(device.id, scid, `127.0.0.1:${port.value}`);
|
|
|
|
// add cb for metadata
|
|
deviceWaitForMetadataTask.push((deviceName: string) => {
|
|
store.controledDevice = {
|
|
scid,
|
|
deviceName,
|
|
device,
|
|
}
|
|
nextTick(() => {
|
|
store.hideLoading();
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
//#endregion
|
|
|
|
async function refreshDevices() {
|
|
store.showLoading();
|
|
devices.value = await adbDevices();
|
|
store.hideLoading();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="device">
|
|
<NSpin :show="store.showLoadingRef">
|
|
<NH4 prefix="bar">本地端口</NH4>
|
|
<NInputNumber
|
|
v-model:value="port"
|
|
:show-button="false"
|
|
:min="16384"
|
|
:max="49151"
|
|
style="max-width: 300px"
|
|
/>
|
|
<NH4 prefix="bar">受控设备</NH4>
|
|
<div class="controled-device-list">
|
|
<NEmpty
|
|
size="small"
|
|
description="No Controled Device"
|
|
v-if="!store.controledDevice"
|
|
/>
|
|
<div class="controled-device" v-if="store.controledDevice">
|
|
<div>{{ store.controledDevice.deviceName }} ({{ store.controledDevice.device.id }})</div>
|
|
<div class="device-op">
|
|
<NTooltip trigger="hover">
|
|
<template #trigger>
|
|
<NButton quaternary circle type="info">
|
|
<template #icon>
|
|
<NIcon><InformationCircle /></NIcon>
|
|
</template>
|
|
</NButton>
|
|
</template>
|
|
scid: {{ store.controledDevice.scid }}
|
|
<br />status: {{ store.controledDevice.device.status }}
|
|
</NTooltip>
|
|
|
|
<NButton
|
|
quaternary
|
|
circle
|
|
type="error"
|
|
@click="shutdownSC()"
|
|
>
|
|
<template #icon>
|
|
<NIcon><CloseCircle /></NIcon>
|
|
</template>
|
|
</NButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<NFlex justify="space-between" align="center">
|
|
<NH4 prefix="bar">可用设备</NH4>
|
|
<NButton
|
|
tertiary
|
|
circle
|
|
type="primary"
|
|
@click="refreshDevices"
|
|
style="margin-right: 20px"
|
|
>
|
|
<template #icon>
|
|
<NIcon><Refresh /></NIcon>
|
|
</template>
|
|
</NButton>
|
|
</NFlex>
|
|
<NDataTable
|
|
max-height="120"
|
|
:columns="tableCols"
|
|
:data="availableDevice"
|
|
:row-props="tableRowProps"
|
|
:pagination="false"
|
|
:bordered="false"
|
|
/>
|
|
<NDropdown
|
|
placement="bottom-start"
|
|
trigger="manual"
|
|
:x="menuX"
|
|
:y="menuY"
|
|
:options="menuOptions"
|
|
:show="showMenu"
|
|
:on-clickoutside="onMenuClickoutside"
|
|
@select="onMenuSelect"
|
|
/>
|
|
</NSpin>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.device {
|
|
color: var(--light-color);
|
|
background-color: var(--bg-color);
|
|
padding: 0 25px;
|
|
}
|
|
|
|
.n-h4 {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.controled-device-list {
|
|
.controled-device {
|
|
padding: 10px 20px;
|
|
background-color: var(--content-bg-color);
|
|
border: 2px solid var(--content-hl-color);
|
|
border-bottom: none;
|
|
border-radius: 5px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
transition: background-color 0.3s;
|
|
&:last-child {
|
|
border-bottom: 2px solid var(--content-hl-color);
|
|
}
|
|
&:hover {
|
|
background-color: var(--content-hl-color);
|
|
}
|
|
|
|
.device-op {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
}
|
|
}
|
|
</style>
|