diff --git a/browser/src/App.tsx b/browser/src/App.tsx
index 2c11e7a..c79e2f9 100644
--- a/browser/src/App.tsx
+++ b/browser/src/App.tsx
@@ -10,7 +10,12 @@ import { Keyboard } from '@/components/keyboard';
import { Menu } from '@/components/menu';
import { Mouse } from '@/components/mouse';
import { VirtualKeyboard } from '@/components/virtual-keyboard';
-import { resolutionAtom, serialStateAtom, videoStateAtom } from '@/jotai/device.ts';
+import {
+ resolutionAtom,
+ serialStateAtom,
+ videoScaleAtom,
+ videoStateAtom
+} from '@/jotai/device.ts';
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
import { mouseStyleAtom } from '@/jotai/mouse.ts';
import { camera } from '@/libs/camera';
@@ -23,6 +28,7 @@ const App = () => {
const isBigScreen = useMediaQuery({ minWidth: 850 });
const mouseStyle = useAtomValue(mouseStyleAtom);
+ const videoScale = useAtomValue(videoScaleAtom);
const videoState = useAtomValue(videoStateAtom);
const serialState = useAtomValue(serialStateAtom);
const isKeyboardEnable = useAtomValue(isKeyboardEnableAtom);
@@ -107,7 +113,13 @@ const App = () => {
diff --git a/browser/src/components/menu/video/index.tsx b/browser/src/components/menu/video/index.tsx
index 8b78ed9..dd44766 100644
--- a/browser/src/components/menu/video/index.tsx
+++ b/browser/src/components/menu/video/index.tsx
@@ -3,11 +3,13 @@ import { MonitorIcon } from 'lucide-react';
import { Device } from './device.tsx';
import { Resolution } from './resolution.tsx';
+import { Scale } from './scale.tsx';
export const Video = () => {
const content = (
+
);
diff --git a/browser/src/components/menu/video/scale.tsx b/browser/src/components/menu/video/scale.tsx
new file mode 100644
index 0000000..d21f5e3
--- /dev/null
+++ b/browser/src/components/menu/video/scale.tsx
@@ -0,0 +1,58 @@
+import { ReactElement, useEffect } from 'react'
+import { Popover, Slider } from 'antd'
+import { useAtom } from 'jotai'
+import { ScalingIcon } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
+
+import { videoScaleAtom } from '@/jotai/device.ts';
+import * as storage from '@/libs/storage';
+
+export const Scale = (): ReactElement => {
+ const { t } = useTranslation()
+
+ const [videoScale, setVideoScale] = useAtom(videoScaleAtom)
+
+ useEffect(() => {
+ const scale = storage.getVideoScale()
+ if (scale) {
+ setVideoScale(scale)
+ }
+ }, [])
+
+ async function updateScale(scale: number): Promise {
+ setVideoScale(scale)
+ storage.setVideoScale(scale)
+ }
+
+ const content = (
+
+ x0.5,
+ 1: x1.0,
+ 1.5: x1.5,
+ 2: x2.0
+ }}
+ range={false}
+ included={false}
+ min={0.5}
+ max={2}
+ step={0.1}
+ defaultValue={videoScale}
+ onChange={updateScale}
+ />
+
+ )
+
+ return (
+
+
+
+
+
+
{t('video.scale')}
+
+
+ )
+}
diff --git a/browser/src/i18n/languages.ts b/browser/src/i18n/languages.ts
index 59019eb..9edb392 100644
--- a/browser/src/i18n/languages.ts
+++ b/browser/src/i18n/languages.ts
@@ -4,7 +4,8 @@ const languages = [
{ key: 'zh', name: '中文' },
{ key: 'de', name: 'Deutsch' },
{ key: 'nl', name: 'Nederlands' },
- { key: 'be', name: 'België' }
+ { key: 'be', name: 'België' },
+ { key: 'ko', name: '한국어' }
];
languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }));
diff --git a/browser/src/i18n/locales/be.ts b/browser/src/i18n/locales/be.ts
index 3afee03..d08ed39 100644
--- a/browser/src/i18n/locales/be.ts
+++ b/browser/src/i18n/locales/be.ts
@@ -24,6 +24,7 @@ const be = {
},
video: {
resolution: 'Resolutie',
+ scale: 'Schaal',
customResolution: 'Aangepast',
device: 'Toestel',
custom: {
diff --git a/browser/src/i18n/locales/de.ts b/browser/src/i18n/locales/de.ts
index 9d91768..9d23237 100644
--- a/browser/src/i18n/locales/de.ts
+++ b/browser/src/i18n/locales/de.ts
@@ -24,6 +24,7 @@ const de = {
},
video: {
resolution: 'Auflösung',
+ scale: 'Skalierung',
customResolution: 'Benutzerdefiniert',
device: 'Gerät',
custom: {
diff --git a/browser/src/i18n/locales/en.ts b/browser/src/i18n/locales/en.ts
index 285d052..daa9767 100644
--- a/browser/src/i18n/locales/en.ts
+++ b/browser/src/i18n/locales/en.ts
@@ -24,6 +24,7 @@ const en = {
},
video: {
resolution: 'Resolution',
+ scale: 'Scale',
customResolution: 'Custom',
device: 'Device',
custom: {
diff --git a/browser/src/i18n/locales/ko.ts b/browser/src/i18n/locales/ko.ts
new file mode 100644
index 0000000..d1be2ed
--- /dev/null
+++ b/browser/src/i18n/locales/ko.ts
@@ -0,0 +1,70 @@
+const ko = {
+ translation: {
+ serial: {
+ notSupported:
+ '시리얼이 지원되지 않습니다. 마우스와 키보드를 사용하려면 Chrome 브라우저를 사용하세요.',
+ failed: '시리얼 연결에 실패했습니다. 다시 시도해 주세요.'
+ },
+ camera: {
+ tip: '권한을 기다리는 중...',
+ denied: '권한이 거부되었습니다.',
+ authorize:
+ 'Target PC 연결에 카메라 권한이 필요합니다. 브라우저 설정에서 카메라 권한을 허용해 주세요.',
+ failed: '카메라 연결에 실패했습니다. 다시 시도해 주세요.'
+ },
+ modal: {
+ title: 'USB 장치 선택',
+ selectVideo: '비디오 입력 장치를 선택해 주세요.',
+ selectSerial: '시리얼 장치를 선택해 주세요.'
+ },
+ menu: {
+ serial: '시리얼',
+ keyboard: '키보드',
+ mouse: '마우스'
+ },
+ video: {
+ resolution: '해상도',
+ scale: '배율',
+ customResolution: '사용자 정의',
+ device: '장치',
+ custom: {
+ title: '사용자 정의 해상도',
+ width: '가로',
+ height: '세로',
+ confirm: '확인',
+ cancel: '취소'
+ }
+ },
+ keyboard: {
+ paste: '붙여넣기',
+ virtualKeyboard: '가상 키보드',
+ ctrlAltDel: 'Ctrl + Alt + Delete'
+ },
+ mouse: {
+ cursor: {
+ title: '커서 모양',
+ pointer: '포인터',
+ grab: '손',
+ cell: '플러스',
+ hide: '숨기기'
+ },
+ mode: '마우스 모드',
+ absolute: '절대 모드',
+ relative: '상대 모드',
+ direction: '휠 방향',
+ scrollUp: '위로 스크롤',
+ scrollDown: '아래로 스크롤',
+ speed: '휠 속도',
+ fast: '빠르게',
+ slow: '느리게',
+ requestPointer: '상대 모드를 사용 중입니다. 마우스 포인터를 가져오려면 스크린을 클릭하세요.'
+ },
+ settings: {
+ language: '언어',
+ document: '문서',
+ download: '다운로드'
+ }
+ }
+};
+
+export default ko;
diff --git a/browser/src/i18n/locales/nl.ts b/browser/src/i18n/locales/nl.ts
index 21934e6..2235d17 100644
--- a/browser/src/i18n/locales/nl.ts
+++ b/browser/src/i18n/locales/nl.ts
@@ -24,6 +24,7 @@ const nl = {
},
video: {
resolution: 'Resolutie',
+ scale: 'Schaal',
customResolution: 'Aangepast',
device: 'Apparaat',
custom: {
diff --git a/browser/src/i18n/locales/ru.ts b/browser/src/i18n/locales/ru.ts
index c973ee5..0e8a299 100644
--- a/browser/src/i18n/locales/ru.ts
+++ b/browser/src/i18n/locales/ru.ts
@@ -24,6 +24,7 @@ const ru = {
},
video: {
resolution: 'Разрешение',
+ scale: 'Масштаб',
customResolution: 'Пользовательское',
device: 'Видеоустройство',
custom: {
diff --git a/browser/src/i18n/locales/zh.ts b/browser/src/i18n/locales/zh.ts
index 47602f9..51a5ce9 100644
--- a/browser/src/i18n/locales/zh.ts
+++ b/browser/src/i18n/locales/zh.ts
@@ -22,6 +22,7 @@ const zh = {
},
video: {
resolution: '分辨率',
+ scale: '缩放',
customResolution: '自定义',
device: '设备',
custom: {
diff --git a/browser/src/jotai/device.ts b/browser/src/jotai/device.ts
index a768ee3..195dc28 100644
--- a/browser/src/jotai/device.ts
+++ b/browser/src/jotai/device.ts
@@ -10,6 +10,8 @@ export const resolutionAtom = atom({
height: 1080
});
+export const videoScaleAtom = atom(1.0)
+
export const videoDeviceIdAtom = atom('');
export const videoStateAtom = atom('disconnected');
diff --git a/browser/src/libs/storage/index.ts b/browser/src/libs/storage/index.ts
index 6340370..01543d7 100644
--- a/browser/src/libs/storage/index.ts
+++ b/browser/src/libs/storage/index.ts
@@ -4,6 +4,7 @@ const LANGUAGE_KEY = 'nanokvm-usb-language';
const VIDEO_DEVICE_ID_KEY = 'nanokvm-usb-video-device-id';
const VIDEO_RESOLUTION_KEY = 'nanokvm-usb-video-resolution';
const CUSTOM_RESOLUTION_KEY = 'nanokvm-usb-custom-resolution';
+const VIDEO_SCALE_KEY = 'nanokvm-usb-video-scale'
const IS_MENU_OPEN_KEY = 'nanokvm-is-menu-open';
const MOUSE_STYLE_KEY = 'nanokvm-usb-mouse-style';
const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode';
@@ -57,6 +58,18 @@ export function removeCustomResolutions() {
localStorage.removeItem(CUSTOM_RESOLUTION_KEY);
}
+export function getVideoScale(): number | null {
+ const scale = localStorage.getItem(VIDEO_SCALE_KEY)
+ if (scale && Number(scale)) {
+ return Number(scale)
+ }
+ return null
+}
+
+export function setVideoScale(scale: number): void {
+ localStorage.setItem(VIDEO_SCALE_KEY, String(scale))
+}
+
export function getIsMenuOpen(): boolean {
const state = localStorage.getItem(IS_MENU_OPEN_KEY);
if (!state) {
diff --git a/desktop/src/renderer/src/App.tsx b/desktop/src/renderer/src/App.tsx
index 97f9bb4..7ab90ac 100644
--- a/desktop/src/renderer/src/App.tsx
+++ b/desktop/src/renderer/src/App.tsx
@@ -11,7 +11,12 @@ import { Keyboard } from '@renderer/components/keyboard'
import { Menu } from '@renderer/components/menu'
import { Mouse } from '@renderer/components/mouse'
import { VirtualKeyboard } from '@renderer/components/virtual-keyboard'
-import { resolutionAtom, serialPortStateAtom, videoStateAtom } from '@renderer/jotai/device'
+import {
+ resolutionAtom,
+ serialPortStateAtom,
+ videoScaleAtom,
+ videoStateAtom
+} from '@renderer/jotai/device'
import { isKeyboardEnableAtom } from '@renderer/jotai/keyboard'
import { mouseStyleAtom } from '@renderer/jotai/mouse'
import { camera } from '@renderer/libs/camera'
@@ -24,6 +29,7 @@ const App = (): ReactElement => {
const { t } = useTranslation()
const isBigScreen = useMediaQuery({ minWidth: 850 })
+ const videoScale = useAtomValue(videoScaleAtom)
const videoState = useAtomValue(videoStateAtom)
const serialPortState = useAtomValue(serialPortStateAtom)
const mouseStyle = useAtomValue(mouseStyleAtom)
@@ -115,7 +121,13 @@ const App = (): ReactElement => {
diff --git a/desktop/src/renderer/src/components/menu/video/index.tsx b/desktop/src/renderer/src/components/menu/video/index.tsx
index 45896b5..8bcf218 100644
--- a/desktop/src/renderer/src/components/menu/video/index.tsx
+++ b/desktop/src/renderer/src/components/menu/video/index.tsx
@@ -4,11 +4,13 @@ import { MonitorIcon } from 'lucide-react'
import { Device } from './device'
import { Resolution } from './resolution'
+import { Scale } from './scale'
export const Video = (): ReactElement => {
const content = (
+
)
diff --git a/desktop/src/renderer/src/components/menu/video/scale.tsx b/desktop/src/renderer/src/components/menu/video/scale.tsx
new file mode 100644
index 0000000..7c39b15
--- /dev/null
+++ b/desktop/src/renderer/src/components/menu/video/scale.tsx
@@ -0,0 +1,58 @@
+import { ReactElement, useEffect } from 'react'
+import { Popover, Slider } from 'antd'
+import { useAtom } from 'jotai'
+import { ScalingIcon } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
+
+import { videoScaleAtom } from '@renderer/jotai/device'
+import * as storage from '@renderer/libs/storage'
+
+export const Scale = (): ReactElement => {
+ const { t } = useTranslation()
+
+ const [videoScale, setVideoScale] = useAtom(videoScaleAtom)
+
+ useEffect(() => {
+ const scale = storage.getVideoScale()
+ if (scale) {
+ setVideoScale(scale)
+ }
+ }, [setVideoScale])
+
+ async function updateScale(scale: number): Promise {
+ setVideoScale(scale)
+ storage.setVideoScale(scale)
+ }
+
+ const content = (
+
+ x0.5,
+ 1: x1.0,
+ 1.5: x1.5,
+ 2: x2.0
+ }}
+ range={false}
+ included={false}
+ min={0.5}
+ max={2}
+ step={0.1}
+ defaultValue={videoScale}
+ onChange={updateScale}
+ />
+
+ )
+
+ return (
+
+
+
+
+
+
{t('video.scale')}
+
+
+ )
+}
diff --git a/desktop/src/renderer/src/i18n/languages.ts b/desktop/src/renderer/src/i18n/languages.ts
index eb2fcb8..035e848 100644
--- a/desktop/src/renderer/src/i18n/languages.ts
+++ b/desktop/src/renderer/src/i18n/languages.ts
@@ -4,7 +4,8 @@ const languages = [
{ key: 'zh', name: '中文' },
{ key: 'de', name: 'Deutsch' },
{ key: 'nl', name: 'Nederlands' },
- { key: 'be', name: 'België' }
+ { key: 'be', name: 'België' },
+ { key: 'ko', name: '한국어' }
]
languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))
diff --git a/desktop/src/renderer/src/i18n/locales/be.ts b/desktop/src/renderer/src/i18n/locales/be.ts
index ce82906..5858b17 100644
--- a/desktop/src/renderer/src/i18n/locales/be.ts
+++ b/desktop/src/renderer/src/i18n/locales/be.ts
@@ -19,6 +19,7 @@ const be = {
},
video: {
resolution: 'Resolutie',
+ scale: 'Schaal',
customResolution: 'Aangepast',
device: 'Toestel',
custom: {
@@ -76,5 +77,5 @@ const be = {
}
}
}
-
- export default be
\ No newline at end of file
+
+ export default be
diff --git a/desktop/src/renderer/src/i18n/locales/de.ts b/desktop/src/renderer/src/i18n/locales/de.ts
index 5e0fc83..a80f385 100644
--- a/desktop/src/renderer/src/i18n/locales/de.ts
+++ b/desktop/src/renderer/src/i18n/locales/de.ts
@@ -19,6 +19,7 @@ const de = {
},
video: {
resolution: 'Auflösung',
+ scale: 'Skalierung',
customResolution: 'Benutzerdefiniert',
device: 'Gerät',
custom: {
@@ -79,5 +80,5 @@ const de = {
}
}
}
-
- export default de
\ No newline at end of file
+
+ export default de
diff --git a/desktop/src/renderer/src/i18n/locales/en.ts b/desktop/src/renderer/src/i18n/locales/en.ts
index 83e1775..033e42d 100644
--- a/desktop/src/renderer/src/i18n/locales/en.ts
+++ b/desktop/src/renderer/src/i18n/locales/en.ts
@@ -19,6 +19,7 @@ const en = {
},
video: {
resolution: 'Resolution',
+ scale: 'Scale',
customResolution: 'Custom',
device: 'Device',
custom: {
diff --git a/desktop/src/renderer/src/i18n/locales/ko.ts b/desktop/src/renderer/src/i18n/locales/ko.ts
new file mode 100644
index 0000000..6b58ec8
--- /dev/null
+++ b/desktop/src/renderer/src/i18n/locales/ko.ts
@@ -0,0 +1,84 @@
+const ko = {
+ translation: {
+ camera: {
+ tip: '권한을 기다리는 중...',
+ denied: '권한이 거부되었습니다.',
+ authorize:
+ 'Target PC 연결에 카메라 권한이 필요합니다. 설정에서 카메라 권한을 허용해 주세요.',
+ failed: '카메라 연결에 실패했습니다. 다시 시도해 주세요.'
+ },
+ modal: {
+ title: 'USB 장치 선택',
+ selectVideo: '비디오 입력 장치를 선택해 주세요.',
+ selectSerial: '시리얼 장치를 선택해 주세요.'
+ },
+ menu: {
+ serial: '시리얼',
+ keyboard: '키보드',
+ mouse: '마우스'
+ },
+ video: {
+ resolution: '해상도',
+ scale: '배율',
+ customResolution: '사용자 정의',
+ device: '장치',
+ custom: {
+ title: '사용자 정의 해상도',
+ width: '가로',
+ height: '세로',
+ confirm: '확인',
+ cancel: '취소'
+ }
+ },
+ keyboard: {
+ paste: '붙여넣기',
+ virtualKeyboard: '가상 키보드',
+ ctrlAltDel: 'Ctrl + Alt + Delete'
+ },
+ mouse: {
+ cursor: {
+ title: '커서 모양',
+ pointer: '포인터',
+ grab: '손',
+ cell: '플러스',
+ hide: '숨기기'
+ },
+ mode: '마우스 모드',
+ absolute: '절대 모드',
+ relative: '상대 모드',
+ direction: '휠 방향',
+ scrollUp: '위로 스크롤',
+ scrollDown: '아래로 스크롤',
+ speed: '휠 속도',
+ fast: '빠르게',
+ slow: '느리게',
+ requestPointer: '상대 모드를 사용 중입니다. 마우스 포인터를 가져오려면 스크린을 클릭하세요.'
+ },
+ settings: {
+ title: '설정',
+ appearance: {
+ title: '화면 설정',
+ language: '언어',
+ menu: '메뉴 바',
+ menuTips: '시작 시 메뉴 바 열기'
+ },
+ update: {
+ title: '업데이트 확인',
+ latest: '최신 버전을 사용 중입니다.',
+ outdated: '업데이트가 가능합니다. 지금 업데이트하시겠습니까?',
+ downloading: '다운로드 중...',
+ installing: '설치 중...',
+ failed: '업데이트에 실패했습니다. 다시 시도해 주세요.',
+ confirm: '확인',
+ cancel: '취소'
+ },
+ about: {
+ title: '정보',
+ version: '버전',
+ community: '커뮤니티'
+ }
+ }
+ }
+}
+
+export default ko
diff --git a/desktop/src/renderer/src/i18n/locales/nl.ts b/desktop/src/renderer/src/i18n/locales/nl.ts
index f2dd737..395ff32 100644
--- a/desktop/src/renderer/src/i18n/locales/nl.ts
+++ b/desktop/src/renderer/src/i18n/locales/nl.ts
@@ -19,6 +19,7 @@ const nl = {
},
video: {
resolution: 'Resolutie',
+ scale: 'Schaal',
customResolution: 'Aangepast',
device: 'Apparaat',
custom: {
@@ -76,5 +77,5 @@ const nl = {
}
}
}
-
- export default nl
\ No newline at end of file
+
+ export default nl
diff --git a/desktop/src/renderer/src/i18n/locales/ru.ts b/desktop/src/renderer/src/i18n/locales/ru.ts
index 98f9ef9..259ab3c 100644
--- a/desktop/src/renderer/src/i18n/locales/ru.ts
+++ b/desktop/src/renderer/src/i18n/locales/ru.ts
@@ -19,6 +19,7 @@ const ru = {
},
video: {
resolution: 'Разрешение',
+ scale: 'Масштаб',
customResolution: 'Пользовательское',
device: 'Видеоустройство',
custom: {
diff --git a/desktop/src/renderer/src/i18n/locales/zh.ts b/desktop/src/renderer/src/i18n/locales/zh.ts
index 01b5add..8adde4f 100644
--- a/desktop/src/renderer/src/i18n/locales/zh.ts
+++ b/desktop/src/renderer/src/i18n/locales/zh.ts
@@ -18,6 +18,7 @@ const zh = {
},
video: {
resolution: '分辨率',
+ scale: '缩放',
customResolution: '自定义',
device: '设备',
custom: {
diff --git a/desktop/src/renderer/src/jotai/device.ts b/desktop/src/renderer/src/jotai/device.ts
index c42460f..84dbee6 100644
--- a/desktop/src/renderer/src/jotai/device.ts
+++ b/desktop/src/renderer/src/jotai/device.ts
@@ -10,6 +10,8 @@ export const resolutionAtom = atom({
height: 1080
})
+export const videoScaleAtom = atom(1.0)
+
export const videoDeviceIdAtom = atom('')
export const videoStateAtom = atom('disconnected')
diff --git a/desktop/src/renderer/src/libs/storage/index.ts b/desktop/src/renderer/src/libs/storage/index.ts
index 4a4b701..98c0961 100644
--- a/desktop/src/renderer/src/libs/storage/index.ts
+++ b/desktop/src/renderer/src/libs/storage/index.ts
@@ -5,6 +5,7 @@ import { getWithExpiry, setWithExpiry } from './expiry'
const LANGUAGE_KEY = 'nanokvm-usb-language'
const VIDEO_DEVICE_ID_KEY = 'nanokvm-usb-video-device-id'
const VIDEO_RESOLUTION_KEY = 'nanokvm-usb-video-resolution'
+const VIDEO_SCALE_KEY = 'nanokvm-usb-video-scale'
const CUSTOM_RESOLUTION_KEY = 'nanokvm-usb-custom-resolution'
const SERIAL_PORT_KEY = 'nanokvm-serial-port'
const IS_MENU_OPEN_KEY = 'nanokvm-is-menu-open'
@@ -64,6 +65,18 @@ export function removeCustomResolutions(): void {
localStorage.removeItem(CUSTOM_RESOLUTION_KEY)
}
+export function getVideoScale(): number | null {
+ const scale = localStorage.getItem(VIDEO_SCALE_KEY)
+ if (scale && Number(scale)) {
+ return Number(scale)
+ }
+ return null
+}
+
+export function setVideoScale(scale: number): void {
+ localStorage.setItem(VIDEO_SCALE_KEY, String(scale))
+}
+
export function getSerialPort(): string | null {
return localStorage.getItem(SERIAL_PORT_KEY)
}