scrcpy-mask/src/components/Device.vue

430 lines
11 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";
import { useI18n } from "vue-i18n";
2024-04-13 09:53:41 +08:00
const { t } = useI18n();
2024-04-13 09:53:41 +08:00
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":
2024-05-07 10:20:44 +08:00
console.log("ClipboardChanged", payload.clipboard);
2024-04-13 09:53:41 +08:00
break;
case "ClipboardSetAck":
2024-05-07 10:20:44 +08:00
console.log("ClipboardSetAck", payload.sequence);
2024-04-13 09:53:41 +08:00
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?.deviceID !== d.id;
2024-04-13 09:53:41 +08:00
});
});
const tableCols: DataTableColumns = [
{
title: "ID",
key: "id",
},
{
title: t("pages.Device.status"),
2024-04-13 09:53:41 +08:00
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: t("pages.Device.shutdown.title"),
content: t("pages.Device.shutdown.content"),
positiveText: t("pages.Device.shutdown.positiveText"),
negativeText: t("pages.Device.shutdown.negativeText"),
2024-04-13 09:53:41 +08:00
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", t("pages.Device.menu.control")),
2024-04-13 09:53:41 +08:00
key: "control",
},
2024-04-30 09:57:47 +08:00
{
label: () => h("span", t("pages.Device.menu.screen")),
2024-04-30 09:57:47 +08:00
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(t("pages.Device.deviceControl.inputScreenSize"));
2024-04-30 09:57:47 +08:00
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(t("pages.Device.deviceControl.closeCurDevice"));
2024-04-30 09:57:47 +08:00
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(t("pages.Device.deviceControl.controlInfo"));
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(t("pages.Device.deviceControl.connectTimeout"));
2024-04-30 09:57:47 +08:00
}
}, 6000);
// add cb for metadata
deviceWaitForMetadataTask = (deviceName: string) => {
store.controledDevice = {
scid,
deviceName,
deviceID: device.id,
2024-04-30 09:57:47 +08:00
};
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(
t("pages.Device.deviceGetScreenSize") + `${size[0]} x ${size[1]}`
);
2024-04-30 09:57:47 +08:00
}
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(t("pages.Device.inputWirelessAddress"));
2024-04-30 22:02:40 +08:00
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">{{ $t("pages.Device.localPort") }}</NH4>
2024-04-16 21:14:37 +08:00
<NInputNumber
v-model:value="port"
:show-button="false"
:min="16384"
:max="49151"
:placeholder="$t('pages.Device.localPortPlaceholder')"
2024-04-16 21:14:37 +08:00
style="max-width: 300px"
2024-04-13 09:53:41 +08:00
/>
<NH4 prefix="bar">{{ $t("pages.Device.wireless") }}</NH4>
2024-04-30 22:02:40 +08:00
<NInputGroup style="max-width: 300px">
<NInput
v-model:value="address"
clearable
:placeholder="$t('pages.Device.wirelessPlaceholder')"
2024-04-30 22:02:40 +08:00
/>
<NButton type="primary" @click="connectDevice">{{
$t("pages.Device.connect")
}}</NButton>
2024-04-30 22:02:40 +08:00
</NInputGroup>
<NH4 prefix="bar">{{ $t("pages.Device.deviceSize.title") }}</NH4>
2024-04-16 21:14:37 +08:00
<NFlex justify="left" align="center">
<NFormItem :label="$t('pages.Device.deviceSize.width')">
2024-04-16 21:14:37 +08:00
<NInputNumber
v-model:value="store.screenSizeW"
:placeholder="$t('pages.Device.deviceSize.widthPlaceholder')"
2024-04-16 21:14:37 +08:00
:min="0"
:disabled="store.controledDevice !== null"
/>
</NFormItem>
<NFormItem :label="$t('pages.Device.deviceSize.height')">
2024-04-16 21:14:37 +08:00
<NInputNumber
v-model:value="store.screenSizeH"
:placeholder="$t('pages.Device.deviceSize.heightPlaceholder')"
2024-04-16 21:14:37 +08:00
:min="0"
:disabled="store.controledDevice !== null"
/>
</NFormItem>
</NFlex>
<NP>{{ $t("pages.Device.deviceSize.tip") }}</NP>
<NH4 prefix="bar">{{ $t("pages.Device.controledDevice") }}</NH4>
2024-04-16 21:14:37 +08:00
<div class="controled-device-list">
<NEmpty
size="small"
:description="$t('pages.Device.noControledDevice')"
2024-04-16 21:14:37 +08:00
v-if="!store.controledDevice"
/>
<div class="controled-device" v-if="store.controledDevice">
<div>
{{ store.controledDevice.deviceName }} ({{
store.controledDevice.deviceID
2024-04-16 21:14:37 +08:00
}})
</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 }}
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">
<NH4 style="margin: 20px 0" prefix="bar">{{
$t("pages.Device.availableDevice")
}}</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>