Merge pull request #9 from AkiChase/dev

Scrcpy Mask v0.1.4
This commit is contained in:
如初 2024-05-05 14:44:05 +08:00 committed by GitHub
commit ec9e0fba07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 243 additions and 27 deletions

View File

@ -1,6 +1,6 @@
# Scrcpy-mask
A Scrcpy (control) client in Rust & Tarui aimed at providing mouse and key mapping.
A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.
Due to the delay and blurred image quality of the mirror screen. This project found another way, directly abandoned the mirror screen, and instead used a transparent mask to display the screen content behind the window, which fundamentally put an end to the delay in casting the screen.
@ -9,10 +9,20 @@ Due to the delay and blurred image quality of the mirror screen. This project fo
- [x] Wired and wireless connections to Android devices
- [x] Start scrcpy-server and connect to it
- [x] Implement scrcpy client control protocol
- [x] Mouse and keyboard mapping
- [x] Mouse and keyboard key mapping
- [x] Visually setting the mapping
- [x] Key mapping config import and export
- [x] Update check
- [ ] Switch between key mapping and raw input
- [ ] Better macro support
- [ ] Provide external interface through websocket
## contribution.
## Screenshot
![Device](https://pic.superbed.cc/item/6637190cf989f2fb975b6162.png)
![Key mapping](https://pic.superbed.cc/item/66371911f989f2fb975b62a3.png)
![Mask](https://pic.superbed.cc/item/66371a03f989f2fb975c07f3.png)
## Contribution.
If you are interested in this project, you are welcome to submit pull request or issue. But my time and energy is limited, so I may not be able to deal with it all.

View File

@ -1,7 +1,7 @@
{
"name": "scrcpy-mask",
"private": true,
"version": "0.1.3",
"version": "0.1.4",
"type": "module",
"scripts": {
"dev": "vite",
@ -13,7 +13,9 @@
"dependencies": {
"@tauri-apps/api": ">=2.0.0-beta.8",
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.0",
"@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",
"@tauri-apps/plugin-store": "2.0.0-beta.2",
"pinia": "^2.1.7",
"vue": "^3.3.4",

View File

@ -1,6 +1,6 @@
[package]
name = "scrcpy-mask"
version = "0.1.3"
version = "0.1.4"
description = "A Tauri App"
authors = ["AkiChase"]
edition = "2021"
@ -11,7 +11,7 @@ edition = "2021"
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
tauri = { version = "2.0.0-beta.15", features = ["macos-private-api"] }
tauri = { version = "2.0.0-beta.15", features = ["macos-private-api", "devtools"] }
tauri-plugin-store = "2.0.0-beta.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@ -19,3 +19,5 @@ anyhow = "1.0"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "net", "macros", "io-util", "time", "sync"] }
tauri-plugin-clipboard-manager = "2.1.0-beta.1"
tauri-plugin-process = "2.0.0-beta.3"
tauri-plugin-shell = "2.0.0-beta.4"
tauri-plugin-http = "2.0.0-beta.7"

View File

@ -27,6 +27,18 @@
"process:default",
"process:allow-restart",
"webview:default",
"webview:allow-internal-toggle-devtools"
"webview:allow-internal-toggle-devtools",
"shell:default",
"shell:allow-open",
"http:default",
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.github.com/repos/AkiChase/scrcpy-mask/*" }
]
},
"http:allow-fetch",
"app:default",
"app:allow-version"
]
}

View File

@ -142,6 +142,8 @@ fn load_default_keyconfig(app: tauri::AppHandle) -> Result<String, String> {
#[tokio::main]
async fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_store::Builder::new().build())

View File

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

View File

@ -71,6 +71,20 @@ onMounted(async () => {
localStore.set("curKeyMappingIndex", curKeyMappingIndex);
}
store.curKeyMappingIndex = curKeyMappingIndex;
// loading maskButton from local store
let maskButton = await localStore.get<{
show: boolean;
transparency: number;
}>("maskButton");
if (maskButton === null) {
maskButton = {
show: true,
transparency: 0.5,
};
await localStore.set("maskButton", maskButton);
}
store.maskButton = maskButton;
});
</script>

View File

@ -65,7 +65,11 @@ function toStartServer() {
</div>
<template v-if="store.keyMappingConfigList.length">
<div @contextmenu.prevent class="mask" id="maskElement"></div>
<div class="button-layer">
<div
v-if="store.maskButton.show"
:style="'--transparency: ' + store.maskButton.transparency"
class="button-layer"
>
<!-- <div style="position: absolute;height: 100%;width: 1px;background-color: red;left: 50%;"></div>
<div style="position: absolute;width: 100%;height: 1px;background-color: red;top: 56.6%;"></div> -->
<template
@ -133,18 +137,20 @@ function toStartServer() {
z-index: 1;
& > .mask-button {
opacity: var(--transparency);
position: absolute;
background-color: rgba(0, 0, 0, 0.2);
color: rgba(255, 255, 255, 0.6);
background-color: black;
color: white;
border-radius: 5px;
padding: 5px;
font-size: 12px;
}
& > .mask-steering-wheel {
opacity: var(--transparency);
position: absolute;
background-color: rgba(0, 0, 0, 0.2);
color: rgba(255, 255, 255, 0.6);
background-color: black;
color: white;
border-radius: 50%;
width: 150px;
height: 150px;

View File

@ -0,0 +1,134 @@
<script setup lang="ts">
import {
NFlex,
NH4,
NButton,
NIcon,
NP,
useMessage,
useDialog,
} from "naive-ui";
import { LogoGithub, Planet } from "@vicons/ionicons5";
import { open } from "@tauri-apps/plugin-shell";
import { fetch } from "@tauri-apps/plugin-http";
import { getVersion } from "@tauri-apps/api/app";
import { h, onMounted, ref } from "vue";
import { useGlobalStore } from "../../store/global";
const message = useMessage();
const dialog = useDialog();
const store = useGlobalStore();
const appVersion = ref("");
onMounted(async () => {
appVersion.value = await getVersion();
});
function opendWebsite(url: string) {
open(url);
}
function renderUpdateInfo(content: string) {
const pList = content.split("\r\n").map((line: string) => h("p", line));
return h("div", { style: "margin: 20px 0" }, pList);
}
async function checkUpdate() {
store.showLoading();
try {
const res = await fetch(
"https://api.github.com/repos/AkiChase/scrcpy-mask/releases/latest",
{
connectTimeout: 5000,
}
);
store.hideLoading();
if (res.status !== 200) {
message.error("检查更新失败");
} else {
const data = await res.json();
const latestVersion = (data.tag_name as string).slice(1);
if (latestVersion <= appVersion.value) {
message.success(`最新版本: ${latestVersion},当前已是最新版本`);
return;
}
const body = data.body as string;
dialog.info({
title: `最新版本:${data.tag_name}`,
content: () => renderUpdateInfo(body),
positiveText: "前往发布页",
negativeText: "取消",
onPositiveClick: () => {
opendWebsite(data.html_url);
},
});
}
} catch (e) {
store.hideLoading();
console.error(e);
message.error("检查更新失败");
}
}
</script>
<template>
<div class="setting-page">
<NH4 prefix="bar">关于</NH4>
<NP
>A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping
to control Android device.</NP
>
<NFlex class="website">
<NButton
text
@click="opendWebsite('https://github.com/AkiChase/scrcpy-mask')"
>
<template #icon>
<NIcon><LogoGithub /> </NIcon>
</template>
Github repo
</NButton>
<NButton
text
@click="opendWebsite('https://space.bilibili.com/440760180')"
>
<template #icon>
<NIcon
><svg
t="1714874601502"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M977.2 208.2c33.4 36.2 48.8 79.4 46.6 131.4v404.8c-0.8 52.8-18.4 96.2-53 130.2-34.4 34-78.2 51.8-131 53.4H184.04c-52.9-1.6-96.42-19.6-130.56-54.4C19.364 838.8 1.534 793 0 736.4V339.6c1.534-52 19.364-95.2 53.48-131.4C87.62 175.5 131.14 157.54 184.04 156h58.76L192.1 104.38c-11.5-11.46-17.26-26-17.26-43.58 0-17.6 5.76-32.12 17.26-43.594C203.6 5.736 218.2 0 235.8 0s32.2 5.736 43.8 17.206L426.2 156h176l149-138.794C763.4 5.736 778.4 0 796 0c17.6 0 32.2 5.736 43.8 17.206 11.4 11.474 17.2 25.994 17.2 43.594 0 17.58-5.8 32.12-17.2 43.58L789.2 156h58.6c52.8 1.54 96 19.5 129.4 52.2z m-77.6 139.4c-0.8-19.2-7.4-34.8-21.4-47-10.4-12.2-28-18.8-45.4-19.6H192.1c-19.18 0.8-34.9 7.4-47.16 19.6-12.28 12.2-18.8 27.8-19.56 47v388.8c0 18.4 6.52 34 19.56 47s28.76 19.6 47.16 19.6H832.8c18.4 0 34-6.6 46.6-19.6 12.6-13 19.4-28.6 20.2-47V347.6z m-528.6 85.4c12.6 12.6 19.4 28.2 20.2 46.4V546c-0.8 18.4-7.4 33.8-19.6 46.4-12.4 12.6-28 19-47.2 19-19.2 0-35-6.4-47.2-19-12.2-12.6-18.8-28-19.6-46.4v-66.6c0.8-18.2 7.6-33.8 20.2-46.4 12.6-12.6 26.4-19.2 46.6-20 18.4 0.8 34 7.4 46.6 20z m383 0c12.6 12.6 19.4 28.2 20.2 46.4V546c-0.8 18.4-7.4 33.8-19.6 46.4-12.2 12.6-28 19-47.2 19-19.2 0-34.8-6.4-47.2-19-14-12.6-18.8-28-19.4-46.4v-66.6c0.6-18.2 7.4-33.8 20-46.4 12.6-12.6 28.2-19.2 46.6-20 18.4 0.8 34 7.4 46.6 20z"
p-id="4281"
></path>
</svg>
</NIcon>
</template>
BiliBili
</NButton>
<NButton text @click="opendWebsite('https://www.akichase.top/')">
<template #icon>
<NIcon><Planet /> </NIcon>
</template>
AkiChase's Blog
</NButton>
</NFlex>
<NH4 prefix="bar">更新</NH4>
<NP>当前版本{{ appVersion }}</NP>
<NButton @click="checkUpdate">检查更新</NButton>
</div>
</template>
<style scoped>
.setting-page {
padding: 10px 25px;
.website .n-button {
margin-right: 30px;
}
}
</style>

View File

@ -13,6 +13,9 @@ import {
NIcon,
FormInst,
useMessage,
NSlider,
NFormItem,
NCheckbox,
} from "naive-ui";
import {
LogicalPosition,
@ -24,12 +27,14 @@ import {
import { Store } from "@tauri-apps/plugin-store";
import { SettingsOutline } from "@vicons/ionicons5";
import { UnlistenFn } from "@tauri-apps/api/event";
import { useGlobalStore } from "../../store/global";
let unlistenResize: UnlistenFn = () => {};
let unlistenMove: UnlistenFn = () => {};
let factor = 1;
const localStore = new Store("store.bin");
const store = useGlobalStore();
const message = useMessage();
const formRef = ref<FormInst | null>(null);
@ -158,6 +163,32 @@ onUnmounted(() => {
<template>
<div class="setting-page">
<NH4 prefix="bar">按键提示</NH4>
<NFormItem label="是否显示" label-placement="left">
<NCheckbox
v-model:checked="store.maskButton.show"
@update:checked="localStore.set('maskButton', store.maskButton)"
/>
</NFormItem>
<NFormItem label="不透明度" label-placement="left">
<NSlider
v-model:value="store.maskButton.transparency"
@update:checked="localStore.set('maskButton', store.maskButton)"
:min="0"
:max="1"
:step="0.01"
style="max-width: 300px"
></NSlider>
</NFormItem>
<NForm
ref="formRef"
:model="areaModel"
:rules="areaFormRules"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
>
<NFlex justify="space-between" align="center">
<NH4 prefix="bar">蒙版调整</NH4>
<NButton
@ -172,15 +203,6 @@ onUnmounted(() => {
</template>
</NButton>
</NFlex>
<NForm
ref="formRef"
:model="areaModel"
:rules="areaFormRules"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
>
<NGrid :cols="2" :x-gap="24">
<NFormItemGi label="X" path="posX">
<NInputNumber

View File

@ -2,6 +2,7 @@
import Basic from "./Basic.vue";
import Script from "./Script.vue";
import Mask from "./Mask.vue";
import About from "./About.vue";
import { NTabs, NTabPane, NScrollbar } from "naive-ui";
</script>
@ -23,6 +24,11 @@ import { NTabs, NTabPane, NScrollbar } from "naive-ui";
<Script />
</NScrollbar>
</NTabPane>
<NTabPane tab="关于" name="about">
<NScrollbar>
<About />
</NScrollbar>
</NTabPane>
</NTabs>
</div>
</template>

View File

@ -34,6 +34,11 @@ export const useGlobalStore = defineStore("global", () => {
const curKeyMappingIndex = ref(0);
const editKeyMappingList: Ref<KeyMapping[]> = ref([]);
const maskButton = ref({
transparency: 0.5,
show: true,
});
function applyEditKeyMappingList(): boolean {
const set = new Set<string>();
for (const keyMapping of editKeyMappingList.value) {
@ -82,6 +87,7 @@ export const useGlobalStore = defineStore("global", () => {
keyMappingConfigList,
curKeyMappingIndex,
editKeyMappingList,
maskButton,
applyEditKeyMappingList,
resetEditKeyMappingList,
setKeyMappingIndex,