mirror of
https://github.com/AkiChase/scrcpy-mask
synced 2024-11-12 20:11:21 +08:00
commit
ec9e0fba07
16
README.md
16
README.md
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "scrcpy-mask",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"identifier": "com.akichase.mask",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
14
src/App.vue
14
src/App.vue
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
134
src/components/setting/About.vue
Normal file
134
src/components/setting/About.vue
Normal 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>
|
@ -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,20 +163,23 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NH4 prefix="bar">蒙版调整</NH4>
|
||||
<NButton
|
||||
tertiary
|
||||
circle
|
||||
type="primary"
|
||||
@click="handleAdjustClick"
|
||||
style="margin-right: 20px"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon><SettingsOutline /></NIcon>
|
||||
</template>
|
||||
</NButton>
|
||||
</NFlex>
|
||||
<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"
|
||||
@ -181,6 +189,20 @@ onUnmounted(() => {
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NH4 prefix="bar">蒙版调整</NH4>
|
||||
<NButton
|
||||
tertiary
|
||||
circle
|
||||
type="primary"
|
||||
@click="handleAdjustClick"
|
||||
style="margin-right: 20px"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon><SettingsOutline /></NIcon>
|
||||
</template>
|
||||
</NButton>
|
||||
</NFlex>
|
||||
<NGrid :cols="2" :x-gap="24">
|
||||
<NFormItemGi label="X" path="posX">
|
||||
<NInputNumber
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user