├── .gitignore
├── .npmrc
├── .simple-git-hooks.cjs
├── .vscode
├── .debug.script.mjs
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── Dockerfile
├── LICENSE
├── README.md
├── electron-builder.json
├── nano-staged.mjs
├── package.json
├── packages
├── main
│ ├── PvlDialog.ts
│ ├── UsbConfigMain.ts
│ ├── index.ts
│ └── vite.config.ts
├── preload
│ ├── index.ts
│ ├── loading.ts
│ ├── utils.ts
│ └── vite.config.ts
└── renderer
│ ├── index.html
│ ├── public
│ ├── favicon.ico
│ └── images
│ │ ├── node.png
│ │ └── quick-start.gif
│ ├── src
│ ├── App.vue
│ ├── Pvl.d.ts
│ ├── assets
│ │ ├── font
│ │ │ ├── Orbitron-Bold.ttf
│ │ │ ├── Orbitron-Regular.ttf
│ │ │ ├── OverpassMono-Bold.ttf
│ │ │ └── OverpassMono-Regular.ttf
│ │ ├── logo.png
│ │ ├── vite.svg
│ │ └── vue.png
│ ├── components
│ │ ├── HelloWorld.vue
│ │ └── util
│ │ │ ├── PvlBtn.vue
│ │ │ ├── PvlCheckbox.vue
│ │ │ ├── PvlDropDown.vue
│ │ │ ├── PvlInput.vue
│ │ │ ├── PvlLog.vue
│ │ │ ├── PvlRadios.vue
│ │ │ ├── PvlSwitch.vue
│ │ │ ├── PvlTitleBar.vue
│ │ │ ├── PvlTree.vue
│ │ │ └── PvlTreeJson.ts
│ ├── env.d.ts
│ ├── global.d.ts
│ ├── lang
│ │ ├── index.ts
│ │ └── zh_cn.ts
│ ├── main.ts
│ ├── mainModules
│ │ ├── PvlFile.ts
│ │ ├── UsbConfigRender.ts
│ │ ├── UsbDescriptor.ts
│ │ ├── builtinModuleSample.ts
│ │ ├── compileCherryUSB.ts
│ │ ├── ipcRendererSample.ts
│ │ └── nodeModulesSample.ts
│ ├── pages
│ │ ├── UsbConfig.vue
│ │ └── usbConfigPages
│ │ │ ├── BUSB.vue
│ │ │ ├── BUSBGroup.vue
│ │ │ ├── CUSBAlternateInfo.vue
│ │ │ ├── CUSBAssociateInfo.vue
│ │ │ ├── CUSBConfigGroupInfo.vue
│ │ │ ├── CUSBConfigInfo.vue
│ │ │ ├── CUSBDeviceInfo.vue
│ │ │ ├── CUSBEndpointInfo.vue
│ │ │ ├── CUSBSpecificInfo.vue
│ │ │ ├── CUSBStringInfo.vue
│ │ │ ├── tip.vue
│ │ │ └── usbConfigUtil.ts
│ ├── router
│ │ └── index.ts
│ └── theme
│ │ ├── dark.ts
│ │ ├── index.ts
│ │ ├── light.ts
│ │ └── theme.css
│ ├── tsconfig.json
│ └── vite.config.ts
├── playwright.config.ts
├── scripts
├── build.mjs
└── watch.mjs
├── test
├── demo
│ ├── cdc.chry
│ ├── demo.c
│ ├── demo.chrybase
│ ├── hid_keyboard.chry
│ └── msc.chry
├── example.spec.ts
└── uac
│ ├── uac_audio_speaker.chry
│ ├── uac_audio_speaker_voice_mic.chry
│ ├── uac_voice_mic.chry
│ └── uac_voice_speaker.chry
├── tsconfig.json
└── types.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | .debug.env
7 |
8 | yarn.lock
9 |
10 | tmp
11 | **/.tmp
12 | release
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com/
2 | ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
3 | ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
--------------------------------------------------------------------------------
/.simple-git-hooks.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // "pre-commit": "npx nano-staged",
3 | };
--------------------------------------------------------------------------------
/.vscode/.debug.script.mjs:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { fileURLToPath } from 'url'
4 | import { createRequire } from 'module'
5 |
6 | const pkg = createRequire(import.meta.url)('../package.json')
7 | const __dirname = path.dirname(fileURLToPath(import.meta.url))
8 |
9 | // write .debug.env
10 | const envContent = Object.entries(pkg.env).map(([key, val]) => `${key}=${val}`)
11 | fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n'))
12 |
13 | // bootstrap
14 | import('../scripts/watch.mjs?debug=vscode')
15 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["johnsoncodehk.volar", "johnsoncodehk.vscode-typescript-vue-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "compounds": [
7 | {
8 | "name": "Debug App",
9 | "preLaunchTask": "start .debug.script.mjs",
10 | "configurations": [
11 | "Debug Main Process",
12 | "Debug Renderer Process"
13 | ],
14 | "presentation": {
15 | "hidden": false,
16 | "group": "",
17 | "order": 1
18 | },
19 | "stopAll": true
20 | }
21 | ],
22 | "configurations": [
23 | {
24 | "name": "Debug Main Process",
25 | "type": "pwa-node",
26 | "request": "launch",
27 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
28 | "windows": {
29 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
30 | },
31 | "runtimeArgs": [
32 | "--remote-debugging-port=9229",
33 | "${workspaceRoot}/dist/main/index.cjs"
34 | ],
35 | "envFile": "${workspaceFolder}/.vscode/.debug.env"
36 | },
37 | {
38 | "name": "Debug Renderer Process",
39 | "port": 9229,
40 | "request": "attach",
41 | "type": "pwa-chrome"
42 | },
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true,
4 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "start .debug.script.mjs",
8 | "type": "shell",
9 | "command": "node .vscode/.debug.script.mjs",
10 | "isBackground": true,
11 | "problemMatcher": []
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # use the version that corresponds to your electron version
2 | FROM node:14.16
3 |
4 | LABEL NAME="electron-wrapper"
5 | LABEL RUN="docker run --rm -it electron-wrapper bash"
6 |
7 | # install electron dependencies or more if your library has other dependencies
8 | RUN apt-get update && apt-get install \
9 | git libx11-xcb1 libxcb-dri3-0 libxtst6 libnss3 libatk-bridge2.0-0 libgtk-3-0 libxss1 libasound2 \
10 | -yq --no-install-suggests --no-install-recommends \
11 | && apt-get clean && rm -rf /var/lib/apt/lists/*
12 |
13 | # copy the source into /app
14 | WORKDIR /app
15 | COPY . .
16 | RUN chown -R node /app
17 |
18 | # install node modules and perform an electron rebuild
19 | USER node
20 | RUN npm install
21 | RUN npm run build
22 |
23 | USER node
24 | CMD bash
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 草鞋没号
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cherry USB Configurator
2 |
3 | Cherry USB Configurator 是一个简单的易于上手的用于生成 USB 描述符的图形化配置工具.
4 |
5 | ## [使用教程](https://cherryusb.readthedocs.io/zh_CN/latest/tools/index.html)
6 |
7 | ## 功能简介
8 |
9 | Cherry USB Configurator 基于Electron11 + Vue3.2 + Vite2 + TypeScript开发。
10 | 在Cherry USB Configurator里USB描述符被分割成了两种文件
11 | 一种是chrybase工程文件, 包含设备描述符、配置描述符和多组功能描述符的配置
12 | 另一种是chry文件, 包含了一组功能的接口关联描述符, 接口描述符, 类特定描述符, 端点描述符的配置
13 | 参考 test/demo文件内容
14 |
15 | Cherry USB Configurator 预计实现以下功能:
16 | - [x] 支持 USB2.0 全速和高速设备描述符配置
17 | - [x] 支持设备描述符生成
18 | - [x] 支持配置描述符生成
19 | - [x] 支持字符串描述符生成
20 | - [x] 支持接口描述符生成
21 | - [x] 支持接口关联描述符生成
22 | - [x] 支持端点描述符生成
23 | - [x] 支持 HID 类描述符生成
24 | - [x] 支持 MSC 类描述符生成
25 | - [x] 支持 CDC ACM 类描述符生成
26 | - [ ] 支持多组配置描述符生成
27 | - [ ] Other Speed 描述符生成
28 | - [ ] 支持 UAC 1.0 描述符生成
29 | - [ ] 支持 UAC 2.0 描述符生成
30 | - [ ] 支持 UVC 描述符生成
31 |
32 |
33 |
34 | ## 构建
35 |
36 | ``` bash
37 |
38 | # python 2.7
39 | # electron 11.4.12
40 | # nodejs 14.18.0
41 | # vue 3.2.29
42 | # vite ^2.7.13
43 |
44 | # install dependencies~
45 | yarn
46 |
47 | # reinstall dependencies~
48 | rm -rf node_modules
49 | yarn
50 |
51 | # serve with hot reload at localhost:9080
52 | yarn dev
53 |
54 | # build electron application for production
55 | yarn build
56 |
57 | ```
58 |
--------------------------------------------------------------------------------
/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "chryusb.configurator.com",
3 | "asar": true,
4 | "productName":"ChryUSB_Configurator",
5 | "directories": {
6 | "output": "release/${version}"
7 | },
8 | "files": [
9 | "dist"
10 | ],
11 | "mac": {
12 | "artifactName": "${productName}_${version}.${ext}",
13 | "target": [
14 | "dmg"
15 | ]
16 | },
17 | "win": {
18 | "icon": "release/icons/icon.ico",
19 | "target": [
20 | {
21 | "target": "nsis",
22 | "arch": [
23 | "x64"
24 | ]
25 | }
26 | ],
27 | "artifactName": "${productName}_${version}.${ext}"
28 | },
29 | "nsis": {
30 | "oneClick": false,
31 | "allowElevation": true,
32 | "perMachine": true,
33 | "allowToChangeInstallationDirectory": true,
34 | "deleteAppDataOnUninstall": false,
35 | "installerIcon": "release/icons/icon.ico",
36 | "uninstallerIcon": "release/icons/icon.ico",
37 | "installerHeaderIcon": "release/icons/icon.ico",
38 | "createDesktopShortcut": true,
39 | "createStartMenuShortcut": true
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nano-staged.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | // eslint
3 | '*.{js,ts,tsx,vue}': 'eslint --cache --fix',
4 | // typecheck
5 | 'packages/renderer/**/{*.ts,*.tsx,*.vue,tsconfig.json}': ({ filenames }) =>
6 | 'npm run typecheck',
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chryusb_configurator",
3 | "version": "1.0.0",
4 | "main": "dist/main/index.cjs",
5 | "author": "Egahp <2687434412@qq.com>",
6 | "license": "MIT",
7 | "scripts": {
8 | "dev": "node scripts/watch.mjs",
9 | "prebuild": "vue-tsc --noEmit --p packages/renderer/tsconfig.json && node scripts/build.mjs",
10 | "build": "electron-builder",
11 | "init": "git config core.hooksPath .git/hooks/ && rm -rf .git/hooks && npx simple-git-hooks",
12 | "test:e2e": "npx playwright test",
13 | "test:e2e:headless": "npx playwright test --headed"
14 | },
15 | "engines": {
16 | "node": ">=14.17.0"
17 | },
18 | "devDependencies": {
19 | "@playwright/test": "^1.19.2",
20 | "@types/sqlite3": "^3.1.8",
21 | "@vitejs/plugin-vue": "^2.1.0",
22 | "electron": "16.0.8",
23 | "electron-builder": "^22.14.5",
24 | "nano-staged": "^0.6.0",
25 | "simple-git-hooks": "^2.7.0",
26 | "typescript": "^4.5.5",
27 | "vite": "^2.7.13",
28 | "vite-plugin-resolve": "^1.8.0",
29 | "vue": "^3.2.29",
30 | "vue-tsc": "^0.31.1"
31 | },
32 | "env": {
33 | "VITE_DEV_SERVER_HOST": "127.0.0.1",
34 | "VITE_DEV_SERVER_PORT": 3344
35 | },
36 | "keywords": [
37 | "vite",
38 | "electron",
39 | "vue3",
40 | "rollup",
41 | "element-plus"
42 | ],
43 | "dependencies": {
44 | "@mdi/font": "^6.5.95",
45 | "animate.css": "^4.1.1",
46 | "axios": "^0.26.1",
47 | "element-plus": "^2.1.5",
48 | "jsonc-parser": "^3.0.0",
49 | "sqlite3": "^5.0.2",
50 | "vue-i18n": "^9.1.9",
51 | "vue-router": "^4.0.14"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/main/PvlDialog.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file PvlDialog.ts
4 | * @brief Dialog
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.03.29
9 | ******************************************************************************
10 | * @attention
11 | *
12 | *
© Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.03.29|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 | /* import --------------------------------------------------------------------*/
26 | import { dialog, BrowserWindow, FileFilter, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron';
27 |
28 | /* interface -----------------------------------------------------------------*/
29 | export interface IPvlDialogOptions
30 | {
31 | defaultPath?: string;
32 | title?: string;
33 | buttonLabel?: string;
34 | filters?: FileFilter[];
35 | multiSelections?: boolean;
36 | }
37 |
38 | interface IinternalPvlDialogOptions extends IPvlDialogOptions
39 | {
40 | pickFolders?: boolean;
41 | pickFiles?: boolean;
42 | }
43 |
44 | export interface IPvlDialog {
45 | pickFile(options:IPvlDialogOptions, window?:BrowserWindow): Promise;
46 | pickFolder(options:IPvlDialogOptions, window?:BrowserWindow): Promise;
47 | pickFileFolder(options:IPvlDialogOptions, window?:BrowserWindow): Promise;
48 | saveAs(options:IPvlDialogOptions, window?:BrowserWindow): Promise;
49 |
50 | showMessageBox(options:MessageBoxOptions, window?:BrowserWindow): Promise;
51 | showSaveDialog(options:SaveDialogOptions, window?:BrowserWindow): Promise;
52 | showOpenDialog(options:OpenDialogOptions, window?:BrowserWindow): Promise;
53 | }
54 |
55 | /* data ----------------------------------------------------------------------*/
56 |
57 | /* methods -------------------------------------------------------------------*/
58 |
59 | /* class ---------------------------------------------------------------------*/
60 | export class PvlDialog implements IPvlDialog {
61 |
62 | pickFile(options:IPvlDialogOptions, window?:BrowserWindow): Promise{
63 | return this.doPick({ ...options, pickFiles:true, title: (options.title || "Open File")}, window);
64 | }
65 |
66 | pickFolder(options:IPvlDialogOptions, window?:BrowserWindow): Promise{
67 | return this.doPick({ ...options, pickFolders:true, title: (options.title || "Open Folder")}, window);
68 | }
69 |
70 | pickFileFolder(options:IPvlDialogOptions, window?:BrowserWindow): Promise{
71 | return this.doPick({ ...options, pickFiles:true, pickFolders:true, title: (options.title || "Open")}, window);
72 | }
73 |
74 | saveAs(options: IPvlDialogOptions, window?: BrowserWindow): Promise {
75 | return this.doSaveAs({ ...options, title: (options.title || "Save As")}, window);
76 | }
77 |
78 | private withNullAsUndefined(x: T | null): T | undefined {
79 | return x === null ? undefined : x;
80 | }
81 |
82 | private withUndefinedAsNull(x: T | undefined): T | null {
83 | return typeof x === 'undefined' ? null : x;
84 | }
85 |
86 | private async doPick(options:IinternalPvlDialogOptions, window?: BrowserWindow): Promise {
87 | const dialogOptions: OpenDialogOptions = {
88 | title: options.title,
89 | buttonLabel: options.buttonLabel,
90 | filters: options.filters
91 | };
92 |
93 | dialogOptions.defaultPath = options.defaultPath;
94 |
95 | if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
96 | dialogOptions.properties = undefined; // let it override based on the booleans
97 |
98 | if (options.pickFiles && options.pickFolders) {
99 | if (typeof options.multiSelections === 'boolean'){
100 | if (options.multiSelections){
101 | dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
102 | }
103 | else{
104 | dialogOptions.properties = ['openDirectory', 'openFile', 'createDirectory'];
105 | }
106 | }
107 | else{
108 | dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
109 | }
110 | }
111 | }
112 |
113 | if (!dialogOptions.properties) {
114 | if (typeof options.multiSelections === 'boolean'){
115 | if (options.multiSelections){
116 | dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
117 | }
118 | else{
119 | dialogOptions.properties = [options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
120 | }
121 | }
122 | else{
123 | dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
124 | }
125 |
126 | }
127 |
128 | // Show Dialog
129 | const windowToUse = window || BrowserWindow.getFocusedWindow();
130 |
131 | const result = await this.showOpenDialog(dialogOptions, this.withNullAsUndefined(windowToUse));
132 | if (result && result.filePaths && result.filePaths.length > 0) {
133 | return result.filePaths;
134 | }
135 |
136 | return;
137 | }
138 |
139 | private async doSaveAs(options: IPvlDialogOptions, window?: BrowserWindow): Promise {
140 | const dialogOptions:SaveDialogOptions = {
141 | title: options.title,
142 | buttonLabel: options.buttonLabel,
143 | filters: options.filters
144 | };
145 |
146 | dialogOptions.defaultPath = options.defaultPath;
147 |
148 | dialogOptions.properties = ['showHiddenFiles','showOverwriteConfirmation','createDirectory'];
149 |
150 | const windowToUse = window || BrowserWindow.getFocusedWindow();
151 |
152 | const result = await this.showSaveDialog(dialogOptions, this.withNullAsUndefined(windowToUse));
153 |
154 | if (result && result.filePath){
155 | return result.filePath;
156 | }
157 |
158 | return undefined;
159 | }
160 |
161 | showMessageBox(options:MessageBoxOptions, window?:BrowserWindow): Promise{
162 | const windowToUse = window || this.withNullAsUndefined(BrowserWindow.getFocusedWindow());
163 |
164 | if (typeof windowToUse !== "undefined"){
165 | return dialog.showMessageBox(windowToUse, options);
166 | } else {
167 | return dialog.showMessageBox(options);
168 | }
169 | }
170 |
171 | showSaveDialog(options:SaveDialogOptions, window?:BrowserWindow): Promise{
172 | if (window){
173 | return dialog.showSaveDialog(window, options);
174 | } else {
175 | return dialog.showSaveDialog(options);
176 | }
177 | }
178 |
179 | showOpenDialog(options:OpenDialogOptions, window?:BrowserWindow): Promise{
180 | if (window){
181 | return dialog.showOpenDialog(window, options);
182 | } else {
183 | return dialog.showOpenDialog(options);
184 | }
185 | }
186 | }
187 | /* export --------------------------------------------------------------------*/
188 |
189 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
190 |
--------------------------------------------------------------------------------
/packages/main/UsbConfigMain.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file UsbConfigMain.ts
4 | * @brief use in main
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.03.29
9 | ******************************************************************************
10 | * @attention
11 | *
12 | * © Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.03.29|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 | /* import --------------------------------------------------------------------*/
26 | import { ipcMain } from "electron";
27 | import { PvlDialog } from "./PvlDialog";
28 | /* interface -----------------------------------------------------------------*/
29 | export interface IUsbConfigDialogMessage{
30 | title?: string,
31 | message: string,
32 | type?: string,
33 | buttons?: string[],
34 | cancelId?: number
35 | }
36 |
37 | /* data ----------------------------------------------------------------------*/
38 |
39 | /* methods -------------------------------------------------------------------*/
40 | function withNullAsUndefined(x: T | null): T | undefined {
41 | return x === null ? undefined : x;
42 | }
43 |
44 | /* class ---------------------------------------------------------------------*/
45 | const UsbConfigIpcMainMount = () => {
46 |
47 | ipcMain.handle("usbconfig-basefile-open", async(event, arg)=>{
48 | const paths = await PvlDialog.prototype.pickFile({
49 | title: arg.title,
50 | filters: [{
51 | name: arg.name,
52 | extensions: ['chrybase']
53 | }],
54 | multiSelections: false
55 | })
56 | .then((res) => {
57 | return res;
58 | })
59 | .catch((err) => {
60 | console.log(err);
61 | })
62 |
63 | return paths;
64 | })
65 |
66 | ipcMain.handle("usbconfig-file-open", async (event, arg) => {
67 | var paths = await PvlDialog.prototype.pickFile(
68 | {
69 | title: arg.title,
70 | filters: [{
71 | name: arg.name,
72 | extensions: ['chry']
73 | }]
74 | })
75 | .then((res) => {
76 | return res;
77 | })
78 | .catch((err) => {
79 | console.log(err);
80 | })
81 |
82 | return paths;
83 | })
84 |
85 | ipcMain.handle("usbconfig-basefile-saveas", async(event, arg) => {
86 | var paths = await PvlDialog.prototype.saveAs(
87 | {
88 | title: arg.title,
89 | filters: [{
90 | name: arg.name,
91 | extensions: ['chrybase']
92 | }],
93 | })
94 | .then((res)=>{
95 | return res;
96 | })
97 | .catch((err) => {
98 | console.log(err);
99 | })
100 |
101 | return paths;
102 | })
103 |
104 | ipcMain.handle("usbconfig-c-saveas", async(event, arg) => {
105 | var paths = await PvlDialog.prototype.saveAs(
106 | {
107 | title: arg.title,
108 | filters: [{
109 | name: arg.name,
110 | extensions: ['c','cpp']
111 | }],
112 | })
113 | .then((res)=>{
114 | return res;
115 | })
116 | .catch((err) => {
117 | console.log(err);
118 | })
119 |
120 | return paths;
121 | })
122 |
123 | ipcMain.handle("usbconfig-file-saveas", async(event, arg) => {
124 | var paths = await PvlDialog.prototype.saveAs(
125 | {
126 | title: arg.title,
127 | filters: [{
128 | name: arg.name,
129 | extensions: ['chry']
130 | }]
131 | })
132 | .then((res)=>{
133 | return res;
134 | })
135 | .catch((err) => {
136 | console.log(err);
137 | })
138 |
139 | return paths;
140 | })
141 |
142 | ipcMain.handle("usbconfig-message", async(event, arg) => {
143 | if (typeof arg !== "undefined"){
144 | var result:number|void = await PvlDialog.prototype.showMessageBox({
145 | title: arg.title || "Warning",
146 | message:arg.message || "Message",
147 | type: arg.type || "warning",
148 | buttons: arg.buttons || undefined,
149 | cancelId: arg.cancelId || 0
150 | })
151 | .then((res)=>{
152 | return res.response;
153 | })
154 | .catch((err)=>{
155 | console.log(err)
156 | })
157 | } else {
158 | var result = await PvlDialog.prototype.showMessageBox({
159 | title: "Warning",
160 | message: "Message",
161 | type: "warning"
162 | })
163 | .then((res)=>{
164 | return res.response;
165 | })
166 | .catch((err)=>{
167 | console.log(err)
168 | })
169 | }
170 |
171 | return result;
172 | })
173 |
174 | ipcMain.on("usbconfig.close",(event)=>{
175 | event.sender.send("usbconfig.close");
176 | })
177 |
178 | }
179 | /* export --------------------------------------------------------------------*/
180 | export default UsbConfigIpcMainMount
181 |
182 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
183 |
--------------------------------------------------------------------------------
/packages/main/index.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, ipcMain, shell} from 'electron'
2 | import { release } from 'os'
3 | import theme from 'packages/renderer/src/theme'
4 | import { join } from 'path'
5 | import UsbConfigIpcMainMount from './UsbConfigMain'
6 |
7 | // Disable GPU Acceleration for Windows 7
8 | if (release().startsWith('6.1')) app.disableHardwareAcceleration()
9 |
10 | // Set application name for Windows 10+ notifications
11 | if (process.platform === 'win32') app.setAppUserModelId(app.getName())
12 |
13 | if (!app.requestSingleInstanceLock()) {
14 | app.quit()
15 | process.exit(0)
16 | }
17 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
18 |
19 | let win: BrowserWindow | null = null
20 |
21 | async function createWindow() {
22 | win = new BrowserWindow({
23 | minWidth: 1024,
24 | minHeight: 720,
25 | width: 1024,
26 | height: 720,
27 | resizable: true,
28 | frame: false,
29 | show:false,
30 | transparent: false,
31 | useContentSize: true,
32 | webPreferences: {
33 | preload: join(__dirname, '../preload/index.cjs'),
34 | nodeIntegration: true,
35 | contextIsolation: false,
36 | devTools: true,
37 | webSecurity: true
38 | },
39 | })
40 |
41 | if (app.isPackaged) {
42 | win.loadFile(join(__dirname, '../renderer/index.html'))
43 | } else {
44 | // 🚧 Use ['ENV_NAME'] avoid vite:define plugin
45 | const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
46 |
47 | win.loadURL(url)
48 | win.webContents.openDevTools()
49 | }
50 |
51 | win.once('ready-to-show', ()=>{
52 | if (win){
53 | win.show()
54 | // console.log(app.getPath("userData"));
55 | }
56 | })
57 |
58 |
59 | // Communicate with the Renderer-process.
60 | win.webContents.on('ipc-message', (_, channel, ...args) => {
61 | switch (channel) {
62 | case 'app.getPath':
63 | win?.webContents.send('app.getPath', app.getPath(args[0]))
64 | break
65 | default:
66 | break
67 | }
68 | })
69 |
70 | // Test active push message to Renderer-process
71 | win.webContents.on('did-finish-load', () => {
72 | win?.webContents.send('main-process-message', new Date().toLocaleString())
73 | })
74 |
75 | // Make all links open with the browser, not with the application
76 | win.webContents.setWindowOpenHandler(({ url }) => {
77 | if (url.startsWith('https:')) shell.openExternal(url)
78 | return { action: 'deny' }
79 | })
80 | }
81 |
82 | app.whenReady().then(createWindow)
83 |
84 | app.on('window-all-closed', () => {
85 | win = null
86 | if (process.platform !== 'darwin') app.quit()
87 | })
88 |
89 | app.on('second-instance', () => {
90 | if (win) {
91 | // Focus on the main window if the user tried to open another
92 | if (win.isMinimized()) win.restore()
93 | win.focus()
94 | }
95 | })
96 |
97 | app.on('activate', () => {
98 | const allWindows = BrowserWindow.getAllWindows()
99 | if (allWindows.length) {
100 | allWindows[0].focus()
101 | } else {
102 | createWindow()
103 | }
104 | })
105 |
106 |
107 | ipcMain.on('main.window.min', () => {
108 | if (win) {
109 | win.minimize()
110 | }
111 | })
112 |
113 | let main_window_is_max:boolean = false
114 | ipcMain.on('main.window.max', () => {
115 | if (win) {
116 | if (main_window_is_max){
117 | win.unmaximize()
118 | main_window_is_max = false
119 | }
120 | else {
121 | win.maximize()
122 | main_window_is_max = true;
123 | }
124 | }
125 | })
126 |
127 | ipcMain.on('main.window.close', ()=>{
128 | if (win){
129 | win.close()
130 | }
131 | })
132 |
133 | function withNullAsUndefined(x: T | null): T | undefined {
134 | return x === null ? undefined : x;
135 | }
136 |
137 |
138 | UsbConfigIpcMainMount()
139 |
--------------------------------------------------------------------------------
/packages/main/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { builtinModules } from 'module'
2 | import { defineConfig } from 'vite'
3 | import pkg from '../../package.json'
4 |
5 | export default defineConfig({
6 | root: __dirname,
7 | build: {
8 | outDir: '../../dist/main',
9 | lib: {
10 | entry: 'index.ts',
11 | formats: ['cjs'],
12 | fileName: () => '[name].cjs',
13 | },
14 | minify: process.env./* from mode option */NODE_ENV === 'production',
15 | sourcemap: true,
16 | rollupOptions: {
17 | external: [
18 | 'electron',
19 | ...builtinModules,
20 | ...Object.keys(pkg.dependencies || {}),
21 | ],
22 | },
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/packages/preload/index.ts:
--------------------------------------------------------------------------------
1 | import { domReady } from './utils'
2 | import { useLoading } from './loading'
3 |
4 | const { appendLoading, removeLoading } = useLoading()
5 | window.removeLoading = removeLoading;
6 |
7 | domReady().then(appendLoading)
8 |
--------------------------------------------------------------------------------
/packages/preload/loading.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | /**
4 | * https://tobiasahlin.com/spinkit
5 | * https://connoratherton.com/loaders
6 | * https://projects.lukehaas.me/css-loaders
7 | * https://matejkustec.github.io/SpinThatShit
8 | */
9 | export function useLoading() {
10 | const className = `loaders-css__square-spin`
11 | const styleContent = `
12 | @keyframes square-spin {
13 | 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
14 | 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
15 | 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
16 | 100% { transform: perspective(100px) rotateX(0) rotateY(0); }
17 | }
18 | .${className} > div {
19 | animation-fill-mode: both;
20 | width: 50px;
21 | height: 50px;
22 | background: #fff;
23 | animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
24 | }
25 | .app-loading-wrap {
26 | position: fixed;
27 | top: 0;
28 | left: 0;
29 | width: 100vw;
30 | height: 100vh;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 | background: #282c34;
35 | z-index: 9;
36 | }
37 | `
38 | const oStyle = document.createElement('style')
39 | const oDiv = document.createElement('div')
40 |
41 | oStyle.id = 'app-loading-style'
42 | oStyle.innerHTML = styleContent
43 | oDiv.className = 'app-loading-wrap'
44 | oDiv.innerHTML = ``
45 |
46 | return {
47 | appendLoading() {
48 | document.head.appendChild(oStyle)
49 | document.body.appendChild(oDiv)
50 | },
51 | removeLoading() {
52 | document.head.removeChild(oStyle)
53 | document.body.removeChild(oDiv)
54 | },
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/preload/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | /** docoment ready */
3 | export function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
4 | return new Promise(resolve => {
5 | if (condition.includes(document.readyState)) {
6 | resolve(true)
7 | } else {
8 | document.addEventListener('readystatechange', () => {
9 | if (condition.includes(document.readyState)) {
10 | resolve(true)
11 | }
12 | })
13 | }
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/packages/preload/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { builtinModules } from 'module'
2 | import { defineConfig } from 'vite'
3 | import pkg from '../../package.json'
4 |
5 | export default defineConfig({
6 | root: __dirname,
7 | build: {
8 | outDir: '../../dist/preload',
9 | lib: {
10 | entry: 'index.ts',
11 | formats: ['cjs'],
12 | fileName: () => '[name].cjs',
13 | },
14 | minify: process.env./* from mode option */NODE_ENV === 'production',
15 | // https://github.com/caoxiemeihao/electron-vue-vite/issues/61
16 | sourcemap: 'inline',
17 | rollupOptions: {
18 | external: [
19 | 'electron',
20 | ...builtinModules,
21 | ...Object.keys(pkg.dependencies || {}),
22 | ],
23 | },
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/packages/renderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite App
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/renderer/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/public/favicon.ico
--------------------------------------------------------------------------------
/packages/renderer/public/images/node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/public/images/node.png
--------------------------------------------------------------------------------
/packages/renderer/public/images/quick-start.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/public/images/quick-start.gif
--------------------------------------------------------------------------------
/packages/renderer/src/App.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
61 |
62 |
87 |
--------------------------------------------------------------------------------
/packages/renderer/src/Pvl.d.ts:
--------------------------------------------------------------------------------
1 | export { }
2 |
3 | export declare namespace Pvl {
4 | export interface IRadio{
5 | disable?:boolean,
6 | checked:boolean,
7 | label:string,
8 | icon?:string
9 | }
10 |
11 | export interface IOnRadios{
12 | (radio:number, radios?: Array):void;
13 | }
14 |
15 | export interface IList{
16 | disable?:boolean,
17 | checked?:boolean,
18 | label:string,
19 | icon?:string
20 | }
21 |
22 | export interface IOnList{
23 | (index:number, list?: Array):void;
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/packages/renderer/src/assets/font/Orbitron-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/font/Orbitron-Bold.ttf
--------------------------------------------------------------------------------
/packages/renderer/src/assets/font/Orbitron-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/font/Orbitron-Regular.ttf
--------------------------------------------------------------------------------
/packages/renderer/src/assets/font/OverpassMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/font/OverpassMono-Bold.ttf
--------------------------------------------------------------------------------
/packages/renderer/src/assets/font/OverpassMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/font/OverpassMono-Regular.ttf
--------------------------------------------------------------------------------
/packages/renderer/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/logo.png
--------------------------------------------------------------------------------
/packages/renderer/src/assets/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/renderer/src/assets/vue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CherryUSB/chryusb_configurator/037517332c4924423f156ec1668abe3e30b37c78/packages/renderer/src/assets/vue.png
--------------------------------------------------------------------------------
/packages/renderer/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {{ msg }}
11 |
12 |
13 | Recommended IDE setup:
14 | VSCode
15 | +
16 | Volar
17 |
18 |
19 | See README.md
for more information.
20 |
21 |
22 |
23 | Vite Docs
24 |
25 | |
26 | Vue 3 Docs
27 |
28 |
29 |
30 |
31 | Edit
32 | components/HelloWorld.vue
to test hot module replacement.
33 |
34 |
35 |
36 |
53 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
116 |
117 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlCheckbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlDropDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
18 |
19 | {{item.label}}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
185 |
186 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
147 |
148 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlLog.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
128 |
129 |
189 |
190 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlRadios.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
{{item.label}}
12 |
13 |
14 |
15 |
16 |
180 |
181 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlSwitch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlTitleBar.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{ props.title +" " + props.version}}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
104 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlTree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 | {{item.label}}
15 |
16 |
17 |
18 |
19 |
255 |
256 |
--------------------------------------------------------------------------------
/packages/renderer/src/components/util/PvlTreeJson.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file PvlTreeJson.ts
4 | * @brief json operations
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.03.29
9 | ******************************************************************************
10 | * @attention
11 | *
12 | * © Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.03.29|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 | /* import --------------------------------------------------------------------*/
26 | import * as json from "jsonc-parser"
27 | import i18n from "../../lang";
28 | /* interface -----------------------------------------------------------------*/
29 |
30 | /* data ----------------------------------------------------------------------*/
31 |
32 | /* methods -------------------------------------------------------------------*/
33 | export interface PvlTreeNode {
34 | show:boolean;
35 | chShow:boolean;
36 | checked:boolean;
37 | color:string;
38 | icon:string;
39 | label:string;
40 | depth:number;
41 | path:json.JSONPath;
42 | };
43 |
44 | export class PvlTree {
45 | public json_tree:json.Node;
46 | public view: Array;
47 | private getColorIcon:(label:string)=>[string,string];
48 |
49 | constructor(reactive:any, json_tree:json.Node, getColorIcon:(label:string)=>[string,string])
50 | {
51 | this.view = reactive;
52 | this.json_tree = json_tree
53 | this.getColorIcon = getColorIcon;
54 | this.parseNode(this.json_tree);
55 | }
56 |
57 | /**
58 | * 更新树
59 | * @param json_tree json 树
60 | */
61 | public update(json_tree?:json.Node){
62 | if (json_tree){
63 | this.json_tree = json_tree;
64 | }
65 |
66 | this.view.splice(0, this.view.length);
67 | this.parseNode(this.json_tree);
68 | }
69 |
70 | private addViewItem(show:boolean, checked:boolean, color:string, icon:string, label:string, path:json.JSONPath):void
71 | {
72 | let node:PvlTreeNode =
73 | {
74 | show:show,
75 | chShow:true,
76 | checked:checked,
77 | color:color,
78 | icon:icon,
79 | label:label,
80 | depth:path.length,
81 | path:path
82 | };
83 | this.view.push(node);
84 | }
85 |
86 | private parseNode(node:json.Node):void
87 | {
88 | /*!< property 节点有children属性, 并且0是属性的键, 1是属性的值 */
89 | if(node.type === 'property')
90 | {
91 | let key = (node.children as json.Node[])[0];
92 | let val = (node.children as json.Node[])[1];
93 |
94 | /*!< 叶子节点 */
95 | if (val.type !== 'object' && val.type !== 'array'){
96 | /*!< 叶子节点不再进行递归,并且数据不由树显示 */
97 | }
98 | else{
99 | let path = json.getNodePath(val);
100 | let label:string = key.value;
101 | if(!(label.length === 0)){
102 | label = label.trim().toLowerCase().replace(label[0], label[0].toUpperCase());
103 | }
104 | let [color, icon] = this.getColorIcon(label);
105 | this.addViewItem(true,false,color,icon,label,path);
106 | this.parseNode(val);
107 | }
108 | }
109 | else if (node.type === 'object')
110 | {
111 | if (typeof node.children === 'undefined'){
112 | console.log("children undefined");
113 | return;
114 | }
115 |
116 | for (let idx = 0; idx < node.children?.length; idx++){
117 | this.parseNode(node.children[idx]);
118 | }
119 | }
120 | else if (node.type === 'array')
121 | {
122 | let nodeLabel:string = "";
123 | let color:string = "";
124 | let icon:string = "";
125 |
126 | if (typeof node.parent === 'undefined'){
127 | console.log("parent undefined");
128 | return;
129 | }
130 |
131 | if(node.parent.type === 'property'){
132 | nodeLabel = (node.parent.children as json.Node[])[0].value;
133 | if(!(nodeLabel.length === 0)){
134 | nodeLabel = nodeLabel.trim().toLowerCase().replace(nodeLabel[0], nodeLabel[0].toUpperCase());
135 | }
136 | [color, icon] = this.getColorIcon(nodeLabel);
137 | }
138 | else{
139 | if(!(nodeLabel.length === 0)){
140 | nodeLabel = nodeLabel.trim().toLowerCase().replace(nodeLabel[0], nodeLabel[0].toUpperCase());
141 | }
142 | }
143 |
144 | for (let idx = 0; idx < (node.children as json.Node[]).length; idx++)
145 | {
146 | let path = json.getNodePath((node.children as json.Node[])[idx]);
147 | let label = nodeLabel + '.' + idx;
148 |
149 | this.addViewItem(true,false,color,icon,label,path);
150 | this.parseNode((node.children as json.Node[])[idx]);
151 | }
152 | }
153 | else{
154 | }
155 | }
156 | }
157 |
158 |
159 | /* class ---------------------------------------------------------------------*/
160 |
161 | /* export --------------------------------------------------------------------*/
162 |
163 |
164 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
165 |
--------------------------------------------------------------------------------
/packages/renderer/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/packages/renderer/src/global.d.ts:
--------------------------------------------------------------------------------
1 |
2 | export { }
3 |
4 | declare global {
5 | interface Window {
6 | removeLoading: () => void
7 | }
8 | }
9 |
10 | declare function $t(key:string, params?:any):string
11 |
--------------------------------------------------------------------------------
/packages/renderer/src/lang/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n'
2 |
3 | import zh_cn from './zh_cn'
4 |
5 | const i18n = createI18n({
6 | locale: 'zh-cn',
7 | fallbackLocale: 'zh-cn',
8 | messages: {
9 | "zh-cn": zh_cn
10 | }
11 | })
12 |
13 | export default i18n;
--------------------------------------------------------------------------------
/packages/renderer/src/lang/zh_cn.ts:
--------------------------------------------------------------------------------
1 |
2 | const zh = {
3 | lang:"中文简体",
4 | usbconfig:{
5 | title:{
6 | button:{
7 | file:"文件",
8 | project:"工程",
9 | view:"视图",
10 | compile:"编译",
11 | help:"帮助",
12 | about:"关于"
13 | },
14 | dropdown:{
15 | file0:"新建文件",
16 | file1:"打开文件",
17 | file2:"关闭文件",
18 | file3:"保存文件",
19 | file4:"另存为…",
20 | file5:"全部保存",
21 | file6:"退出",
22 | project0:"新建工程",
23 | project1:"打开工程",
24 | project2:"关闭工程",
25 | project3:"保存工程",
26 | project4:"另存为…",
27 | view0:"深色模式",
28 | view1:"浅色模式",
29 | view2:"高对比深色",
30 | view3:"高对比浅色",
31 | compile0:"生成 CherryUSB 风格描述符",
32 | compile1:"生成 STM32 风格描述符",
33 | compile2:"生成 ESP32 风格描述符",
34 | help0:"文档教程",
35 | help1:"视频教程",
36 | help2:"GITHUB 仓库",
37 | about0:"联系我们",
38 | about1:"赞助我们",
39 | about2:"软件说明"
40 | }
41 | },
42 | file:{
43 | basename:"CherryUSB 工程文件",
44 | filename:"CherryUSB 描述文件",
45 | cname:"C 文件",
46 | savebase:"保存工程文件",
47 | savefile:"保存描述文件",
48 | savec:"保存 C 文件",
49 | openbase:"打开工程文件",
50 | openfile:"打开描述文件",
51 | fileok:"加载文件成功",
52 | filefaild:"加载文件失败",
53 | filecancel:"未加载文件",
54 | parseok:"解析文件成功"
55 | },
56 | jsonerror:{
57 | error0:"存在语法错误位于第 [",
58 | error1:"] 个字符, 错误类型: ",
59 | error2:"",
60 | "0":"成功",
61 | "1":"无效的标识符",
62 | "2":"无效的数字格式",
63 | "3":"缺失属性名",
64 | "4":"缺失属性值",
65 | "5":"缺失 ':'",
66 | "6":"缺失 ','",
67 | "7":"缺失 '}'",
68 | "8":"缺失 ']'",
69 | "9":"缺失 'EOF'",
70 | "10":"非法的注释",
71 | "11":"多余的多行注释结束符",
72 | "12":"多余的 '\'' 或 '\"'",
73 | "13":"多余的小数点",
74 | "14":"非法的Unicode字符",
75 | "15":"非法的转义字符",
76 | "16":"非法的字符"
77 | }
78 | }
79 | }
80 |
81 | export default zh
--------------------------------------------------------------------------------
/packages/renderer/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | import router from './router'
5 |
6 | import ElementPlus from 'element-plus'
7 | import 'element-plus/dist/index.css'
8 |
9 | import '@mdi/font/css/materialdesignicons.css';
10 | import "animate.css"
11 |
12 | import ipcRendererSample from './mainModules/ipcRendererSample'
13 | import fsExample from './mainModules/builtinModuleSample'
14 | import sqliteExample from './mainModules/nodeModulesSample'
15 |
16 | import i18n from './lang'
17 |
18 | import './theme/theme.css'
19 | import theme from './theme'
20 |
21 | theme.SetTheme("dark");
22 |
23 | const app = createApp(App)
24 | .use(ElementPlus)
25 | .use(i18n)
26 | .use(router)
27 | .mount('#app')
28 | .$nextTick(() => {
29 | window.removeLoading()
30 | })
31 |
32 |
--------------------------------------------------------------------------------
/packages/renderer/src/mainModules/PvlFile.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file PvlFile.ts
4 | * @brief file operations
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.03.30
9 | ******************************************************************************
10 | * @attention
11 | *
12 | * © Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.03.30|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 |
26 |
27 | /* import --------------------------------------------------------------------*/
28 | import * as fs from 'fs';
29 | import * as pathM from "path";
30 |
31 | /* interface -----------------------------------------------------------------*/
32 | export interface IPvlFile{
33 | save(data?:string): [boolean,number?];
34 | saveForce(data?:string): [boolean, number?];
35 |
36 | saveAs(
37 | openSaveDialog:()=>Promise,
38 | data?:string,
39 | ): Promise<[boolean,number?]>;
40 |
41 | saveAsForce(
42 | openSaveDialog?:()=>Promise,
43 | data?:string
44 | ): Promise<[boolean,number?]>;
45 |
46 | rename(name:string, suffix:string): [boolean, number?];
47 |
48 | update(): [boolean,number?];
49 | isChanged():[boolean, (string|number)?];
50 | isSaved():boolean;
51 |
52 | get data():string;
53 | set data(data:string);
54 | }
55 |
56 | export declare const enum PvlFileError {
57 | PathNotExist = 1,
58 | ReadError = 2,
59 | WriteError = 3,
60 | FileChange = 4,
61 | Cancel = 5,
62 | RenameError = 6,
63 | FirstSave = 7,
64 | }
65 |
66 | /* data ----------------------------------------------------------------------*/
67 |
68 | /* methods -------------------------------------------------------------------*/
69 | function withNullAsUndefined(x: T | null): T | undefined {
70 | return x === null ? undefined : x;
71 | }
72 | /* class ---------------------------------------------------------------------*/
73 | export class PvlFile implements IPvlFile{
74 | public name: string;
75 | public path: string;
76 | public suffix: string;
77 | public dir: string;
78 | public change: boolean = false;
79 | public saved: boolean = false;
80 |
81 | private hash: number = 0;
82 | private newHash: number = 0;
83 | private internaldata: string = "";
84 |
85 | public static path2Info(path:string):[string, string, string, string]
86 | {
87 | var basename = pathM.basename(path);
88 | var suffix = pathM.extname(path);
89 | var pos = basename.lastIndexOf('.');
90 | if (suffix.indexOf('.') === 0)
91 | {
92 | suffix = suffix.substring(1);
93 | }
94 |
95 | if (pos < 0){
96 | return [
97 | pathM.dirname(path),
98 | path,
99 | suffix,
100 | path
101 | ];
102 | } else {
103 | return [
104 | pathM.dirname(path),
105 | basename.substring(0, pos),
106 | suffix,
107 | path
108 | ];
109 | }
110 | }
111 |
112 | public static info2Path(dir:string, name:string, suffix:string):string{
113 | return pathM.join(dir, name + '.' + suffix);
114 | }
115 |
116 | constructor(path:string){
117 | [this.dir, this.name, this.suffix, this.path] = PvlFile.path2Info(path);
118 | }
119 |
120 | public get data(): string {
121 | return this.internaldata
122 | }
123 | public set data(data: string) {
124 | this.newHash = PvlFile.getHash(data);
125 | this.internaldata = data;
126 |
127 | // if (this.newHash != this.hash){
128 | this.saved = false;
129 | // } else {
130 | // this.saved = true;
131 | // }
132 | }
133 |
134 | public static getHash(data:string):number{
135 | var hash = 0, i, char;
136 | if (data.length === 0){
137 | return 0;
138 | }
139 | for (i=0;iPromise, data?: string): Promise<[boolean,number?]> {
330 | return this.saveAsUtil(openSaveDialog, data, false);
331 | }
332 |
333 | /**
334 | * 文件另存为
335 | * 当文件路径不存在,直接保存当前数据到新文件中
336 | * 当文件原始文件产生改变,无视改变,仅写入当前数据到文件中
337 | * @param options 对话框属性
338 | * @param data 保存的数据(可选)
339 | * @returns 成功返回true, 取消或写入失败返回false和错误代码
340 | */
341 | public saveAsForce(openSaveDialog:()=>Promise, data?: string): Promise<[boolean,number?]> {
342 | return this.saveAsUtil(openSaveDialog, data, true);
343 | }
344 |
345 | private updateInfo():void{
346 | var nouse;
347 | [this.dir, this.name, this.suffix, nouse] = PvlFile.path2Info(this.path);
348 | }
349 |
350 | private async saveAsUtil(openSaveDialog:()=>Promise, data?: string, ignoreChange?:boolean): Promise<[boolean,number?]>
351 | {
352 | const [isChange, ndata] = this.isChanged();
353 |
354 | if (isChange){
355 | if (typeof ndata === 'string')
356 | {
357 | if ((typeof ignoreChange === 'boolean') && (ignoreChange === false)){
358 | return [false, 4];
359 | }
360 | }
361 | }
362 |
363 | const path = await openSaveDialog();
364 |
365 | if (typeof path === "undefined"){
366 | return [false, 5];
367 | }
368 |
369 | try{
370 | if (typeof data === 'undefined'){
371 | fs.writeFileSync(path as string, this.internaldata);
372 | } else {
373 | fs.writeFileSync(path as string, data);
374 | this.internaldata = data;
375 | }
376 |
377 | this.hash = PvlFile.getHash(this.internaldata);
378 | this.path = path;
379 | this.updateInfo();
380 | this.saved = true;
381 | this.change = false;
382 | return [true];
383 | } catch (err:any)
384 | {
385 | console.log(err.toString());
386 | return [false, 3]
387 | }
388 | }
389 |
390 | /**
391 | * 更新文件
392 | * 重新从文件读取数据, 覆盖当前内容
393 | * @returns
394 | */
395 | public update(): [boolean,number?] {
396 | const [isChange, ndata] = this.isChanged();
397 |
398 | if (isChange){
399 | if (typeof ndata === 'number')
400 | {
401 | return [false, ndata];
402 | }
403 | else{
404 | this.internaldata = ndata as string;
405 | this.saved = true;
406 | this.change = false;
407 | this.hash = PvlFile.getHash(this.internaldata);
408 | this.newHash = this.hash;
409 | return [true]
410 | }
411 | }
412 |
413 | this.change = false;
414 | return [true]
415 | }
416 | }
417 | /* export --------------------------------------------------------------------*/
418 |
419 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
420 |
--------------------------------------------------------------------------------
/packages/renderer/src/mainModules/UsbDescriptor.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file usbDescriptor.ts
4 | * @brief 简述
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.04.03
9 | ******************************************************************************
10 | * @attention
11 | *
12 | * © Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.04.03|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 | /* import --------------------------------------------------------------------*/
26 |
27 | /* interface -----------------------------------------------------------------*/
28 |
29 | /*!< chry base */
30 |
31 | /*!< 字符串描述符 */
32 | export interface IUSBStringInfo{
33 | name:string,
34 | value:string
35 | }
36 |
37 | /*!< chry 分组文件地址描述 */
38 | export type IUSBConfigGroupInfo = string;
39 |
40 | /*!< 配置描述符 */
41 | export interface IUSBConfigInfo{
42 | string:number,
43 | selfpower:boolean,
44 | remotewakeup:boolean,
45 | power:number,
46 | group:IUSBConfigGroupInfo[]
47 | }
48 |
49 | export interface IUSBDeviceInfo {
50 | usb:number,
51 | class:number,
52 | subclass:number,
53 | protocol:number,
54 | ep0size:number,
55 | pid:number,
56 | vid:number,
57 | version:number
58 | }
59 |
60 | /*!< 设备描述符 */
61 | export interface IUSBBaseInfo {
62 | device:IUSBDeviceInfo,
63 | string:IUSBStringInfo[],
64 | config:IUSBConfigInfo[]
65 | }
66 |
67 |
68 | /*!< chry */
69 | /*!< 端点描述符 */
70 | export interface IUSBEndpointInfo{
71 | address:number,
72 | direction:boolean,
73 | type:number,
74 | size:number,
75 | interval:number
76 | }
77 |
78 | export interface IUSBSpecificInfo{
79 | type:number
80 | }
81 |
82 | /*!< MSC 类特定描述符 */
83 | export interface IUSBSpecificMSCInfo{
84 | type:number
85 | }
86 |
87 | /*!< CDC ACM类特定描述符 */
88 | export interface IUSBSpecificCDCACMInfo{
89 | type:number
90 | }
91 |
92 | export interface IUSBSpecificCDCACMDATAInfo{
93 | type:number
94 | }
95 |
96 | /*!< HID类特定描述符 */
97 | export interface IUSBSpecificHIDInfo{
98 | type:number,
99 | }
100 |
101 | /*!< UAC10CTRL类特定描述符 */
102 | export interface IUSBSpecificUAC10CTRLInfo{
103 | type:number,
104 | uac_input:[],
105 | uac_output:[],
106 | uac_unit:[]
107 | }
108 |
109 | /*!< UAC10STREAM类特定描述符 */
110 | export interface IUSBSpecificUACS10STREAMInfo{
111 | type:number
112 | }
113 |
114 |
115 | /*!< 接口复用描述符 */
116 | export interface IUSBAlternateInfo{
117 | string:number,
118 | class:number,
119 | subclass:number,
120 | protocol:number,
121 | endpoint:IUSBEndpointInfo[],
122 | specific?:IUSBSpecificMSCInfo|IUSBSpecificCDCACMInfo|IUSBSpecificHIDInfo
123 | }
124 |
125 | /*!< 接口描述符 */
126 | export interface IUSBInterfaceInfo{
127 | alternate: IUSBAlternateInfo[]
128 | }
129 |
130 | /*!< IAD描述符 */
131 | export interface IUSBAssociateInfo{
132 | string:number,
133 | class:number,
134 | subclass:number,
135 | protocol:number,
136 | first:number,
137 | count:number
138 | }
139 |
140 | /*!< chry 分组描述符 */
141 | export interface IUSBGroupInfo {
142 | associate?: IUSBAssociateInfo
143 | interface: IUSBInterfaceInfo[]
144 | }
145 |
146 | /* data ----------------------------------------------------------------------*/
147 |
148 | /* methods -------------------------------------------------------------------*/
149 |
150 | /* class ---------------------------------------------------------------------*/
151 |
152 | /* export --------------------------------------------------------------------*/
153 |
154 |
155 |
156 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
157 |
--------------------------------------------------------------------------------
/packages/renderer/src/mainModules/builtinModuleSample.ts:
--------------------------------------------------------------------------------
1 |
2 | import fs from 'fs';
3 |
4 | const fsExample = () => {
5 | fs.lstat(process.cwd(),(err,stats)=>{
6 | if(err){
7 | console.log(err)
8 | }else{
9 | console.log(stats);
10 | }
11 | })
12 | }
13 | export default fsExample
--------------------------------------------------------------------------------
/packages/renderer/src/mainModules/ipcRendererSample.ts:
--------------------------------------------------------------------------------
1 | import {ipcRenderer} from 'electron' // rename from cjs to esm by plugin `resolveElectron` in packages/renderer/vite.config.ts
2 |
3 | const ipcRendererHelloWorld = () => {
4 | // Usage of ipcRenderer.on
5 | ipcRenderer.on('main-process-message', (_event, ...args) => {
6 | console.log('[Receive Main-process message]:', ...args)
7 | })
8 | }
9 | export default ipcRendererHelloWorld
10 |
--------------------------------------------------------------------------------
/packages/renderer/src/mainModules/nodeModulesSample.ts:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { ipcRenderer } from 'electron'
3 | import sqlite3 from 'sqlite3'
4 |
5 | const createSqlite3 = (userDataPath: string): Promise => {
6 | return new Promise((resolve, reject) => {
7 | const dbpath = path.join(userDataPath, 'sqlite3.db')
8 | console.log(dbpath)
9 | const db = new sqlite3.Database(dbpath, (error) => {
10 | if (error) {
11 | reject(error)
12 | return
13 | }
14 | resolve(db)
15 | })
16 | })
17 | }
18 |
19 | const sqliteExample = () => {
20 | ipcRenderer.on('app.getPath', async (_, userDataPath) => {
21 | try {
22 | const db = await createSqlite3(userDataPath)
23 | console.log(db)
24 | } catch (error) {
25 | console.error(error)
26 | }
27 | })
28 | ipcRenderer.send('app.getPath', 'userData')
29 |
30 | }
31 | export default sqliteExample
32 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/BUSB.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/BUSBGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
添加 IAD 接口关联描述符
4 |
初始化为 MSC
5 |
初始化为 HID
6 |
初始化为 CDC ACM
7 |
初始化为 UAC 1.0
8 |
初始化为 UAC 2.0
9 |
初始化为 UVC
10 |
11 |
12 |
13 |
14 |
64 |
65 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBAlternateInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{infoStr.string}}
5 |
CLASS
6 |
SubCLASS
7 |
PROTOCOL
8 |
9 |
10 |
11 |
12 |
191 |
192 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBAssociateInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{infoStr.first}}
5 |
COUNT
6 |
{{infoStr.string}}
7 |
8 |
9 |
CLASS
10 |
SubCLASS
11 |
PROTOCOL
12 |
13 |
14 |
15 |
16 |
17 |
257 |
258 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBConfigGroupInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
95 |
96 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBConfigInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{infoStr.selfpower}}
5 |
{{infoStr.remotewakeup}}
6 |
7 |
8 |
9 |
{{infoStr.string}}
10 |
POWER mA
11 |
12 |
13 |
14 |
15 |
16 |
229 |
230 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBDeviceInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{infoStr.usb}}
6 |
EP0SIZE
7 |
8 |
9 |
10 |
CLASS
11 |
SubCLASS
12 |
PROTOCOL
13 |
14 |
15 |
16 |
PID
17 |
VID
18 |
VERSION
19 |
20 |
21 |
22 |
186 |
187 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBEndpointInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{infoStr.direction}}
6 |
{{infoStr.type}}
7 |
8 |
9 |
ADDRESS
10 |
SIZE
11 |
12 |
15 |
16 |
17 |
18 |
19 |
266 |
267 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBSpecificInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
128 |
129 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/CUSBStringInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
STR NAME
4 |
{{info.name.toUpperCase()}}
5 |
6 |
7 |
90 |
91 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/tip.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/renderer/src/pages/usbConfigPages/usbConfigUtil.ts:
--------------------------------------------------------------------------------
1 | /****
2 | ******************************************************************************
3 | * @file usbConfigUtil.ts
4 | * @brief 简述
5 | * @author Egahp
6 | * 2687434412@qq.com
7 | * @version 1.0
8 | * @date 2022.04.03
9 | ******************************************************************************
10 | * @attention
11 | *
12 | * © Copyright 2021 Egahp.
13 | * All rights reserved.
14 | *
15 | * @htmlonly
16 | * History
17 | * @endhtmlonly
18 | * 版本|作者|时间|描述
19 | * ----|----|----|----
20 | * 1.0|Egahp|2022.04.03|创建文件
21 | *****************************************************************************
22 | */
23 |
24 |
25 | /* import --------------------------------------------------------------------*/
26 |
27 | /* interface -----------------------------------------------------------------*/
28 |
29 | /* data ----------------------------------------------------------------------*/
30 |
31 | /* methods -------------------------------------------------------------------*/
32 |
33 | /* class ---------------------------------------------------------------------*/
34 |
35 | /* export --------------------------------------------------------------------*/
36 | export function num2str(num:number, radix?:number, n?:number):string{
37 |
38 | if (radix && ((radix ==8) || (radix === 16) || (radix === 10))){
39 | var str = num.toString(radix);
40 | var prefix = (radix == 8) ? "0o" : ( radix == 16 ? "0x" : '' )
41 | } else {
42 | var str = num.toString();
43 | var prefix = '';
44 | }
45 |
46 | if (n)
47 | {
48 | let len = str.length;
49 | while(len < n){
50 | str = "0"+str;
51 | len++;
52 | }
53 | }
54 |
55 | return prefix+str;
56 | }
57 |
58 | export function str2num(str:string):number{
59 | if (str.toLocaleLowerCase().includes('0x')){
60 | return parseInt(str,16);
61 | } else if (str.toLocaleLowerCase().includes('0o')){
62 | return parseInt(str,8);
63 | } else {
64 | return parseInt(str,10);
65 | }
66 | }
67 |
68 | export function isUint8(num:number):boolean{
69 | return !isNaN(num) && num <= 0xff && num >= 0;
70 | }
71 |
72 | export function isUint16(num:number):boolean{
73 | return !isNaN(num) && num <= 0xffff && num >= 0;
74 | }
75 |
76 | export function isUint32(num:number):boolean{
77 | return !isNaN(num) && num <= 0xffffffff && num >= 0;
78 | }
79 |
80 | export function isUint64(num:number):boolean{
81 | return !isNaN(num) && num <= 0xffffffffffffffff && num >= 0;
82 | }
83 |
84 |
85 | /************************ (C) COPYRIGHT 2021 Egahp *****END OF FILE************/
86 |
--------------------------------------------------------------------------------
/packages/renderer/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from "vue-router";
2 |
3 | import UsbConfig from '../pages/UsbConfig.vue'
4 |
5 | const routes = [
6 | {
7 | path:'/',
8 | redirect:'/usb'
9 | },
10 | {
11 | path:'/usb',
12 | component: UsbConfig
13 | }
14 | ]
15 |
16 | export const router = createRouter({
17 | history:createWebHashHistory(),
18 | routes:routes
19 | })
20 |
21 | export default router
--------------------------------------------------------------------------------
/packages/renderer/src/theme/dark.ts:
--------------------------------------------------------------------------------
1 |
2 | const dark = {
3 | primary:[
4 | "#A197BF",
5 | "#D2C8F2",
6 | "#72698F"
7 | ],
8 | secondary:[
9 | "#97BF97",
10 | "#C8F2C8",
11 | "#688F69"
12 | ],
13 | success:[
14 | "#62FFA5",
15 | "#9BFFD7",
16 | "#12CB75"
17 | ],
18 | warning:[
19 | "#FFCF62",
20 | "#FFFF93",
21 | "#C99E32"
22 | ],
23 | danger:[
24 | "#FF6262",
25 | "#FF9590",
26 | "#C62C38"
27 | ],
28 | info:[
29 | "#BDBDBD",
30 | "#EFEFEF",
31 | "#8D8D8D"
32 | ],
33 | border:[
34 | "#222222",
35 | "#a197bf",
36 | "#383838"
37 | ],
38 | text:[
39 | "#CFD8DC",
40 | "#FFFFFF",
41 | "#8D8D8D"
42 | ],
43 | background:[
44 | "#353535",
45 | "#484848",
46 | "#2E2E2E"
47 | ],
48 | white:"#FFFFFF",
49 | black:"#000000"
50 |
51 | }
52 |
53 | export default dark
--------------------------------------------------------------------------------
/packages/renderer/src/theme/index.ts:
--------------------------------------------------------------------------------
1 | import dark from './dark'
2 | import light from './light'
3 |
4 | const theme = {
5 | theme:'light',
6 | themes: {
7 | "dark": dark,
8 | "light": light
9 | },
10 | SetTheme: function(key:string):void{
11 | theme.theme = key
12 | },
13 | Get : function(key:string,index:number):string{
14 | return theme.themes[theme.theme][key][index];
15 | },
16 | GetBlock: function(key:string):string{
17 | return theme.themes[theme.theme][key];
18 | },
19 | GetStyle: function():any{
20 | return {
21 | "--primary0":theme.Get("primary",0),
22 | "--primary1":theme.Get("primary",1),
23 | "--primary2":theme.Get("primary",2),
24 | "--secondary0":theme.Get("secondary",0),
25 | "--secondary1":theme.Get("secondary",1),
26 | "--secondary2":theme.Get("secondary",2),
27 | "--success0":theme.Get("success",0),
28 | "--success1":theme.Get("success",1),
29 | "--success2":theme.Get("success",2),
30 | "--warning0":theme.Get("warning",0),
31 | "--warning1":theme.Get("warning",1),
32 | "--warning2":theme.Get("warning",2),
33 | "--danger0":theme.Get("danger",0),
34 | "--danger1":theme.Get("danger",1),
35 | "--danger2":theme.Get("danger",2),
36 | "--info0":theme.Get("info",0),
37 | "--info1":theme.Get("info",1),
38 | "--info2":theme.Get("info",2),
39 | "--border0":theme.Get("border",0),
40 | "--border1":theme.Get("border",1),
41 | "--border2":theme.Get("border",2),
42 | "--text0":theme.Get("text",0),
43 | "--text1":theme.Get("text",1),
44 | "--text2":theme.Get("text",2),
45 | "--background0":theme.Get("background",0),
46 | "--background1":theme.Get("background",1),
47 | "--background2":theme.Get("background",2)
48 | }
49 | }
50 | }
51 |
52 | export default theme;
--------------------------------------------------------------------------------
/packages/renderer/src/theme/light.ts:
--------------------------------------------------------------------------------
1 |
2 | const light = {
3 | primary:[
4 | "#BBDEFB",
5 | "#EEFFFF",
6 | "#8AACC8"
7 | ],
8 | secondary:[
9 | "#4DD0E1",
10 | "#88FFFF",
11 | "#009FAF"
12 | ],
13 | success:[
14 | "#4DD0E1",
15 | "#88FFFF",
16 | "#009FAF"
17 | ],
18 | warning:[
19 | "#FFEE58",
20 | "#FFFF8B",
21 | "#C9BC1F"
22 | ],
23 | danger:[
24 | "#F44336",
25 | "#FF7961",
26 | "#BA000D"
27 | ],
28 | info:[
29 | "#424242",
30 | "#6D6D6D",
31 | "#1B1B1B"
32 | ],
33 | border:[
34 | "#E0E0E0",
35 | "#FFFFFF",
36 | "#AEAEAE"
37 | ],
38 | text:[
39 | "#9E9E9E",
40 | "#000000",
41 | "#EFEFEF"
42 | ],
43 | background:[
44 | "#F5F5F5",
45 | "#FFFFFF",
46 | "#C2C2C2"
47 | ],
48 | white:"#FFFFFF",
49 | black:"#000000"
50 |
51 | }
52 |
53 | export default light
--------------------------------------------------------------------------------
/packages/renderer/src/theme/theme.css:
--------------------------------------------------------------------------------
1 | :root{
2 | --primary0:#A197BF;
3 | --primary1:#D2C8F2;
4 | --primary2:#72698F;
5 |
6 | --secondary0:#97BF97;
7 | --secondary1:#C8F2C8;
8 | --secondary2:#688F69;
9 |
10 | --success0:#97BF97;
11 | --success1:#C8F2C8;
12 | --success2:#688F69;
13 |
14 | --warning0:#BEBF97;
15 | --warning1:#F1F2C8;
16 | --warning2:#8D8F69;
17 |
18 | --danger0:#BF9797;
19 | --danger1:#F2C8C8;
20 | --danger2:#8E6969;
21 |
22 | --info0:#BDBDBD;
23 | --info1:#EFEFEF;
24 | --info2:#8D8D8D;
25 |
26 | --border0:#222222;
27 | --border1:#a197bf;
28 | --border2:#383838;
29 |
30 | --text0:#CFD8DC;
31 | --text1:#FFFFFF;
32 | --text2:#8D8D8D;
33 |
34 | --background0:#353535;
35 | --background1:#484848;
36 | --background2:#2E2E2E;
37 | }
--------------------------------------------------------------------------------
/packages/renderer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "useDefineForClassFields": true,
6 | "module": "esnext",
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "jsx": "preserve",
10 | "sourceMap": true,
11 | "resolveJsonModule": true,
12 | "esModuleInterop": true,
13 | "lib": ["esnext", "dom"]
14 | },
15 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/renderer/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { builtinModules } from 'module'
2 | import { defineConfig, Plugin } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import resolve from 'vite-plugin-resolve'
5 | import pkg from '../../package.json'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | mode: process.env.NODE_ENV,
10 | root: __dirname,
11 | plugins: [
12 | vue(),
13 | resolveElectron(
14 | /**
15 | * Here you can specify other modules
16 | * 🚧 You have to make sure that your module is in `dependencies` and not in the` devDependencies`,
17 | * which will ensure that the electron-builder can package it correctly
18 | * @example
19 | * {
20 | * 'electron-store': 'const Store = require("electron-store"); export default Store;',
21 | * }
22 | */
23 | {
24 | sqlite3: 'const sqlite3 = require("sqlite3"); export default sqlite3;'
25 | },
26 | ),
27 | ],
28 | base: './',
29 | build: {
30 | sourcemap: true,
31 | outDir: '../../dist/renderer',
32 | },
33 | server: {
34 | host: pkg.env.VITE_DEV_SERVER_HOST,
35 | port: pkg.env.VITE_DEV_SERVER_PORT,
36 | },
37 | })
38 |
39 | /**
40 | * For usage of Electron and NodeJS APIs in the Renderer process
41 | * @see https://github.com/caoxiemeihao/electron-vue-vite/issues/52
42 | */
43 | export function resolveElectron(
44 | resolves: Parameters[0] = {}
45 | ): Plugin {
46 | const builtins = builtinModules.filter((t) => !t.startsWith('_'))
47 | /**
48 | * @see https://github.com/caoxiemeihao/vite-plugins/tree/main/packages/resolve#readme
49 | */
50 | return resolve({
51 | electron: electronExport(),
52 | ...builtinModulesExport(builtins),
53 | ...resolves,
54 | })
55 |
56 | function electronExport() {
57 | return `
58 | /**
59 | * For all exported modules see https://www.electronjs.org/docs/latest/api/clipboard -> Renderer Process Modules
60 | */
61 | const electron = require("electron");
62 | const {
63 | clipboard,
64 | nativeImage,
65 | shell,
66 | contextBridge,
67 | crashReporter,
68 | ipcRenderer,
69 | webFrame,
70 | desktopCapturer,
71 | deprecate,
72 | } = electron;
73 |
74 | export {
75 | electron as default,
76 | clipboard,
77 | nativeImage,
78 | shell,
79 | contextBridge,
80 | crashReporter,
81 | ipcRenderer,
82 | webFrame,
83 | desktopCapturer,
84 | deprecate,
85 | }
86 | `
87 | }
88 |
89 | function builtinModulesExport(modules: string[]) {
90 | return modules
91 | .map((moduleId) => {
92 | const nodeModule = require(moduleId)
93 | const requireModule = `const M = require("${moduleId}");`
94 | const exportDefault = `export default M;`
95 | const exportMembers =
96 | Object.keys(nodeModule)
97 | .map((attr) => `export const ${attr} = M.${attr}`)
98 | .join(';\n') + ';'
99 | const nodeModuleCode = `
100 | ${requireModule}
101 |
102 | ${exportDefault}
103 |
104 | ${exportMembers}
105 | `
106 |
107 | return { [moduleId]: nodeModuleCode }
108 | })
109 | .reduce((memo, item) => Object.assign(memo, item), {})
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { PlaywrightTestConfig, devices } from '@playwright/test';
2 |
3 | const config: PlaywrightTestConfig = {
4 | forbidOnly: !!process.env.CI,
5 | retries: process.env.CI ? 2 : 0,
6 | use: {
7 | trace: 'on-first-retry',
8 | },
9 | projects: [
10 | {
11 | name: 'chromium',
12 | use: { ...devices['Desktop Chrome'] },
13 | },
14 | ],
15 | };
16 | export default config;
--------------------------------------------------------------------------------
/scripts/build.mjs:
--------------------------------------------------------------------------------
1 | import { build } from 'vite'
2 |
3 | await build({ configFile: 'packages/main/vite.config.ts' })
4 | await build({ configFile: 'packages/preload/vite.config.ts' })
5 | await build({ configFile: 'packages/renderer/vite.config.ts' })
6 |
--------------------------------------------------------------------------------
/scripts/watch.mjs:
--------------------------------------------------------------------------------
1 | import { spawn } from 'child_process'
2 | import { createServer, build } from 'vite'
3 | import electron from 'electron'
4 |
5 | const query = new URLSearchParams(import.meta.url.split('?')[1])
6 | const debug = query.has('debug')
7 |
8 | /**
9 | * @type {(server: import('vite').ViteDevServer) => Promise}
10 | */
11 | function watchMain(server) {
12 | /**
13 | * @type {import('child_process').ChildProcessWithoutNullStreams | null}
14 | */
15 | let electronProcess = null
16 | const address = server.httpServer.address()
17 | const env = Object.assign(process.env, {
18 | VITE_DEV_SERVER_HOST: address.address,
19 | VITE_DEV_SERVER_PORT: address.port,
20 | })
21 | /**
22 | * @type {import('vite').Plugin}
23 | */
24 | const startElectron = {
25 | name: 'electron-main-watcher',
26 | writeBundle() {
27 | electronProcess && electronProcess.kill()
28 | electronProcess = spawn(electron, ['.'], { stdio: 'inherit', env })
29 | },
30 | }
31 |
32 | return build({
33 | configFile: 'packages/main/vite.config.ts',
34 | mode: 'development',
35 | plugins: [!debug && startElectron].filter(Boolean),
36 | build: {
37 | watch: true,
38 | },
39 | })
40 | }
41 |
42 | /**
43 | * @type {(server: import('vite').ViteDevServer) => Promise}
44 | */
45 | function watchPreload(server) {
46 | return build({
47 | configFile: 'packages/preload/vite.config.ts',
48 | mode: 'development',
49 | plugins: [{
50 | name: 'electron-preload-watcher',
51 | writeBundle() {
52 | server.ws.send({ type: 'full-reload' })
53 | },
54 | }],
55 | build: {
56 | watch: true,
57 | },
58 | })
59 | }
60 |
61 | // bootstrap
62 | const server = await createServer({ configFile: 'packages/renderer/vite.config.ts' })
63 |
64 | await server.listen()
65 | await watchPreload(server)
66 | await watchMain(server)
67 |
--------------------------------------------------------------------------------
/test/demo/cdc.chry:
--------------------------------------------------------------------------------
1 | {
2 | "associate": {
3 | "string": 0,
4 | "class": "0x02",
5 | "subclass": "0x02",
6 | "protocol": "0x01",
7 | "first": 0,
8 | "count": 2
9 | },
10 | "interface": [
11 | {
12 | "alternate": [
13 | {
14 | "string": 0,
15 | "class": "0x02",
16 | "subclass": "0x02",
17 | "protocol": "0x01",
18 | "specific": {
19 | "type": 3
20 | },
21 | "endpoint": [
22 | {
23 | "address": 4,
24 | "direction": "out",
25 | "type": "intr",
26 | "size": 64,
27 | "interval": 1
28 | }
29 | ]
30 | }
31 | ]
32 | },
33 | {
34 | "alternate": [
35 | {
36 | "string": 0,
37 | "class": "0x0a",
38 | "subclass": "0x00",
39 | "protocol": "0x00",
40 | "specific": {
41 | "type": 4
42 | },
43 | "endpoint": [
44 | {
45 | "address": 5,
46 | "direction": "out",
47 | "type": "bulk",
48 | "size": 64,
49 | "interval": 0
50 | },
51 | {
52 | "address": 6,
53 | "direction": "in",
54 | "type": "bulk",
55 | "size": 64,
56 | "interval": 0
57 | }
58 | ]
59 | }
60 | ]
61 | }
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/test/demo/demo.chrybase:
--------------------------------------------------------------------------------
1 | {
2 | "device": {
3 | "usb": 1.1,
4 | "class": "0x00",
5 | "subclass": "0x00",
6 | "protocol": "0x00",
7 | "ep0size": 64,
8 | "pid": "0xffff",
9 | "vid": "0xffff",
10 | "version": 1
11 | },
12 | "config": [
13 | {
14 | "string": 3,
15 | "selfpower": false,
16 | "remotewakeup": true,
17 | "power": 500,
18 | "group": [
19 | "X:\\web\\electron\\chryusb_configurator\\test\\test3\\hid_keyboard.chry",
20 | "X:\\web\\electron\\chryusb_configurator\\test\\test3\\msc.chry"
21 | ]
22 | }
23 | ],
24 | "string": [
25 | {
26 | "name": "manufacturer",
27 | "value": "Cherry USB Team"
28 | },
29 | {
30 | "name": "product",
31 | "value": "Demo Product"
32 | },
33 | {
34 | "name": "serial number",
35 | "value": "202204111454"
36 | },
37 | {
38 | "name": "hid keyboard",
39 | "value": "Demo HID Keyboard"
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/test/demo/hid_keyboard.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 4,
7 | "class": "0x03",
8 | "subclass": "0x00",
9 | "protocol": "0x00",
10 | "specific": {
11 | "type": 2
12 | },
13 | "endpoint": [
14 | {
15 | "address": 1,
16 | "direction": "in",
17 | "type": "intr",
18 | "size": 64,
19 | "interval": 1
20 | }
21 | ]
22 | }
23 | ]
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/test/demo/msc.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 0,
7 | "class": "0x08",
8 | "subclass": "0x06",
9 | "protocol": "0x50",
10 | "specific": {
11 | "type": 1
12 | },
13 | "endpoint": [
14 | {
15 | "address": 2,
16 | "direction": "out",
17 | "type": "bulk",
18 | "size": 64,
19 | "interval": 0
20 | },
21 | {
22 | "address": 3,
23 | "direction": "in",
24 | "type": "bulk",
25 | "size": 64,
26 | "interval": 0
27 | }
28 | ]
29 | }
30 | ]
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/test/example.spec.ts:
--------------------------------------------------------------------------------
1 | // example.spec.ts
2 | import { test, expect } from '@playwright/test'
3 | import { env } from '../package.json'
4 | const VITE_SERVER_ADDRESS = `http://127.0.0.1:${env.PORT || 3344}`
5 |
6 | test('example test case', async ({ page }) => {
7 | await page.goto(VITE_SERVER_ADDRESS)
8 |
9 | // Expect a title "to contain" a substring.
10 | await expect(page).toHaveTitle(/Vite App/)
11 |
12 | // Expect an attribute "Hello Vue 3 + TypeScript + Vite" to be visible on the page.
13 | await expect(
14 | page.locator('text=Hello Vue 3 + TypeScript + Vite').first(),
15 | ).toBeVisible()
16 | })
17 |
--------------------------------------------------------------------------------
/test/uac/uac_audio_speaker.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 0,
7 | "class": "0x01",
8 | "subclass": "0x01",
9 | "protocol": "0x00",
10 | "specific": {
11 | "uac-header": "default",
12 | "uac-group": [
13 | {
14 | "uac-input": "0x0101",
15 | "uac-feature": "default",
16 | "uac-output": "0x0302"
17 | }
18 | ]
19 | }
20 | }
21 | ]
22 | },
23 | {
24 | "alternate": [
25 | {
26 | "string": 0,
27 | "class": "0x01",
28 | "subclass": "0x02",
29 | "protocol": "0x00"
30 | },
31 | {
32 | "string": 0,
33 | "class": "0x01",
34 | "subclass": "0x02",
35 | "protocol": "0x00",
36 | "specific": {
37 | "uac-specific": 0,
38 | "uac-format": {
39 | "channels": 2,
40 | "width": 24,
41 | "rate": [
42 | 48000,
43 | 44100,
44 | 32000,
45 | 22050,
46 | 16000,
47 | 8000
48 | ]
49 | },
50 | "uac-endpoint": "default"
51 | },
52 | "endpoint": [
53 | {
54 | "address": 1,
55 | "direction": "out",
56 | "type": "isoc",
57 | "size": 64,
58 | "interval": 1,
59 | "refresh": 0,
60 | "sync-address": 0
61 | }
62 | ]
63 | }
64 | ]
65 | }
66 | ]
67 | }
--------------------------------------------------------------------------------
/test/uac/uac_audio_speaker_voice_mic.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 0,
7 | "class": "0x01",
8 | "subclass": "0x01",
9 | "protocol": "0x00",
10 | "specific": {
11 | "uac-header": "default",
12 | "uac-group": [
13 | {
14 | "uac-input": "0x0101",
15 | "uac-feature": "default",
16 | "uac-output": "0x0302"
17 | },
18 | {
19 | "uac-input": "0x0402",
20 | "uac-feature": "default",
21 | "uac-output": "0x0101"
22 | }
23 | ]
24 | }
25 | }
26 | ]
27 | },
28 | {
29 | "alternate": [
30 | {
31 | "string": 0,
32 | "class": "0x01",
33 | "subclass": "0x02",
34 | "protocol": "0x00"
35 | },
36 | {
37 | "string": 0,
38 | "class": "0x01",
39 | "subclass": "0x02",
40 | "protocol": "0x00",
41 | "specific": {
42 | "uac-specific": 0,
43 | "uac-format": {
44 | "channels": 2,
45 | "width": 24,
46 | "rate": [
47 | 48000,
48 | 44100,
49 | 32000,
50 | 22050,
51 | 16000,
52 | 8000
53 | ]
54 | },
55 | "uac-endpoint": "default"
56 | },
57 | "endpoint": [
58 | {
59 | "address": 1,
60 | "direction": "out",
61 | "type": "isoc",
62 | "size": 64,
63 | "interval": 1,
64 | "refresh": 0,
65 | "sync-address": 0
66 | }
67 | ]
68 | }
69 | ]
70 | },
71 | {
72 | "alternate": [
73 | {
74 | "string": 0,
75 | "class": "0x01",
76 | "subclass": "0x02",
77 | "protocol": "0x00"
78 | },
79 | {
80 | "string": 0,
81 | "class": "0x01",
82 | "subclass": "0x02",
83 | "protocol": "0x00",
84 | "specific": {
85 | "uac-specific": 1,
86 | "uac-format": {
87 | "channels": 1,
88 | "width": 16,
89 | "rate": [
90 | 16000,
91 | 8000
92 | ]
93 | },
94 | "uac-endpoint": "default"
95 | },
96 | "endpoint": [
97 | {
98 | "address": 1,
99 | "direction": "in",
100 | "type": "isoc",
101 | "size": 64,
102 | "interval": 1,
103 | "refresh": 0,
104 | "sync-address": 0
105 | }
106 | ]
107 | }
108 | ]
109 | }
110 | ]
111 | }
--------------------------------------------------------------------------------
/test/uac/uac_voice_mic.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 0,
7 | "class": "0x01",
8 | "subclass": "0x01",
9 | "protocol": "0x00",
10 | "specific": {
11 | "uac-header": "default",
12 | "uac-group": [
13 | {
14 | "uac-input": "0x0402",
15 | "uac-feature": "default",
16 | "uac-output": "0x0101"
17 | }
18 | ]
19 | }
20 | }
21 | ]
22 | },
23 | {
24 | "alternate": [
25 | {
26 | "string": 0,
27 | "class": "0x01",
28 | "subclass": "0x02",
29 | "protocol": "0x00"
30 | },
31 | {
32 | "string": 0,
33 | "class": "0x01",
34 | "subclass": "0x02",
35 | "protocol": "0x00",
36 | "specific": {
37 | "uac-specific": 0,
38 | "uac-format": {
39 | "channels": 1,
40 | "width": 16,
41 | "rate": [
42 | 16000,
43 | 8000
44 | ]
45 | },
46 | "uac-endpoint": "default"
47 | },
48 | "endpoint": [
49 | {
50 | "address": 1,
51 | "direction": "in",
52 | "type": "isoc",
53 | "size": 64,
54 | "interval": 1,
55 | "refresh": 0,
56 | "sync-address": 0
57 | }
58 | ]
59 | }
60 | ]
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/test/uac/uac_voice_speaker.chry:
--------------------------------------------------------------------------------
1 | {
2 | "interface": [
3 | {
4 | "alternate": [
5 | {
6 | "string": 0,
7 | "class": "0x01",
8 | "subclass": "0x01",
9 | "protocol": "0x00",
10 | "specific": {
11 | "uac-header": "default",
12 | "uac-group": [
13 | {
14 | "uac-input": "0x0101",
15 | "uac-feature": "default",
16 | "uac-output": "0x0402"
17 | }
18 | ]
19 | }
20 | }
21 | ]
22 | },
23 | {
24 | "alternate": [
25 | {
26 | "string": 0,
27 | "class": "0x01",
28 | "subclass": "0x02",
29 | "protocol": "0x00"
30 | },
31 | {
32 | "string": 0,
33 | "class": "0x01",
34 | "subclass": "0x02",
35 | "protocol": "0x00",
36 | "specific": {
37 | "uac-specific": 0,
38 | "uac-format": {
39 | "channels": 1,
40 | "width": 16,
41 | "rate": [
42 | 16000,
43 | 8000
44 | ]
45 | },
46 | "uac-endpoint": "default"
47 | },
48 | "endpoint": [
49 | {
50 | "address": 1,
51 | "direction": "out",
52 | "type": "isoc",
53 | "size": 64,
54 | "interval": 1,
55 | "refresh": 0,
56 | "sync-address": 0
57 | }
58 | ]
59 | }
60 | ]
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "preserve",
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "sourceMap": true,
11 | "baseUrl": "./",
12 | "strict": true,
13 | "paths": {},
14 | "suppressImplicitAnyIndexErrors": true,
15 | "allowSyntheticDefaultImports": true,
16 | "skipLibCheck": true,
17 | "types": ["element-plus/global"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/types.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare namespace NodeJS {
3 | interface ProcessEnv {
4 | NODE_ENV: 'development' | 'production'
5 | readonly VITE_DEV_SERVER_HOST: string
6 | readonly VITE_DEV_SERVER_PORT: string
7 | }
8 | }
9 |
--------------------------------------------------------------------------------