feat(websocket): add external control

This commit is contained in:
AkiChase 2024-05-16 23:25:31 +08:00
parent e832abff58
commit 365c915103
6 changed files with 147 additions and 9 deletions

View File

@ -40,13 +40,13 @@ import {
useMessage,
NInputGroup,
} from "naive-ui";
import { CloseCircle, InformationCircle } from "@vicons/ionicons5";
import { Refresh } from "@vicons/ionicons5";
import { CloseCircle, InformationCircle, Refresh } from "@vicons/ionicons5";
import { UnlistenFn, listen } from "@tauri-apps/api/event";
import { Store } from "@tauri-apps/plugin-store";
import { shutdown } from "../frontcommand/scrcpyMaskCmd";
import { useGlobalStore } from "../store/global";
import { useI18n } from "vue-i18n";
import { closeExternalControl, connectExternalControl } from "../websocket";
const { t } = useI18n();
const dialog = useDialog();
@ -54,7 +54,8 @@ const store = useGlobalStore();
const message = useMessage();
const port = ref(27183);
const address = ref("");
const wireless_address = ref("");
const ws_address = ref("");
const localStore = new Store("store.bin");
@ -300,15 +301,29 @@ async function refreshDevices() {
}
async function connectDevice() {
if (!address.value) {
if (!wireless_address.value) {
message.error(t("pages.Device.inputWirelessAddress"));
return;
}
store.showLoading();
message.info(await adbConnect(address.value));
message.info(await adbConnect(wireless_address.value));
await refreshDevices();
}
function connectWS() {
if (!ws_address.value) {
message.error(t("pages.Device.inputWsAddress"));
return;
}
store.showLoading();
connectExternalControl(ws_address.value, message, store, t);
}
function closeWS() {
closeExternalControl();
}
</script>
<template>
@ -327,7 +342,7 @@ async function connectDevice() {
<NH4 prefix="bar">{{ $t("pages.Device.wireless") }}</NH4>
<NInputGroup style="max-width: 300px">
<NInput
v-model:value="address"
v-model:value="wireless_address"
clearable
:placeholder="$t('pages.Device.wirelessPlaceholder')"
/>
@ -335,6 +350,24 @@ async function connectDevice() {
$t("pages.Device.connect")
}}</NButton>
</NInputGroup>
<NH4 prefix="bar">{{ $t("pages.Device.externalControl") }}</NH4>
<NInputGroup style="max-width: 300px">
<NInput
v-model:value="ws_address"
clearable
:placeholder="$t('pages.Device.wsAddress')"
:disabled="store.externalControlled"
/>
<NButton
v-if="store.externalControlled"
type="error"
@click="closeWS"
>{{ $t("pages.Device.wsClose") }}</NButton
>
<NButton v-else type="primary" @click="connectWS">{{
$t("pages.Device.wsConnect")
}}</NButton>
</NInputGroup>
<NH4 prefix="bar">{{ $t("pages.Device.deviceSize.title") }}</NH4>
<NFlex justify="left" align="center">
<NFormItem :label="$t('pages.Device.deviceSize.width')">

View File

@ -37,7 +37,12 @@
"availableDevice": "Available devices",
"noControledDevice": "No Controled Device",
"alreadyControled": "Controlled device already exists",
"alreadyDisconnected": "Controlled device connection has been disconnected"
"alreadyDisconnected": "Controlled device connection has been disconnected",
"externalControl": "External control",
"wsAddress": "Websocket address",
"inputWsAddress": "Please enter the Websocket address",
"wsClose": "Close",
"wsConnect": "Control"
},
"Mask": {
"inputBoxPlaceholder": "Input text and then press enter/esc",
@ -218,5 +223,10 @@
},
"sidebar": {
"noControledDevice": "No devices are controlled"
},
"websocket": {
"open": "Connected to external control server",
"close": "External control connection disconnected",
"error": "Something was wrong, the exter connection is closed"
}
}

View File

@ -8,6 +8,7 @@ const localStore = new Store("store.bin");
const i18n = createI18n({
allowComposition: true,
legacy: false,
messages: {
"en-US": enUS,
"zh-CN": zhCN,
@ -15,7 +16,7 @@ const i18n = createI18n({
});
localStore.get<"en-US" | "zh-CN">("language").then((language) => {
i18n.global.locale = language ?? "en-US";
i18n.global.locale.value = language ?? "en-US";
});
export default i18n;

View File

@ -37,7 +37,12 @@
"noControledDevice": "无受控设备",
"availableDevice": "可用设备",
"alreadyControled": "已存在受控设备",
"alreadyDisconnected": "受控设备连接已断开"
"alreadyDisconnected": "受控设备连接已断开",
"inputWsAddress": "请输入 Websocket 地址",
"externalControl": "外部控制",
"wsAddress": "Websocket 地址",
"wsClose": "断开",
"wsConnect": "控制"
},
"Mask": {
"keyconfigException": "按键方案异常,请删除此方案",
@ -218,5 +223,10 @@
},
"sidebar": {
"noControledDevice": "未控制任何设备"
},
"websocket": {
"open": "已连接到外部控制服务端",
"close": "外部控制连接断开",
"error": "未知错误,外部控制连接断开"
}
}

View File

@ -69,6 +69,8 @@ export const useGlobalStore = defineStore("global", () => {
localStore.set("curKeyMappingIndex", index);
}
const externalControlled = ref(false);
// persistent storage
const screenSizeW: Ref<number> = ref(0);
const screenSizeH: Ref<number> = ref(0);
@ -88,6 +90,7 @@ export const useGlobalStore = defineStore("global", () => {
curKeyMappingIndex,
maskButton,
checkUpdateAtStart,
externalControlled,
// in-memory storage
showLoading,
hideLoading,

81
src/websocket.ts Normal file
View File

@ -0,0 +1,81 @@
import { useMessage } from "naive-ui";
import { useGlobalStore } from "./store/global";
import { sendKey, shutdown, swipe, touch } from "./frontcommand/scrcpyMaskCmd";
import { useI18n } from "vue-i18n";
let ws: WebSocket;
let sharedMessage: ReturnType<typeof useMessage>;
let sharedStore: ReturnType<typeof useGlobalStore>;
let t: ReturnType<typeof useI18n>["t"];
export function connectExternalControl(
url: string,
message: ReturnType<typeof useMessage>,
store: ReturnType<typeof useGlobalStore>,
i18nT: ReturnType<typeof useI18n>["t"]
) {
sharedMessage = message;
sharedStore = store;
t = i18nT;
ws = new WebSocket(url);
ws.addEventListener("open", handleOpen);
ws.addEventListener("message", handleMessage);
ws.addEventListener("close", handleClose);
ws.addEventListener("error", handleError);
}
export function closeExternalControl() {
if (ws) ws.close();
}
function handleOpen() {
sharedStore.externalControlled = true;
sharedStore.hideLoading();
sharedMessage.success(t("websocket.open"));
}
async function handleMessage(event: MessageEvent) {
try {
const msg = JSON.parse(event.data);
if (msg.type === "showMessage") {
sharedMessage.create(msg.msgContent, { type: msg.msgType });
} else if (msg.type === "getControlledDevice") {
msg.controledDevice = sharedStore.controledDevice;
ws.send(JSON.stringify(msg));
} else if (msg.type === "sendKey") {
delete msg.type;
await sendKey(msg);
} else if (msg.type === "touch") {
msg.screen = { w: sharedStore.screenSizeW, h: sharedStore.screenSizeH };
delete msg.type;
await touch(msg);
} else if (msg.type === "swipe") {
console.log(msg);
msg.screen = { w: sharedStore.screenSizeW, h: sharedStore.screenSizeH };
delete msg.type;
await swipe(msg);
} else if (msg.type === "shutdown") {
await shutdown();
sharedStore.controledDevice = null;
} else {
console.error("Invalid message received", msg);
}
} catch (error) {
console.error("Message received failed", error);
}
}
function handleClose() {
sharedMessage.info(t("websocket.close"));
ws.close();
sharedStore.externalControlled = false;
sharedStore.hideLoading();
}
function handleError() {
sharedMessage.error(t("websocket.error"));
ws.close();
sharedStore.externalControlled = false;
sharedStore.hideLoading();
}