Merge pull request #55 from AkiChase/dev

Scrcpy Mask v0.6.0
This commit is contained in:
如初 2024-05-31 19:16:38 +08:00 committed by GitHub
commit 5071b9abd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 855 additions and 265 deletions

View File

@ -1,7 +1,7 @@
{
"name": "scrcpy-mask",
"private": true,
"version": "0.5.0",
"version": "0.6.0",
"type": "module",
"scripts": {
"dev": "vite",
@ -12,6 +12,7 @@
},
"dependencies": {
"@tauri-apps/api": ">=2.0.0-beta.8",
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.1",
"@tauri-apps/plugin-http": "2.0.0-beta.3",
"@tauri-apps/plugin-process": "2.0.0-beta.2",
"@tauri-apps/plugin-shell": "2.0.0-beta.3",

BIN
public/favicon.ico Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,6 +1,6 @@
[package]
name = "scrcpy-mask"
version = "0.5.0"
version = "0.6.0"
description = "A Tauri App"
authors = ["AkiChase"]
edition = "2021"
@ -21,3 +21,4 @@ tokio = { version = "1.36.0", features = ["rt-multi-thread", "net", "macros", "i
tauri-plugin-process = "2.0.0-beta"
tauri-plugin-shell = "2.0.0-beta"
tauri-plugin-http = "2.0.0-beta"
tauri-plugin-clipboard-manager = "2.1.0-beta.2"

View File

@ -34,11 +34,16 @@
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.github.com/repos/AkiChase/scrcpy-mask/*" }
{
"url": "https://api.github.com/repos/AkiChase/scrcpy-mask/*"
}
]
},
"http:allow-fetch",
"app:default",
"app:allow-version"
"app:allow-version",
"clipboard-manager:default",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,5 +1,6 @@
[
{"list":[{"key":{"down":"KeyS","left":"KeyA","right":"KeyD","up":"KeyW"},"note":"方向轮盘","offset":175,"pointerId":1,"posX":183,"posY":566,"type":"SteeringWheel"},{"key":"KeyQ","note":"技能1","pointerId":2,"posX":951,"posY":636,"range":30,"type":"DirectionalSkill"},{"key":"KeyE","note":"技能2","pointerId":2,"posX":1025,"posY":500,"range":30,"type":"DirectionalSkill"},{"key":"AltLeft","note":"技能3","pointerId":2,"posX":1160,"posY":420,"range":30,"type":"DirectionalSkill"},{"key":"Space","note":"取消技能","pointerId":2,"posX":1160,"posY":140,"type":"CancelSkill"},{"key":"KeyB","note":"回城","pointerId":3,"posX":650,"posY":650,"time":80,"type":"Tap"},{"key":"KeyC","note":"回复","pointerId":3,"posX":740,"posY":650,"time":80,"type":"Tap"},{"key":"KeyF","note":"召唤师技能","pointerId":2,"posX":838,"posY":647,"range":50,"type":"DirectionalSkill"},{"key":"M2","note":"攻击","pointerId":3,"posX":1174,"posY":618,"time":80,"type":"Tap"},{"key":"Digit1","note":"技能1升级","pointerId":3,"posX":880,"posY":560,"time":80,"type":"Tap"},{"key":"Digit2","note":"技能2升级","pointerId":3,"posX":960,"posY":430,"time":80,"type":"Tap"},{"key":"Digit3","note":"技能3升级","pointerId":3,"posX":1090,"posY":350,"time":80,"type":"Tap"},{"key":"Digit5","note":"快速购买1","pointerId":3,"posX":133,"posY":289,"time":80,"type":"Tap"},{"key":"Digit6","note":"快速购买2","pointerId":3,"posX":130,"posY":370,"time":80,"type":"Tap"},{"key":"M3","note":"观察","pointerId":4,"posX":1000,"posY":200,"scale":1,"type":"Observation"},{"key":"Tab","macro":{"down":[{"args":["default",5,1185,40,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1220,100,80],"type":"touch"}]},"note":"战绩面板","posX":1185,"posY":40,"type":"Macro"},{"key":"ShiftLeft","macro":{"down":[{"args":["default",5,40,300,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1200,60,80],"type":"touch"}]},"note":"商店","posX":44,"posY":302,"type":"Macro"},{"key":"KeyZ","macro":{"down":[{"args":["default",5,250,230,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,640,150,80],"type":"touch"}]},"note":"地图","posX":250,"posY":230,"type":"Macro"},{"key":"Backquote","macro":{"down":[{"args":["default",5,40,300,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,510,630,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,1165,575,80],"type":"touch"},{"args":["default",5,1200,65,80],"type":"touch"},{"args":[200],"type":"sleep"},{"args":["default",5,125,300,80],"type":"touch"}],"loop":null,"up":null},"note":"换装2","posX":236,"posY":66,"type":"Macro"},{"directional":false,"key":"ControlLeft","note":"","pointerId":2,"posX":839,"posY":647,"rangeOrTime":80,"type":"TriggerWhenPressedSkill"},{"directional":false,"key":"M4","note":"","pointerId":2,"posX":952,"posY":636,"rangeOrTime":80,"type":"TriggerWhenPressedSkill"},{"directional":false,"key":"WheelDown","note":"","pointerId":2,"posX":1155,"posY":280,"rangeOrTime":80,"type":"TriggerWhenPressedSkill"},{"key":"Enter","macro":{"down":[{"args":["default",5,1245,280,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,1160,560,80],"type":"touch"},{"args":[],"type":"key-input-mode"}],"loop":null,"up":null},"note":"聊天","posX":1244,"posY":281,"type":"Macro"}],"relativeSize":{"h":720,"w":1280},"title":"AVD-王者荣耀-三技能-导入默认"},
{"list":[{"key":{"down":"KeyS","left":"KeyA","right":"KeyD","up":"KeyW"},"note":"方向轮盘","offset":175,"pointerId":1,"posX":183,"posY":566,"type":"SteeringWheel"},{"key":"KeyQ","note":"技能1","pointerId":2,"posX":952,"posY":636,"range":50,"type":"DirectionalSkill"},{"key":"AltLeft","note":"技能2","pointerId":2,"posX":979,"posY":526,"range":50,"type":"DirectionalSkill"},{"key":"KeyE","note":"技能3","pointerId":2,"posX":1074,"posY":438,"range":50,"type":"DirectionalSkill"},{"key":"Space","note":"取消技能","pointerId":2,"posX":1160,"posY":140,"type":"CancelSkill"},{"key":"KeyB","note":"回城","pointerId":3,"posX":644,"posY":650,"time":80,"type":"Tap"},{"key":"KeyC","note":"回复","pointerId":3,"posX":742,"posY":650,"time":80,"type":"Tap"},{"key":"KeyF","note":"召唤师技能","pointerId":2,"posX":838,"posY":647,"range":50,"type":"DirectionalSkill"},{"key":"M2","note":"攻击","pointerId":3,"posX":1179,"posY":621,"time":80,"type":"Tap"},{"key":"Digit1","note":"技能1升级","pointerId":3,"posX":895,"posY":564,"time":80,"type":"Tap"},{"key":"Digit2","note":"技能2升级","pointerId":3,"posX":922,"posY":456,"time":80,"type":"Tap"},{"key":"Digit3","note":"技能3升级","pointerId":3,"posX":1015,"posY":376,"time":80,"type":"Tap"},{"key":"Digit5","note":"快速购买1","pointerId":3,"posX":133,"posY":289,"time":80,"type":"Tap"},{"key":"Digit6","note":"快速购买2","pointerId":3,"posX":130,"posY":370,"time":80,"type":"Tap"},{"key":"M3","note":"观察","pointerId":4,"posX":1000,"posY":200,"scale":1,"type":"Observation"},{"key":"Tab","macro":{"down":[{"args":["default",5,1185,40,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1220,100,80],"type":"touch"}]},"note":"战绩面板","posX":1185,"posY":40,"type":"Macro"},{"key":"ShiftLeft","macro":{"down":[{"args":["default",5,40,300,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1200,60,80],"type":"touch"}]},"note":"商店","posX":44,"posY":302,"type":"Macro"},{"key":"KeyZ","macro":{"down":[{"args":["default",5,250,230,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,640,150,80],"type":"touch"}]},"note":"地图","posX":250,"posY":230,"type":"Macro"},{"key":"Backquote","macro":{"down":[{"args":["default",5,40,300,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,510,630,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,1165,575,80],"type":"touch"},{"args":["default",5,1200,65,80],"type":"touch"},{"args":[200],"type":"sleep"},{"args":["default",5,125,300,80],"type":"touch"}],"loop":null,"up":null},"note":"换装2","posX":236,"posY":66,"type":"Macro"},{"key":"WheelDown","note":"","pointerId":2,"posX":1189,"posY":422,"range":50,"type":"DirectionalSkill"},{"directional":false,"key":"M4","note":"","pointerId":2,"posX":951,"posY":636,"rangeOrTime":80,"type":"TriggerWhenPressedSkill"},{"type":"Macro","key":"Enter","note":"聊天","posX":1250,"posY":307,"macro":{"down":[{"args":["default",5,1245,280,80],"type":"touch"},{"args":[150],"type":"sleep"},{"args":["default",5,1160,560,80],"type":"touch"},{"args":[],"type":"key-input-mode"}],"loop":null,"up":null}},{"type":"TriggerWhenPressedSkill","key":"WheelUp","note":"装备技能","posX":1157,"posY":276,"pointerId":2,"directional":false,"rangeOrTime":80}],"relativeSize":{"h":720,"w":1280},"title":"AVD-王者荣耀-四技能-导入默认"},
{"list":[{"key":"Backquote","note":"准星键","pointerId":0,"posX":640,"posY":361,"scaleX":1,"scaleY":1,"type":"Sight"},{"key":{"down":"KeyS","left":"KeyA","right":"KeyD","up":"KeyW"},"note":"","offset":150,"pointerId":1,"posX":208,"posY":542,"type":"SteeringWheel"},{"key":"KeyC","note":"","pointerId":3,"posX":1085,"posY":668,"time":80,"type":"Tap"},{"key":"KeyZ","note":"","pointerId":3,"posX":1207,"posY":648,"time":80,"type":"Tap"},{"key":"Space","note":"","pointerId":3,"posX":1230,"posY":503,"time":80,"type":"Tap"},{"key":"M3","note":"","pointerId":4,"posX":992,"posY":229,"scale":0.6,"type":"Observation"},{"key":"Digit1","note":"","pointerId":3,"posX":543,"posY":642,"time":80,"type":"Tap"},{"key":"Digit2","note":"","pointerId":3,"posX":729,"posY":644,"time":80,"type":"Tap"},{"key":"ShiftLeft","note":"","pointerId":3,"posX":1075,"posY":194,"time":80,"type":"Tap"},{"key":"KeyF","note":"","pointerId":3,"posX":861,"posY":310,"time":80,"type":"Tap"},{"key":"KeyG","note":"","pointerId":3,"posX":865,"posY":388,"time":80,"type":"Tap"},{"key":"KeyR","note":"","pointerId":3,"posX":976,"posY":674,"time":80,"type":"Tap"},{"key":"KeyE","note":"","pointerId":3,"posX":872,"posY":495,"time":80,"type":"Tap"},{"key":"KeyB","note":"","pointerId":3,"posX":543,"posY":593,"time":80,"type":"Tap"},{"key":"KeyN","note":"","pointerId":3,"posX":721,"posY":589,"time":80,"type":"Tap"},{"drag":false,"note":"","pointerId":2,"posX":1097,"posY":549,"scaleX":0.5,"scaleY":0.5,"type":"Fire"},{"key":"M2","note":"","pointerId":3,"posX":1227,"posY":376,"time":80,"type":"Tap"},{"key":"Tab","macro":{"down":[{"args":["default",5,100,650,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1250,40,80],"type":"touch"}]},"note":"背包宏","posX":95,"posY":656,"type":"Macro"},{"key":"M1","note":"地图","pointerId":3,"posX":1244,"posY":43,"time":80,"type":"Tap"},{"key":"KeyV","note":"","pointerId":3,"posX":1156,"posY":300,"time":80,"type":"Tap"},{"key":"M4","note":"","pointerId":3,"posX":862,"posY":180,"time":80,"type":"Tap"},{"key":"KeyT","note":"","pointerId":3,"posX":880,"posY":674,"time":80,"type":"Tap"},{"key":"KeyQ","note":"","pointerId":3,"posX":400,"posY":679,"time":80,"type":"Tap"},{"key":"Digit4","note":"","pointerId":3,"posX":766,"posY":251,"time":80,"type":"Tap"},{"key":"Digit5","note":"","pointerId":3,"posX":770,"posY":320,"time":80,"type":"Tap"},{"key":"Digit3","note":"","pointerId":3,"posX":788,"posY":591,"time":80,"type":"Tap"}],"relativeSize":{"h":720,"w":1280},"title":"AVD-和平精英-导入默认"}
{"list":[{"key":"Backquote","note":"准星键","pointerId":0,"posX":640,"posY":361,"scaleX":1,"scaleY":1,"type":"Sight"},{"key":{"down":"KeyS","left":"KeyA","right":"KeyD","up":"KeyW"},"note":"","offset":150,"pointerId":1,"posX":208,"posY":542,"type":"SteeringWheel"},{"key":"KeyC","note":"","pointerId":3,"posX":1085,"posY":668,"time":80,"type":"Tap"},{"key":"KeyZ","note":"","pointerId":3,"posX":1207,"posY":648,"time":80,"type":"Tap"},{"key":"Space","note":"","pointerId":3,"posX":1230,"posY":503,"time":80,"type":"Tap"},{"key":"M3","note":"","pointerId":4,"posX":992,"posY":229,"scale":0.6,"type":"Observation"},{"key":"Digit1","note":"","pointerId":3,"posX":543,"posY":642,"time":80,"type":"Tap"},{"key":"Digit2","note":"","pointerId":3,"posX":729,"posY":644,"time":80,"type":"Tap"},{"key":"ShiftLeft","note":"","pointerId":3,"posX":1075,"posY":194,"time":80,"type":"Tap"},{"key":"KeyF","note":"","pointerId":3,"posX":861,"posY":310,"time":80,"type":"Tap"},{"key":"KeyG","note":"","pointerId":3,"posX":865,"posY":388,"time":80,"type":"Tap"},{"key":"KeyR","note":"","pointerId":3,"posX":976,"posY":674,"time":80,"type":"Tap"},{"key":"KeyE","note":"","pointerId":3,"posX":872,"posY":495,"time":80,"type":"Tap"},{"key":"KeyB","note":"","pointerId":3,"posX":543,"posY":593,"time":80,"type":"Tap"},{"key":"KeyN","note":"","pointerId":3,"posX":721,"posY":589,"time":80,"type":"Tap"},{"drag":false,"note":"","pointerId":2,"posX":1097,"posY":549,"scaleX":0.5,"scaleY":0.5,"type":"Fire"},{"key":"M2","note":"","pointerId":3,"posX":1227,"posY":376,"time":80,"type":"Tap"},{"key":"Tab","macro":{"down":[{"args":["default",5,100,650,80],"type":"touch"}],"loop":null,"up":[{"args":["default",5,1250,40,80],"type":"touch"}]},"note":"背包宏","posX":95,"posY":656,"type":"Macro"},{"key":"M1","note":"地图","pointerId":3,"posX":1244,"posY":43,"time":80,"type":"Tap"},{"key":"KeyV","note":"","pointerId":3,"posX":1156,"posY":300,"time":80,"type":"Tap"},{"key":"M4","note":"","pointerId":3,"posX":862,"posY":180,"time":80,"type":"Tap"},{"key":"KeyT","note":"","pointerId":3,"posX":880,"posY":674,"time":80,"type":"Tap"},{"key":"KeyQ","note":"","pointerId":3,"posX":400,"posY":679,"time":80,"type":"Tap"},{"key":"Digit4","note":"","pointerId":3,"posX":766,"posY":251,"time":80,"type":"Tap"},{"key":"Digit5","note":"","pointerId":3,"posX":770,"posY":320,"time":80,"type":"Tap"},{"key":"Digit3","note":"","pointerId":3,"posX":788,"posY":591,"time":80,"type":"Tap"}],"relativeSize":{"h":720,"w":1280},"title":"AVD-和平精英-导入默认"},
{"list":[{"key":{"down":"KeyS","left":"KeyA","right":"KeyD","up":"KeyW"},"note":"","offset":150,"pointerId":1,"posX":204,"posY":531,"type":"SteeringWheel"},{"key":"KeyE","note":"","pointerId":3,"posX":1036,"posY":660,"time":80,"type":"Tap"},{"key":"KeyL","note":"","pointerId":2,"posX":1166,"posY":328,"type":"DirectionlessSkill"},{"key":"KeyI","note":"","pointerId":3,"posX":947,"posY":657,"time":80,"type":"Tap"},{"key":"KeyU","note":"","pointerId":3,"posX":970,"posY":542,"time":80,"type":"Tap"},{"key":"KeyF","note":"","pointerId":3,"posX":1056,"posY":458,"time":80,"type":"Tap"},{"key":"Digit1","note":"","pointerId":3,"posX":560,"posY":666,"time":80,"type":"Tap"},{"key":"Digit2","note":"","pointerId":3,"posX":639,"posY":666,"time":80,"type":"Tap"},{"key":"KeyZ","note":"","pointerId":3,"posX":1163,"posY":433,"time":80,"type":"Tap"},{"key":"Escape","note":"","pointerId":3,"posX":1198,"posY":43,"time":80,"type":"Tap"},{"key":"Enter","macro":{"down":[{"args":[],"type":"key-input-mode"}],"loop":null,"up":null},"note":"","posX":1086,"posY":85,"type":"Macro"},{"key":"KeyK","note":"","pointerId":3,"posX":1188,"posY":528,"time":80,"type":"Tap"},{"key":"Space","note":"","pointerId":3,"posX":732,"posY":653,"time":80,"type":"Tap"},{"key":"Semicolon","note":"","pointerId":3,"posX":1200,"posY":234,"time":80,"type":"Tap"},{"key":"KeyN","note":"","pointerId":3,"posX":1054,"posY":232,"time":80,"type":"Tap"},{"key":"KeyO","note":"","pointerId":3,"posX":1133,"posY":232,"time":80,"type":"Tap"},{"key":"ShiftLeft","note":"","pointerId":2,"posX":991,"posY":233,"type":"DirectionlessSkill"},{"key":"KeyC","note":"","pointerId":2,"posX":1131,"posY":628,"type":"DirectionlessSkill"},{"key":"KeyQ","note":"","pointerId":2,"posX":838,"posY":656,"type":"DirectionlessSkill"},{"key":"KeyJ","note":"","pointerId":3,"posX":1132,"posY":628,"time":80,"type":"Tap"},{"intervalBetweenPos":100,"key":"KeyH","note":"","pointerId":3,"pos":[{"x":1055,"y":357},{"x":1059,"y":430}],"posX":1053,"posY":393,"type":"Swipe"},{"type":"Swipe","key":"BracketLeft","note":"","posX":987,"posY":354,"pointerId":3,"pos":[{"x":1055,"y":356},{"x":942,"y":356}],"intervalBetweenPos":100},{"type":"Swipe","key":"Backslash","note":"","posX":1055,"posY":306,"pointerId":3,"pos":[{"x":1052,"y":356},{"x":1052,"y":288}],"intervalBetweenPos":100},{"type":"Swipe","key":"BracketRight","note":"","posX":1094,"posY":353,"pointerId":3,"pos":[{"x":1050,"y":356},{"x":1117,"y":354}],"intervalBetweenPos":100}],"relativeSize":{"h":720,"w":1280},"title":"AVD-DNF-狂战-导入默认"}
]

View File

@ -4,5 +4,5 @@ pub mod client;
pub mod control_msg;
pub mod resource;
pub mod scrcpy_mask_cmd;
pub mod socket;
pub mod share;
pub mod socket;

View File

@ -181,6 +181,7 @@ fn set_adb_path(adb_path: String, app: tauri::AppHandle) -> Result<(), String> {
#[tokio::main]
async fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_process::init())

View File

@ -1,6 +1,6 @@
{
"productName": "scrcpy-mask",
"version": "0.5.0",
"version": "0.6.0",
"identifier": "com.akichase.mask",
"build": {
"beforeDevCommand": "pnpm dev",

View File

@ -58,4 +58,9 @@ onMounted(async () => {
width: 100%;
height: 100%;
}
.n-message {
user-select: none;
-webkit-user-select: none;
}
</style>

View File

@ -45,6 +45,7 @@ import { useGlobalStore } from "../store/global";
import { useI18n } from "vue-i18n";
import { closeExternalControl, connectExternalControl } from "../websocket";
import { LogicalSize, getCurrent } from "@tauri-apps/api/window";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
const { t } = useI18n();
const dialog = useDialog();
@ -73,15 +74,8 @@ onMounted(async () => {
case "ClipboardChanged":
if (payload.clipboard === lastClipboard) break;
lastClipboard = payload.clipboard;
navigator.clipboard
.writeText(payload.clipboard)
.then(() => {
message.info(t("pages.Device.clipboard.deviceSync.success"));
})
.catch((e) => {
console.error(e);
message.error(t("pages.Device.clipboard.deviceSync.failed"));
});
writeText(payload.clipboard);
console.log(payload);
break;
case "ClipboardSetAck":
break;

View File

@ -50,6 +50,7 @@ async function maximizeOrRestore() {
align-items: center;
border-radius: 0 10px 0 0;
user-select: none;
-webkit-user-select: none;
.n-button-group{
flex-shrink: 0;

View File

@ -10,7 +10,7 @@ import {
listenToEvent,
unlistenToEvent,
} from "../hotkey";
import { KeyMappingConfig, KeySteeringWheel } from "../keyMappingConfig";
import { KeySteeringWheel } from "../keyMappingConfig";
import ScreenStream from "./ScreenStream.vue";
import { getVersion } from "@tauri-apps/api/app";
import { fetch } from "@tauri-apps/plugin-http";
@ -19,6 +19,7 @@ import { getCurrent, PhysicalSize } from "@tauri-apps/api/window";
import { Store } from "@tauri-apps/plugin-store";
import { useI18n } from "vue-i18n";
import { checkAdbAvailable } from "../invoke";
import { loadLocalStorage } from "../storeLoader";
const { t } = useI18n();
const store = useGlobalStore();
@ -103,79 +104,7 @@ function genClientId() {
async function loadLocalStore() {
const localStore = new Store("store.bin");
// loading keyMappingConfigList from local store
let keyMappingConfigList = await localStore.get<KeyMappingConfig[]>(
"keyMappingConfigList"
);
if (keyMappingConfigList === null || keyMappingConfigList.length === 0) {
// add empty key mapping config
// unable to get mask element when app is not ready
// so we use the stored mask area to get relative size
const maskArea = await localStore.get<{
posX: number;
posY: number;
sizeW: number;
sizeH: number;
}>("maskArea");
let relativeSize = { w: 800, h: 600 };
if (maskArea !== null) {
relativeSize = {
w: maskArea.sizeW,
h: maskArea.sizeH,
};
}
keyMappingConfigList = [
{
relativeSize,
title: t("pages.Mask.blankConfig"),
list: [],
},
];
await localStore.set("keyMappingConfigList", keyMappingConfigList);
}
store.keyMappingConfigList = keyMappingConfigList;
// loading curKeyMappingIndex from local store
let curKeyMappingIndex = await localStore.get<number>("curKeyMappingIndex");
if (
curKeyMappingIndex === null ||
curKeyMappingIndex >= keyMappingConfigList.length
) {
curKeyMappingIndex = 0;
localStore.set("curKeyMappingIndex", curKeyMappingIndex);
}
store.curKeyMappingIndex = curKeyMappingIndex;
// loading maskButton from local store
let maskButton = await localStore.get<{
show: boolean;
transparency: number;
}>("maskButton");
store.maskButton = maskButton ?? {
show: true,
transparency: 0.5,
};
// loading checkUpdateAtStart from local store
const checkUpdateAtStart = await localStore.get<boolean>(
"checkUpdateAtStart"
);
store.checkUpdateAtStart = checkUpdateAtStart ?? true;
// loading rotation from local store
const rotation = await localStore.get<{
enable: boolean;
verticalLength: number;
horizontalLength: number;
}>("rotation");
if (rotation) store.rotation = rotation;
// loading screenStream from local store
const screenStream = await localStore.get<{
enable: boolean;
address: string;
}>("screenStream");
if (screenStream) store.screenStream = screenStream;
await loadLocalStorage(localStore, store, t);
}
async function cleanAfterimage() {

View File

@ -66,10 +66,12 @@ onBeforeUnmount(() => {
z-index: 0;
pointer-events: none;
user-select: none;
-webkit-user-select: none;
img {
pointer-events: none;
user-select: none;
-webkit-user-select: none;
}
}
</style>

View File

@ -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 KeySwipe from "./KeySwipe.vue";
import ScreenStream from "../ScreenStream.vue";
import {
@ -17,6 +18,7 @@ import {
KeyTap,
KeyMacro,
KeyMapping,
KeySwipe as KeyMappingKeySwipe,
KeySight as KeyMappingKeySight,
KeyFire as KeyMappingKeyFire,
} from "../../keyMappingConfig";
@ -43,6 +45,10 @@ const addButtonOptions: DropdownOption[] = [
label: () => t("pages.KeyBoard.addButton.SteeringWheel"),
key: "SteeringWheel",
},
{
label: () => t("pages.KeyBoard.addButton.Swipe"),
key: "Swipe",
},
{
label: () => t("pages.KeyBoard.addButton.Skill"),
key: "DirectionalSkill",
@ -72,6 +78,7 @@ const addButtonOptions: DropdownOption[] = [
function onAddButtonSelect(
type:
| "Tap"
| "Swipe"
| "SteeringWheel"
| "DirectionalSkill"
| "CancelSkill"
@ -92,6 +99,12 @@ function onAddButtonSelect(
if (type === "Tap") {
keyMapping.pointerId = 3;
(keyMapping as KeyTap).time = 80;
} else if (type === "Swipe") {
keyMapping.pointerId = 3;
(keyMapping as KeyMappingKeySwipe).pos = [
{ x: keyMapping.posX, y: keyMapping.posY },
];
(keyMapping as KeyMappingKeySwipe).intervalBetweenPos = 100;
} else if (type === "SteeringWheel") {
keyMapping.pointerId = 1;
(keyMapping as unknown as KeyMappingSteeringWheel).key = {
@ -139,6 +152,7 @@ function onAddButtonSelect(
} else return;
keyboardStore.edited = true;
store.editKeyMappingList.push(keyMapping as KeyMapping);
keyboardStore.activeButtonIndex = store.editKeyMappingList.length - 1;
}
function isKeyUnique(curKey: string): boolean {
@ -172,6 +186,7 @@ function setCurButtonKey(curKey: string) {
keyboardStore.showButtonSettingFlag ||
keyboardStore.activeButtonIndex >= store.editKeyMappingList.length ||
keyboardStore.showButtonSettingFlag ||
keyboardStore.editSwipePointsFlag ||
keyboardStore.showButtonAddFlag
)
return;
@ -362,6 +377,10 @@ onBeforeRouteLeave(() => {
v-else-if="store.editKeyMappingList[index].type === 'Observation'"
:index="index"
/>
<KeySwipe
v-else-if="store.editKeyMappingList[index].type === 'Swipe'"
:index="index"
/>
<KeySight
v-else-if="store.editKeyMappingList[index].type === 'Sight'"
:index="index"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Settings, CloseCircle } from "@vicons/ionicons5";
import { Settings, CloseCircle, ReturnUpBack } from "@vicons/ionicons5";
import {
NButton,
NIcon,
@ -19,6 +19,7 @@ import { loadDefaultKeyconfig } from "../../invoke";
import { KeyMappingConfig } from "../../keyMappingConfig";
import { useKeyboardStore } from "../../store/keyboard";
import { useI18n } from "vue-i18n";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
const { t } = useI18n();
const store = useGlobalStore();
@ -121,14 +122,18 @@ function dragHandler(downEvent: MouseEvent) {
localStore.set("keySettingPos", keySettingPos.value);
} else {
// click up
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
keyboardStore.showSettingFlag = !keyboardStore.showSettingFlag;
if (
keyboardStore.showSettingFlag &&
store.keyMappingConfigList.length === 1
) {
message.info(t("pages.KeyBoard.KeySetting.onlyOneConfig"));
if (keyboardStore.editSwipePointsFlag) {
keyboardStore.editSwipePointsFlag = false;
} else {
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
keyboardStore.showSettingFlag = !keyboardStore.showSettingFlag;
if (
keyboardStore.showSettingFlag &&
store.keyMappingConfigList.length === 1
) {
message.info(t("pages.KeyBoard.KeySetting.onlyOneConfig"));
}
}
}
};
@ -251,8 +256,7 @@ function renameKeyMappingConfig() {
function exportKeyMappingConfig() {
const config = store.keyMappingConfigList[store.curKeyMappingIndex];
const data = JSON.stringify(config, null, 2);
navigator.clipboard
.writeText(data)
writeText(data)
.then(() => {
message.success(t("pages.KeyBoard.KeySetting.exportSuccess"));
})
@ -374,7 +378,10 @@ function resetKeyMappingConfig() {
}"
>
<template #icon>
<NIcon><Settings /></NIcon>
<NIcon>
<ReturnUpBack v-if="keyboardStore.editSwipePointsFlag" />
<Settings v-else />
</NIcon>
</template>
</NButton>
<div

View File

@ -0,0 +1,355 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { useGlobalStore } from "../../store/global";
import {
NIcon,
NButton,
NFormItem,
NInput,
NH4,
NInputNumber,
useMessage,
} from "naive-ui";
import { Analytics, CloseCircle, Settings } from "@vicons/ionicons5";
import { useKeyboardStore } from "../../store/keyboard";
import { KeySwipe } from "../../keyMappingConfig";
import { useI18n } from "vue-i18n";
const props = defineProps<{
index: number;
}>();
const keyboardStore = useKeyboardStore();
const message = useMessage();
const store = useGlobalStore();
const { t } = useI18n();
const elementRef = ref<HTMLElement | null>(null);
const isActive = computed(
() => props.index === keyboardStore.activeButtonIndex
);
const keyMapping = computed(
() => store.editKeyMappingList[props.index] as KeySwipe
);
const trackPoints = computed(() => {
let s = "";
if (isActive.value) {
for (const point of keyMapping.value.pos) {
s += `${point.x},${point.y} `;
}
}
return s;
});
function dragHandler(downEvent: MouseEvent) {
keyboardStore.activeButtonIndex = props.index;
keyboardStore.showButtonSettingFlag = false;
const oldX = keyMapping.value.posX;
const oldY = keyMapping.value.posY;
const element = elementRef.value;
if (element) {
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const maxX = keyboardElement.clientWidth - 30;
const maxY = keyboardElement.clientHeight - 30;
const x = downEvent.clientX;
const y = downEvent.clientY;
const moveHandler = (moveEvent: MouseEvent) => {
let newX = oldX + moveEvent.clientX - x;
let newY = oldY + moveEvent.clientY - y;
newX = Math.max(30, Math.min(newX, maxX));
newY = Math.max(30, Math.min(newY, maxY));
keyMapping.value.posX = newX;
keyMapping.value.posY = newY;
};
window.addEventListener("mousemove", moveHandler);
const upHandler = () => {
window.removeEventListener("mousemove", moveHandler);
window.removeEventListener("mouseup", upHandler);
if (oldX !== keyMapping.value.posX || oldY !== keyMapping.value.posY) {
keyboardStore.edited = true;
}
};
window.addEventListener("mouseup", upHandler);
}
}
function delCurKeyMapping() {
keyboardStore.edited = true;
keyboardStore.activeButtonIndex = -1;
store.editKeyMappingList.splice(props.index, 1);
}
const settingPosX = ref(0);
const settingPosY = ref(0);
function showSetting() {
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const maxWidth = keyboardElement.clientWidth - 150;
const maxHeight = keyboardElement.clientHeight - 380;
settingPosX.value = Math.min(keyMapping.value.posX + 40, maxWidth);
settingPosY.value = Math.min(keyMapping.value.posY - 40, maxHeight);
keyboardStore.showButtonSettingFlag = true;
}
function editSwipePoints() {
message.info(t("pages.KeyBoard.Swipe.editTips"));
keyboardStore.showButtonSettingFlag = false;
keyboardStore.editSwipePointsFlag = true;
keyboardStore.edited = true;
}
function swipePointDragHandlue(downEvent: MouseEvent, index: number) {
if (downEvent.button === 2) {
// del point
keyMapping.value.pos.splice(index, 1);
return;
}
if (downEvent.button !== 0) return;
const oldX = keyMapping.value.pos[index].x;
const oldY = keyMapping.value.pos[index].y;
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const maxX = keyboardElement.clientWidth;
const maxY = keyboardElement.clientHeight;
const x = downEvent.clientX;
const y = downEvent.clientY;
const moveHandler = (moveEvent: MouseEvent) => {
let newX = oldX + moveEvent.clientX - x;
let newY = oldY + moveEvent.clientY - y;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
keyMapping.value.pos[index].x = newX;
keyMapping.value.pos[index].y = newY;
};
const upHandler = () => {
window.removeEventListener("mousemove", moveHandler);
window.removeEventListener("mouseup", upHandler);
};
window.addEventListener("mousemove", moveHandler);
window.addEventListener("mouseup", upHandler);
}
function swipeTrackClickHandler(event: MouseEvent) {
if (event.button !== 0) return;
console.log(event.target, event.currentTarget);
if (event.target !== event.currentTarget) return;
keyMapping.value.pos.push({ x: event.clientX - 70, y: event.clientY - 30 });
}
</script>
<template>
<div
:class="{ active: isActive }"
:style="{
left: `${keyMapping.posX - 30}px`,
top: `${keyMapping.posY - 30}px`,
}"
@mousedown="dragHandler"
class="key-swipe"
ref="elementRef"
>
<NIcon size="30"><Analytics /></NIcon>
<span>{{ keyMapping.key }}</span>
<NButton
class="key-close-btn"
text
@click="delCurKeyMapping"
:type="isActive ? 'primary' : 'info'"
>
<template #icon>
<NIcon size="15">
<CloseCircle />
</NIcon>
</template>
</NButton>
<NButton
class="key-setting-btn"
text
@click="showSetting"
:type="isActive ? 'primary' : 'info'"
>
<template #icon>
<NIcon size="15">
<Settings />
</NIcon>
</template>
</NButton>
</div>
<div
class="key-setting"
v-if="isActive && keyboardStore.showButtonSettingFlag"
:style="{
left: `${settingPosX}px`,
top: `${settingPosY}px`,
}"
>
<NH4 prefix="bar">{{ $t("pages.KeyBoard.Swipe.swipe") }}</NH4>
<NFormItem :label="$t('pages.KeyBoard.Swipe.pos')">
<NButton type="success" @click="editSwipePoints">{{
$t("pages.KeyBoard.Swipe.editPos")
}}</NButton>
</NFormItem>
<NFormItem :label="$t('pages.KeyBoard.Swipe.interval')">
<NInputNumber
v-model:value="keyMapping.intervalBetweenPos"
:placeholder="$t('pages.KeyBoard.Swipe.intervalPlaceholder')"
@update:value="keyboardStore.edited = true"
/>
</NFormItem>
<NFormItem :label="$t('pages.KeyBoard.setting.pointerID')">
<NInputNumber
v-model:value="keyMapping.pointerId"
:min="0"
:placeholder="$t('pages.KeyBoard.setting.pointerIDPlaceholder')"
@update:value="keyboardStore.edited = true"
/>
</NFormItem>
<NFormItem :label="$t('pages.KeyBoard.setting.note')">
<NInput
v-model:value="keyMapping.note"
:placeholder="$t('pages.KeyBoard.setting.notePlaceholder')"
@update:value="keyboardStore.edited = true"
/>
</NFormItem>
</div>
<template v-if="isActive">
<div
v-if="isActive"
class="track"
:class="{ 'edit-track': keyboardStore.editSwipePointsFlag }"
>
<svg @click="swipeTrackClickHandler">
<polyline :points="trackPoints" />
<circle
v-for="(pos, i) in keyMapping.pos"
:cx="pos.x"
:cy="pos.y"
r="5"
@mousedown="(e) => swipePointDragHandlue(e, i)"
/>
<text v-for="(pos, i) in keyMapping.pos" :x="pos.x + 5" :y="pos.y - 5">
{{ i }}
</text>
</svg>
</div>
</template>
</template>
<style scoped lang="scss">
.track {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: -1;
svg {
height: 100%;
width: 100%;
polyline {
fill: none;
stroke: var(--primary-hover-color);
stroke-width: 2;
}
circle {
cursor: pointer;
fill: var(--primary-color);
&:hover {
fill: var(--primary-pressed-color);
}
}
text {
cursor: default;
fill: var(--primary-pressed-color);
font-size: 15px;
text-anchor: end-alignment;
}
}
}
.edit-track {
z-index: 4;
background-color: rgba(0, 0, 0, 0.6);
}
.key-setting {
position: absolute;
display: flex;
flex-direction: column;
padding: 10px 20px;
box-sizing: border-box;
width: 150px;
height: 380px;
border-radius: 5px;
border: 2px solid var(--light-color);
background-color: var(--bg-color);
z-index: 3;
}
.key-swipe {
position: absolute;
height: 60px;
width: 60px;
box-sizing: border-box;
border-radius: 50%;
border: 2px solid var(--blue-color);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 10px;
font-weight: bold;
cursor: pointer;
.n-icon {
color: var(--blue-color);
}
&:not(.active):hover {
border: 2px solid var(--light-color);
color: var(--light-color);
.n-icon {
color: var(--light-color);
}
}
.key-close-btn {
position: absolute;
left: 65px;
bottom: 45px;
}
.key-setting-btn {
position: absolute;
left: 65px;
top: 45px;
}
}
.active {
border: 2px solid var(--primary-color);
color: var(--primary-color);
z-index: 2;
.n-icon {
color: var(--primary-color);
}
}
</style>

View File

@ -1,23 +1,16 @@
<script setup lang="ts">
import { Store } from "@tauri-apps/plugin-store";
import { Refresh, TrashBinOutline } from "@vicons/ionicons5";
import {
NH4,
NP,
NButton,
NFlex,
NList,
NListItem,
NModal,
NInput,
useDialog,
NCard,
NIcon,
NSelect,
NInputGroup,
useMessage,
NFlex,
NCheckbox,
NTooltip,
} from "naive-ui";
import { relaunch } from "@tauri-apps/plugin-process";
import { onMounted, ref } from "vue";
import i18n from "../../i18n";
import { useI18n } from "vue-i18n";
@ -28,12 +21,6 @@ const { t } = useI18n();
const localStore = new Store("store.bin");
const store = useGlobalStore();
const message = useMessage();
const dialog = useDialog();
const localStoreEntries = ref<[string, unknown][]>([]);
const showDataModal = ref(false);
const dataModalInputVal = ref("");
let curDataIndex = -1;
const languageOptions = [
{ label: "简体中文", value: "zh-CN" },
@ -45,52 +32,10 @@ const curLanguage = ref("en-US");
const adbPath = ref("");
onMounted(async () => {
refreshLocalData();
curLanguage.value = (await localStore.get<string>("language")) ?? "en-US";
adbPath.value = (await localStore.get<string>("adbPath")) ?? "";
});
async function refreshLocalData() {
localStoreEntries.value = await localStore.entries();
}
function showLocalStore(index: number) {
curDataIndex = index;
dataModalInputVal.value = JSON.stringify(
localStoreEntries.value[index][1],
null,
2
);
showDataModal.value = true;
}
function delLocalStore(key?: string) {
if (key) {
dialog.warning({
title: t("pages.Setting.Basic.delLocalStore.dialog.title"),
content: t("pages.Setting.Basic.delLocalStore.dialog.delKey", [key]),
positiveText: t("pages.Setting.Basic.delLocalStore.dialog.positiveText"),
negativeText: t("pages.Setting.Basic.delLocalStore.dialog.negativeText"),
onPositiveClick: () => {
localStore.delete(key);
localStoreEntries.value.splice(curDataIndex, 1);
showDataModal.value = false;
},
});
} else {
dialog.warning({
title: t("pages.Setting.Basic.delLocalStore.dialog.title"),
content: t("pages.Setting.Basic.delLocalStore.dialog.delAll"),
positiveText: t("pages.Setting.Basic.delLocalStore.dialog.positiveText"),
negativeText: t("pages.Setting.Basic.delLocalStore.dialog.negativeText"),
onPositiveClick: () => {
localStore.clear();
relaunch();
},
});
}
}
function changeLanguage(language: "zh-CN" | "en-US") {
if (language === curLanguage.value) return;
curLanguage.value = language;
@ -106,6 +51,10 @@ async function adjustAdbPath() {
adbPath.value = (await localStore.get<string>("adbPath")) ?? "";
store.hideLoading();
}
function changeClipboardSync() {
localStore.set("clipboardSync", store.clipboardSync);
}
</script>
<template>
@ -128,72 +77,32 @@ async function adjustAdbPath() {
$t("pages.Setting.Basic.adbPath.set")
}}</NButton>
</NInputGroup>
<NFlex justify="space-between">
<NH4 prefix="bar">{{ $t("pages.Setting.Basic.localStore") }}</NH4>
<NFlex>
<NButton
tertiary
circle
type="primary"
@click="delLocalStore()"
style="margin-right: 20px"
>
<template #icon>
<NIcon><TrashBinOutline /></NIcon>
</template>
</NButton>
<NButton
tertiary
circle
type="primary"
@click="refreshLocalData()"
style="margin-right: 20px"
>
<template #icon>
<NIcon><Refresh /></NIcon>
</template>
</NButton>
</NFlex>
<NH4 prefix="bar">剪切板同步</NH4>
<NFlex vertical>
<NCheckbox
v-model:checked="store.clipboardSync.syncFromDevice"
@update:checked="changeClipboardSync"
>
<NTooltip trigger="hover">
<template #trigger>从设备同步</template>
设备剪切板发生变化时自动同步更新电脑剪切板
</NTooltip>
</NCheckbox>
<NCheckbox
v-model:checked="store.clipboardSync.pasteFromPC"
@update:checked="changeClipboardSync"
>
<NTooltip trigger="hover">
<template #trigger>粘贴时同步</template>
在按键输入模式下按下 Ctrl + V 可将电脑剪切板内容同步粘贴到设备
</NTooltip>
</NCheckbox>
</NFlex>
<NP>{{ $t("pages.Setting.Basic.delLocalStore.warning") }}</NP>
<NList class="data-list" hoverable clickable>
<NListItem v-for="(entrie, index) in localStoreEntries">
<div @click="showLocalStore(index)">
{{ entrie[0] }}
</div>
</NListItem>
</NList>
</div>
<NModal v-model:show="showDataModal">
<NCard
style="width: 50%; height: 80%"
:title="localStoreEntries[curDataIndex][0]"
>
<NFlex vertical style="height: 100%">
<NInput
type="textarea"
style="flex-grow: 1"
:value="dataModalInputVal"
round
readonly
/>
<NButton
type="success"
round
@click="delLocalStore(localStoreEntries[curDataIndex][0])"
>{{ $t("pages.Setting.Basic.delCurData") }}</NButton
>
</NFlex>
</NCard>
</NModal>
</template>
<style scoped>
.setting-page {
padding: 10px 25px;
.data-list {
margin: 20px 0;
}
}
</style>

View File

@ -0,0 +1,149 @@
<script setup lang="ts">
import { Store } from "@tauri-apps/plugin-store";
import { Refresh, TrashBinOutline } from "@vicons/ionicons5";
import {
NH4,
NP,
NButton,
NFlex,
NList,
NListItem,
NModal,
NInput,
useDialog,
NCard,
NIcon,
} from "naive-ui";
import { relaunch } from "@tauri-apps/plugin-process";
import { onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const localStore = new Store("store.bin");
const dialog = useDialog();
const localStoreEntries = ref<[string, unknown][]>([]);
const showDataModal = ref(false);
const dataModalInputVal = ref("");
let curDataIndex = -1;
onMounted(async () => {
refreshLocalData();
});
async function refreshLocalData() {
localStoreEntries.value = await localStore.entries();
}
function showLocalStore(index: number) {
curDataIndex = index;
dataModalInputVal.value = JSON.stringify(
localStoreEntries.value[index][1],
null,
2
);
showDataModal.value = true;
}
function delLocalStore(key?: string) {
if (key) {
dialog.warning({
title: t("pages.Setting.Data.delLocalStore.dialog.title"),
content: t("pages.Setting.Data.delLocalStore.dialog.delKey", [key]),
positiveText: t("pages.Setting.Data.delLocalStore.dialog.positiveText"),
negativeText: t("pages.Setting.Data.delLocalStore.dialog.negativeText"),
onPositiveClick: () => {
localStore.delete(key);
localStoreEntries.value.splice(curDataIndex, 1);
showDataModal.value = false;
},
});
} else {
dialog.warning({
title: t("pages.Setting.Data.delLocalStore.dialog.title"),
content: t("pages.Setting.Data.delLocalStore.dialog.delAll"),
positiveText: t("pages.Setting.Data.delLocalStore.dialog.positiveText"),
negativeText: t("pages.Setting.Data.delLocalStore.dialog.negativeText"),
onPositiveClick: () => {
localStore.clear();
relaunch();
},
});
}
}
</script>
<template>
<div class="setting-page">
<NFlex justify="space-between">
<NH4 prefix="bar">{{ $t("pages.Setting.Data.localStore") }}</NH4>
<NFlex>
<NButton
tertiary
circle
type="primary"
@click="delLocalStore()"
style="margin-right: 20px"
>
<template #icon>
<NIcon><TrashBinOutline /></NIcon>
</template>
</NButton>
<NButton
tertiary
circle
type="primary"
@click="refreshLocalData()"
style="margin-right: 20px"
>
<template #icon>
<NIcon><Refresh /></NIcon>
</template>
</NButton>
</NFlex>
</NFlex>
<NP>{{ $t("pages.Setting.Data.delLocalStore.warning") }}</NP>
<NList class="data-list" hoverable clickable>
<NListItem
v-for="(entrie, index) in localStoreEntries"
@click="showLocalStore(index)"
>
<div>
{{ entrie[0] }}
</div>
</NListItem>
</NList>
</div>
<NModal v-model:show="showDataModal">
<NCard
style="width: 50%; height: 80%"
:title="localStoreEntries[curDataIndex][0]"
>
<NFlex vertical style="height: 100%">
<NInput
type="textarea"
style="flex-grow: 1"
:value="dataModalInputVal"
round
readonly
/>
<NButton
type="success"
round
@click="delLocalStore(localStoreEntries[curDataIndex][0])"
>{{ $t("pages.Setting.Data.delCurData") }}</NButton
>
</NFlex>
</NCard>
</NModal>
</template>
<style scoped>
.setting-page {
padding: 10px 25px;
.data-list {
margin: 20px 0;
}
}
</style>

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import Basic from "./Basic.vue";
import Mask from "./Mask.vue";
import Data from "./Data.vue";
import About from "./About.vue";
import { NTabs, NTabPane, NScrollbar, NSpin } from "naive-ui";
import { useGlobalStore } from "../../store/global";
@ -22,6 +23,11 @@ const store = useGlobalStore();
<Mask />
</NScrollbar>
</NTabPane>
<NTabPane :tab="$t('pages.Setting.tabs.data')" name="data">
<NScrollbar>
<Data />
</NScrollbar>
</NTabPane>
<NTabPane :tab="$t('pages.Setting.tabs.about')" name="about">
<NScrollbar>
<About />

View File

@ -163,13 +163,13 @@ interface InjectKeycode {
metastate: AndroidMetastate;
}
enum ScCopyKey {
export enum ScCopyKey {
SC_COPY_KEY_NONE,
SC_COPY_KEY_COPY,
SC_COPY_KEY_CUT,
}
enum ScScreenPowerMode {
export enum ScScreenPowerMode {
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SC_SCREEN_POWER_MODE_OFF = 0,
SC_SCREEN_POWER_MODE_NORMAL = 2,

View File

@ -17,12 +17,14 @@ import {
KeyObservation,
KeySight,
KeySteeringWheel,
KeySwipe,
KeyTap,
KeyTriggerWhenDoublePressedSkill,
KeyTriggerWhenPressedSkill,
} from "./keyMappingConfig";
import { useGlobalStore } from "./store/global";
import { LogicalPosition, getCurrent } from "@tauri-apps/api/window";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
import { useI18n } from "vue-i18n";
import { KeyToCodeMap } from "./frontcommand/KeyToCodeMap";
import {
@ -30,7 +32,7 @@ import {
AndroidMetastate,
} from "./frontcommand/android";
import { UIEventsCode } from "./frontcommand/UIEventsCode";
import { sendInjectKeycode } from "./frontcommand/controlMsg";
import { sendInjectKeycode, sendSetClipboard } from "./frontcommand/controlMsg";
function clientxToPosx(clientx: number) {
return clientx < 70
@ -947,6 +949,40 @@ function addSightShortcuts(
});
}
function addSwipeShortcuts(
key: string,
relativeSize: { w: number; h: number },
// pos relative to the mask
pos: { x: number; y: number }[],
pointerId: number,
intervalBetweenPos: number
) {
const newPosList = pos.map((posObj) => {
return {
x: Math.round((posObj.x / relativeSize.w) * store.screenSizeW),
y: Math.round((posObj.y / relativeSize.h) * store.screenSizeH),
};
});
addShortcut(
key,
async () => {
await swipe({
action: SwipeAction.Default,
pointerId,
screen: {
w: store.screenSizeW,
h: store.screenSizeH,
},
pos: newPosList,
intervalBetweenPos,
});
},
undefined,
undefined
);
}
function createMouseRangeBox(): HTMLElement {
const box = document.createElement("div");
box.id = "mouseRangeBox";
@ -1046,6 +1082,8 @@ export class KeyInputHandler {
let action: AndroidKeyEventAction;
let repeatCount = 0;
if (event.type === "keydown") {
if (event.getModifierState("Control") && event.code === "KeyV") return;
action = AndroidKeyEventAction.AKEY_EVENT_ACTION_DOWN;
if (event.repeat) {
let count = KeyInputHandler.repeatCounter.get(keycode);
@ -1058,6 +1096,18 @@ export class KeyInputHandler {
KeyInputHandler.repeatCounter.set(keycode, count);
}
} else if (event.type === "keyup") {
if (event.getModifierState("Control") && event.code === "KeyV") {
(async () => {
const text = await readText();
await sendSetClipboard({
sequence: Math.floor(Math.random() * 10000),
text,
paste: true,
});
})();
return;
}
action = AndroidKeyEventAction.AKEY_EVENT_ACTION_UP;
KeyInputHandler.repeatCounter.delete(keycode);
} else {
@ -1078,12 +1128,6 @@ export class KeyInputHandler {
? AndroidMetastate.AMETA_NUM_LOCK_ON
: 0);
// const controlMessage = new KeyCodeControlMessage(
// action,
// keyCode,
// repeatCount,
// metaState
// );
sendInjectKeycode({
action,
keycode,
@ -1378,6 +1422,16 @@ function applyKeyMappingConfigShortcuts(
item.pointerId
);
break;
case "Swipe":
asType<KeySwipe>(item);
addSwipeShortcuts(
item.key,
relativeSize,
item.pos,
item.pointerId,
item.intervalBetweenPos
);
break;
case "TriggerWhenPressedSkill":
asType<KeyTriggerWhenPressedSkill>(item);
addTriggerWhenPressedSkillShortcuts(

View File

@ -36,13 +36,7 @@
"wsConnect": "Control",
"adbDeviceError": "Unable to get available devices",
"adbConnectError": "Wireless connection failed",
"rotation": "Device rotation {0}°",
"clipboard": {
"deviceSync": {
"success": "Device clipboard synced",
"failed": "Device clipboard sync failed"
}
}
"rotation": "Device rotation {0}°"
},
"Mask": {
"keyconfigException": "The key mapping config is abnormal, please delete this config",
@ -62,14 +56,15 @@
"positiveText": "To control"
},
"sightMode": "Mouse is locked, press {0} to unlock",
"checkAdb": "adb is not available and the software cannot run normally. Please ensure that adb is installed on the system and added to the Path environment variable correctly: {0}",
"checkAdb": "adb is not available and the software cannot run normally. Fill in the path of the adb file in the setup page, or add the folder where it is located to the Path environment variable.: {0}",
"keyInputMode": "Has entered the keystroke input mode, close this message to exit."
},
"Setting": {
"tabs": {
"basic": "Basic settings",
"mask": "Mask setting",
"about": "About"
"about": "About",
"data": "Data management"
},
"Mask": {
"areaFormMissing": {
@ -105,6 +100,15 @@
}
},
"Basic": {
"language": "Language",
"adbPath": {
"placeholder": "adb path",
"set": "Save",
"setSuccess": "adb path set successfully",
"title": "adb path"
}
},
"Data": {
"delLocalStore": {
"dialog": {
"title": "Warning",
@ -115,15 +119,8 @@
},
"warning": "Deleting data may lead to unpredictable consequences, so please operate with caution. \nIf an exception occurs, please try clearing the data and restarting the software."
},
"language": "Language",
"localStore": "Local data",
"delCurData": "Delete current data",
"adbPath": {
"placeholder": "adb path",
"set": "Save",
"setSuccess": "adb path set successfully",
"title": "adb path"
}
"delCurData": "Delete current data"
},
"About": {
"introduction": "A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.",
@ -154,7 +151,8 @@
"Sight": "Front sight",
"Fire": "Fire",
"existFire": "Fire button already exists",
"existSight": "Front sight button already exists"
"existSight": "Front sight button already exists",
"Swipe": "Swipe"
},
"buttonKeyRepeat": "Key repeat: {0}",
"KeyCommon": {
@ -256,6 +254,14 @@
"scaleX": "Horizontal sensitivity",
"scalePlaceholder": "Please enter sensitivity",
"scaleY": "Vertical sensitivity"
},
"Swipe": {
"swipe": "Swipe",
"interval": "Swipe time interval",
"intervalPlaceholder": "Enter the time interval between points",
"pos": "Points",
"editPos": "Edit",
"editTips": "Left-click on a blank area to add a new coordinate point. \nLeft-click and drag to move a specific point, and right-click to delete the point."
}
}
},

View File

@ -36,13 +36,7 @@
"wsConnect": "控制",
"adbDeviceError": "无法获取可用设备",
"adbConnectError": "无线连接失败",
"rotation": "设备旋转 {0}°",
"clipboard": {
"deviceSync": {
"success": "已从设备同步剪切板",
"failed": "从设备同步剪切板失败"
}
}
"rotation": "设备旋转 {0}°"
},
"Mask": {
"keyconfigException": "按键方案异常,请删除此方案",
@ -62,14 +56,15 @@
"positiveText": "去控制"
},
"sightMode": "鼠标已锁定, 按 {0} 键解锁",
"checkAdb": "adb不可用软件无法正常运行请确保系统已安装adb,并正确添加到了Path环境变量中: {0}",
"checkAdb": "adb不可用软件无法正常运行请确保系统已安装adb。请将adb文件路径填入软件设置界面或者将其所在文件夹添加到Path环境变量中: {0}",
"keyInputMode": "已进入按键输入模式,关闭本消息可退出"
},
"Setting": {
"tabs": {
"basic": "基本设置",
"mask": "蒙版设置",
"about": "关于"
"about": "关于",
"data": "数据管理"
},
"Mask": {
"incorrectArea": "请正确输入蒙版的坐标和尺寸",
@ -105,6 +100,15 @@
}
},
"Basic": {
"language": "语言",
"adbPath": {
"setSuccess": "adb 路径设置成功",
"title": "adb 路径",
"placeholder": "adb 路径",
"set": "设置"
}
},
"Data": {
"delLocalStore": {
"dialog": {
"title": "警告",
@ -115,15 +119,8 @@
},
"warning": "删除数据可能导致无法预料的后果,请慎重操作。若出现异常请尝试清空数据并重启软件。"
},
"language": "语言",
"localStore": "本地数据",
"delCurData": "删除当前数据",
"adbPath": {
"setSuccess": "adb 路径设置成功",
"title": "adb 路径",
"placeholder": "adb 路径",
"set": "设置"
}
"delCurData": "删除当前数据"
},
"About": {
"about": "关于",
@ -147,7 +144,8 @@
"Sight": "准星",
"Fire": "开火",
"existSight": "已存在准星按钮",
"existFire": "已存在开火按钮"
"existFire": "已存在开火按钮",
"Swipe": "滑动"
},
"buttonKeyRepeat": "按键重复: {0}",
"noSaveDialog": {
@ -256,6 +254,14 @@
"scaleX": "水平灵敏度",
"scaleY": "垂直灵敏度",
"scalePlaceholder": "请输入灵敏度"
},
"Swipe": {
"swipe": "滑动",
"interval": "滑动时间间隔",
"intervalPlaceholder": "输入坐标点之间的时间间隔",
"pos": "坐标点",
"editPos": "编辑",
"editTips": "左键点击空白区域添加新坐标点。左键拖拽移动特定坐标点,右键删除特定坐标点"
}
}
},

View File

@ -65,6 +65,14 @@ export interface KeyTap extends KeyBase {
time: number;
}
export interface KeySwipe extends KeyBase {
type: "Swipe";
pointerId: number;
key: string;
pos: { x: number; y: number }[];
intervalBetweenPos: number;
}
export type KeyMacroList = Array<{
type: "touch" | "sleep" | "swipe" | "key-input-mode";
args: any[];
@ -106,6 +114,7 @@ export type KeyMapping =
| KeyMacro
| KeyCancelSkill
| KeyTap
| KeySwipe
| KeySight
| KeyFire;

View File

@ -100,6 +100,11 @@ export const useGlobalStore = defineStore("global", () => {
horizontalLength: 800,
});
const clipboardSync = ref({
syncFromDevice: true,
pasteFromPC: true,
});
return {
// persistent storage
keyMappingConfigList,
@ -109,6 +114,7 @@ export const useGlobalStore = defineStore("global", () => {
externalControlled,
screenStream,
rotation,
clipboardSync,
// in-memory storage
screenStreamClientId,
maskSizeW,

View File

@ -6,6 +6,7 @@ export const useKeyboardStore = defineStore("keyboard", () => {
const showSettingFlag = ref(false);
const showButtonSettingFlag = ref(false);
const showButtonAddFlag = ref(false);
const editSwipePointsFlag = ref(false);
const activeButtonIndex = ref(-1);
const activeSteeringWheelButtonKeyIndex = ref(-1);
const edited = ref(false);
@ -15,6 +16,7 @@ export const useKeyboardStore = defineStore("keyboard", () => {
showSettingFlag,
showButtonSettingFlag,
showButtonAddFlag,
editSwipePointsFlag,
activeButtonIndex,
activeSteeringWheelButtonKeyIndex,
edited,

122
src/storeLoader.ts Normal file
View File

@ -0,0 +1,122 @@
import { Store } from "@tauri-apps/plugin-store";
import { KeyMappingConfig } from "./keyMappingConfig";
import { useGlobalStore } from "./store/global";
import { useI18n } from "vue-i18n";
let localStore: Store;
let store: ReturnType<typeof useGlobalStore>;
let t: ReturnType<typeof useI18n>["t"];
async function loadKeyMappingConfigList() {
// loading keyMappingConfigList from local store
let keyMappingConfigList = await localStore.get<KeyMappingConfig[]>(
"keyMappingConfigList"
);
if (keyMappingConfigList === null || keyMappingConfigList.length === 0) {
// add empty key mapping config
// unable to get mask element when app is not ready
// so we use the stored mask area to get relative size
const maskArea = await localStore.get<{
posX: number;
posY: number;
sizeW: number;
sizeH: number;
}>("maskArea");
let relativeSize = { w: 800, h: 600 };
if (maskArea !== null) {
relativeSize = {
w: maskArea.sizeW,
h: maskArea.sizeH,
};
}
keyMappingConfigList = [
{
relativeSize,
title: t("pages.Mask.blankConfig"),
list: [],
},
];
await localStore.set("keyMappingConfigList", keyMappingConfigList);
}
store.keyMappingConfigList = keyMappingConfigList;
}
async function loadCurKeyMappingIndex() {
// loading curKeyMappingIndex from local store
let curKeyMappingIndex = await localStore.get<number>("curKeyMappingIndex");
if (
curKeyMappingIndex === null ||
curKeyMappingIndex >= store.keyMappingConfigList.length
) {
curKeyMappingIndex = 0;
localStore.set("curKeyMappingIndex", curKeyMappingIndex);
}
store.curKeyMappingIndex = curKeyMappingIndex;
}
async function loadMaskButton() {
// loading maskButton from local store
let maskButton = await localStore.get<{
show: boolean;
transparency: number;
}>("maskButton");
store.maskButton = maskButton ?? {
show: true,
transparency: 0.5,
};
}
async function loadCheckUpdateAtStart() {
// loading checkUpdateAtStart from local store
const checkUpdateAtStart = await localStore.get<boolean>(
"checkUpdateAtStart"
);
store.checkUpdateAtStart = checkUpdateAtStart ?? true;
}
async function loadRotation() {
// loading rotation from local store
const rotation = await localStore.get<{
enable: boolean;
verticalLength: number;
horizontalLength: number;
}>("rotation");
if (rotation) store.rotation = rotation;
}
async function loadScreenStream() {
// loading screenStream from local store
const screenStream = await localStore.get<{
enable: boolean;
address: string;
}>("screenStream");
if (screenStream) store.screenStream = screenStream;
}
async function loadClipboardSync() {
// loading clipboardSync from local store
const clipboardSync = await localStore.get<{
syncFromDevice: boolean;
pasteFromPC: boolean;
}>("clipboardSync");
if (clipboardSync) store.clipboardSync = clipboardSync;
console.log(store.clipboardSync);
}
export async function loadLocalStorage(
theLocalStore: Store,
theStore: ReturnType<typeof useGlobalStore>,
theT: ReturnType<typeof useI18n>["t"]
) {
localStore = theLocalStore;
store = theStore;
t = theT;
await loadKeyMappingConfigList();
await loadCurKeyMappingIndex();
await loadMaskButton();
await loadCheckUpdateAtStart();
await loadRotation();
await loadScreenStream();
await loadClipboardSync();
}