Merge pull request #32 from AkiChase/dev

Scrcpy Mask v0.4.1
This commit is contained in:
如初 2024-05-22 09:15:57 +08:00 committed by GitHub
commit a4da2d4ebe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 119 additions and 21 deletions

View File

@ -66,9 +66,8 @@
2. 对于模拟器,不仅不需要投屏,而且模拟器通常默认启用 ADB 有线调试。所以几乎不用操作就能获得最好的体验。 2. 对于模拟器,不仅不需要投屏,而且模拟器通常默认启用 ADB 有线调试。所以几乎不用操作就能获得最好的体验。
3. 启动软件并导航到设备页面。 3. 启动软件并导航到设备页面。
1. 在可用的设备中查找你的设备(如果未找到,请自行搜索如何为安装设备启用 ADB 调试)。 1. 在可用的设备中查找你的设备(如果未找到,请自行搜索如何为安装设备启用 ADB 调试)。
2. 右击你的设备并选择“获取屏幕大小”。根据获得的屏幕尺寸为参考,正确输入设备的宽度和高度。注意:如果宽度或高度不正确 (例如,在纵向和横向模式下这两个参数是颠倒的),所有触摸操作将被忽略,但是不会有任何错误消息。 2. 右击设备并选择“控制此设备”。
3. 再次右击设备并选择“控制此设备”。 4. 导航到设置页面->蒙版设置,将蒙版的宽度和高度设置为设备屏幕尺寸相同的比例,确保蒙版大小合适。
4. 导航到设置页面->蒙版设置,将蒙版的宽度和高度设置为设备大小的一定倍数,以确保蒙版大小合适。
5. 导航到蒙版页面,你可以在其中看到一个完全透明的蒙版区域。接下来,调整并移动模拟器窗口或投屏窗口,让其内容区域与透明蒙版区域完全对齐。 5. 导航到蒙版页面,你可以在其中看到一个完全透明的蒙版区域。接下来,调整并移动模拟器窗口或投屏窗口,让其内容区域与透明蒙版区域完全对齐。
6. 导航到键映射页面,切换或编辑键映射配置。 6. 导航到键映射页面,切换或编辑键映射配置。
7. 返回到蒙版界面,开始使用吧! 7. 返回到蒙版界面,开始使用吧!

View File

@ -68,9 +68,8 @@ Furthermore, to better support interaction between Scrcpy Mask and Android devic
2. For emulator, you don't need screen mirror, and emulator generally default to enabling ADB wired debugging. So this is the best way for game, I think. 2. For emulator, you don't need screen mirror, and emulator generally default to enabling ADB wired debugging. So this is the best way for game, I think.
3. Launch the software and navigate to the Device page. 3. Launch the software and navigate to the Device page.
1. Find your device among the available devices (if not found, please search for how to enable ADB debugging for your device). 1. Find your device among the available devices (if not found, please search for how to enable ADB debugging for your device).
2. Right-click on your device and choose "Get Screen Size". Use the obtained screen size as a reference and enter the device's width and height correctly. Note: If the width or height is incorrect (for example, they are reversed in portrait and landscape modes), all touch operations will be ignored, but no error message will appear.
3. Right-click on your device again and choose "Control this device". 3. Right-click on your device again and choose "Control this device".
4. Navigate to the Settings page -> Mask Settings, and set the width and height of the mask to a certain multiple of the device's size to ensure the mask size is appropriate. 4. Navigate to the Settings page -> Mask Settings, set the width and height of the mask to the same ratio of the device screen size and ensure that the mask size is appropriate.
5. Navigate to the Mask page where you can see a transparent mask. Next, adjust and move your emulator window or screen mirroring window to align the displayed content area with the transparent mask area. 5. Navigate to the Mask page where you can see a transparent mask. Next, adjust and move your emulator window or screen mirroring window to align the displayed content area with the transparent mask area.
6. Navigate to the Key mapping page and switch or edit the key mapping configs. 6. Navigate to the Key mapping page and switch or edit the key mapping configs.
7. Return to the Mask page and start enjoying. 7. Return to the Mask page and start enjoying.

View File

@ -1,7 +1,7 @@
{ {
"name": "scrcpy-mask", "name": "scrcpy-mask",
"private": true, "private": true,
"version": "0.4.0", "version": "0.4.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "scrcpy-mask" name = "scrcpy-mask"
version = "0.4.0" version = "0.4.1"
description = "A Tauri App" description = "A Tauri App"
authors = ["AkiChase"] authors = ["AkiChase"]
edition = "2021" edition = "2021"

View File

@ -8,6 +8,8 @@ use std::os::windows::process::CommandExt;
use anyhow::{Context, Ok, Result}; use anyhow::{Context, Ok, Result};
use crate::share;
#[derive(Clone, Debug, serde::Serialize)] #[derive(Clone, Debug, serde::Serialize)]
pub struct Device { pub struct Device {
pub id: String, pub id: String,
@ -85,13 +87,14 @@ pub struct Adb;
/// But some output of command won't be output, like adb service startup information. /// But some output of command won't be output, like adb service startup information.
impl Adb { impl Adb {
pub fn cmd_base() -> Command { pub fn cmd_base() -> Command {
let adb_path = share::ADB_PATH.lock().unwrap().clone();
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let mut cmd = Command::new("adb"); let mut cmd = Command::new(adb_path);
cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
return cmd; return cmd;
} }
Command::new("adb") Command::new(adb_path)
} }
/// execute "adb devices" and return devices list /// execute "adb devices" and return devices list

View File

@ -157,6 +157,24 @@ fn check_adb_available() -> Result<(), String> {
} }
} }
#[tauri::command]
fn set_adb_path(adb_path: String, app: tauri::AppHandle) -> Result<(), String> {
let app_h = app.app_handle().clone();
let stores = app_h.state::<tauri_plugin_store::StoreCollection<tauri::Wry>>();
let path = std::path::PathBuf::from("store.bin");
let store_res: Result<(), tauri_plugin_store::Error> =
tauri_plugin_store::with_store(app, stores, path, |store| {
store.insert("adbPath".to_string(), serde_json::json!(adb_path))?;
*share::ADB_PATH.lock().unwrap() = adb_path;
Ok(())
});
match store_res {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tauri::Builder::default() tauri::Builder::default()
@ -168,8 +186,16 @@ async fn main() {
let stores = app let stores = app
.app_handle() .app_handle()
.state::<tauri_plugin_store::StoreCollection<tauri::Wry>>(); .state::<tauri_plugin_store::StoreCollection<tauri::Wry>>();
let path = std::path::PathBuf::from("store.bin"); let path: std::path::PathBuf = std::path::PathBuf::from("store.bin");
tauri_plugin_store::with_store(app.app_handle().clone(), stores, path, |store| { tauri_plugin_store::with_store(app.app_handle().clone(), stores, path, |store| {
// load adb path
match store.get("adbPath") {
Some(value) => *share::ADB_PATH.lock().unwrap() = value.to_string(),
None => store
.insert("adbPath".to_string(), serde_json::json!("adb"))
.unwrap(),
};
// restore window position and size // restore window position and size
match store.get("maskArea") { match store.get("maskArea") {
Some(value) => { Some(value) => {
@ -239,7 +265,8 @@ async fn main() {
get_device_screen_size, get_device_screen_size,
adb_connect, adb_connect,
load_default_keyconfig, load_default_keyconfig,
check_adb_available check_adb_available,
set_adb_path
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -21,3 +21,7 @@ impl ClientInfo {
lazy_static! { lazy_static! {
pub static ref CLIENT_INFO: Mutex<Option<ClientInfo>> = Mutex::new(None); pub static ref CLIENT_INFO: Mutex<Option<ClientInfo>> = Mutex::new(None);
} }
lazy_static! {
pub static ref ADB_PATH: Mutex<String> = Mutex::new(String::from("adb"));
}

View File

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

View File

@ -320,8 +320,13 @@ async function connectDevice() {
} }
store.showLoading(); store.showLoading();
message.info(await adbConnect(wireless_address.value)); try {
await refreshDevices(); message.info(await adbConnect(wireless_address.value));
await refreshDevices();
} catch (e) {
message.error("t('pages.Device.adbConnectError')");
console.error(e);
}
} }
function connectWS() { function connectWS() {

View File

@ -1,6 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { h, nextTick, onActivated, onMounted, ref } from "vue"; import { h, nextTick, onActivated, onMounted, ref } from "vue";
import { NDialog, NInput, useDialog, useMessage } from "naive-ui"; import {
MessageReactive,
NDialog,
NInput,
useDialog,
useMessage,
} from "naive-ui";
import { useGlobalStore } from "../store/global"; import { useGlobalStore } from "../store/global";
import { onBeforeRouteLeave, useRouter } from "vue-router"; import { onBeforeRouteLeave, useRouter } from "vue-router";
import { import {
@ -60,6 +66,7 @@ onActivated(async () => {
}); });
onMounted(async () => { onMounted(async () => {
store.checkAdb = checkAdb;
await checkAdb(); await checkAdb();
await loadLocalStore(); await loadLocalStore();
store.checkUpdate = checkUpdate; store.checkUpdate = checkUpdate;
@ -67,11 +74,16 @@ onMounted(async () => {
if (store.checkUpdateAtStart) checkUpdate(); if (store.checkUpdateAtStart) checkUpdate();
}); });
let checkAdbMessage: MessageReactive | null = null;
async function checkAdb() { async function checkAdb() {
try { try {
if (checkAdbMessage) {
checkAdbMessage.destroy();
checkAdbMessage = null;
}
await checkAdbAvailable(); await checkAdbAvailable();
} catch (e) { } catch (e) {
message.error(t("pages.Mask.checkAdb", [e]), { checkAdbMessage = message.error(t("pages.Mask.checkAdb", [e]), {
duration: 0, duration: 0,
}); });
} }

View File

@ -14,14 +14,20 @@ import {
NCard, NCard,
NIcon, NIcon,
NSelect, NSelect,
NInputGroup,
useMessage,
} from "naive-ui"; } from "naive-ui";
import { relaunch } from "@tauri-apps/plugin-process"; import { relaunch } from "@tauri-apps/plugin-process";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import i18n from "../../i18n"; import i18n from "../../i18n";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { setAdbPath } from "../../invoke";
import { useGlobalStore } from "../../store/global";
const { t } = useI18n(); const { t } = useI18n();
const localStore = new Store("store.bin"); const localStore = new Store("store.bin");
const store = useGlobalStore();
const message = useMessage();
const dialog = useDialog(); const dialog = useDialog();
const localStoreEntries = ref<[string, unknown][]>([]); const localStoreEntries = ref<[string, unknown][]>([]);
@ -36,9 +42,12 @@ const languageOptions = [
const curLanguage = ref("en-US"); const curLanguage = ref("en-US");
const adbPath = ref("");
onMounted(async () => { onMounted(async () => {
refreshLocalData(); refreshLocalData();
curLanguage.value = (await localStore.get<string>("language")) ?? "en-US"; curLanguage.value = (await localStore.get<string>("language")) ?? "en-US";
adbPath.value = (await localStore.get<string>("adbPath")) ?? "";
}); });
async function refreshLocalData() { async function refreshLocalData() {
@ -88,6 +97,15 @@ function changeLanguage(language: "zh-CN" | "en-US") {
localStore.set("language", language); localStore.set("language", language);
i18n.global.locale.value = language; i18n.global.locale.value = language;
} }
async function adjustAdbPath() {
store.showLoading();
await setAdbPath(adbPath.value);
message.success(t("pages.Setting.Basic.adbPath.setSuccess"));
await store.checkAdb();
adbPath.value = (await localStore.get<string>("adbPath")) ?? "";
store.hideLoading();
}
</script> </script>
<template> <template>
@ -99,6 +117,17 @@ function changeLanguage(language: "zh-CN" | "en-US") {
:options="languageOptions" :options="languageOptions"
style="max-width: 300px; margin: 20px 0" style="max-width: 300px; margin: 20px 0"
/> />
<NH4 prefix="bar">{{ $t("pages.Setting.Basic.adbPath.title") }}</NH4>
<NInputGroup style="max-width: 300px; margin-bottom: 20px">
<NInput
v-model:value="adbPath"
clearable
:placeholder="$t('pages.Setting.Basic.adbPath.placeholder')"
/>
<NButton type="primary" @click="adjustAdbPath">{{
$t("pages.Setting.Basic.adbPath.set")
}}</NButton>
</NInputGroup>
<NFlex justify="space-between"> <NFlex justify="space-between">
<NH4 prefix="bar">{{ $t("pages.Setting.Basic.localStore") }}</NH4> <NH4 prefix="bar">{{ $t("pages.Setting.Basic.localStore") }}</NH4>
<NFlex> <NFlex>

View File

@ -35,7 +35,8 @@
"inputWsAddress": "Please enter the Websocket address", "inputWsAddress": "Please enter the Websocket address",
"wsClose": "Close", "wsClose": "Close",
"wsConnect": "Control", "wsConnect": "Control",
"adbDeviceError": "Unable to get available devices" "adbDeviceError": "Unable to get available devices",
"adbConnectError": "Wireless connection failed"
}, },
"Mask": { "Mask": {
"inputBoxPlaceholder": "Input text and then press enter/esc", "inputBoxPlaceholder": "Input text and then press enter/esc",
@ -100,7 +101,13 @@
}, },
"language": "Language", "language": "Language",
"localStore": "Local data", "localStore": "Local data",
"delCurData": "Delete current data" "delCurData": "Delete current data",
"adbPath": {
"placeholder": "adb path",
"set": "Save",
"setSuccess": "adb path set successfully",
"title": "adb path"
}
}, },
"About": { "About": {
"introduction": "A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.", "introduction": "A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.",

View File

@ -35,7 +35,8 @@
"wsAddress": "Websocket 地址", "wsAddress": "Websocket 地址",
"wsClose": "断开", "wsClose": "断开",
"wsConnect": "控制", "wsConnect": "控制",
"adbDeviceError": "无法获取可用设备" "adbDeviceError": "无法获取可用设备",
"adbConnectError": "无线连接失败"
}, },
"Mask": { "Mask": {
"keyconfigException": "按键方案异常,请删除此方案", "keyconfigException": "按键方案异常,请删除此方案",
@ -100,7 +101,13 @@
}, },
"language": "语言", "language": "语言",
"localStore": "本地数据", "localStore": "本地数据",
"delCurData": "删除当前数据" "delCurData": "删除当前数据",
"adbPath": {
"setSuccess": "adb 路径设置成功",
"title": "adb 路径",
"placeholder": "adb 路径",
"set": "设置"
}
}, },
"About": { "About": {
"about": "关于", "about": "关于",

View File

@ -51,8 +51,12 @@ export async function loadDefaultKeyconfig(): Promise<string> {
return await invoke("load_default_keyconfig"); return await invoke("load_default_keyconfig");
} }
export async function checkAdbAvailable(): Promise<void>{ export async function checkAdbAvailable(): Promise<void> {
return await invoke("check_adb_available"); return await invoke("check_adb_available");
} }
export async function setAdbPath(path: string): Promise<string> {
return await invoke("set_adb_path", { adbPath: path });
}
export type { Device }; export type { Device };

View File

@ -30,6 +30,7 @@ export const useGlobalStore = defineStore("global", () => {
const showInputBox: (_: boolean) => void = (_: boolean) => {}; const showInputBox: (_: boolean) => void = (_: boolean) => {};
let checkUpdate: () => Promise<void> = async () => {}; let checkUpdate: () => Promise<void> = async () => {};
let checkAdb: () => Promise<void> = async () => {};
function applyEditKeyMappingList(): boolean { function applyEditKeyMappingList(): boolean {
const set = new Set<string>(); const set = new Set<string>();
@ -103,5 +104,6 @@ export const useGlobalStore = defineStore("global", () => {
resetEditKeyMappingList, resetEditKeyMappingList,
setKeyMappingIndex, setKeyMappingIndex,
checkUpdate, checkUpdate,
checkAdb,
}; };
}); });