debug needed

This commit is contained in:
Sense T
2022-09-26 03:04:07 +00:00
parent 0ce8374276
commit 4c80f8a25e
39 changed files with 8233 additions and 0 deletions

23
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
web/README.md Normal file
View File

@@ -0,0 +1,24 @@
# web
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
web/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

22
web/jsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"vueCompilerOptions": {
"experimentalDisableTemplateSupport": true
}
}

48
web/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vicons/ionicons5": "^0.12.0",
"core-js": "^3.8.3",
"marked": "^4.1.0",
"naive-ui": "^2.33.2",
"vue": "^3.2.13"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser",
"requireConfigFile": false
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
],
"_id": "web@0.1.0",
"readme": "ERROR: No README data found!"
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
web/public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

27
web/src/App.vue Normal file
View File

@@ -0,0 +1,27 @@
<template>
<n-message-provider placement="top-right">
<main-app />
</n-message-provider>
</template>
<script setup>
import MainApp from "./components/MainApp.vue";
import { NMessageProvider } from "naive-ui";
import { onMounted } from "vue";
import { store } from "./store";
const setTitle = async () => {
document.title = await store.loadTitle();
};
onMounted(() => {
setTitle();
});
</script>
<style>
body {
background-color: black;
}
</style>

16
web/src/ajax/ajax.js Normal file
View File

@@ -0,0 +1,16 @@
export const ajax = async (url, options) => {
try {
const r = await fetch(url, options)
if (r.status >= 400) {
throw Error(`request error with status ${r.status}`)
}
return await r.json()
} catch (e) {
console.log(e)
return {
succeed: false,
message: e,
data: null
}
}
}

23
web/src/ajax/index.js Normal file
View File

@@ -0,0 +1,23 @@
import { ajax } from './ajax'
export default {
getTitle: async () => {
const j = await ajax('api/v1/name')
return j.data
},
getInstruction: async () => {
const j = await ajax('api/v1/instruction')
return j.data
},
getICEUrl: async () => {
const j = await ajax('api/v1/iceserver/url')
return j.data
},
exchangeSDP: async offer => {
const answer = ajax('api/v1/sdp', {
method: "POST",
body: JSON.stringify(offer)
})
return answer
}
}

View File

@@ -0,0 +1,155 @@
<template>
<video autoplay muted id="video"></video>
</template>
<script setup>
import { onMounted, defineProps } from "vue";
import { store } from "../store";
import ajax from "../ajax";
const props = defineProps(["methods"]);
const eventTypes = {
mouseMove: 0,
mouseButton: 1,
keyboard: 2,
control: 3,
};
const controlEventTypes = {
restart: 0,
};
const makeEvent = (evType, args) => {
return {
type: eventTypes[evType],
args: args,
};
};
// eslint-disable-next-line
const sendSpecialKey = (key) => {
console.log(key);
const specialKey = "ctrl-alt-" + key;
dataChannel.send(
JSON.stringify(
makeEvent("keyboard", {
key: specialKey,
})
)
);
};
// eslint-disable-next-line
const resetVM = () => {
dataChannel.send(
JSON.stringify(
makeEvent("control", {
cmd: controlEventTypes.restart,
})
)
);
};
let dataChannel;
onMounted(() => {
console.log(props.methods);
// eslint-disable-next-line
props.methods.sendSpecialKey = sendSpecialKey;
// eslint-disable-next-line
props.methods.resetVM = resetVM;
const video = document.querySelector("video#video");
video.oncontextmenu = () => false;
store.getICEServers().then((servers) => {
const pc = new RTCPeerConnection({
iceServers: [
{
urls: servers,
},
],
});
pc.oniceconnectionstatechange = () => console.log(pc.iceConnectionState);
pc.addTransceiver("video");
//pc.addTransceiver('audio')
dataChannel = pc.createDataChannel("control");
dataChannel.onmessage = (e) => {
const d = JSON.parse(e.data);
store.delay = +new Date() - d.server_time;
store.qemuStatus = d.qemu_status;
};
pc.ontrack = (ev) => {
video.srcObject = ev.streams[0];
video.autoplay = true;
video.controls = false;
};
video.onmousemove = (ev) => {
dataChannel.send(
JSON.stringify(
makeEvent("mouseMove", {
dx: ev.clientX,
dy: ev.clientY,
dz: 0,
})
)
);
};
video.onmousedown = (ev) => {
dataChannel.send(
JSON.stringify(
makeEvent("mouseButton", {
button: ev.button << 1,
})
)
);
};
//video.onmousewheel = (ev) => {};
window.onkeydown = (ev) => {
let key = "";
if (ev.ctrlKey && ev.which !== 17) key = "ctrl-" + ev.key;
else key = "0x" + ev.which.toString(16);
if (ev.shiftKey && ev.which !== 16) key = "shift-" + ev.key;
else key = "0x" + ev.which.toString(16);
if (ev.altKey && ev.which !== 18) key = "alt-" + ev.key;
else key = "0x" + ev.which.toString(16);
if (ev.metaKey && ev.which !== 91 && ev.which !== 93)
key = "meta-" + ev.key;
else key = "0x" + ev.which.toString(16);
if (!ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey)
key = "0x" + ev.which.toString(16);
dataChannel.send(
JSON.stringify(
makeEvent("keyboard", {
key: key,
})
)
);
};
pc.createOffer().then((offer) => {
pc.setLocalDescription(offer);
ajax
.exchangeSDP(offer)
.then((answer) =>
pc.setRemoteDescription(new RTCSessionDescription(answer))
);
});
});
});
</script>
<style scoped>
video#video {
vertical-align: middle;
top: 50%;
transform: translateY(-50%) translateX(-50%);
left: 50%;
position: absolute;
max-height: 100%;
max-width: 100%;
}
</style>

View File

@@ -0,0 +1,176 @@
<template>
<div @contextmenu="noContextMenu">
<n-watermark
cross
fullscreen
v-if="show"
:content="'ACE-' + title"
:font-size="16"
:line-height="16"
:width="384"
:height="384"
:x-offset="12"
:y-offset="60"
:rotate="-15"
></n-watermark>
<div @mouseenter="menu" id="showMenu"></div>
<div id="screenArea"><ace-screen :methods="screenController" /></div>
<!-- eslint-disable vue/no-v-model-argument -->
<n-drawer :width="480" v-model:show="show" :native-scrollbar="true">
<n-drawer-content :title="title">
<n-collapse
:default-expanded-names="['controlButtons', 'instructions']"
>
<n-collapse-item title="控制按钮" name="controlButtons">
<n-grid :cols="1" :y-gap="8">
<n-grid-item>
<p>Ctrl+Alt+?</p>
<n-button-group>
<n-button type="error" strong @click="sendCtrlAltDelete">
Delete
</n-button>
<n-button strong @click="sendCtrlAltF1">F1</n-button>
<n-button strong @click="sendCtrlAltF2">F2</n-button>
<n-button strong @click="sendCtrlAltF3">F3</n-button>
<n-button strong @click="sendCtrlAltF4">F4</n-button>
<n-button strong @click="sendCtrlAltF5">F5</n-button>
<n-button strong @click="sendCtrlAltF6">F6</n-button>
<n-button strong @click="sendCtrlAltF7">F7</n-button>
</n-button-group>
</n-grid-item>
<n-grid-item>
<n-button type="error" strong @click="resetVM">
重置
<template #icon>
<n-icon>
<reload-icon />
</n-icon>
</template>
</n-button>
</n-grid-item>
</n-grid>
</n-collapse-item>
<n-collapse-item title="介绍" name="instructions">
<n-card>
<n-skeleton v-if="loading" text :repeat="10" />
<n-skeleton v-if="loading" text style="width: 60%" />
<div v-else v-html="renderMarkdown"></div>
</n-card>
</n-collapse-item>
<n-collapse-item title="统计信息" name="stat">
<n-card>
<p>当前延迟{{ store.delay }}ms</p>
<p>云电脑状态{{ store.qemuStatus }}</p>
</n-card>
</n-collapse-item>
</n-collapse>
</n-drawer-content>
</n-drawer>
</div>
</template>
<script setup>
import {
NDrawer,
NDrawerContent,
NWatermark,
NCollapse,
NCollapseItem,
NButtonGroup,
NButton,
NIcon,
NGridItem,
NGrid,
//NSpace,
useMessage,
NCard,
NSkeleton,
} from "naive-ui";
import { Reload as ReloadIcon } from "@vicons/ionicons5";
import { ref, onMounted, computed } from "vue";
import AceScreen from "./AceScreen.vue";
import { store } from "../store";
import { marked } from "marked";
const show = ref(false);
const title = ref("test123");
const loading = ref(true);
const menu = () => {
show.value = true;
};
const noContextMenu = () => false;
const message = useMessage();
const instruction = ref("");
const renderMarkdown = computed(() => {
return marked.parse(
instruction.value ? instruction.value : "# To be continued..."
);
});
const screenController = {};
const sendCtrlAltDelete = () => {
sendSpecialKey("del");
};
const sendCtrlAltF1 = () => {
sendSpecialKey("f1");
};
const sendCtrlAltF2 = () => {
sendSpecialKey("f2");
};
const sendCtrlAltF3 = () => {
sendSpecialKey("f3");
};
const sendCtrlAltF4 = () => {
sendSpecialKey("f4");
};
const sendCtrlAltF5 = () => {
sendSpecialKey("f5");
};
const sendCtrlAltF6 = () => {
sendSpecialKey("f6");
};
const sendCtrlAltF7 = () => {
sendSpecialKey("f7");
};
const sendSpecialKey = (key) => {
screenController.sendSpecialKey(key);
};
const resetVM = () => {
screenController.resetVM();
};
onMounted(() => {
message.info("鼠标移动到屏幕右侧可打开控制面板");
store.getTitle().then((t) => (title.value = t));
store.loadInstruction().then((i) => {
loading.value = false;
instruction.value = i;
});
});
</script>
<style scoped>
div#showMenu {
float: right;
color: white;
height: 100vh;
width: 1px;
z-index: 100;
}
div#screenArea {
height: 100vh;
width: 100vw;
zoom: 1;
}
</style>

4
web/src/main.js Normal file
View File

@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

33
web/src/store/index.js Normal file
View File

@@ -0,0 +1,33 @@
import { reactive } from 'vue'
import ajax from '../ajax'
export const store = reactive({
title: '',
iceServers: [],
instruction: "",
delay: 0,
qemuStatus: "",
async loadTitle() {
this.title = await ajax.getTitle()
return this.title
},
async loadICEServers() {
this.iceServers = await ajax.getICEUrl()
return this.iceServers
},
async loadInstruction() {
this.instruction = await ajax.getInstruction()
return this.instruction
},
async getTitle() {
return this.title
},
async getICEServers() {
return this.iceServers
},
async getInstruction() {
return this.instruction
}
})

4
web/vue.config.js Normal file
View File

@@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

6243
web/yarn.lock Normal file

File diff suppressed because it is too large Load Diff