diff --git a/package.json b/package.json index 52efd79..b594c24 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scrcpy-mask", "private": true, - "version": "0.4.4", + "version": "0.5.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1aef3eb..9433407 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrcpy-mask" -version = "0.4.4" +version = "0.5.0" description = "A Tauri App" authors = ["AkiChase"] edition = "2021" diff --git a/src-tauri/src/share.rs b/src-tauri/src/share.rs index 3402b9a..dd6ce79 100644 --- a/src-tauri/src/share.rs +++ b/src-tauri/src/share.rs @@ -6,6 +6,8 @@ pub struct ClientInfo { pub device_name: String, pub device_id: String, pub scid: String, + pub width: i32, + pub height: i32, } impl ClientInfo { @@ -14,8 +16,15 @@ impl ClientInfo { device_name, device_id, scid, + width: 0, + height: 0, } } + + pub fn set_size(&mut self, width: i32, height: i32) { + self.width = width; + self.height = height; + } } lazy_static! { diff --git a/src-tauri/src/socket.rs b/src-tauri/src/socket.rs index bb84fbe..630009e 100644 --- a/src-tauri/src/socket.rs +++ b/src-tauri/src/socket.rs @@ -166,6 +166,12 @@ async fn handle_device_message( "height": height }) .to_string(); + share::CLIENT_INFO + .lock() + .unwrap() + .as_mut() + .unwrap() + .set_size(width, height); device_reply_sender.send(msg).await?; } }; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index b1dd50e..bb7612d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "scrcpy-mask", - "version": "0.4.4", + "version": "0.5.0", "identifier": "com.akichase.mask", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/components/Device.vue b/src/components/Device.vue index 68f6fbb..a134d32 100644 --- a/src/components/Device.vue +++ b/src/components/Device.vue @@ -44,6 +44,7 @@ import { shutdown } from "../frontcommand/scrcpyMaskCmd"; import { useGlobalStore } from "../store/global"; import { useI18n } from "vue-i18n"; import { closeExternalControl, connectExternalControl } from "../websocket"; +import { LogicalSize, getCurrent } from "@tauri-apps/api/window"; const { t } = useI18n(); const dialog = useDialog(); @@ -79,7 +80,19 @@ onMounted(async () => { } else { store.screenSizeW = payload.width; store.screenSizeH = payload.height; - message.info(t("pages.Device.deviceRotation")); + message.info(t("pages.Device.rotation", [payload.rotation * 90])); + } + if (store.rotation.enable) { + let maskW: number; + let maskH: number; + if (payload.width >= payload.height) { + maskW = Math.round(store.rotation.horizontalLength); + maskH = Math.round(maskW * (payload.height / payload.width)); + } else { + maskH = Math.round(store.rotation.verticalLength); + maskW = Math.round(maskH * (payload.width / payload.height)); + } + getCurrent().setSize(new LogicalSize(maskW + 70, maskH + 30)); } break; default: @@ -105,6 +118,8 @@ onActivated(async () => { // restore controledDevice if client exists if (curClientInfo) { message.warning(t("pages.Device.alreadyControled")); + store.screenSizeW = curClientInfo.width; + store.screenSizeH = curClientInfo.height; store.controledDevice = { scid: curClientInfo.scid, deviceName: curClientInfo.device_name, diff --git a/src/components/Header.vue b/src/components/Header.vue index 570e893..087cfd4 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -49,6 +49,7 @@ async function maximizeOrRestore() { justify-content: end; align-items: center; border-radius: 0 10px 0 0; + user-select: none; .n-button-group{ flex-shrink: 0; diff --git a/src/components/Mask.vue b/src/components/Mask.vue index c1468d5..047e6bd 100644 --- a/src/components/Mask.vue +++ b/src/components/Mask.vue @@ -1,5 +1,5 @@ + + + + diff --git a/src/components/keyboard/KeyBoard.vue b/src/components/keyboard/KeyBoard.vue index 4ce9f25..82ace9a 100644 --- a/src/components/keyboard/KeyBoard.vue +++ b/src/components/keyboard/KeyBoard.vue @@ -8,6 +8,7 @@ import KeySkill from "./KeySkill.vue"; import KeyObservation from "./KeyObservation.vue"; import KeySight from "./KeySight.vue"; import KeyFire from "./KeyFire.vue"; +import ScreenStream from "../ScreenStream.vue"; import { KeyDirectionalSkill, @@ -31,6 +32,7 @@ const keyboardStore = useKeyboardStore(); const dialog = useDialog(); const message = useMessage(); +const curPageActive = ref(false); const addButtonPos = ref({ x: 0, y: 0 }); const addButtonOptions: DropdownOption[] = [ { @@ -280,12 +282,14 @@ function resetKeyMappingConfig() { } onActivated(() => { + curPageActive.value = true; document.addEventListener("keydown", handleKeyDown); document.addEventListener("keyup", handleKeyUp); document.addEventListener("wheel", handleMouseWheel); }); onBeforeRouteLeave(() => { + curPageActive.value = false; return new Promise((resolve, _) => { document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("keyup", handleKeyUp); @@ -316,6 +320,10 @@ onBeforeRouteLeave(() => { diff --git a/src/hotkey.ts b/src/hotkey.ts index 9ed1a86..618fd71 100644 --- a/src/hotkey.ts +++ b/src/hotkey.ts @@ -24,7 +24,6 @@ import { import { useGlobalStore } from "./store/global"; import { LogicalPosition, getCurrent } from "@tauri-apps/api/window"; import { useI18n } from "vue-i18n"; -import { UnlistenFn } from "@tauri-apps/api/event"; import { KeyToCodeMap } from "./frontcommand/KeyToCodeMap"; import { AndroidKeyEventAction, @@ -36,13 +35,13 @@ import { sendInjectKeycode } from "./frontcommand/controlMsg"; function clientxToPosx(clientx: number) { return clientx < 70 ? 0 - : Math.floor((clientx - 70) * (store.screenSizeW / maskSizeW)); + : Math.floor((clientx - 70) * (store.screenSizeW / store.maskSizeW)); } function clientyToPosy(clienty: number) { return clienty < 30 ? 0 - : Math.floor((clienty - 30) * (store.screenSizeH / maskSizeH)); + : Math.floor((clienty - 30) * (store.screenSizeH / store.maskSizeH)); } function clientxToPosOffsetx(clientx: number, posx: number, scale = 1) { @@ -59,6 +58,9 @@ function clientPosToSkillOffset( clientPos: { x: number; y: number }, range: number ): { offsetX: number; offsetY: number } { + const maskSizeH = store.maskSizeH; + const maskSizeW = store.maskSizeW; + const maxLength = (120 / maskSizeH) * store.screenSizeH; const centerX = maskSizeW * 0.5; const centerY = maskSizeH * 0.5; @@ -1511,40 +1513,24 @@ export function clearShortcuts() { loopDownKeyCBMap.clear(); upKeyCBMap.clear(); cancelAbleKeyList.length = 0; - - // unlisten to resize - unlistenResize(); } export function applyShortcuts( - element: HTMLElement, keyMappingConfig: KeyMappingConfig, globalStore: ReturnType, messageAPI: ReturnType, i18nT: ReturnType["t"] ) { store = globalStore; - maskElement = element; + maskElement = document.getElementById("maskElement") as HTMLElement; message = messageAPI; t = i18nT; - maskSizeW = maskElement.clientWidth; - maskSizeH = maskElement.clientHeight; - // listen to resize to update mask size - getCurrent() - .onResized(() => { - maskSizeW = maskElement.clientWidth; - maskSizeH = maskElement.clientHeight; - }) - .then((f) => (unlistenResize = f)); - addClickShortcuts("M0", 0); return applyKeyMappingConfigShortcuts(keyMappingConfig); } -let maskSizeW: number; -let maskSizeH: number; let mouseX = 0; let mouseY = 0; let store: ReturnType; @@ -1552,8 +1538,6 @@ let maskElement: HTMLElement; let message: ReturnType; let t: ReturnType["t"]; -let unlistenResize: UnlistenFn; - const downKeyMap: Map = new Map(); const downKeyCBMap: Map Promise> = new Map(); const loopDownKeyCBMap: Map Promise> = new Map(); diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 4de2d50..86c0c72 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -37,10 +37,9 @@ "wsConnect": "Control", "adbDeviceError": "Unable to get available devices", "adbConnectError": "Wireless connection failed", - "deviceRotation": "Device rotation" + "rotation": "Device rotation {0}°" }, "Mask": { - "inputBoxPlaceholder": "Input text and then press enter/esc", "keyconfigException": "The key mapping config is abnormal, please delete this config", "blankConfig": "Blank config", "checkUpdate": { @@ -77,9 +76,9 @@ "areaSaved": "Mask area saved", "incorrectArea": "Please enter the coordinates and size of the mask correctly", "buttonPrompts": "Button prompts", - "ifButtonPrompts": "Whether to display", + "ifButtonPrompts": "Show key prompts", "opacity": "Opacity", - "areaAdjust": "Mask adjustment", + "areaAdjust": "Mask area", "areaPlaceholder": { "x": "X coordinate of upper left corner" }, @@ -88,7 +87,17 @@ "w": "Mask width", "h": "Mask height" }, - "areaTip": "Tip: The mask size and device size will be used for coordinate conversion, please ensure the accuracy of the size" + "rotation": { + "title": "Device rotation", + "rotateWithDevice": "Follow device rotation", + "verticalLength": "Mask height in vertical screen", + "horizontalLength": "Mask width in horizontal screen " + }, + "screenStream": { + "enable": "Enable mirror", + "address": "Screen mirror address", + "addressPlaceholder": "Please enter the ScreenStream screen mirror address" + } }, "Basic": { "delLocalStore": { diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json index 73f080b..86e2040 100644 --- a/src/i18n/zh-CN.json +++ b/src/i18n/zh-CN.json @@ -37,7 +37,7 @@ "wsConnect": "控制", "adbDeviceError": "无法获取可用设备", "adbConnectError": "无线连接失败", - "deviceRotation": "设备旋转" + "rotation": "设备旋转 {0}°" }, "Mask": { "keyconfigException": "按键方案异常,请删除此方案", @@ -56,7 +56,6 @@ "content": "请前往设备页面,控制任意设备", "positiveText": "去控制" }, - "inputBoxPlaceholder": "输入文本后按Enter/Esc", "sightMode": "鼠标已锁定, 按 {0} 键解锁", "checkAdb": "adb不可用,软件无法正常运行,请确保系统已安装adb,并正确添加到了Path环境变量中: {0}", "keyInputMode": "已进入按键输入模式,关闭本消息可退出" @@ -71,9 +70,9 @@ "incorrectArea": "请正确输入蒙版的坐标和尺寸", "areaSaved": "蒙版区域已保存", "buttonPrompts": "按键提示", - "ifButtonPrompts": "是否显示", + "ifButtonPrompts": "显示按键提示", "opacity": "不透明度", - "areaAdjust": "蒙版调整", + "areaAdjust": "蒙版区域", "areaPlaceholder": { "x": "左上角X坐标" }, @@ -82,12 +81,22 @@ "w": "蒙版宽度", "h": "蒙版高度" }, - "areaTip": "提示:蒙版尺寸与设备尺寸将用于坐标转换,请保证尺寸的准确性", "areaFormMissing": { "x": "请输入蒙版左上角X坐标", "y": "请输入蒙版左上角Y坐标", "w": "请输入蒙版宽度", "h": "请输入蒙版高度" + }, + "rotation": { + "title": "设备旋转", + "rotateWithDevice": "跟随设备旋转", + "verticalLength": "竖屏蒙版高度", + "horizontalLength": "横屏蒙版宽度" + }, + "screenStream": { + "enable": "启用投屏", + "address": "投屏地址", + "addressPlaceholder": "请输入 ScreenStream 投屏地址" } }, "Basic": { diff --git a/src/invoke.ts b/src/invoke.ts index f81a0dc..a39f623 100644 --- a/src/invoke.ts +++ b/src/invoke.ts @@ -33,6 +33,8 @@ export async function getCurClientInfo(): Promise<{ device_name: string; device_id: string; scid: string; + width: number; + height: number; } | null> { return await invoke("get_cur_client_info"); } diff --git a/src/screenStream.ts b/src/screenStream.ts new file mode 100644 index 0000000..72945d1 --- /dev/null +++ b/src/screenStream.ts @@ -0,0 +1,40 @@ +export class ScreenStream { + img: HTMLImageElement; + clientId: string; + connectTimeoutId: number | undefined; + + public constructor(imgElement: HTMLImageElement, clientId: string) { + this.img = imgElement; + this.clientId = clientId; + } + + public connect(address: string, onConnect: () => void, onError: () => void) { + if (address.endsWith("/")) address = address.slice(0, -1); + const that = this; + const img = that.img; + const url = `${address}/stream.mjpeg?clientId=${this.clientId}`; + + img.src = ""; + clearTimeout(that.connectTimeoutId); + new Promise((resolve, reject) => { + img.onload = function () { + img.onload = null; + img.onerror = null; + resolve(); + }; + img.onerror = function (e) { + img.onerror = null; + img.onload = null; + reject(e); + }; + img.src = url; + }) + .then(() => { + onConnect(); + }) + .catch(() => { + img.src = ""; + onError(); + }); + } +} diff --git a/src/store/global.ts b/src/store/global.ts index 293029f..483f5bd 100644 --- a/src/store/global.ts +++ b/src/store/global.ts @@ -75,6 +75,11 @@ export const useGlobalStore = defineStore("global", () => { const keyInputFlag = ref(false); + const maskSizeW: Ref = ref(0); + const maskSizeH: Ref = ref(0); + + const screenStreamClientId = ref("scrcpy-mask"); + // persistent storage const keyMappingConfigList: Ref = ref([]); const curKeyMappingIndex = ref(0); @@ -84,6 +89,17 @@ export const useGlobalStore = defineStore("global", () => { }); const checkUpdateAtStart = ref(true); + const screenStream = ref({ + enable: false, + address: "", + }); + + const rotation = ref({ + enable: true, + verticalLength: 600, + horizontalLength: 800, + }); + return { // persistent storage keyMappingConfigList, @@ -91,7 +107,12 @@ export const useGlobalStore = defineStore("global", () => { maskButton, checkUpdateAtStart, externalControlled, + screenStream, + rotation, // in-memory storage + screenStreamClientId, + maskSizeW, + maskSizeH, screenSizeW, screenSizeH, keyInputFlag,