diff --git a/desktop/src/main/device/serial-port.ts b/desktop/src/main/device/serial-port.ts index 248a783..142395a 100644 --- a/desktop/src/main/device/serial-port.ts +++ b/desktop/src/main/device/serial-port.ts @@ -15,12 +15,17 @@ export class SerialPort { ): Promise { try { if (this.port?.isOpen) { + console.log('Closing existing serial port before opening new one') await this.close() + await new Promise(resolve => setTimeout(resolve, 100)) } + console.log(`Opening serial port: ${path} at ${baudRate} baud`) this.port = new SP({ path, baudRate }, (err) => { if (err) { console.error('Error opening port: ', err.message) + } else { + console.log(`Serial port ${path} opened successfully at ${baudRate} baud`) } onOpen(err) }) @@ -72,10 +77,24 @@ export class SerialPort { async close(): Promise { if (this.port?.isOpen) { try { - this.port.close() + console.log('Closing serial port...') + await new Promise((resolve, reject) => { + this.port!.close((err) => { + if (err) { + console.error('close-serial-port error', err) + reject(err) + } else { + console.log('Serial port closed successfully') + resolve() + } + }) + }) } catch (error) { console.error('close-serial-port error', error) + throw error } + } else { + console.log('Serial port is already closed or not initialized') } } } diff --git a/desktop/src/main/events/serial-port.ts b/desktop/src/main/events/serial-port.ts index fbc005b..a53fe56 100644 --- a/desktop/src/main/events/serial-port.ts +++ b/desktop/src/main/events/serial-port.ts @@ -26,7 +26,7 @@ async function getSerialPorts(): Promise { async function openSerialPort( e: IpcMainInvokeEvent, path: string, - baudRate = 57600 + baudRate: number = 57600 ): Promise { try { await device.serialPort.init(path, baudRate, (err) => { diff --git a/desktop/src/renderer/src/components/device-modal/serial-port.tsx b/desktop/src/renderer/src/components/device-modal/serial-port.tsx index 7ad64e9..2f79829 100644 --- a/desktop/src/renderer/src/components/device-modal/serial-port.tsx +++ b/desktop/src/renderer/src/components/device-modal/serial-port.tsx @@ -1,10 +1,10 @@ import { ReactElement, useEffect, useState } from 'react' -import { Select } from 'antd' +import { Select, Space } from 'antd' import { useAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { IpcEvents } from '@common/ipc-events' -import { serialPortAtom, serialPortStateAtom } from '@renderer/jotai/device' +import { serialPortAtom, serialPortStateAtom, baudRateAtom } from '@renderer/jotai/device' import * as storage from '@renderer/libs/storage' type Option = { @@ -21,11 +21,27 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => { const [serialPort, setSerialPort] = useAtom(serialPortAtom) const [serialPortState, setSerialPortState] = useAtom(serialPortStateAtom) + const [baudRate, setBaudRate] = useAtom(baudRateAtom) const [options, setOptions] = useState([]) const [isFailed, setIsFailed] = useState(false) + const baudRateOptions = [ + { value: 1200, label: '1200' }, + { value: 2400, label: '2400' }, + { value: 4800, label: '4800' }, + { value: 9600, label: '9600' }, + { value: 14400, label: '14400' }, + { value: 19200, label: '19200' }, + { value: 38400, label: '38400' }, + { value: 57600, label: '57600' }, + { value: 115200, label: '115200' } + ] + useEffect(() => { + const savedBaudRate = storage.getBaudRate() + setBaudRate(savedBaudRate) + getSerialPorts(true) const rmListener = window.electron.ipcRenderer.on(IpcEvents.OPEN_SERIAL_PORT_RSP, (_, err) => { @@ -63,7 +79,7 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => { setIsFailed(false) setMsg('') - const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port) + const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port, baudRate) if (success) { setSerialPort(port) @@ -73,16 +89,45 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => { } } + async function closeSerialPort(): Promise { + await window.electron.ipcRenderer.invoke(IpcEvents.CLOSE_SERIAL_PORT) + setSerialPort('') + setSerialPortState('disconnected') + storage.setSerialPort('') + } + + async function handleBaudRateChange(newBaudRate: number): Promise { + setBaudRate(newBaudRate) + storage.setBaudRate(newBaudRate) + + if (serialPort && serialPortState === 'connected') { + const currentPort = serialPort + await closeSerialPort() + setTimeout(() => { + selectSerialPort(currentPort) + }, 200) + } + } + return ( - getSerialPorts(false)} + /> + - ))} + ) return ( -
+
diff --git a/desktop/src/renderer/src/components/menu/settings/index.tsx b/desktop/src/renderer/src/components/menu/settings/index.tsx index c62f786..b96aefb 100644 --- a/desktop/src/renderer/src/components/menu/settings/index.tsx +++ b/desktop/src/renderer/src/components/menu/settings/index.tsx @@ -1,7 +1,7 @@ import { ReactElement, useEffect, useState } from 'react' import { Badge, Modal } from 'antd' import clsx from 'clsx' -import { BadgeInfoIcon, CircleArrowUpIcon, PaletteIcon, SettingsIcon } from 'lucide-react' +import { BadgeInfoIcon, CircleArrowUpIcon, PaletteIcon, SettingsIcon, RotateCcwIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { IpcEvents } from '@common/ipc-events' @@ -10,6 +10,7 @@ import * as storage from '@renderer/libs/storage' import { About } from './about' import { Appearance } from './appearance' import { Update } from './update' +import { Reset } from './reset' export const Settings = (): ReactElement => { const { t } = useTranslation() @@ -32,6 +33,7 @@ export const Settings = (): ReactElement => { const tabs = [ { id: 'appearance', icon: , component: }, { id: 'update', icon: , component: }, + { id: 'reset', icon: , component: }, { id: 'about', icon: , component: } ] diff --git a/desktop/src/renderer/src/components/menu/settings/reset.tsx b/desktop/src/renderer/src/components/menu/settings/reset.tsx new file mode 100644 index 0000000..1256977 --- /dev/null +++ b/desktop/src/renderer/src/components/menu/settings/reset.tsx @@ -0,0 +1,64 @@ +import { ReactElement, useState } from 'react' +import { Button, Modal } from 'antd' +import { useTranslation } from 'react-i18next' + +import * as storage from '@renderer/libs/storage' + +export const Reset = (): ReactElement => { + const { t } = useTranslation() + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false) + + function handleReset(): void { + setIsConfirmModalOpen(true) + } + + function confirmReset(): void { + storage.clearAllSettings() + setIsConfirmModalOpen(false) + // 重新加载应用以应用重置 + window.location.reload() + } + + function cancelReset(): void { + setIsConfirmModalOpen(false) + } + + return ( + <> +
+
+
{t('settings.reset.title')}
+
{t('settings.reset.description')}
+
+ +
+
+
{t('settings.reset.warning')}
+
{t('settings.reset.warningDescription')}
+
+ + +
+
+ + +

{t('settings.reset.confirmMessage')}

+
+ + ) +} diff --git a/desktop/src/renderer/src/i18n/locales/be.ts b/desktop/src/renderer/src/i18n/locales/be.ts index ce82906..ef59a6a 100644 --- a/desktop/src/renderer/src/i18n/locales/be.ts +++ b/desktop/src/renderer/src/i18n/locales/be.ts @@ -8,15 +8,22 @@ const be = { failed: 'Kan geen verbinding maken met de camera. Probeer opnieuw.' }, modal: { - title: 'Kies USB-apparaat', - selectVideo: 'Kies een video-invoerapparaat', - selectSerial: 'Kies serieel apparaat' - }, + title: 'Kies USB-apparaat', + selectVideo: 'Selecteer een video-invoerapparaat', + selectSerial: 'Kies serieel apparaat', + selectBaudRate: 'Selecteer baudrate' + }, menu: { - serial: 'Serieel', - keyboard: 'Klavier', - mouse: 'Muis' - }, + serial: 'Serieel', + keyboard: 'Toetsenbord', + mouse: 'Muis', + serialPort: { + device: 'Serieel apparaat', + baudRate: 'Baudrate', + noDeviceFound: 'Geen seriële apparaten gevonden', + clickToSelect: 'Klik om seriële poort te selecteren' + } + }, video: { resolution: 'Resolutie', customResolution: 'Aangepast', @@ -72,6 +79,17 @@ const be = { title: 'Over', version: 'Versie', community: 'Community' + }, + reset: { + title: 'Instellingen resetten', + description: 'Alle applicatie-instellingen resetten naar standaardwaarden', + warning: 'Waarschuwing', + warningDescription: 'Deze actie kan niet ongedaan worden gemaakt. Alle aangepaste instellingen gaan verloren.', + button: 'Alle instellingen resetten', + confirmTitle: 'Reset bevestigen', + confirmMessage: 'Weet u zeker dat u alle instellingen wilt resetten? Deze actie kan niet ongedaan worden gemaakt.', + confirm: 'Reset', + cancel: 'Annuleren' } } } diff --git a/desktop/src/renderer/src/i18n/locales/de.ts b/desktop/src/renderer/src/i18n/locales/de.ts index 8a1ac47..d11a42c 100644 --- a/desktop/src/renderer/src/i18n/locales/de.ts +++ b/desktop/src/renderer/src/i18n/locales/de.ts @@ -8,15 +8,22 @@ const de = { failed: 'Kamera konnte nicht verbunden werden. Bitte versuche es erneut.' }, modal: { - title: 'USB-Gerät auswählen', - selectVideo: 'Bitte wähle ein Video-Eingabegerät aus', - selectSerial: 'Serielles Gerät auswählen' - }, + title: 'USB-Gerät auswählen', + selectVideo: 'Bitte wählen Sie ein Video-Eingabegerät', + selectSerial: 'Serielles Gerät auswählen', + selectBaudRate: 'Bitte wählen Sie die Baudrate' + }, menu: { - serial: 'Seriell', - keyboard: 'Tastatur', - mouse: 'Maus' - }, + serial: 'Seriell', + keyboard: 'Tastatur', + mouse: 'Maus', + serialPort: { + device: 'Serielles Gerät', + baudRate: 'Baudrate', + noDeviceFound: 'Keine seriellen Geräte gefunden', + clickToSelect: 'Klicken Sie, um seriellen Port auszuwählen' + } + }, video: { resolution: 'Auflösung', customResolution: 'Benutzerdefiniert', @@ -72,6 +79,17 @@ const de = { title: 'Über', version: 'Version', community: 'Community' + }, + reset: { + title: 'Einstellungen zurücksetzen', + description: 'Alle Anwendungseinstellungen auf Standardwerte zurücksetzen', + warning: 'Warnung', + warningDescription: 'Diese Aktion kann nicht rückgängig gemacht werden. Alle benutzerdefinierten Einstellungen gehen verloren.', + button: 'Alle Einstellungen zurücksetzen', + confirmTitle: 'Zurücksetzen bestätigen', + confirmMessage: 'Sind Sie sicher, dass Sie alle Einstellungen zurücksetzen möchten? Diese Aktion kann nicht rückgängig gemacht werden.', + confirm: 'Zurücksetzen', + cancel: 'Abbrechen' } } } diff --git a/desktop/src/renderer/src/i18n/locales/en.ts b/desktop/src/renderer/src/i18n/locales/en.ts index 707c660..e88c668 100644 --- a/desktop/src/renderer/src/i18n/locales/en.ts +++ b/desktop/src/renderer/src/i18n/locales/en.ts @@ -10,12 +10,19 @@ const en = { modal: { title: 'Select USB Device', selectVideo: 'Please select a video input device', - selectSerial: 'Please select serial device' + selectSerial: 'Please select serial device', + selectBaudRate: 'Please select baud rate' }, menu: { serial: 'Serial', keyboard: 'Keyboard', - mouse: 'Mouse' + mouse: 'Mouse', + serialPort: { + device: 'Serial Device', + baudRate: 'Baud Rate', + noDeviceFound: 'No serial devices found', + clickToSelect: 'Click to select serial port' + } }, video: { resolution: 'Resolution', @@ -75,6 +82,17 @@ const en = { title: 'About', version: 'Version', community: 'Community' + }, + reset: { + title: 'Reset Settings', + description: 'Reset all application settings to default values', + warning: 'Warning', + warningDescription: 'This action cannot be undone. All your custom settings will be lost.', + button: 'Reset All Settings', + confirmTitle: 'Confirm Reset', + confirmMessage: 'Are you sure you want to reset all settings? This action cannot be undone.', + confirm: 'Reset', + cancel: 'Cancel' } } } diff --git a/desktop/src/renderer/src/i18n/locales/nl.ts b/desktop/src/renderer/src/i18n/locales/nl.ts index f2dd737..dc1a86f 100644 --- a/desktop/src/renderer/src/i18n/locales/nl.ts +++ b/desktop/src/renderer/src/i18n/locales/nl.ts @@ -8,15 +8,22 @@ const nl = { failed: 'Kan geen verbinding maken met de camera. Probeer het opnieuw.' }, modal: { - title: 'Selecteer USB-apparaat', - selectVideo: 'Selecteer een video-invoerapparaat', - selectSerial: 'Selecteer serieel apparaat' - }, + title: 'Selecteer USB-apparaat', + selectVideo: 'Selecteer een video-invoerapparaat', + selectSerial: 'Selecteer serieel apparaat', + selectBaudRate: 'Selecteer baudrate' + }, menu: { - serial: 'Serieel', - keyboard: 'Toetsenbord', - mouse: 'Muis' - }, + serial: 'Serieel', + keyboard: 'Toetsenbord', + mouse: 'Muis', + serialPort: { + device: 'Serieel apparaat', + baudRate: 'Baudrate', + noDeviceFound: 'Geen seriële apparaten gevonden', + clickToSelect: 'Klik om seriële poort te selecteren' + } + }, video: { resolution: 'Resolutie', customResolution: 'Aangepast', @@ -72,6 +79,17 @@ const nl = { title: 'Over', version: 'Versie', community: 'Community' + }, + reset: { + title: 'Instellingen resetten', + description: 'Alle applicatie-instellingen resetten naar standaardwaarden', + warning: 'Waarschuwing', + warningDescription: 'Deze actie kan niet ongedaan worden gemaakt. Alle aangepaste instellingen gaan verloren.', + button: 'Alle instellingen resetten', + confirmTitle: 'Reset bevestigen', + confirmMessage: 'Weet je zeker dat je alle instellingen wilt resetten? Deze actie kan niet ongedaan worden gemaakt.', + confirm: 'Reset', + cancel: 'Annuleren' } } } diff --git a/desktop/src/renderer/src/i18n/locales/ru.ts b/desktop/src/renderer/src/i18n/locales/ru.ts index 4e50e6d..09e19c0 100644 --- a/desktop/src/renderer/src/i18n/locales/ru.ts +++ b/desktop/src/renderer/src/i18n/locales/ru.ts @@ -8,14 +8,21 @@ const ru = { failed: 'Не удалось подключить камеру. Пожалуйста, попробуйте еще раз.' }, modal: { - title: 'Выберите устройство USB', - selectVideo: 'Выбрать источник видео', - selectSerial: 'Выбрать последовательный порт' + title: 'Выбрать USB устройство', + selectVideo: 'Пожалуйста, выберите устройство видеовхода', + selectSerial: 'Выбрать последовательный порт', + selectBaudRate: 'Пожалуйста, выберите скорость передачи данных' }, menu: { serial: 'Последовательный порт', keyboard: 'Клавиатура', - mouse: 'Мышь' + mouse: 'Мышь', + serialPort: { + device: 'Последовательное устройство', + baudRate: 'Скорость передачи', + noDeviceFound: 'Последовательные устройства не найдены', + clickToSelect: 'Нажмите, чтобы выбрать последовательный порт' + } }, video: { resolution: 'Разрешение', @@ -72,6 +79,17 @@ const ru = { title: 'О системе NanoKVM-USB', version: 'Версия', community: 'Сообщество' + }, + reset: { + title: 'Сброс настроек', + description: 'Сбросить все настройки приложения к значениям по умолчанию', + warning: 'Предупреждение', + warningDescription: 'Это действие нельзя отменить. Все пользовательские настройки будут потеряны.', + button: 'Сбросить все настройки', + confirmTitle: 'Подтвердить сброс', + confirmMessage: 'Вы уверены, что хотите сбросить все настройки? Это действие нельзя отменить.', + confirm: 'Сбросить', + cancel: 'Отмена' } } } diff --git a/desktop/src/renderer/src/i18n/locales/zh.ts b/desktop/src/renderer/src/i18n/locales/zh.ts index f6ea3f1..ee37c52 100644 --- a/desktop/src/renderer/src/i18n/locales/zh.ts +++ b/desktop/src/renderer/src/i18n/locales/zh.ts @@ -9,12 +9,19 @@ const zh = { modal: { title: '选择 USB 设备', selectVideo: '请选择视频输入设备', - selectSerial: '请选择串口设备' + selectSerial: '请选择串口设备', + selectBaudRate: '请选择波特率' }, menu: { serial: '串口', keyboard: '键盘', - mouse: '鼠标' + mouse: '鼠标', + serialPort: { + device: '串口设备', + baudRate: '波特率', + noDeviceFound: '未找到串口设备', + clickToSelect: '点击选择串口' + } }, video: { resolution: '分辨率', @@ -73,6 +80,17 @@ const zh = { title: '关于', version: '版本', community: '社区' + }, + reset: { + title: '重置设置', + description: '将所有应用程序设置重置为默认值', + warning: '警告', + warningDescription: '此操作无法撤销。所有自定义设置将丢失。', + button: '重置所有设置', + confirmTitle: '确认重置', + confirmMessage: '您确定要重置所有设置吗?此操作无法撤销。', + confirm: '重置', + cancel: '取消' } } } diff --git a/desktop/src/renderer/src/jotai/device.ts b/desktop/src/renderer/src/jotai/device.ts index c42460f..448101f 100644 --- a/desktop/src/renderer/src/jotai/device.ts +++ b/desktop/src/renderer/src/jotai/device.ts @@ -15,3 +15,4 @@ export const videoStateAtom = atom('disconnected') export const serialPortAtom = atom('') export const serialPortStateAtom = atom('disconnected') +export const baudRateAtom = atom(57600) diff --git a/desktop/src/renderer/src/libs/storage/index.ts b/desktop/src/renderer/src/libs/storage/index.ts index 0024a9d..077f5c4 100644 --- a/desktop/src/renderer/src/libs/storage/index.ts +++ b/desktop/src/renderer/src/libs/storage/index.ts @@ -13,6 +13,7 @@ const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode' const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction' const SKIP_UPDATE_KEY = 'nano-kvm-check-update' const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval' +const BAUD_RATE_KEY = 'nanokvm-usb-baud-rate' export function getLanguage(): string | null { return localStorage.getItem(LANGUAGE_KEY) @@ -71,6 +72,18 @@ export function setSerialPort(port: string): void { localStorage.setItem(SERIAL_PORT_KEY, port) } +export function getBaudRate(): number { + const baudRate = localStorage.getItem(BAUD_RATE_KEY) + if (!baudRate) { + return 57600 + } + return parseInt(baudRate, 10) +} + +export function setBaudRate(baudRate: number): void { + localStorage.setItem(BAUD_RATE_KEY, baudRate.toString()) +} + export function getIsMenuOpen(): boolean { const state = localStorage.getItem(IS_MENU_OPEN_KEY) if (!state) { @@ -132,3 +145,18 @@ export function setSkipUpdate(skip: boolean): void { const expiry = 3 * 24 * 60 * 60 * 1000 setWithExpiry(SKIP_UPDATE_KEY, String(skip), expiry) } + +export function clearAllSettings(): void { + localStorage.removeItem(LANGUAGE_KEY) + localStorage.removeItem(VIDEO_DEVICE_ID_KEY) + localStorage.removeItem(VIDEO_RESOLUTION_KEY) + localStorage.removeItem(CUSTOM_RESOLUTION_KEY) + localStorage.removeItem(SERIAL_PORT_KEY) + localStorage.removeItem(IS_MENU_OPEN_KEY) + localStorage.removeItem(MOUSE_STYLE_KEY) + localStorage.removeItem(MOUSE_MODE_KEY) + localStorage.removeItem(MOUSE_SCROLL_DIRECTION_KEY) + localStorage.removeItem(SKIP_UPDATE_KEY) + localStorage.removeItem(MOUSE_SCROLL_INTERVAL_KEY) + localStorage.removeItem(BAUD_RATE_KEY) +}