├── .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 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | 35 | 36 | 53 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlBtn.vue: -------------------------------------------------------------------------------- 1 | 6 | 116 | 117 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlCheckbox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlDropDown.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 185 | 186 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlInput.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 147 | 148 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlLog.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 128 | 129 | 189 | 190 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlRadios.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 180 | 181 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlSwitch.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlTitleBar.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 48 | 49 | 104 | -------------------------------------------------------------------------------- /packages/renderer/src/components/util/PvlTree.vue: -------------------------------------------------------------------------------- 1 | 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 | 6 | 7 | 63 | 64 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/BUSBGroup.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 64 | 65 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBAlternateInfo.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 191 | 192 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBAssociateInfo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 257 | 258 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBConfigGroupInfo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 95 | 96 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBConfigInfo.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 229 | 230 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBDeviceInfo.vue: -------------------------------------------------------------------------------- 1 | 22 | 186 | 187 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBEndpointInfo.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 266 | 267 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBSpecificInfo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 128 | 129 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/CUSBStringInfo.vue: -------------------------------------------------------------------------------- 1 | 7 | 90 | 91 | -------------------------------------------------------------------------------- /packages/renderer/src/pages/usbConfigPages/tip.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------