scrcpy-mask/src/components/Device.vue

425 lines
10 KiB
Vue
Raw Normal View History

2024-04-13 09:53:41 +08:00
<script setup lang="ts">
import {
Ref,
computed,
h,
nextTick,
onActivated,
onMounted,
onUnmounted,
ref,
} from "vue";
import {
Device,
adbDevices,
pushServerFile,
forwardServerPort,
startScrcpyServer,
2024-04-30 09:57:47 +08:00
getDeviceScreenSize,
2024-04-30 22:02:40 +08:00
adbConnect,
2024-04-13 09:53:41 +08:00
} from "../invoke";
import {
NH4,
2024-04-16 22:18:42 +08:00
NP,
2024-04-30 22:02:40 +08:00
NInput,
2024-04-13 09:53:41 +08:00
NInputNumber,
NButton,
NDataTable,
NDropdown,
NEmpty,
NTooltip,
NFlex,
2024-04-16 17:07:25 +08:00
NFormItem,
2024-04-13 09:53:41 +08:00
NIcon,
NSpin,
2024-04-16 21:14:37 +08:00
NScrollbar,
2024-04-13 09:53:41 +08:00
DataTableColumns,
DropdownOption,
useDialog,
2024-04-16 17:07:25 +08:00
useMessage,
2024-04-30 22:02:40 +08:00
NInputGroup,
2024-04-13 09:53:41 +08:00
} from "naive-ui";
import { CloseCircle, InformationCircle } from "@vicons/ionicons5";
import { Refresh } from "@vicons/ionicons5";
import { UnlistenFn, listen } from "@tauri-apps/api/event";
2024-04-16 22:18:42 +08:00
import { Store } from "@tauri-apps/plugin-store";
2024-04-13 09:53:41 +08:00
import { shutdown } from "../frontcommand/scrcpyMaskCmd";
import { useGlobalStore } from "../store/global";
const dialog = useDialog();
const store = useGlobalStore();
2024-04-16 17:07:25 +08:00
const message = useMessage();
2024-04-13 09:53:41 +08:00
const port = ref(27183);
2024-04-30 22:02:40 +08:00
const address = ref("");
2024-04-13 09:53:41 +08:00
2024-04-16 22:18:42 +08:00
const localStore = new Store("store.bin");
2024-04-13 09:53:41 +08:00
//#region listener
2024-04-17 22:52:17 +08:00
let deviceWaitForMetadataTask: ((deviceName: string) => void) | null = null;
2024-04-13 09:53:41 +08:00
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":
2024-04-17 22:52:17 +08:00
deviceWaitForMetadataTask?.(payload.deviceName);
2024-04-13 09:53:41 +08:00
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",
},
2024-04-30 09:57:47 +08:00
{
label: () => h("span", "获取屏幕尺寸"),
key: "screen",
},
2024-04-13 09:53:41 +08:00
];
function onMenuClickoutside() {
showMenu.value = false;
}
2024-04-30 09:57:47 +08:00
async function deviceControl() {
if (!port.value) {
port.value = 27183;
}
2024-04-16 17:07:25 +08:00
2024-04-30 09:57:47 +08:00
if (!(store.screenSizeW > 0) || !(store.screenSizeH > 0)) {
message.error("请正确输入当前控制设备的屏幕尺寸");
store.screenSizeW = 0;
store.screenSizeH = 0;
store.hideLoading();
return;
}
2024-04-16 17:07:25 +08:00
2024-04-30 09:57:47 +08:00
if (store.controledDevice) {
message.error("请先关闭当前控制设备");
store.hideLoading();
return;
}
2024-04-25 22:48:05 +08:00
2024-04-30 09:57:47 +08:00
localStore.set("screenSize", {
sizeW: store.screenSizeW,
sizeH: store.screenSizeH,
});
message.info("屏幕尺寸已保存,正在启动控制服务,请保持设备亮屏");
2024-04-16 22:18:42 +08:00
2024-04-30 09:57:47 +08:00
const device = devices.value[rowIndex];
2024-04-13 09:53:41 +08:00
2024-04-30 09:57:47 +08:00
let scid = (
"00000000" + Math.floor(Math.random() * 100000).toString(16)
).slice(-8);
2024-04-13 09:53:41 +08:00
2024-04-30 09:57:47 +08:00
await pushServerFile(device.id);
await forwardServerPort(device.id, scid, port.value);
await startScrcpyServer(device.id, scid, `127.0.0.1:${port.value}`);
2024-04-13 09:53:41 +08:00
2024-04-30 09:57:47 +08:00
// connection timeout check
let id = setTimeout(async () => {
if (deviceWaitForMetadataTask) {
await shutdown();
store.controledDevice = null;
store.hideLoading();
message.error("设备连接超时");
}
}, 6000);
// add cb for metadata
deviceWaitForMetadataTask = (deviceName: string) => {
store.controledDevice = {
scid,
deviceName,
device,
};
nextTick(() => {
deviceWaitForMetadataTask = null;
clearTimeout(id);
store.hideLoading();
});
};
}
2024-04-17 22:52:17 +08:00
2024-04-30 09:57:47 +08:00
async function deviceGetScreenSize() {
let id = devices.value[rowIndex].id;
const size = await getDeviceScreenSize(id);
store.hideLoading();
message.success(`设备屏幕尺寸为: ${size[0]} x ${size[1]}`);
}
async function onMenuSelect(key: string) {
showMenu.value = false;
store.showLoading();
switch (key) {
case "control":
await deviceControl();
break;
case "screen":
await deviceGetScreenSize();
2024-04-13 09:53:41 +08:00
break;
}
}
//#endregion
async function refreshDevices() {
store.showLoading();
devices.value = await adbDevices();
store.hideLoading();
}
2024-04-30 22:02:40 +08:00
async function connectDevice() {
if (!address.value) {
message.error("请输入无线调试地址");
return;
}
store.showLoading();
2024-04-30 22:17:56 +08:00
message.info(await adbConnect(address.value));
await refreshDevices();
2024-04-30 22:02:40 +08:00
}
2024-04-13 09:53:41 +08:00
</script>
<template>
2024-04-16 21:14:37 +08:00
<NScrollbar>
<div class="device">
<NSpin :show="store.showLoadingRef">
<NH4 prefix="bar">本地端口</NH4>
<NInputNumber
v-model:value="port"
:show-button="false"
:min="16384"
:max="49151"
2024-04-30 22:02:40 +08:00
placeholder="Scrcpy 本地端口"
2024-04-16 21:14:37 +08:00
style="max-width: 300px"
2024-04-13 09:53:41 +08:00
/>
2024-04-30 22:02:40 +08:00
<NH4 prefix="bar">无线连接</NH4>
<NInputGroup style="max-width: 300px">
<NInput
v-model:value="address"
clearable
placeholder="无线调试地址"
/>
<NButton type="primary" @click="connectDevice">连接</NButton>
</NInputGroup>
2024-04-16 21:14:37 +08:00
<NH4 prefix="bar">设备尺寸</NH4>
<NFlex justify="left" align="center">
<NFormItem label="宽度">
<NInputNumber
v-model:value="store.screenSizeW"
placeholder="屏幕宽度"
:min="0"
:disabled="store.controledDevice !== null"
/>
</NFormItem>
<NFormItem label="高度">
<NInputNumber
v-model:value="store.screenSizeH"
placeholder="屏幕高度"
:min="0"
:disabled="store.controledDevice !== null"
/>
</NFormItem>
</NFlex>
<NP
>提示请正确输入当前控制设备的屏幕尺寸这是成功发送触摸事件的必要参数</NP
>
<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:
2024-04-30 09:57:47 +08:00
{{ store.controledDevice.device.status }}
2024-04-16 21:14:37 +08:00
</NTooltip>
<NButton quaternary circle type="error" @click="shutdownSC()">
<template #icon>
<NIcon><CloseCircle /></NIcon>
</template>
</NButton>
</div>
2024-04-13 09:53:41 +08:00
</div>
</div>
2024-04-16 21:14:37 +08:00
<NFlex justify="space-between" align="center">
2024-04-25 22:48:05 +08:00
<NH4 style="margin: 20px 0" prefix="bar">可用设备</NH4>
2024-04-16 21:14:37 +08:00
<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>
</NScrollbar>
2024-04-13 09:53:41 +08:00
</template>
<style scoped lang="scss">
.device {
color: var(--light-color);
background-color: var(--bg-color);
2024-04-16 21:14:37 +08:00
padding: 0 20px;
2024-04-16 22:18:42 +08:00
height: 100%;
2024-04-13 09:53:41 +08:00
}
.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>