From 2f8f77cb893c018f11ce2a3d54f28338816ce6b1 Mon Sep 17 00:00:00 2001 From: X Date: Fri, 23 May 2025 21:49:17 +0900 Subject: [PATCH 01/88] =?UTF-8?q?=EB=B2=88=EC=97=AD=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/i18n/locales/ko.ts | 102 ++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 5533075..4509139 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -47,7 +47,9 @@ const ko = { finishBtn: '완료' }, screen: { + title: '화면', video: '비디오 모드', + videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요', resolution: '해상도', auto: '자동 설정', autoTips: @@ -65,6 +67,7 @@ const ko = { resetHdmi: 'HDMI 초기화' }, keyboard: { + title: '키보드', paste: '붙여넣기', tips: '표준 키보드 문자 및 기호만 지원됩니다', placeholder: '입력하세요', @@ -73,6 +76,8 @@ const ko = { ctrlaltdel: 'Ctrl+Alt+Del' }, mouse: { + title: '마우스', + cursor: '커서 스타일', default: '기본 커서', pointer: '포인터 커서', cell: '셀 커서', @@ -82,8 +87,20 @@ const ko = { mode: '마우스 모드', absolute: '절대값 모드', relative: '상대값 모드', + speed: '휠 속도', + fast: '빠름', + slow: '느림', requestPointer: '상대값 모드를 사용 중입니다. 커서를 찾으려면 데스크톱을 클릭하세요.', - resetHid: 'HID 초기화' + resetHid: 'HID 초기화', + hidOnly: { + title: 'HID 전용 모드', + desc: '마우스와 키보드가 응답하지 않고 HID 초기화도 도움이 되지 않는다면, NanoKVM과 장치 간의 호환성 문제일 수 있습니다. 더 나은 호환성을 위해 HID 전용 모드를 활성화해 보세요.', + tip1: 'HID 전용 모드를 활성화하면 가상 U-디스크와 가상 네트워크가 언마운트됩니다', + tip2: 'HID 전용 모드에서는 이미지 마운트가 비활성화됩니다', + tip3: '모드 전환 후 NanoKVM이 자동으로 재부팅됩니다', + enable: 'HID 전용 모드 활성화', + disable: 'HID 전용 모드 비활성화' + } }, image: { title: '이미지', @@ -130,6 +147,16 @@ const ko = { serialPort: '시리얼 포트', serialPortPlaceholder: '시리얼 포트를 입력하세요', baudrate: 'Baud rate', + parity: '패리티', + parityNone: '없음', + parityEven: '짝수', + parityOdd: '홀수', + flowControl: '흐름 제어', + flowControlNone: '없음', + flowControlSoft: '소프트', + flowControlHard: '하드', + dataBits: '데이터 비트', + stopBits: '정지 비트', confirm: '확인' }, wol: { @@ -147,10 +174,16 @@ const ko = { }, power: { title: '전원', + showConfirm: '확인', + showConfirmTip: '전원 작업에는 추가 확인이 필요합니다', reset: '리셋', power: '전원', powerShort: '전원 (짧게 누르기)', - powerLong: '전원 (길게 누르기)' + powerLong: '전원 (길게 누르기)', + resetConfirm: '리셋 작업을 진행하시겠습니까?', + powerConfirm: '전원 작업을 진행하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' }, settings: { title: '설정', @@ -164,14 +197,23 @@ const ko = { image: '이미지 버전', imageTip: 'NanoKVM 시스템 이미지 버전', deviceKey: '장치 키', - community: '커뮤니티' + community: '커뮤니티', + hostname: '호스트명', + hostnameUpdated: '호스트명이 업데이트되었습니다. 적용하려면 재부팅하세요.', + ipType: { + Wired: '유선', + Wireless: '무선', + Other: '기타' + } }, appearance: { title: '디자인', display: '표시', language: '언어', menuBar: '메뉴 바', - menuBarDesc: '메뉴 바에 아이콘을 표시' + menuBarDesc: '메뉴 바에 아이콘을 표시', + webTitle: '웹 제목', + webTitleDesc: '웹 페이지 제목 사용자 지정' }, device: { title: '장치', @@ -197,10 +239,40 @@ const ko = { description: 'SSH 원격 접속 활성화', tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' }, + tls: { + description: 'HTTPS 프로토콜 활성화', + tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.' + }, + advanced: '고급 설정', + swap: { + title: '스왑', + disable: '비활성화', + description: '스왑 파일 크기 설정', + tip: '이 기능을 활성화하면 SD 카드의 수명이 단축될 수 있습니다!' + }, + mouseJiggler: { + title: '마우스 지글러', + description: '원격 호스트가 절전 모드로 진입하는 것을 방지', + disable: '비활성화', + absolute: '절대값 모드', + relative: '상대값 모드' + }, + mdns: { + description: 'mDNS 검색 서비스 활성화', + tip: '사용하지 않는 경우 끄는 것이 좋습니다' + }, + hdmi: { + description: 'HDMI/모니터 출력 활성화' + }, + hidOnly: 'HID 전용 모드', disk: '가상 디스크', diskDesc: '원격 호스트에서 가상 U-디스크를 마운트합니다.', network: '가상 네트워크', - networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.' + networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', + reboot: '재부팅', + rebootDesc: 'NanoKVM을 재부팅하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' }, tailscale: { title: 'Tailscale', @@ -234,6 +306,7 @@ const ko = { deviceIP: '장치 IP', account: '계정', logout: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', logout2: '정말로 로그아웃 합니까?', uninstall: 'Tailscale 제거', okBtn: '네', @@ -247,19 +320,32 @@ const ko = { available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', updating: '업데이트 시작. 잠시 기다려주세요...', confirm: '확인', - cancel: '취소' + cancel: '취소', + preview: '미리보기 업데이트', + previewDesc: '새로운 기능과 개선 사항에 미리 접근하세요', + previewTip: '미리보기 버전에는 버그나 완성되지 않은 기능이 포함될 수 있으니 주의하세요!' }, account: { title: '계정', - webAccount: 'Web 계정', + webAccount: '웹 계정', password: '비밀번호', updateBtn: '업데이트', - logoutBtn: '로그아웃' + logoutBtn: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' } }, error: { title: '문제가 발생했습니다.', refresh: '새로고침' + }, + fullscreen: { + toggle: '전체 화면 전환' + }, + menu: { + collapse: '메뉴 접기', + expand: '메뉴 펼치기' } } }; From 9a5ad1dff23dc0517a78572b205123e19cacafd1 Mon Sep 17 00:00:00 2001 From: kmw0410 Date: Fri, 23 May 2025 22:13:24 +0900 Subject: [PATCH 02/88] =?UTF-8?q?=EB=B2=88=EC=97=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/i18n/locales/ko.ts | 272 ++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 143 deletions(-) diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 4509139..841622c 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -49,13 +49,13 @@ const ko = { screen: { title: '화면', video: '비디오 모드', - videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요', + videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요.', resolution: '해상도', - auto: '자동 설정', + auto: '자동', autoTips: '일부 해상도에서는 화면이 왜곡되거나 마우스 동작이 비정상적으로 나타날 수 있습니다. 원격 컴퓨터의 해상도를 변경하거나 자동 설정 대신 수동 설정을 사용해 보세요.', fps: 'FPS', - customizeFps: 'FPS 설정', + customizeFps: '사용자 지정', quality: '품질', qualityLossless: '무손실', qualityHigh: '높음', @@ -87,20 +87,8 @@ const ko = { mode: '마우스 모드', absolute: '절대값 모드', relative: '상대값 모드', - speed: '휠 속도', - fast: '빠름', - slow: '느림', requestPointer: '상대값 모드를 사용 중입니다. 커서를 찾으려면 데스크톱을 클릭하세요.', - resetHid: 'HID 초기화', - hidOnly: { - title: 'HID 전용 모드', - desc: '마우스와 키보드가 응답하지 않고 HID 초기화도 도움이 되지 않는다면, NanoKVM과 장치 간의 호환성 문제일 수 있습니다. 더 나은 호환성을 위해 HID 전용 모드를 활성화해 보세요.', - tip1: 'HID 전용 모드를 활성화하면 가상 U-디스크와 가상 네트워크가 언마운트됩니다', - tip2: 'HID 전용 모드에서는 이미지 마운트가 비활성화됩니다', - tip3: '모드 전환 후 NanoKVM이 자동으로 재부팅됩니다', - enable: 'HID 전용 모드 활성화', - disable: 'HID 전용 모드 비활성화' - } + resetHid: 'HID 초기화' }, image: { title: '이미지', @@ -134,7 +122,7 @@ const ko = { runBackground: '백그라운드에서 실행', runFailed: '실행 실패', attention: '주의', - delDesc: '이 파일을 정말로 삭제합니까?', + delDesc: '이 파일을 정말로 삭제할까요?', confirm: '네', cancel: '아니오', delete: '삭제', @@ -146,15 +134,15 @@ const ko = { serial: '시리얼 포트 터미널', serialPort: '시리얼 포트', serialPortPlaceholder: '시리얼 포트를 입력하세요', - baudrate: 'Baud rate', + baudrate: '전송 속도', parity: '패리티', parityNone: '없음', parityEven: '짝수', parityOdd: '홀수', flowControl: '흐름 제어', flowControlNone: '없음', - flowControlSoft: '소프트', - flowControlHard: '하드', + flowControlSoft: '소프트웨어', + flowControlHard: '하드웨어', dataBits: '데이터 비트', stopBits: '정지 비트', confirm: '확인' @@ -163,32 +151,32 @@ const ko = { title: 'Wake-on-LAN', sending: '패킷 전송 중...', sent: '패킷 전송 완료', - input: 'MAC주소를 입력하세요.', + input: 'MAC주소를 입력하세요', ok: '확인' }, download: { - title: '이미지 다운로드', + title: '이미지 다운로더', input: '원격 이미지 URL을 입력하세요.', ok: '확인', - disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.' + disabled: '/data 파티션이 읽기 전용 상태이므로 이미지를 다운로드할 수 없습니다.' }, power: { title: '전원', showConfirm: '확인', - showConfirmTip: '전원 작업에는 추가 확인이 필요합니다', + showConfirmTip: '전원 작업은 추가 확인이 필요합니다', reset: '리셋', power: '전원', powerShort: '전원 (짧게 누르기)', powerLong: '전원 (길게 누르기)', - resetConfirm: '리셋 작업을 진행하시겠습니까?', - powerConfirm: '전원 작업을 진행하시겠습니까?', + resetConfirm: '리셋 작업을 진행할까요?', + powerConfirm: '전원 작업을 진행할까요?', okBtn: '네', - cancelBtn: '아니오' + cacnelBtn: '아니오', }, settings: { title: '설정', about: { - title: 'NanoKVM 정보', + title: 'NanoKVM에 대하여', information: '정보', ip: 'IP', mdns: 'mDNS', @@ -198,8 +186,8 @@ const ko = { imageTip: 'NanoKVM 시스템 이미지 버전', deviceKey: '장치 키', community: '커뮤니티', - hostname: '호스트명', - hostnameUpdated: '호스트명이 업데이트되었습니다. 적용하려면 재부팅하세요.', + hostname: '호스트 이름', + hostnameUpdated: '호스트 이름이 업데이트되었습니다. 재부팅하여 적용하세요.', ipType: { Wired: '유선', Wireless: '무선', @@ -211,9 +199,7 @@ const ko = { display: '표시', language: '언어', menuBar: '메뉴 바', - menuBarDesc: '메뉴 바에 아이콘을 표시', - webTitle: '웹 제목', - webTitleDesc: '웹 페이지 제목 사용자 지정' + menuBarDesc: '메뉴 바에 아이콘을 표시' }, device: { title: '장치', @@ -230,123 +216,123 @@ const ko = { 1800: '30분', 3600: '1시간' }, - wifi: { - title: 'Wi-Fi', - description: 'Wi-Fi 설정', - setBtn: '설정' - }, - ssh: { - description: 'SSH 원격 접속 활성화', - tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' - }, - tls: { - description: 'HTTPS 프로토콜 활성화', - tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.' - }, - advanced: '고급 설정', - swap: { - title: '스왑', - disable: '비활성화', - description: '스왑 파일 크기 설정', - tip: '이 기능을 활성화하면 SD 카드의 수명이 단축될 수 있습니다!' - }, - mouseJiggler: { - title: '마우스 지글러', - description: '원격 호스트가 절전 모드로 진입하는 것을 방지', - disable: '비활성화', - absolute: '절대값 모드', - relative: '상대값 모드' - }, - mdns: { - description: 'mDNS 검색 서비스 활성화', - tip: '사용하지 않는 경우 끄는 것이 좋습니다' - }, - hdmi: { - description: 'HDMI/모니터 출력 활성화' - }, - hidOnly: 'HID 전용 모드', - disk: '가상 디스크', - diskDesc: '원격 호스트에서 가상 U-디스크를 마운트합니다.', - network: '가상 네트워크', - networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', - reboot: '재부팅', - rebootDesc: 'NanoKVM을 재부팅하시겠습니까?', - okBtn: '네', - cancelBtn: '아니오' + wifi: { + title: 'Wi-Fi', + description: 'Wi-Fi 설정', + setBtn: '설정' }, - tailscale: { - title: 'Tailscale', - memory: { - title: '메모리 최적화', - tip: '메모리 사용량이 제한을 초과하면 가비지 컬렉션이 더 적극적으로 실행되어 메모리를 확보하려고 시도합니다. Tailscale을 사용할 경우 50MB로 설정하는 것이 좋습니다. 변경 사항을 적용하려면 Tailscale을 다시 시작해야 합니다.', - disable: '비활성화' - }, - restart: '정말로 Tailscale을 다시 시작하시겠습니까?', - stop: '정말로 Tailscale을 중지하시겠습니까?', - stopDesc: 'Tailscale에서 로그아웃하고 자동 시작을 비활성화합니다.', - loading: '불러오는 중...', - notInstall: 'Tailscale이 없습니다. 설치해주세요.', - install: '설치', - installing: '설치중', - failed: '설치 실패', - retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', - download: '다운로드 중 :', - package: '패키지 설치', - unzip: '압축 해제', - upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', - upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', - refresh: '현재 페이지 새로고침', - notLogin: - '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', - urlPeriod: '이 주소는 10분간 유효합니다.', - login: '로그인', - loginSuccess: '로그인 성공', - enable: 'Tailscale 활성화', - deviceName: '장치 이름', - deviceIP: '장치 IP', - account: '계정', - logout: '로그아웃', - logoutDesc: '정말로 로그아웃 하시겠습니까?', - logout2: '정말로 로그아웃 합니까?', - uninstall: 'Tailscale 제거', - okBtn: '네', - cancelBtn: '아니오' + ssh: { + description: 'SSH 원격 접속 활성화', + tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' }, - update: { - title: '업데이트 확인', - queryFailed: '버전 확인 실패', - updateFailed: '업데이트 실패, 재시도하세요.', - isLatest: '이미 최신 버전입니다.', - available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', - updating: '업데이트 시작. 잠시 기다려주세요...', - confirm: '확인', - cancel: '취소', - preview: '미리보기 업데이트', - previewDesc: '새로운 기능과 개선 사항에 미리 접근하세요', - previewTip: '미리보기 버전에는 버그나 완성되지 않은 기능이 포함될 수 있으니 주의하세요!' + tls: { + description: 'HTTPS 프로트콜 활성화', + tip: '주의: HTTPS를 사용하면 지연 시간이 늘어날 수 있으며, 특히 MJPEG 비디오 모드에서 그 영향이 클 수 있습니다.' }, - account: { - title: '계정', - webAccount: '웹 계정', - password: '비밀번호', - updateBtn: '업데이트', - logoutBtn: '로그아웃', - logoutDesc: '정말로 로그아웃 하시겠습니까?', - okBtn: '네', - cancelBtn: '아니오' - } + advanced: '고급 설정', + swap: { + title: '스왑', + disable: '비활성화', + description: '스왑 파일의 사이즈를 설정하세요', + tip: "이 기능을 활성화하면 SD카드의 수명이 단축될 수 있습니다!" + }, + mouseJiggler: { + title: '마우스 흔들기', + description: '원격 호스트가 절전 모드로 전환되는 것을 방지합니다', + disable: '비활성화', + absolute: '절대값 모드', + relative: '상대값 모드' + }, + mdns: { + description: 'mDNS 탐색 서비스 활성화', + tip: '필요하지 않은 경우 비활성화하세요.' + }, + hdmi: { + description: 'HDMI/모니터 출력 활성화' + }, + hidOnly: 'HID 전용 모드', + disk: '가상 디스크', + diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.', + network: '가상 네트워크', + networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', + reboot: '재부팅', + rebootDesc: 'NanoKVM을 재부팅 할까요?', + okBtn: '네', + cancelBtn: '아니오' }, - error: { - title: '문제가 발생했습니다.', - refresh: '새로고침' + tailscale: { + title: 'Tailscale', + memory: { + title: '메모리 최적화', + tip: '메모리 사용량이 제한을 초과하면 가비지 컬렉션이 더 적극적으로 실행되어 메모리를 확보하려고 시도합니다. Tailscale을 사용할 경우 50MB로 설정하는 것이 좋습니다. 변경 사항을 적용하려면 Tailscale을 다시 시작해야 합니다.', + disable: '비활성화' + }, + restart: '정말로 Tailscale을 다시 시작하시겠습니까?', + stop: '정말로 Tailscale을 중지하시겠습니까?', + stopDesc: 'Tailscale에서 로그아웃하고 자동 시작을 비활성화합니다.', + loading: '불러오는 중...', + notInstall: 'Tailscale이 없습니다. 설치해주세요.', + install: '설치', + installing: '설치중', + failed: '설치 실패', + retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', + download: '다운로드 중 :', + package: '패키지 설치', + unzip: '압축 해제', + upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', + upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', + refresh: '현재 페이지 새로고침', + notLogin: + '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', + urlPeriod: '이 주소는 10분간 유효합니다.', + login: '로그인', + loginSuccess: '로그인 성공', + enable: 'Tailscale 활성화', + deviceName: '장치 이름', + deviceIP: '장치 IP', + account: '계정', + logout: '로그아웃', + logout2: '정말로 로그아웃 할까요?', + uninstall: 'Tailscale 제거', + okBtn: '네', + cancelBtn: '아니오' }, - fullscreen: { - toggle: '전체 화면 전환' + update: { + title: '업데이트 확인', + queryFailed: '버전 확인 실패', + updateFailed: '업데이트 실패, 재시도하세요.', + isLatest: '이미 최신 버전입니다.', + available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', + updating: '업데이트 시작. 잠시 기다려주세요...', + confirm: '확인', + cancel: '취소', + preview: '미리보기 업데이트', + preveiwDesc: '새 기능과 개선 사항을 미리 사용해 보세요', + previewTip: + '미리보기 버전에는 버그나 불완전한 기능이 포함되어 있을 수 있습니다!' }, - menu: { - collapse: '메뉴 접기', - expand: '메뉴 펼치기' + account: { + title: '계정', + webAccount: '웹 계정 이름', + password: '비밀번호', + updateBtn: '변경', + logoutBtn: '로그아웃', + logoutDesc: '정말로 로그아웃 할까요?', + okBtn: '네', + cancelBtn: '아니오' } + }, + error: { + title: '문제가 발생했습니다.', + refresh: '새로고침' + }, + fullscreen: { + toggle: '전체 화면 전환' + }, + menu: { + collapse: '메뉴 축소', + expand: '메뉴 확장' + } } }; From 4499f9ed445429b4380594e11f7e69822c64e582 Mon Sep 17 00:00:00 2001 From: X Date: Fri, 23 May 2025 22:23:12 +0900 Subject: [PATCH 03/88] =?UTF-8?q?kmw0410,=20U-=EB=94=94=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=20=EB=B2=88=EC=97=AD=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B8=B0=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/i18n/locales/ko.ts | 268 +++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 127 deletions(-) diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 841622c..c2bbe76 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -49,13 +49,13 @@ const ko = { screen: { title: '화면', video: '비디오 모드', - videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요.', + videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요', resolution: '해상도', - auto: '자동', + auto: '자동 설정', autoTips: '일부 해상도에서는 화면이 왜곡되거나 마우스 동작이 비정상적으로 나타날 수 있습니다. 원격 컴퓨터의 해상도를 변경하거나 자동 설정 대신 수동 설정을 사용해 보세요.', fps: 'FPS', - customizeFps: '사용자 지정', + customizeFps: 'FPS 설정', quality: '품질', qualityLossless: '무손실', qualityHigh: '높음', @@ -87,8 +87,20 @@ const ko = { mode: '마우스 모드', absolute: '절대값 모드', relative: '상대값 모드', + speed: '휠 속도', + fast: '빠름', + slow: '느림', requestPointer: '상대값 모드를 사용 중입니다. 커서를 찾으려면 데스크톱을 클릭하세요.', - resetHid: 'HID 초기화' + resetHid: 'HID 초기화', + hidOnly: { + title: 'HID 전용 모드', + desc: '마우스와 키보드가 응답하지 않고 HID 초기화도 도움이 되지 않는다면, NanoKVM과 장치 간의 호환성 문제일 수 있습니다. 더 나은 호환성을 위해 HID 전용 모드를 활성화해 보세요.', + tip1: 'HID 전용 모드를 활성화하면 가상 USB와 가상 네트워크가 언마운트됩니다', + tip2: 'HID 전용 모드에서는 이미지 마운트가 비활성화됩니다', + tip3: '모드 전환 후 NanoKVM이 자동으로 재부팅됩니다', + enable: 'HID 전용 모드 활성화', + disable: 'HID 전용 모드 비활성화' + } }, image: { title: '이미지', @@ -122,7 +134,7 @@ const ko = { runBackground: '백그라운드에서 실행', runFailed: '실행 실패', attention: '주의', - delDesc: '이 파일을 정말로 삭제할까요?', + delDesc: '이 파일을 정말로 삭제합니까?', confirm: '네', cancel: '아니오', delete: '삭제', @@ -141,8 +153,8 @@ const ko = { parityOdd: '홀수', flowControl: '흐름 제어', flowControlNone: '없음', - flowControlSoft: '소프트웨어', - flowControlHard: '하드웨어', + flowControlSoft: '소프트', + flowControlHard: '하드', dataBits: '데이터 비트', stopBits: '정지 비트', confirm: '확인' @@ -151,32 +163,32 @@ const ko = { title: 'Wake-on-LAN', sending: '패킷 전송 중...', sent: '패킷 전송 완료', - input: 'MAC주소를 입력하세요', + input: 'MAC주소를 입력하세요.', ok: '확인' }, download: { - title: '이미지 다운로더', + title: '이미지 다운로드', input: '원격 이미지 URL을 입력하세요.', ok: '확인', - disabled: '/data 파티션이 읽기 전용 상태이므로 이미지를 다운로드할 수 없습니다.' + disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.' }, power: { title: '전원', showConfirm: '확인', - showConfirmTip: '전원 작업은 추가 확인이 필요합니다', + showConfirmTip: '전원 작업에는 추가 확인이 필요합니다', reset: '리셋', power: '전원', powerShort: '전원 (짧게 누르기)', powerLong: '전원 (길게 누르기)', - resetConfirm: '리셋 작업을 진행할까요?', - powerConfirm: '전원 작업을 진행할까요?', + resetConfirm: '리셋 작업을 진행하시겠습니까?', + powerConfirm: '전원 작업을 진행하시겠습니까?', okBtn: '네', - cacnelBtn: '아니오', + cancelBtn: '아니오' }, settings: { title: '설정', about: { - title: 'NanoKVM에 대하여', + title: 'NanoKVM 정보', information: '정보', ip: 'IP', mdns: 'mDNS', @@ -187,7 +199,7 @@ const ko = { deviceKey: '장치 키', community: '커뮤니티', hostname: '호스트 이름', - hostnameUpdated: '호스트 이름이 업데이트되었습니다. 재부팅하여 적용하세요.', + hostnameUpdated: '호스트 이름이 업데이트되었습니다. 적용하려면 재부팅하세요.', ipType: { Wired: '유선', Wireless: '무선', @@ -199,7 +211,9 @@ const ko = { display: '표시', language: '언어', menuBar: '메뉴 바', - menuBarDesc: '메뉴 바에 아이콘을 표시' + menuBarDesc: '메뉴 바에 아이콘을 표시', + webTitle: '웹 제목', + webTitleDesc: '웹 페이지 제목 사용자 지정' }, device: { title: '장치', @@ -216,123 +230,123 @@ const ko = { 1800: '30분', 3600: '1시간' }, - wifi: { - title: 'Wi-Fi', - description: 'Wi-Fi 설정', - setBtn: '설정' + wifi: { + title: 'Wi-Fi', + description: 'Wi-Fi 설정', + setBtn: '설정' + }, + ssh: { + description: 'SSH 원격 접속 활성화', + tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' + }, + tls: { + description: 'HTTPS 프로토콜 활성화', + tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.' + }, + advanced: '고급 설정', + swap: { + title: '스왑', + disable: '비활성화', + description: '스왑 파일 크기 설정', + tip: '이 기능을 활성화하면 SD 카드의 수명이 단축될 수 있습니다!' + }, + mouseJiggler: { + title: '마우스 흔들기', + description: '원격 호스트가 절전 모드로 진입하는 것을 방지', + disable: '비활성화', + absolute: '절대값 모드', + relative: '상대값 모드' + }, + mdns: { + description: 'mDNS 검색 서비스 활성화', + tip: '사용하지 않는 경우 끄는 것이 좋습니다' + }, + hdmi: { + description: 'HDMI/모니터 출력 활성화' + }, + hidOnly: 'HID 전용 모드', + disk: '가상 디스크', + diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.', + network: '가상 네트워크', + networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', + reboot: '재부팅', + rebootDesc: 'NanoKVM을 재부팅하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' }, - ssh: { - description: 'SSH 원격 접속 활성화', - tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)' + tailscale: { + title: 'Tailscale', + memory: { + title: '메모리 최적화', + tip: '메모리 사용량이 제한을 초과하면 가비지 컬렉션이 더 적극적으로 실행되어 메모리를 확보하려고 시도합니다. Tailscale을 사용할 경우 50MB로 설정하는 것이 좋습니다. 변경 사항을 적용하려면 Tailscale을 다시 시작해야 합니다.', + disable: '비활성화' + }, + restart: '정말로 Tailscale을 다시 시작하시겠습니까?', + stop: '정말로 Tailscale을 중지하시겠습니까?', + stopDesc: 'Tailscale에서 로그아웃하고 자동 시작을 비활성화합니다.', + loading: '불러오는 중...', + notInstall: 'Tailscale이 없습니다. 설치해주세요.', + install: '설치', + installing: '설치중', + failed: '설치 실패', + retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', + download: '다운로드 중 :', + package: '패키지 설치', + unzip: '압축 해제', + upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', + upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', + refresh: '현재 페이지 새로고침', + notLogin: + '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', + urlPeriod: '이 주소는 10분간 유효합니다.', + login: '로그인', + loginSuccess: '로그인 성공', + enable: 'Tailscale 활성화', + deviceName: '장치 이름', + deviceIP: '장치 IP', + account: '계정', + logout: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', + logout2: '정말로 로그아웃 합니까?', + uninstall: 'Tailscale 제거', + okBtn: '네', + cancelBtn: '아니오' }, - tls: { - description: 'HTTPS 프로트콜 활성화', - tip: '주의: HTTPS를 사용하면 지연 시간이 늘어날 수 있으며, 특히 MJPEG 비디오 모드에서 그 영향이 클 수 있습니다.' + update: { + title: '업데이트 확인', + queryFailed: '버전 확인 실패', + updateFailed: '업데이트 실패, 재시도하세요.', + isLatest: '이미 최신 버전입니다.', + available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', + updating: '업데이트 시작. 잠시 기다려주세요...', + confirm: '확인', + cancel: '취소', + preview: '미리보기 업데이트', + previewDesc: '새로운 기능과 개선 사항에 미리 접근하세요', + previewTip: '미리보기 버전에는 버그나 완성되지 않은 기능이 포함될 수 있으니 주의하세요!' }, - advanced: '고급 설정', - swap: { - title: '스왑', - disable: '비활성화', - description: '스왑 파일의 사이즈를 설정하세요', - tip: "이 기능을 활성화하면 SD카드의 수명이 단축될 수 있습니다!" - }, - mouseJiggler: { - title: '마우스 흔들기', - description: '원격 호스트가 절전 모드로 전환되는 것을 방지합니다', - disable: '비활성화', - absolute: '절대값 모드', - relative: '상대값 모드' - }, - mdns: { - description: 'mDNS 탐색 서비스 활성화', - tip: '필요하지 않은 경우 비활성화하세요.' - }, - hdmi: { - description: 'HDMI/모니터 출력 활성화' - }, - hidOnly: 'HID 전용 모드', - disk: '가상 디스크', - diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.', - network: '가상 네트워크', - networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.', - reboot: '재부팅', - rebootDesc: 'NanoKVM을 재부팅 할까요?', - okBtn: '네', - cancelBtn: '아니오' + account: { + title: '계정', + webAccount: '웹 계정', + password: '비밀번호', + updateBtn: '업데이트', + logoutBtn: '로그아웃', + logoutDesc: '정말로 로그아웃 하시겠습니까?', + okBtn: '네', + cancelBtn: '아니오' + } }, - tailscale: { - title: 'Tailscale', - memory: { - title: '메모리 최적화', - tip: '메모리 사용량이 제한을 초과하면 가비지 컬렉션이 더 적극적으로 실행되어 메모리를 확보하려고 시도합니다. Tailscale을 사용할 경우 50MB로 설정하는 것이 좋습니다. 변경 사항을 적용하려면 Tailscale을 다시 시작해야 합니다.', - disable: '비활성화' - }, - restart: '정말로 Tailscale을 다시 시작하시겠습니까?', - stop: '정말로 Tailscale을 중지하시겠습니까?', - stopDesc: 'Tailscale에서 로그아웃하고 자동 시작을 비활성화합니다.', - loading: '불러오는 중...', - notInstall: 'Tailscale이 없습니다. 설치해주세요.', - install: '설치', - installing: '설치중', - failed: '설치 실패', - retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', - download: '다운로드 중 :', - package: '패키지 설치', - unzip: '압축 해제', - upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', - upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', - refresh: '현재 페이지 새로고침', - notLogin: - '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', - urlPeriod: '이 주소는 10분간 유효합니다.', - login: '로그인', - loginSuccess: '로그인 성공', - enable: 'Tailscale 활성화', - deviceName: '장치 이름', - deviceIP: '장치 IP', - account: '계정', - logout: '로그아웃', - logout2: '정말로 로그아웃 할까요?', - uninstall: 'Tailscale 제거', - okBtn: '네', - cancelBtn: '아니오' + error: { + title: '문제가 발생했습니다.', + refresh: '새로고침' }, - update: { - title: '업데이트 확인', - queryFailed: '버전 확인 실패', - updateFailed: '업데이트 실패, 재시도하세요.', - isLatest: '이미 최신 버전입니다.', - available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', - updating: '업데이트 시작. 잠시 기다려주세요...', - confirm: '확인', - cancel: '취소', - preview: '미리보기 업데이트', - preveiwDesc: '새 기능과 개선 사항을 미리 사용해 보세요', - previewTip: - '미리보기 버전에는 버그나 불완전한 기능이 포함되어 있을 수 있습니다!' + fullscreen: { + toggle: '전체 화면 전환' }, - account: { - title: '계정', - webAccount: '웹 계정 이름', - password: '비밀번호', - updateBtn: '변경', - logoutBtn: '로그아웃', - logoutDesc: '정말로 로그아웃 할까요?', - okBtn: '네', - cancelBtn: '아니오' + menu: { + collapse: '메뉴 접기', + expand: '메뉴 펼치기' } - }, - error: { - title: '문제가 발생했습니다.', - refresh: '새로고침' - }, - fullscreen: { - toggle: '전체 화면 전환' - }, - menu: { - collapse: '메뉴 축소', - expand: '메뉴 확장' - } } }; From 7f7e31bae9ffa4b19dfeaae7e373a3e7bc029728 Mon Sep 17 00:00:00 2001 From: X Date: Fri, 23 May 2025 22:29:49 +0900 Subject: [PATCH 04/88] =?UTF-8?q?=ED=84=B0=EB=AF=B8=EB=84=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/i18n/locales/ko.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index c2bbe76..115cdf0 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -55,7 +55,7 @@ const ko = { autoTips: '일부 해상도에서는 화면이 왜곡되거나 마우스 동작이 비정상적으로 나타날 수 있습니다. 원격 컴퓨터의 해상도를 변경하거나 자동 설정 대신 수동 설정을 사용해 보세요.', fps: 'FPS', - customizeFps: 'FPS 설정', + customizeFps: '사용자 지정', quality: '품질', qualityLossless: '무손실', qualityHigh: '높음', @@ -153,8 +153,8 @@ const ko = { parityOdd: '홀수', flowControl: '흐름 제어', flowControlNone: '없음', - flowControlSoft: '소프트', - flowControlHard: '하드', + flowControlSoft: '소프트웨어', + flowControlHard: '하드웨어', dataBits: '데이터 비트', stopBits: '정지 비트', confirm: '확인' From a1df6376ecfa60e0905f71177429782c54f8aed4 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Sat, 3 May 2025 21:15:32 +0100 Subject: [PATCH 05/88] split /data attachment from kvm media attachment backend config for media control remove .disabled adding settings toggle add default text --- kvmapp/system/init.d/S03usbdev | 16 +++++---- server/proto/vm.go | 1 + server/service/storage/image.go | 7 ++-- server/service/vm/virtual-device.go | 33 ++++++++++++++++--- web/src/i18n/locales/en.ts | 4 ++- .../menu/settings/device/virtual-devices.tsx | 29 ++++++++++++---- 6 files changed, 70 insertions(+), 20 deletions(-) diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev index c650e6b..ab12274 100755 --- a/kvmapp/system/init.d/S03usbdev +++ b/kvmapp/system/init.d/S03usbdev @@ -82,6 +82,7 @@ start_usb_dev(){ ln -s functions/hid.GS2 configs/c.1 fi + # dedicated for media if [ -e /boot/usb.disk0 ] then mkdir functions/mass_storage.disk0 @@ -95,17 +96,20 @@ start_usb_dev(){ disk=$(cat /boot/usb.disk0) if [ -z "${disk}" ] then - # if [ ! -e /mnt/usbdisk.img ] - # then - # fallocate -l 8G /mnt/usbdisk.img - # mkfs.vfat /mnt/usbdisk.img - # fi - echo /dev/mmcblk0p3 > functions/mass_storage.disk0/lun.0/file + echo "" > functions/mass_storage.disk0/lun.0/file else cat /boot/usb.disk0 > functions/mass_storage.disk0/lun.0/file fi fi + # mount data partition + if [ -e /boot/usb.disk1 ] + then + mkdir functions/mass_storage.disk1 + ln -s functions/mass_storage.disk1 configs/c.1/ + echo /dev/mmcblk0p3 > functions/mass_storage.disk1/lun.0/file + fi + ls /sys/class/udc/ | cat > UDC echo device > /proc/cviusb/otg_role } diff --git a/server/proto/vm.go b/server/proto/vm.go index 21d3844..3c56533 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -57,6 +57,7 @@ type DeleteScriptReq struct { type GetVirtualDeviceRsp struct { Network bool `json:"network"` + Media bool `json:"media"` Disk bool `json:"disk"` } diff --git a/server/service/storage/image.go b/server/service/storage/image.go index 63360ed..5f8118d 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -16,7 +16,7 @@ import ( const ( imageDirectory = "/data" - imageNone = "/dev/mmcblk0p3" + imageNone = "" cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" @@ -105,8 +105,9 @@ func (s *Service) MountImage(c *gin.Context) { // reset usb commands := []string{ - "echo > /sys/kernel/config/usb_gadget/g0/UDC", - "ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", + // this should not be required for media change, reset of the full gadget can break hid on some devices + //"echo > /sys/kernel/config/usb_gadget/g0/UDC", + //"ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", } for _, command := range commands { diff --git a/server/service/vm/virtual-device.go b/server/service/vm/virtual-device.go index 2eb40e9..3804526 100644 --- a/server/service/vm/virtual-device.go +++ b/server/service/vm/virtual-device.go @@ -14,7 +14,8 @@ import ( const ( virtualNetwork = "/boot/usb.rndis0" - virtualDisk = "/boot/usb.disk0" + virtualMedia = "/boot/usb.disk0" + virtualDisk = "/boot/usb.disk1" ) var ( @@ -31,16 +32,29 @@ var ( "/etc/init.d/S03usbdev start", } - mountDiskCommands = []string{ + mountMediaCommands = []string{ "touch /boot/usb.disk0", "/etc/init.d/S03usbdev stop", "/etc/init.d/S03usbdev start", } + unmountMediaCommands = []string{ + "/etc/init.d/S03usbdev stop", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", + "rm /boot/usb.disk0", + "/etc/init.d/S03usbdev start", + } + + mountDiskCommands = []string{ + "touch /boot/usb.disk1", + "/etc/init.d/S03usbdev stop", + "/etc/init.d/S03usbdev start", + } + unmountDiskCommands = []string{ "/etc/init.d/S03usbdev stop", - "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", - "rm /boot/usb.disk0", + "rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk1", + "rm /boot/usb.disk1", "/etc/init.d/S03usbdev start", } ) @@ -49,10 +63,12 @@ func (s *Service) GetVirtualDevice(c *gin.Context) { var rsp proto.Response network, _ := isDeviceExist(virtualNetwork) + media, _ := isDeviceExist(virtualMedia) disk, _ := isDeviceExist(virtualDisk) rsp.OkRspWithData(c, &proto.GetVirtualDeviceRsp{ Network: network, + Media: media, Disk: disk, }) log.Debugf("get virtual device success") @@ -80,6 +96,15 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) { } else { commands = unmountNetworkCommands } + case "media": + device = virtualMedia + + exist, _ := isDeviceExist(device) + if !exist { + commands = mountMediaCommands + } else { + commands = unmountMediaCommands + } case "disk": device = virtualDisk diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 534d4f0..58e24c8 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -266,7 +266,9 @@ const en = { }, hidOnly: 'HID-Only Mode', disk: 'Virtual Disk', - diskDesc: 'Mount virtual U-disk on the remote host', + diskDesc: 'Mount SD card on the remote host', + media: "Virtual Image", + mediaDesc: 'Attach virtual image device to the remote host', network: 'Virtual Network', networkDesc: 'Mount virtual network card on the remote host', reboot: 'Reboot', diff --git a/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx b/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx index e378223..ffe2ad0 100644 --- a/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx +++ b/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx @@ -8,8 +8,9 @@ export const VirtualDevices = () => { const { t } = useTranslation(); const [isNetworkOn, setIsNetworkOn] = useState(false); + const [isMediaOn, setIsMediaOn] = useState(false); const [isDiskOn, setIsDiskOn] = useState(false); - const [loading, setLoading] = useState(''); // '' | 'network' | 'disk' + const [loading, setLoading] = useState(''); // '' | 'network' | 'media' | 'disk' useEffect(() => { api.getVirtualDevice().then((rsp) => { @@ -18,11 +19,12 @@ export const VirtualDevices = () => { } setIsNetworkOn(rsp.data.network); + setIsMediaOn(rsp.data.media); setIsDiskOn(rsp.data.disk); }); }, []); - function update(device: 'network' | 'disk') { + function update(device: 'network' | 'media' | 'disk') { if (loading) return; setLoading(device); @@ -33,10 +35,16 @@ export const VirtualDevices = () => { return; } - if (device === 'network') { - setIsNetworkOn(rsp.data.on); - } else { - setIsDiskOn(rsp.data.on); + switch (device) { + case 'network': + setIsNetworkOn(rsp.data.on); + break; + case 'media': + setIsMediaOn(rsp.data.on); + break; + case 'disk': + setIsDiskOn(rsp.data.on); + break; } }) .finally(() => { @@ -46,6 +54,15 @@ export const VirtualDevices = () => { return ( <> +
+
+ {t('settings.device.media')} + {t('settings.device.mediaDesc')} +
+ + update('media')} /> +
+
{t('settings.device.disk')} From 6e45e6b135af22d1e4876529668f84d92eaebbc3 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Sat, 24 May 2025 14:03:50 +0100 Subject: [PATCH 06/88] make hdmi toggle persist --- server/main.go | 5 ++++- server/service/vm/hdmi.go | 11 +++++------ server/utils/hdmi.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 server/utils/hdmi.go diff --git a/server/main.go b/server/main.go index cb861fc..4a7379b 100644 --- a/server/main.go +++ b/server/main.go @@ -16,6 +16,7 @@ import ( "NanoKVM-Server/middleware" "NanoKVM-Server/router" "NanoKVM-Server/service/vm/jiggler" + "NanoKVM-Server/utils" "github.com/gin-gonic/gin" cors "github.com/rs/cors/wrapper/gin" @@ -38,7 +39,9 @@ func initialize() { vision := common.GetKvmVision() vision.SetHDMI(false) time.Sleep(10 * time.Millisecond) - vision.SetHDMI(true) + if !utils.IsHdmiDisabled() { + vision.SetHDMI(true) + } // run mouse jiggler jiggler.GetJiggler().Run() diff --git a/server/service/vm/hdmi.go b/server/service/vm/hdmi.go index f6556cb..c73e733 100644 --- a/server/service/vm/hdmi.go +++ b/server/service/vm/hdmi.go @@ -5,13 +5,12 @@ import ( "NanoKVM-Server/common" "NanoKVM-Server/proto" + "NanoKVM-Server/utils" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" ) -var hdmiEnabled = true - func (s *Service) ResetHdmi(c *gin.Context) { var rsp proto.Response @@ -20,7 +19,7 @@ func (s *Service) ResetHdmi(c *gin.Context) { vision.SetHDMI(false) time.Sleep(1 * time.Second) vision.SetHDMI(true) - hdmiEnabled = true + utils.PersistHDMIEnabled() rsp.OkRsp(c) log.Debug("reset hdmi") @@ -32,7 +31,7 @@ func (s *Service) EnableHdmi(c *gin.Context) { vision := common.GetKvmVision() vision.SetHDMI(true) - hdmiEnabled = true + utils.PersistHDMIEnabled() rsp.OkRsp(c) log.Debug("enable hdmi") @@ -44,7 +43,7 @@ func (s *Service) DisableHdmi(c *gin.Context) { vision := common.GetKvmVision() vision.SetHDMI(false) - hdmiEnabled = false + utils.PersistHDMIDisabled() rsp.OkRsp(c) log.Debug("disable hdmi") @@ -54,7 +53,7 @@ func (s *Service) GetHdmiState(c *gin.Context) { var rsp proto.Response rsp.OkRspWithData(c, &proto.GetGetHdmiStateRsp{ - Enabled: hdmiEnabled, + Enabled: !utils.IsHdmiDisabled(), }) log.Debug("get hdmi state") diff --git a/server/utils/hdmi.go b/server/utils/hdmi.go new file mode 100644 index 0000000..493f97b --- /dev/null +++ b/server/utils/hdmi.go @@ -0,0 +1,38 @@ +package utils + +import ( + "os" + + log "github.com/sirupsen/logrus" +) + +const ( + HDMIDisableFile = "/etc/kvm/hdmi_disable" +) + +func PersistHDMIDisabled() { + f, err := os.OpenFile(HDMIDisableFile, os.O_CREATE|os.O_RDONLY, 0644) + if err != nil { + log.Error("failed to create hdmi disable file:", err) + return + } + f.Close() +} + +func PersistHDMIEnabled() { + if err := os.Remove(HDMIDisableFile); err != nil { + log.Error("failed to remove hdmi disable file:", err) + return + } +} + +func IsHdmiDisabled() bool { + if _, err := os.Stat(HDMIDisableFile); err != nil { + if os.IsNotExist(err) { + return false // HDMI is enabled + } + log.Error("failed to check hdmi disable file:", err) + return false // Assume HDMI is enabled on error + } + return true // HDMI is disabled +} From dcb87d0d89922943d0b93ae390009cf348688a88 Mon Sep 17 00:00:00 2001 From: kmw0410 Date: Sun, 25 May 2025 14:17:28 +0900 Subject: [PATCH 07/88] add korean readme --- README.md | 2 +- README_JA.md | 2 +- README_KO.md | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++ README_ZH.md | 2 +- 4 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 README_KO.md diff --git a/README.md b/README.md index 2a4e135..503878c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ NanoKVM Hardware -English | [中文](./README_ZH.md) | [日本語](./README_JA.md) +English | [中文](./README_ZH.md) | [日本語](./README_JA.md) | [한국어](./README_KO.md)
diff --git a/README_JA.md b/README_JA.md index d3512e1..b880048 100644 --- a/README_JA.md +++ b/README_JA.md @@ -11,7 +11,7 @@ NanoKVM ハードウェア -[English](./README.md) | [中文](./README_ZH.md) | 日本語 +[English](./README.md) | [中文](./README_ZH.md) | 日本語 | [한국어](./README_KO.md)
diff --git a/README_KO.md b/README_KO.md new file mode 100644 index 0000000..bf3df98 --- /dev/null +++ b/README_KO.md @@ -0,0 +1,155 @@ +NanoKVM +====== + +
+ +![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png) + +

+ 빠른 시작 + | + 하드웨어 +

+ +[English](./README.md) | [中文](./README_ZH.md) | [日本語](./README_JA.md) | 한국어 + +
+ +> NanoKVM은 [RISC-V](https://en.wikipedia.org/wiki/RISC-V)에 의해 구동됩니다! +> 이슈나 제안이 있는 경우, 여기에서 이슈를 생성하거나 [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)에서 말해주세요. + +## 소개 + +Lichee NanoKVM은 LicheeRV Nano를 기반으로 하는 IP-KVM 제품이며, LicheeRV Nano의 극단적인 크기와 강력한 기능을 상속받습니다. + +Lichee NanoKVM은 세 가지 버전으로 제공됩니다: + +* NanoKVM-Cube Lite는 일정 수준의 DIY 능력을 갖춘 개인 사용자나 대량 수요가 있는 기업 사용자에게 적합한 구성입니다. + +* NanoKVM-Cube Full은 정교한 케이스와 액세서리, 부팅 시 바로 사용할 수 있는 시스템 미러 카드가 포함된 전체 구성으로, 개인 사용자에게 권장됩니다. + +* NanoKVM-PCle는 케이스 내부에 고정할 수 있는 PCIe 브래킷이 내장된 새로운 형태입니다. NanoKVM-PCIe는 NanoKVM Cube를 기반으로 WiFi와 PoE 기능을 선택적으로 추가할 수 있으며, 자체 PCIe 슬롯을 통해 메인보드에서 전원을 공급받을 수 있습니다. 또한 유선 연결(ETH)이 더 안정적이여서 전문적인 요구를 충족할 수 있습니다. + +
+ +![NanoKVM-Cube](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) + +![NanoKVM-PCIe](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM-PCIe.png) + +
+ +> 보안에 대해 우려하는 사용자는 이 이슈([#301](https://github.com/sipeed/NanoKVM/issues/301))를 읽어볼 수 있습니다. 모든 보안 관련 우려에 대한 설명을 확인할 수 있습니다. +> GitHub는 개방적이고 투명한 플랫폼으로서, 이론바 "백도어" 문제에 대해 논의할 수 있는 공간을 제공합니다. 오픈 소스는 제품을 더 안전하게 만듭니다! + +## 프로젝트 구조 + +``` shell +├── kvmapp # APP 업데이트 패키지 +│ ├── jpg_stream # 매우 오래된 버전에서도 직접 업데이트 가능하도록 호환됨 +│ ├── kvm_new_app # kvm_system 업데이트에 필요한 구성 요소를 트리거함 +│ ├── kvm_system # kvm_system 애플리케이션 +│ ├── server # NanoKVM 프론트 엔드와 백엔드 애플리케이션 +│ └── system # 필요한 시스템 구성 요소 +├── web # NanoKVM 프론트 엔드 +├── server # NanoKVM 백엔드 +├── support # 보조 기능 (이미지 하위 시스템, 시스템 상태, 시스템 업데이트, 화면, 키, 기타.) +│ ├── sg2002 # NanoKVM-Lite/Full/PCIe +│ └── h618 # NanoKVM-Pro +├── ... +``` + +## 기술 사양 + +| 제품 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- | +| 컴퓨팅 단위 | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | +| 해상도 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | +| 비디오 인코딩 | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | +| 비디오 지연 시간 | 90~230ms | 90~230ms | 100~230ms | +| UEFI/BIOS | ✓ | ✓ | ✓ | +| 가상 HID | ✓ | ✓ | ✓ | +| 가상 CD-ROM | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | +| Wake-on-LAN | ✓ | ✓ | ✓ | +| 이더넷 | 100M/10M | 100M/10M | 1000M/100M/10M | +| ATX 전원 조작 | 없음,사용자 소유 장비 연결 가능 | USB 인터페이스 IO 제어 보드 | RJ-45 인터페이스 IO 제어 보드 | +| OLED | 없음,사용자 소유 장비 연결 가능 | 128x64 0.96" 하얀색 | 128x32 0.91" 하얀색 | +| UART | 2 | 2 | 1 | +| TF 카드 | 없음 | ✓ | ✓ | +| 확장 | 없음 | PoE | WiFi/LTE | +| 전력 소모 | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V | +| 전원 입력 | PC USB can be powered | PC USB 또는 보조 전원 공급 장치 | DC 5V 3A 전원 공급 장치 | +| 냉각 | 무소음 팬리스 | 무소음 팬리스 | 팬 냉각 | +| 크기 | 23x37x15mm ~1/30 PiKVM V4 크기 | 40x36x36mm ~1/7 PiKVM V4 크기 | 120x68x44mm | + +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## NanoKVM 하드웨어 플랫폼 + +NanoKVM은 Sipped [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)의 기반으로 하며, 사양, 도식, 치수 도면은 [여기](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)에 있습니다. + +NanoKVM Lite는 LicheeRV Nano와 HDMItoCSI 보드로 구성되어 있으며, NanoKVM FULL은 NanoKVM Lite에 NasnoKVM-A/B 보드와 케이스를 추가한 구성입니다. HDMItoCSI 보드는 HDMI 신호를 변환하는 역할을 하고 NanoKVM-A는 OLED, ATX 전원 출력 (USB Type-C 인터페이스), 보조 전원 공급 장치, ATX 전원 켜기/끄기 및 리셋 버튼을 포함합니다. NanoKVM-B는 한쪽 끝이 컴퓨터와 연결되고 다른 한쪽 끝은 컴퓨터의 ATX 핀에 연결되어 컴퓨터의 전원을 원격으로 제어하는 데 사용됩니다. + +NanoKVM 이미지는 LicheeRV Nano SDK와 MaixCDK로 구축되었으며, LicheeRV Nano를 사용하는 자재와 호환됩니다. 반면에 KVM 소프트웨어는 LicheeRV Nano나 다른 SG2002 제품과 함께 사용할 수 없습니다. 만약 LicheeRV Nano나 MaixCam에서 HDMI 입력 애플리케이션을 구축하고 싶으시다면, 기술 지원에 문의해 주세요. + +참고: SG2002의 256MB 메모리 중에 158MB는 멀티미디어 하위 폴더에 할당되어 있으며, NanoKVM은 비디오 이미지 획득과 처리에 사용됩니다. + +* [NanoKVM-A 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) +* [NanoKVM-B 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf) +* [NanoKVM 이미지](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) + +## 로드맵 + +**개선 사항:** + +* [x] HDMI 모듈 재구성과 libmaixcam_lib.so 종속성 삭제 +* [x] 기본적으로 SSH를 비활성화 하고 웹 UI에 활성화/비활성화 토글을 추가 +* [x] 기본적으로 Tailscale을 비활성화 하고 웹 UI에 활성화/비활성화 토글을 추가 +* [x] 구성 파일에 JWT 구성 옵션 추가 +* [x] bcrypt로 안전한 비밀번호 저장 + +**배포:** + +* [x] GitHub에 애플리케이션 배포 +* [x] 온라인 업데이트에 대한 무결성 검사 구현 +* [ ] 오프라인 업데이트 지원 + +**버그 수정:** + +* [x] DNS 문제 해결 +* [x] CSRF 취약점 + +**기능** + +* [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Zerotier 지원 +* [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) WireGuard 지원 +* [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) 마우스 흔들기 추가 + +## 기여 + +1. 레포지토리를 포크하세요 +2. 기능 브랜치를 만드세요 +3. 변경한 것을 커밋하세요 +4. 브랜치로 푸시하세요 +5. 풀 리퀘스트를 생성하세요 + +NanoKVM에 관심을 가져주셔서 감사합니다! + +작고 핵심적인 내용으로 풀 리퀘스트를 유지해 주세요. 이렇게 하면 검토하고 병합하기가 쉬워집니다. + +> 고품질 풀 리퀘스트를 기여한 개발자들은 감사의 표시로 NanoKVM Cube, PCIe 또는 Pro를 받게 됩니다. + +## 구매처 + +* [Aliexpress(미국&러시아를 제외한 전 세계)](https://www.aliexpress.com/item/1005007369816019.html) +* [淘宝](https://item.taobao.com/item.htm?id=811206560480) +* [사전 주문 (Aliexpress나 Taobao를 지원하지 않는 국가)](https://sipeed.com/nanokvm) + +## 커뮤니티 + +* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) +* QQ 그룹: 703230713 + +## 라이센스 + +이 프로젝트는 GPL-3.0 라이센스에 따라 라이센스가 부여됩니다. 자세한 사항은 LICENSE 파일을 참조하세요. diff --git a/README_ZH.md b/README_ZH.md index c2e2ad1..478c16c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -11,7 +11,7 @@ NanoKVM Hardware -[English](./README.md) | 中文 | [日本語](./README_JA.md) +[English](./README.md) | 中文 | [日本語](./README_JA.md) | [한국어](./README_KO.md) From 9d6cf49c3f3c7246a4d3867f4ac39dda0e091db8 Mon Sep 17 00:00:00 2001 From: kmw_ <128137094+kmw0410@users.noreply.github.com> Date: Sun, 25 May 2025 14:26:02 +0900 Subject: [PATCH 08/88] fix missed translations --- README_KO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_KO.md b/README_KO.md index bf3df98..f32f1ce 100644 --- a/README_KO.md +++ b/README_KO.md @@ -77,8 +77,8 @@ Lichee NanoKVM은 세 가지 버전으로 제공됩니다: | UART | 2 | 2 | 1 | | TF 카드 | 없음 | ✓ | ✓ | | 확장 | 없음 | PoE | WiFi/LTE | -| 전력 소모 | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V | -| 전원 입력 | PC USB can be powered | PC USB 또는 보조 전원 공급 장치 | DC 5V 3A 전원 공급 장치 | +| 전력 소모 | 0.2A@5V | 0.2A@5V | 최대 2.6A@5V | +| 전원 입력 | PC USB 전원 공급 가능 | PC USB 또는 보조 전원 공급 장치 | DC 5V 3A 전원 공급 장치 | | 냉각 | 무소음 팬리스 | 무소음 팬리스 | 팬 냉각 | | 크기 | 23x37x15mm ~1/30 PiKVM V4 크기 | 40x36x36mm ~1/7 PiKVM V4 크기 | 120x68x44mm | From 2a34eb55e64f1e79af0d8c521433fd51caddf565 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Sun, 25 May 2025 19:22:58 +0100 Subject: [PATCH 09/88] Update image.go set imageNone to newline --- server/service/storage/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/service/storage/image.go b/server/service/storage/image.go index 5f8118d..9eb3f28 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -16,7 +16,7 @@ import ( const ( imageDirectory = "/data" - imageNone = "" + imageNone = "\n" cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" From 7771d1f326f847c498cc73236d63f3f47dfa57f5 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Sun, 25 May 2025 21:44:53 +0100 Subject: [PATCH 10/88] cleanup and simplification --- server/service/storage/image.go | 34 +++++---------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/server/service/storage/image.go b/server/service/storage/image.go index 9eb3f28..608d328 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -2,11 +2,9 @@ package storage import ( "os" - "os/exec" "path/filepath" "strconv" "strings" - "time" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" @@ -16,7 +14,6 @@ import ( const ( imageDirectory = "/data" - imageNone = "\n" cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" @@ -91,32 +88,14 @@ func (s *Service) MountImage(c *gin.Context) { } } - // mount + // mount if file provided image := req.File - if image == "" { - image = imageNone - } - - if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { - log.Errorf("mount file %s failed: %s", image, err) - rsp.ErrRsp(c, -2, "mount image failed") - return - } - - // reset usb - commands := []string{ - // this should not be required for media change, reset of the full gadget can break hid on some devices - //"echo > /sys/kernel/config/usb_gadget/g0/UDC", - //"ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", - } - - for _, command := range commands { - err := exec.Command("sh", "-c", command).Run() - if err != nil { - rsp.ErrRsp(c, -2, "execute command failed") + if image != "" { + if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { + log.Errorf("mount file %s failed: %s", image, err) + rsp.ErrRsp(c, -2, "mount image failed") return } - time.Sleep(100 * time.Millisecond) } rsp.OkRsp(c) @@ -133,9 +112,6 @@ func (s *Service) GetMountedImage(c *gin.Context) { } image := strings.ReplaceAll(string(content), "\n", "") - if image == imageNone { - image = "" - } data := &proto.GetMountedImageRsp{ File: image, From 393328eaf642f0544ff4e61654b8cfdd0f1b6b67 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Mon, 26 May 2025 07:47:48 +0100 Subject: [PATCH 11/88] Revert "cleanup and simplification" and adding reset flag This reverts commit 7771d1f326f847c498cc73236d63f3f47dfa57f5. --- server/service/storage/image.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server/service/storage/image.go b/server/service/storage/image.go index 608d328..69ea8d8 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -2,9 +2,11 @@ package storage import ( "os" + "os/exec" "path/filepath" "strconv" "strings" + "time" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" @@ -17,6 +19,7 @@ const ( cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" + usbNoRstMarker = "/boot/usb.norst" ) func (s *Service) GetImages(c *gin.Context) { @@ -98,6 +101,29 @@ func (s *Service) MountImage(c *gin.Context) { } } + // check to see if usb gadget reset is disabled + resetUsb := true + if _, err := os.Stat(usbNoRstMarker); err == nil { + resetUsb = false + } + + // reset usb + if resetUsb { + commands := []string{ + "echo > /sys/kernel/config/usb_gadget/g0/UDC", + "ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC", + } + + for _, command := range commands { + err := exec.Command("sh", "-c", command).Run() + if err != nil { + rsp.ErrRsp(c, -2, "execute command failed") + return + } + time.Sleep(100 * time.Millisecond) + } + } + rsp.OkRsp(c) log.Debugf("mount image %s success", req.File) } From 34746afea3bc0a770de8dc6e964f9936b31af363 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Mon, 26 May 2025 18:17:51 +0800 Subject: [PATCH 12/88] update README.md --- README.md | 150 +++++++++++++++++++++++---------------------------- README_ZH.md | 118 ++++++++++++++++++++++++---------------- 2 files changed, 140 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 2a4e135..1935768 100644 --- a/README.md +++ b/README.md @@ -6,42 +6,77 @@ NanoKVM ![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png)

- Quick Start - | - Hardware + 🚀 Quick Start + | + 🛠️ Hardware Details + | + 💾 Firmware Releases

English | [中文](./README_ZH.md) | [日本語](./README_JA.md) -> Your NanoKVM is powered by [RISC-V](https://en.wikipedia.org/wiki/RISC-V)! -> If you have any issues or suggestions, creating issue here, or tell us with [MaixHub Discussion](https://maixhub.com/discussion/nanokvm). +## 🌟 What is NanoKVM? -## Introduction +NanoKVM is a series of compact, open-source IP-KVM devices. Built upon the LicheeRV Nano (RISC-V), NanoKVM allows you to remotely access and control computers as if you were sitting right in front of them – perfect for managing servers, embedded systems, or any headless machine. -Lichee NanoKVM is an IP-KVM product based on LicheeRV Nano, inheriting the extreme size and powerful features of LicheeRV Nano. +## 📦 Product Family -The Lichee NanoKVM is now available in three versions: +We offer several NanoKVM versions to suit your needs: -* NanoKVM-Cube Lite is a basic configuration that is suitable for individual users with certain DIY capabilities and enterprise users with bulk requirements. +* **NanoKVM-Cube Lite:** A barebones kit for DIY enthusiasts and enterprise users needing bulk deployment. +* **NanoKVM-Cube Full:** A complete package with a sleek case, accessories, and a pre-flashed system SD card. Ready to use out-of-the-box, ideal for individual users. +* **NanoKVM-PCIe:** A unique form factor with a PCIe bracket for internal chassis mounting, drawing power directly from a PCIe slot. And add optional WiFi and PoE functions. -* NanoKVM-Cube Full is a full version with a sophisticated case and complete accessories, as well as a built-in system mirror card that is ready to use at boot, and is recommended for individual users. +### 🚀 NanoKVM-Pro: The 4K Powerhouse -* NanoKVM-PCle is a new form with a built-in PCle baffle that can be fixed inside the chassis. NanoKVM-PCle adds optional WiFi and PoE functions based on NanoKVM-Cube; it has its own PCle slot and can draw power from the motherboard PCle slot; in addition, the wired connection (ETH) is also more stable, meeting more professional needs. +Based on popular demand, we've upgraded NanoKVM to the **NanoKVM-Pro**: + +* **Resolution Upgrade:** From 1080P to stunning **4K@30fps / 2K@60fps**. +* **Network Upgrade:** Blazing fast **GbE + PoE + WiFi 6** (up from 100M Ethernet). +* **Latency Upgrade:** Encoder accelerated latency reduced from 100-150ms to **50-100ms**. + +> The NanoKVM-Pro is now available for pre-sale! [Click here for product details](https://sipeed.com/nanokvm/pro).
-![NanoKVM-Cube](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) - -![NanoKVM-PCIe](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM-PCIe.png) +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg)
-> For users concerned about security, you can read this issue: [#301](https://github.com/sipeed/NanoKVM/issues/301). You will find explanations addressing all security concerns. -> GitHub, as an open and transparent platform, provides a space for discussing so-called "backdoor" concerns. Open-source makes products more secure! +## 🛠️ Technical Specifications -## Project Structure +| Product | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| Core | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| Memory & Storage | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| System | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| Resolution | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI Loopout | 4K loopout | x | x | x | +| Video Encoding | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| Audio Transmit | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| Emulated USB Keyboard & Mouse | ✓ | ✓ | ✓ | ✓ | +| Emulated USB ISO | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| Wake-on-LAN | ✓ | ✓ | ✓ | ✓ | +| Web Terminal | ✓ | ✓ | ✓ | ✓ | +| Serial Terminal | 3 channels | 2 channels | x | 1 channel | +| Custom Scripts | ✓ | ✓ | x | x | +| Storage | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| Ethernet | 1000M | 100M | 1000M | 100M | +| PoE | Optional | Optional | x | x | +| WiFi | Optional WiFi6 | Optional WiFi6 | x | x | +| ATX Power Control | ✓ | ✓ | Extra $15 | Extra $10 | +| Display | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| More Features | Sync LED Strip / Smart Assistant | - | - | - | +| Power Consumption | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| Power Input | USB-C or PoE | USB-C | USB-C | USB-C | +| Dimensions | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| Price | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(with ATX) | $89 no ATX / $102 with ATX | $69 no ATX / $79 with ATX | + +## 📂 Project Structure ``` shell ├── kvmapp # APP update package @@ -58,33 +93,7 @@ The Lichee NanoKVM is now available in three versions: ├── ... ``` -## Tech Specs - -| Products | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | -|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- | -| Compute Units | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | -| Resolution | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | -| Video encoding | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | -| Video delay | 90~230ms | 90~230ms | 100~230ms | -| UEFI/BIOS | ✓ | ✓ | ✓ | -| Virtual HID | ✓ | ✓ | ✓ | -| Virtual CD-ROM | ✓ | ✓ | ✓ | -| IPMI | ✓ | ✓ | ✓ | -| Wake-on-LAN | ✓ | ✓ | ✓ | -| ETH | 100M/10M | 100M/10M | 1000M/100M/10M | -| ATX power control | None,Users can connect by their own | USB interface IO control board | RJ-45 interface IO control board | -| OLED | None,Users can connect by their own | 128x64 0.96" white | 128x32 0.91" white | -| UART | 2 | 2 | 1 | -| TF Card | None | ✓ | ✓ | -| Expansion | None | PoE | WiFi/LTE | -| Power consumption | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V | -| Power input | PC USB can be powered | PC USB or auxiliary power supply | DC 5V 3A power supply is required | -| Cooling | Silent fanless | Silent fanless | Fan cooling | -| Size | 23x37x15mm ~1/30 PiKVM V4 size | 40x36x36mm ~1/7 PiKVM V4 size | 120x68x44mm | - -![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) - -## Hardware platform NanoKVM +## 🔩 Hardware Platform (NanoKVM Cube/PCIe) NanoKVM is based on Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html),you can find specifications, schematics, and dimensional drawings [here](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano). @@ -98,58 +107,33 @@ Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the mu * [NanoKVM-B Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf) * [NanoKVM img](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) -## Roadmap +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) -**Enhancements:** +## 🤝 Contributing -* [x] Refactor the HDMI module and remove the libmaixcam_lib.so dependency -* [x] Disable SSH by default and add an enable/disable toggle in the web UI -* [x] Disable Tailscale by default and add an enable/disable toggle in the web UI -* [x] Add JWT configuration options to the configuration file -* [x] Secure password storage with bcrypt +We welcome contributions! Here's how you can help: -**Distribution:** +1. Fork the repository. +2. Create a feature branch. +3. Commit your changes. +4. Push to the branch. +5. Open a Pull Request. -* [x] Distribute applications via GitHub -* [x] Implement integrity checks for online updates -* [ ] Support offline updates +Please keep your pull requests small and focused to facilitate easier review and merging. -**Bug Fixes:** +> 🎁 **Contributors who submit high-quality Pull Requests may receive a NanoKVM Cube, PCIe, or Pro as a token of our appreciation!** -* [x] Fix the DNS issue -* [x] Fix the CSRF vulnerability - -**Features** - -* [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Support Zerotier -* [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) Support WireGuard -* [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) Add Mouse Jiggler - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request - -Thanks for your interest in contributing to NanoKVM! - -Please keep your pull requests small and focused. This will make it easier to review and merge. - -> Developers who have contributed high-quality Pull Requests will receive a NanoKVM Cube, PCIe, or Pro as a token of our appreciation. - -## Where to buy +## 🛒 Where to Buy * [Aliexpress(global except USA&Russia)](https://www.aliexpress.com/item/1005007369816019.html) * [淘宝](https://item.taobao.com/item.htm?id=811206560480) -* [Preorder (anyother country that not support in Aliexpress or Taobao)](https://sipeed.com/nanokvm) +* [Preorder (any other country that not support in Aliexpress or Taobao)](https://sipeed.com/nanokvm) -## Community +## 💬 Community & Support * [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) * QQ group: 703230713 -## License +## 📜 License This project is licensed under the GPL-3.0 License - see the LICENSE file for details. diff --git a/README_ZH.md b/README_ZH.md index c2e2ad1..48a73e8 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,39 +6,77 @@ NanoKVM ![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png)

- Quick Start - | - Hardware + 🚀 快速开始 + | + 🛠️ 硬件信息 + | + 💾 固件发布

[English](./README.md) | 中文 | [日本語](./README_JA.md) -> Your NanoKVM Power by RISC-V ! -> 如果您在使用过程中有任何问题或建议,请在这里提交 issue, 或在 [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)告诉我们. +## 🌟 NanoKVM 是什么? -## 简介 +NanoKVM 是一系列紧凑型开源 IP-KVM 设备。NanoKVM 基于 LicheeRV Nano (RISC-V) 构建,让您能够身临其境般的远程访问和控制计算机,是管理服务器、嵌入式系统或任何无头设备的理想之选。 -Lichee NanoKVM 是基于 LicheeRV Nano 的 IP-KVM 产品,继承了 LicheeRV Nano的极致体积 和 强大功能。 +## 📦 产品系列 -Lichee NanoKVM 目前有三个版本: +我们提供多种 NanoKVM 版本以满足您的需求: -* NanoKVM-Cube Lite 为基础版配置,适合 具有一定DIY能力的个人用户 和 有批量需求的企业用户。 -* NanoKVM-Cube Full 为完整版配置,带精致外壳和完整配件,内置开机即用的系统镜像卡,推荐个人用户购买。 -* NanoKVM-PCle 为全新形态,内置 PCle 挡板,可固定在机箱内部。NanoKVM-PCle 在 NanoKVM-Cube 基础上增加了可选的 WiFi 和 PoE 功能;拥有自家的 PCle 插槽,可从主板 PCle 插槽取电;另外有线连接(ETH)也更加稳定,满足更多专业需求。 +* **NanoKVM-Cube Lite**:为 DIY 爱好者和需要批量部署的企业用户提供的准系统套件。 +* **NanoKVM-Cube Full**:包含外壳、配件以及预装系统 SD 卡的完整套装。开箱即用,非常适合个人用户。 +* **NanoKVM-PCIe**:独特的外形设计,配备 PCIe 支架,可安装在机箱内部,并直接从 PCIe 插槽供电。可选配 WiFi 和 PoE 功能。 + +### 🚀 NanoKVM-Pro + +根据大众的需求,我们将 NanoKVM 升级到了 **NanoKVM-Pro**: + +* **分辨率升级**:从 1080P 升级到 **4K@30fps / 2K@60fps**。 +* **网络升级**:从百兆网口升级到超快的 **GbE + WiFi 6**。 +* **延迟升级**:编码器加速延迟从 100-150 毫秒减少到 **50-100 毫秒**。 + +> NanoKVM-Pro 现已开启预售![点击此处查看产品详情](https://sipeed.com/nanokvm/pro)。
-![NanoKVM-Cube](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) - -![NanoKVM-PCIe](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM-PCIe.png) +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg)
-> 对于关注安全的用户,可以阅读此问题 [#301](https://github.com/sipeed/NanoKVM/issues/301),您会找到有关所有安全问题的解释。GitHub 作为一个开放和透明的平台,为讨论所谓的“后门”担忧提供了空间。开源使产品更加安全! +## 🛠️ 技术规格 -## 项目结构 +| 产品 | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| 核心 | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| 内存&存储 | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| 系统 | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| 分辨率 | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI 环出 | 4K 环出 | x | x | x | +| 视频编码 | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| 音频输出 | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| 模拟 USB 键鼠 | ✓ | ✓ | ✓ | ✓ | +| 模拟 USB ISO | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| 局域网唤醒 | ✓ | ✓ | ✓ | ✓ | +| 网页终端 | ✓ | ✓ | ✓ | ✓ | +| 串口终端 | 3 channels | 2 channels | x | 1 channel | +| 用户脚本 | ✓ | ✓ | x | x | +| 存储 | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| 网口 | 1000M | 100M | 1000M | 100M | +| PoE | 可选 | 可选 | x | x | +| WiFi | 可选 WiFi6 | 可选 WiFi6 | x | x | +| ATX 电源控制 | ✓ | ✓ | ✓ | ✓ | +| 显示屏 | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| 特色功能 | LED 同步灯带 / 智能助手 | - | - | - | +| 功耗 | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| 电源输入 | USB-C or PoE | USB-C | USB-C | USB-C | +| 尺寸 | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| 价格 | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(带ATX) | $89 无ATX / $102 带ATX | $69 无ATX / $79 带ATX | + +## 📂 项目结构 ``` shell ├── kvmapp # APP更新包 @@ -59,33 +97,7 @@ Lichee NanoKVM 目前有三个版本: └── CHANGELOG.md ``` -## 参数 - -| 产品 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | -| --- | --- | --- | --- | -| 计算单元 | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | -| 分辨率 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | -| 视频编码 | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | -| 视频延迟 | 90~230ms | 90~230ms | 100~230ms | -| UEFI/BIOS | ✓ | ✓ | ✓ | -| 模拟USB键鼠 | ✓ | ✓ | ✓ | -| 模拟USB存储 | ✓ | ✓ | ✓ | -| IPMI | ✓ | ✓ | ✓ | -| Wake-on-LAN | ✓ | ✓ | ✓ | -| ETH | 100M/10M | 100M/10M | 1000M/100M/10M | -| ATX电源控制 | 无,用户可自行连接 | USB接口IO控制板 | RJ45接口IO控制板 | -| OLED显示 | 无,用户可自行扩展 | 128x64 0.96" white | 128x32 0.91" white | -| 外接串口 | 2路 | 2路 | 1路 | -| TF卡 | 无,用户自备 | 有,开机即用 | 有 | -| 扩展配件 | 无 | PoE | WiFi/LTE | -| 功耗 | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V | -| 电源输入 | PC USB即可供电 | PC USB即可供电
也支持额外辅助供电 | 需要DC 5V 3A供电 | -| 散热 | 静音无风扇 | 静音无风扇 | 需要风扇主动散热 | -| 尺寸 | 23x37x15mm
~1/30 PiKVM V4 体积 | 40x36x36mm
~1/7 PiKVM V4 体积 | 120x68x44mm | - -![](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) - -## NanoKVM 硬件平台 +## 🔩 硬件平台(NanoKVM Cube/PCIe) NanoKVM 基于 Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html) 核心板搭建,这部分硬件的规格书、原理图、尺寸图等均可在这里找到:[点击这里](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano) @@ -99,13 +111,29 @@ NanoKVM 镜像在LicheeRV Nano SDK 和 MaixCDK 基础上构建,可以兼容使 * [NanoKVM-B 原理图](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf) * [NanoKVM 镜像下载](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) -## 购买渠道 +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) + +## 🤝 贡献代码 + +我们十分欢迎任何人来贡献代码!您可以通过以下方式提供帮助: + +1. Fork 本仓库. +2. 创建一个分支. +3. 提交你的代码. +4. 推送代码到分支. +5. 创建一个PR. + +请保持你的 PR 尽量简单且集中,以便于更轻松地审查和合并。 + +> 🎁 **提交高质量 PR 的贡献者可能会收到 NanoKVM Cube、PCIe 或 Pro 作为感谢礼物!** + +## 🛒 购买渠道 * [Aliexpress(全球,除了美国和俄罗斯)](https://www.aliexpress.com/item/1005007369816019.html) * [淘宝](https://item.taobao.com/item.htm?id=811206560480) * [Preorder (其它不支持淘宝和速卖通的国家)](https://sipeed.com/nanokvm) -## 社区 +## 💬 社区 * [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) * QQ group: 703230713 From 8591aecb9c181b370be6f6e3da829951d324bdb1 Mon Sep 17 00:00:00 2001 From: kmw_ <128137094+kmw0410@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:14:02 +0900 Subject: [PATCH 13/88] update korean readme updated to include the addition of nanokvm-pro --- README_KO.md | 144 +++++++++++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 80 deletions(-) diff --git a/README_KO.md b/README_KO.md index f32f1ce..8860573 100644 --- a/README_KO.md +++ b/README_KO.md @@ -6,42 +6,77 @@ NanoKVM ![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png)

- 빠른 시작 - | - 하드웨어 + 🚀 빠른 시작 + | + 🛠️ 하드웨어 정보 + | + 💾 펌웨어 릴리즈

[English](./README.md) | [中文](./README_ZH.md) | [日本語](./README_JA.md) | 한국어 -> NanoKVM은 [RISC-V](https://en.wikipedia.org/wiki/RISC-V)에 의해 구동됩니다! -> 이슈나 제안이 있는 경우, 여기에서 이슈를 생성하거나 [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)에서 말해주세요. +## 🌟 NanoKVM이 무엇인가요? -## 소개 +NanoKVM은 소형 오픈 소스 IP-KVM 장치 시리즈입니다. LicheeRV Nano(RISC-V)를 기반으로 구축된 NanoKVM을 사용하면 마치 컴퓨터 앞에 바로 앉아있는 것처럼 원격으로 컴퓨터에 접근하고 제어할 수 있어 서버, 임베디드 시스템 또는 모든 헤드리스 머신을 관리하는 데 완벽합니다. -Lichee NanoKVM은 LicheeRV Nano를 기반으로 하는 IP-KVM 제품이며, LicheeRV Nano의 극단적인 크기와 강력한 기능을 상속받습니다. +## 📦 제품군 -Lichee NanoKVM은 세 가지 버전으로 제공됩니다: +다양한 요구를 충족할 수 있도록 여러 NanoKVM 버전을 제공합니다: -* NanoKVM-Cube Lite는 일정 수준의 DIY 능력을 갖춘 개인 사용자나 대량 수요가 있는 기업 사용자에게 적합한 구성입니다. +* **NanoKVM-Cube Lite:** DIY 사용자 및 대량 배포가 필요한 기업용을 위한 최소 구성 키트입니다. +* **NanoKVM-Cube Full:** 세련된 케이스, 액세서리, 사전 플래시된 시스템 SD 카드가 포함된 완전 구성 패키지입니다. 개봉 후 즉시 사용할 수 있어 개인 사용자에게 적합합니다. +* **NanoKVM-PCIe:** 독특한 폼팩터로, PCIe 브래킷을 통해 섀시 내부에 장착하며, 전원인 PCIe 슬롯에서 직접 공급받습니다. 선택적으로 WiFi와 PoE 기능을 추가할 수 있습니다. -* NanoKVM-Cube Full은 정교한 케이스와 액세서리, 부팅 시 바로 사용할 수 있는 시스템 미러 카드가 포함된 전체 구성으로, 개인 사용자에게 권장됩니다. +### 🚀 NanoKVM-Pro: 4K를 위한 파워 머신 -* NanoKVM-PCle는 케이스 내부에 고정할 수 있는 PCIe 브래킷이 내장된 새로운 형태입니다. NanoKVM-PCIe는 NanoKVM Cube를 기반으로 WiFi와 PoE 기능을 선택적으로 추가할 수 있으며, 자체 PCIe 슬롯을 통해 메인보드에서 전원을 공급받을 수 있습니다. 또한 유선 연결(ETH)이 더 안정적이여서 전문적인 요구를 충족할 수 있습니다. +대중적인 수요를 기반으로, NanoKVM을 **NanoKVM-Pro**로 업그레이드했습니다: + +* **해상도 업그레이드:** 1080P에서 놀라운 **4K@30fps / 2K@60fps**까지. +* **네트워크 업그레이드:** 초고속 **GbE + PoE + WiFi 6** (100M 이더넷 대비 향상). +* **지연 시간 업그레이드:** 인코더 가속 지연시간이 100-150ms에서 **50-100ms**로 감소. + +> NanoKVM-Pro가 예약 판매 중입니다! [제품 세부 정보는 여기를 클릭하세요](https://sipeed.com/nanokvm/pro).
-![NanoKVM-Cube](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) - -![NanoKVM-PCIe](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM-PCIe.png) +![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg)
-> 보안에 대해 우려하는 사용자는 이 이슈([#301](https://github.com/sipeed/NanoKVM/issues/301))를 읽어볼 수 있습니다. 모든 보안 관련 우려에 대한 설명을 확인할 수 있습니다. -> GitHub는 개방적이고 투명한 플랫폼으로서, 이론바 "백도어" 문제에 대해 논의할 수 있는 공간을 제공합니다. 오픈 소스는 제품을 더 안전하게 만듭니다! +## 🛠️ 기술 사양 -## 프로젝트 구조 +| 제품 | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM | +|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ | +| 코어 | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G | +| 메모리 & 저장 공간 | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC | +| 시스템 | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM | +| 해상도 | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps | +| HDMI 루프아웃 | 4K 루프아웃 | x | x | x | +| 영상 인코딩 | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 | +| 오디오 전송 | ✓ | x | ✓ | x | +| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ | +| USB 키보드 & 마우스 에뮬레이션 | ✓ | ✓ | ✓ | ✓ | +| USB ISO 에뮬레이션 | ✓ | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | x | +| Wake-on-LAN | ✓ | ✓ | ✓ | ✓ | +| 웹 터미널 | ✓ | ✓ | ✓ | ✓ | +| 시리얼 터미널 | 3 채널 | 2 채널 | x | 1 채널 | +| 사용자 정의 스크립트 | ✓ | ✓ | x | x | +| 저장 공간 | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s | +| 이더넷 | 1000M | 100M | 1000M | 100M | +| PoE | 옵션 | 옵션 | x | x | +| WiFi | WiFi6 옵션 | WiFi6 옵션 | x | x | +| ATX 전원 조작 | ✓ | ✓ | $15 추가 | $10 추가 | +| 디스플레이 | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 | +| 기타 기능 | LED 줄 동기화 / 스마트 어시스턴트 | - | - | - | +| 전력 소비량 | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V | +| 전원 입력 | USB-C 또는 PoE | USB-C | USB-C | USB-C | +| 크기 | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm | +| 가격 | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(with ATX) | ATX 미사용 시 $89 / ATX 사용 시 $102 | ATX 미사용 시 $69 / ATX 사용 시 $79 | + +## 📂 프로젝트 구조 ``` shell ├── kvmapp # APP 업데이트 패키지 @@ -50,7 +85,7 @@ Lichee NanoKVM은 세 가지 버전으로 제공됩니다: │ ├── kvm_system # kvm_system 애플리케이션 │ ├── server # NanoKVM 프론트 엔드와 백엔드 애플리케이션 │ └── system # 필요한 시스템 구성 요소 -├── web # NanoKVM 프론트 엔드 +├── web # NanoKVM 프론트엔드 ├── server # NanoKVM 백엔드 ├── support # 보조 기능 (이미지 하위 시스템, 시스템 상태, 시스템 업데이트, 화면, 키, 기타.) │ ├── sg2002 # NanoKVM-Lite/Full/PCIe @@ -58,35 +93,9 @@ Lichee NanoKVM은 세 가지 버전으로 제공됩니다: ├── ... ``` -## 기술 사양 +## 🔩 하드웨어 플랫폼 (NanoKVM Cube/PCIe) -| 제품 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | -|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- | -| 컴퓨팅 단위 | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | -| 해상도 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | -| 비디오 인코딩 | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | -| 비디오 지연 시간 | 90~230ms | 90~230ms | 100~230ms | -| UEFI/BIOS | ✓ | ✓ | ✓ | -| 가상 HID | ✓ | ✓ | ✓ | -| 가상 CD-ROM | ✓ | ✓ | ✓ | -| IPMI | ✓ | ✓ | ✓ | -| Wake-on-LAN | ✓ | ✓ | ✓ | -| 이더넷 | 100M/10M | 100M/10M | 1000M/100M/10M | -| ATX 전원 조작 | 없음,사용자 소유 장비 연결 가능 | USB 인터페이스 IO 제어 보드 | RJ-45 인터페이스 IO 제어 보드 | -| OLED | 없음,사용자 소유 장비 연결 가능 | 128x64 0.96" 하얀색 | 128x32 0.91" 하얀색 | -| UART | 2 | 2 | 1 | -| TF 카드 | 없음 | ✓ | ✓ | -| 확장 | 없음 | PoE | WiFi/LTE | -| 전력 소모 | 0.2A@5V | 0.2A@5V | 최대 2.6A@5V | -| 전원 입력 | PC USB 전원 공급 가능 | PC USB 또는 보조 전원 공급 장치 | DC 5V 3A 전원 공급 장치 | -| 냉각 | 무소음 팬리스 | 무소음 팬리스 | 팬 냉각 | -| 크기 | 23x37x15mm ~1/30 PiKVM V4 크기 | 40x36x36mm ~1/7 PiKVM V4 크기 | 120x68x44mm | - -![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) - -## NanoKVM 하드웨어 플랫폼 - -NanoKVM은 Sipped [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)의 기반으로 하며, 사양, 도식, 치수 도면은 [여기](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)에 있습니다. +NanoKVM은 Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)를 기반으로 하며,[여기](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)에서 사양, 도식, 치수, 도면은 여기에서 찾을 수 있습니다. NanoKVM Lite는 LicheeRV Nano와 HDMItoCSI 보드로 구성되어 있으며, NanoKVM FULL은 NanoKVM Lite에 NasnoKVM-A/B 보드와 케이스를 추가한 구성입니다. HDMItoCSI 보드는 HDMI 신호를 변환하는 역할을 하고 NanoKVM-A는 OLED, ATX 전원 출력 (USB Type-C 인터페이스), 보조 전원 공급 장치, ATX 전원 켜기/끄기 및 리셋 버튼을 포함합니다. NanoKVM-B는 한쪽 끝이 컴퓨터와 연결되고 다른 한쪽 끝은 컴퓨터의 ATX 핀에 연결되어 컴퓨터의 전원을 원격으로 제어하는 데 사용됩니다. @@ -98,34 +107,11 @@ NanoKVM 이미지는 LicheeRV Nano SDK와 MaixCDK로 구축되었으며, LicheeR * [NanoKVM-B 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf) * [NanoKVM 이미지](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) -## 로드맵 +![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) -**개선 사항:** +## 🤝 기여 -* [x] HDMI 모듈 재구성과 libmaixcam_lib.so 종속성 삭제 -* [x] 기본적으로 SSH를 비활성화 하고 웹 UI에 활성화/비활성화 토글을 추가 -* [x] 기본적으로 Tailscale을 비활성화 하고 웹 UI에 활성화/비활성화 토글을 추가 -* [x] 구성 파일에 JWT 구성 옵션 추가 -* [x] bcrypt로 안전한 비밀번호 저장 - -**배포:** - -* [x] GitHub에 애플리케이션 배포 -* [x] 온라인 업데이트에 대한 무결성 검사 구현 -* [ ] 오프라인 업데이트 지원 - -**버그 수정:** - -* [x] DNS 문제 해결 -* [x] CSRF 취약점 - -**기능** - -* [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Zerotier 지원 -* [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) WireGuard 지원 -* [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) 마우스 흔들기 추가 - -## 기여 +여러분의 기여를 환영합니다! 도움을 줄 수 있는 방법은 다음과 같습니다: 1. 레포지토리를 포크하세요 2. 기능 브랜치를 만드세요 @@ -133,23 +119,21 @@ NanoKVM 이미지는 LicheeRV Nano SDK와 MaixCDK로 구축되었으며, LicheeR 4. 브랜치로 푸시하세요 5. 풀 리퀘스트를 생성하세요 -NanoKVM에 관심을 가져주셔서 감사합니다! +Please keep your pull requests small and focused to facilitate easier review and merging. -작고 핵심적인 내용으로 풀 리퀘스트를 유지해 주세요. 이렇게 하면 검토하고 병합하기가 쉬워집니다. +> 🎁 **고품질 풀 리퀘스트를 제출한 기여자는 감사의 표시로 NanoKVM Cube, PCIe, 또는 Pro를 받을 수 있습니다!** -> 고품질 풀 리퀘스트를 기여한 개발자들은 감사의 표시로 NanoKVM Cube, PCIe 또는 Pro를 받게 됩니다. - -## 구매처 +## 🛒 어디서 구매하나요? * [Aliexpress(미국&러시아를 제외한 전 세계)](https://www.aliexpress.com/item/1005007369816019.html) * [淘宝](https://item.taobao.com/item.htm?id=811206560480) * [사전 주문 (Aliexpress나 Taobao를 지원하지 않는 국가)](https://sipeed.com/nanokvm) -## 커뮤니티 +## 💬 커뮤니티 & 지원 * [MaixHub Discussion](https://maixhub.com/discussion/nanokvm) -* QQ 그룹: 703230713 +* QQ group: 703230713 -## 라이센스 +## 📜 라이센스 이 프로젝트는 GPL-3.0 라이센스에 따라 라이센스가 부여됩니다. 자세한 사항은 LICENSE 파일을 참조하세요. From cd767d16df9854873d89ac7cac26814b6e5a7b41 Mon Sep 17 00:00:00 2001 From: protonchang <2095341+protonchang@users.noreply.github.com> Date: Sat, 7 Jun 2025 18:51:39 +0800 Subject: [PATCH 14/88] Update zhTW Translation --- web/src/i18n/locales/zh_tw.ts | 102 ++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/web/src/i18n/locales/zh_tw.ts b/web/src/i18n/locales/zh_tw.ts index 161d3c7..897e96a 100644 --- a/web/src/i18n/locales/zh_tw.ts +++ b/web/src/i18n/locales/zh_tw.ts @@ -46,7 +46,9 @@ const zh_tw = { finishBtn: '完成' }, screen: { + title: '螢幕', video: '編碼格式', + videoDirectTips: '本模式需先啟用HTTPS,請前往「設定 -> 設備」中開啟', resolution: '解析度', auto: '自動', autoTips: @@ -63,6 +65,7 @@ const zh_tw = { resetHdmi: '重置 HDMI' }, keyboard: { + title: 'Keyboard', paste: '貼上', tips: '僅支援標準鍵盤字母和符號', placeholder: '請輸入內容', @@ -71,6 +74,8 @@ const zh_tw = { ctrlaltdel: 'Ctrl+Alt+Del' }, mouse: { + title: '滑鼠', + cursor: '游標樣式', default: '預設游標', pointer: '懸浮游標', cell: '儲存格游標', @@ -80,8 +85,20 @@ const zh_tw = { mode: '滑鼠模式', absolute: '絕對模式', relative: '相對模式', + speed: '滾輪速度', + fast: '快', + slow: '慢', requestPointer: '使用相對模式。請按一下桌面以取得滑鼠游標。', - resetHid: '重設 HID' + resetHid: '重設 HID', + hidOnly: { + title: 'HID-Only 模式', + desc: "如果您的滑鼠和鍵盤停止回應,且重設 HID 無效,可能是 NanoKVM 與裝置間的相容性問題。請嘗試啟用 HID-Only 模式以獲得更好的相容性。", + tip1: '啟用 HID-Only 模式將會卸載虛擬隨身碟和虛擬網卡', + tip2: '在 HID-Only 模式下,映像掛載功能將被停用', + tip3: 'NanoKVM 將在切換模式後自動重新啟動', + enable: '啟用 HID-Only 模式', + disable: '停用 HID-Only 模式' + } }, image: { title: '映像', @@ -90,6 +107,7 @@ const zh_tw = { cdrom: '以CD-ROM模式掛載', mountFailed: '掛載失敗', mountDesc: '在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。', + refresh: 'Refresh the image list', tips: { title: '如何上傳', usb1: '透過 USB 將 NanoKVM 連接到您的電腦。', @@ -126,6 +144,16 @@ const zh_tw = { serialPort: 'Serial Port', serialPortPlaceholder: '請輸入 Serial Port', baudrate: 'Baud rate', + parity: 'Parity', + parityNone: 'None', + parityEven: 'Even', + parityOdd: 'Odd', + flowControl: 'Flow control', + flowControlNone: 'None', + flowControlSoft: 'Soft', + flowControlHard: 'Hard', + dataBits: 'Data bits', + stopBits: 'Stop bits', confirm: '確定' }, wol: { @@ -143,10 +171,16 @@ const zh_tw = { }, power: { title: '電源', + showConfirm: 'Confirmation', + showConfirmTip: 'Power operations require an extra confirmation', reset: '重新啟動', power: '電源', powerShort: '電源 (短按)', - powerLong: '電源 (長按)' + powerLong: '電源 (長按)', + resetConfirm: '執行重新啟動按鍵操作?', + powerConfirm: '執行電源按鍵操作?', + okBtn: '是', + cancelBtn: '取消' }, settings: { title: '設定', @@ -160,14 +194,23 @@ const zh_tw = { image: '映像版本', imageTip: 'NanoKVM 系统镜像版本', deviceKey: '設備序號', - community: '社群' + community: '社群', + hostname: '主機名稱', + hostnameUpdated: '已更新主機名稱. 請重新啟動以生效', + ipType: { + Wired: '有線', + Wireless: '無線', + Other: '其他' + } }, appearance: { title: '外觀', display: '顯示', language: '語言', menuBar: '選單列', - menuBarDesc: '是否在選單列中顯示圖案' + menuBarDesc: '是否在選單列中顯示圖案', + webTitle: '網頁標題', + webTitleDesc: '自訂網頁標題' }, device: { title: '設備', @@ -193,10 +236,40 @@ const zh_tw = { description: '啟用SSH', tip: '啟用前請務必設定強密碼(帳號 - 更改密碼)' }, + tls: { + description: '啟用 HTTPS', + tip: '啟用 HTTPS 可以提高安全性,但可能會增加延遲,特別是使用 MJPEG 編碼格式時。' + }, + advanced: '進階設定', + swap: { + title: 'Swap', + disable: '停用', + description: '設定Swap檔大小', + tip: "啟用此功能可能會減少SD卡的使用壽命!" + }, + mouseJiggler: { + title: 'Mouse Jiggler', + description: '避免遠端主機進入休眠狀態', + disable: '停用', + absolute: '絕對模式', + relative: '相對模式' + }, + mdns: { + description: '啟用mDNS服務', + tip: "若無需求,建議關閉此功能" + }, + hdmi: { + description: '啟用 HDMI/螢幕 輸出' + }, + hidOnly: 'HID-Only 模式', disk: '虛擬隨身碟', diskDesc: '在遠端主機上連接虛擬隨身碟', network: '虛擬網卡', - networkDesc: '在遠端主機上新增虛擬網卡' + networkDesc: '在遠端主機上新增虛擬網卡', + reboot: '重新啟動', + rebootDesc: '你確定要重新啟動 NanoKVM?', + okBtn: '是', + cancelBtn: '取消' }, tailscale: { title: 'Tailscale', @@ -229,7 +302,7 @@ const zh_tw = { deviceIP: '裝置 IP', account: '帳號', logout: '登出', - logout2: '確認登出?', + logoutDesc: '確認登出?', uninstall: '移除 Tailscale', okBtn: '確認', cancelBtn: '取消' @@ -242,26 +315,33 @@ const zh_tw = { available: '有可用更新。確定要更新嗎?', updating: '正在更新。請稍等...', confirm: '確定', - cancel: '取消' + cancel: '取消', + preview: '預覽更新', + previewDesc: '搶先體驗新功能和改進', + previewTip: + '請注意,預覽版本可能包含錯誤或功能不完整!' }, account: { title: '帳號', webAccount: '網頁帳號', password: '密碼', updateBtn: '修改', - logoutBtn: '登出' + logoutBtn: '登出', + logoutDesc: '你確定要登出嗎?', + okBtn: '是', + cancelBtn: '取消' } }, error: { title: '我們遇到了一些問題', - refresh: '重新整理', + refresh: '重新整理' }, fullscreen: { - toggle: '進入全螢幕模式', + toggle: '進入全螢幕模式' }, menu: { collapse: '收起選單', - expand: '展開選單', + expand: '展開選單' } } }; From c77981cc0ceebd8f6705b6c5d8c3cf4edf4f6717 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Fri, 13 Jun 2025 17:50:11 +0800 Subject: [PATCH 15/88] fix(security): resolve parameter injection in serial port terminal --- web/src/pages/terminal/index.tsx | 10 ++- web/src/pages/terminal/validater.ts | 120 ++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 web/src/pages/terminal/validater.ts diff --git a/web/src/pages/terminal/index.tsx b/web/src/pages/terminal/index.tsx index c15b027..09f5d5c 100644 --- a/web/src/pages/terminal/index.tsx +++ b/web/src/pages/terminal/index.tsx @@ -9,6 +9,8 @@ import '@xterm/xterm/css/xterm.css'; import { getBaseUrl } from '@/lib/service.ts'; import { Head } from '@/components/head.tsx'; +import { validatePicocomParameters } from './validater.ts'; + export const Terminal = () => { const { t } = useTranslation(); @@ -54,7 +56,13 @@ export const Terminal = () => { const stopBits = searchParams.get('stopBits'); if (!port || !baud) return; - ws.send(`picocom ${port} --baud ${baud} --parity ${parity} --flow ${flowControl} --databits ${dataBits} --stopbits ${stopBits}\r`); + if (!validatePicocomParameters({ port, baud, parity, flowControl, dataBits, stopBits })) { + return; + } + + ws.send( + `picocom ${port} --baud ${baud} --parity ${parity} --flow ${flowControl} --databits ${dataBits} --stopbits ${stopBits}\r` + ); }; const resizeScreen = () => { diff --git a/web/src/pages/terminal/validater.ts b/web/src/pages/terminal/validater.ts new file mode 100644 index 0000000..051ccee --- /dev/null +++ b/web/src/pages/terminal/validater.ts @@ -0,0 +1,120 @@ +type PicocomParameters = { + port: string | null; + baud: string | null; + parity: string | null; + flowControl: string | null; + dataBits: string | null; + stopBits: string | null; +}; + +const ALLOWED_BAUD_RATES = [ + '50', + '75', + '110', + '134', + '150', + '200', + '300', + '600', + '1200', + '1800', + '2400', + '4800', + '9600', + '19200', + '38400', + '57600', + '115200', + '230400', + '460800', + '500000', + '576000', + '921600', + '1000000', + '1152000', + '1500000', + '2000000', + '2500000', + '3000000', + '3500000', + '4000000' +]; +const ALLOWED_PARITIES = ['n', 'o', 'e', 'm', 's', 'none', 'even', 'odd']; +const ALLOWED_FLOW_CONTROLS = ['n', 'h', 's', 'x', 'none', 'soft', 'hard']; +const ALLOWED_DATA_BITS = ['5', '6', '7', '8']; +const ALLOWED_STOP_BITS = ['1', '2']; + +const PROHIBITED_PORT_CHARS_REGEX = /[;&|`$()<>!*?"'\s\\]/; + +export function validatePicocomParameters(params: PicocomParameters) { + if (!params.port || params.port.trim() === '') { + return false; + } + + const port = params.port.trim(); + if (!port.startsWith('/')) { + return false; + } + if (port.includes('..')) { + return false; + } + if (PROHIBITED_PORT_CHARS_REGEX.test(port)) { + return false; + } + if (!/^\/dev\/[a-zA-Z0-9.-_]+$/.test(port)) { + return false; + } + + if (params.baud !== undefined && params.baud !== null && String(params.baud).trim() !== '') { + const baudStr = String(params.baud).trim(); + if (!ALLOWED_BAUD_RATES.includes(baudStr)) { + return false; + } + } + + if ( + params.parity !== undefined && + params.parity !== null && + String(params.parity).trim() !== '' + ) { + const parityStr = String(params.parity).trim().toLowerCase(); + if (!ALLOWED_PARITIES.includes(parityStr)) { + return false; + } + } + + if ( + params.flowControl !== undefined && + params.flowControl !== null && + String(params.flowControl).trim() !== '' + ) { + const flowStr = String(params.flowControl).trim().toLowerCase(); + if (!ALLOWED_FLOW_CONTROLS.includes(flowStr)) { + return false; + } + } + + if ( + params.dataBits !== undefined && + params.dataBits !== null && + String(params.dataBits).trim() !== '' + ) { + const dataBitsStr = String(params.dataBits).trim(); + if (!ALLOWED_DATA_BITS.includes(dataBitsStr)) { + return false; + } + } + + if ( + params.stopBits !== undefined && + params.stopBits !== null && + String(params.stopBits).trim() !== '' + ) { + const stopBitsStr = String(params.stopBits).trim(); + if (!ALLOWED_STOP_BITS.includes(stopBitsStr)) { + return false; + } + } + + return true; +} From 32c81f220ded4c05efbc06efdeafc62b7ebde2d6 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Fri, 13 Jun 2025 18:27:04 +0800 Subject: [PATCH 16/88] update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdcf406..6fd5919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.9 [c77981c](https://github.com/sipeed/NanoKVM/commit/c77981cc0ceebd8f6705b6c5d8c3cf4edf4f6717) (2025-06-13) + +* fix(security): resolve parameter injection in serial port terminal + ## 2.2.8 [01e28f1](https://github.com/sipeed/NanoKVM/commit/01e28f10ae8b581d484bb6077ddfe7bbe4e57919) (2025-05-22) * feat: add AZERTY virtual keyboard Layout (thanks to [@felix068](https://github.com/felix068)) From f2c91c09c788fd7aaa99e801437f1d44d297c09d Mon Sep 17 00:00:00 2001 From: chiconws Date: Fri, 13 Jun 2025 16:03:07 -0300 Subject: [PATCH 17/88] Add PT-BR translation --- web/src/i18n/locales/pt_br.ts | 345 ++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 web/src/i18n/locales/pt_br.ts diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts new file mode 100644 index 0000000..a3534ab --- /dev/null +++ b/web/src/i18n/locales/pt_br.ts @@ -0,0 +1,345 @@ +const en = { + translation: { + head: { + desktop: 'Área de Trabalho Remota', + login: 'Login', + changePassword: 'Mudar Senha', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Login', + placeholderUsername: 'Nome de usuário', + placeholderPassword: 'Senha', + placeholderPassword2: 'Por favor, digite a senha novamente', + noEmptyUsername: 'Nome de usuário é obrigatório', + noEmptyPassword: 'Senha é obrigatória', + noAccount: 'Falha ao obter informações do usuário, por favor atualize a página ou redefina a senha', + invalidUser: 'Nome de usuário ou senha inválidos', + error: 'Erro inesperado', + changePassword: 'Mudar Senha', + changePasswordDesc: 'Para a segurança do seu dispositivo, por favor, mude a senha!', + differentPassword: 'Senhas não conferem', + illegalUsername: 'Nome de usuário contém caracteres inválidos', + illegalPassword: 'Senha contém caracteres inválidos', + forgetPassword: 'Esqueci a senha', + ok: 'Ok', + cancel: 'Cancelar', + loginButtonText: 'Login', + tips: { + reset1: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM por 10 segundos.', + reset2: 'Para etapas detalhadas, por favor, consulte este documento:', + reset3: 'Conta padrão da Web:', + reset4: 'Conta padrão SSH:', + change1: 'Por favor, note que esta ação irá alterar as seguintes senhas:', + change2: 'Senha de login da Web', + change3: 'Senha root do sistema (senha de login SSH)', + change4: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi para o NanoKVM', + success: 'Por favor, verifique o status da rede do NanoKVM e visite o novo endereço IP.', + failed: 'Operação falhou, por favor, tente novamente.', + confirmBtn: 'Ok', + finishBtn: 'Finalizado' + }, + screen: { + title: 'Tela', + video: 'Modo de Vídeo', + videoDirectTips: 'Ative HTTPS em "Configurações > Dispositivo" para usar este modo', + resolution: 'Resolução', + auto: 'Automático', + autoTips: 'Rasgos na tela ou desvio do mouse podem ocorrer em resoluções específicas. Considere ajustar a resolução do host remoto ou desativar o modo automático.', + fps: 'FPS', + customizeFps: 'Personalizar', + quality: 'Qualidade', + qualityLossless: 'Sem perdas', + qualityHigh: 'Alta', + qualityMedium: 'Média', + qualityLow: 'Baixa', + frameDetect: 'Detecção de Quadros', + frameDetectTip: 'Calcular a diferença entre os quadros. Parar a transmissão de vídeo quando nenhuma alteração for detectada na tela do host remoto.', + resetHdmi: 'Redefinir HDMI' + }, + keyboard: { + title: 'Teclado', + paste: 'Colar', + tips: 'Apenas letras e símbolos de teclado padrão são suportados', + placeholder: 'Por favor, digite', + submit: 'Enviar', + virtual: 'Teclado', + ctrlaltdel: 'Ctrl+Alt+Del' + }, + mouse: { + title: 'Mouse', + cursor: 'Estilo do cursor', + default: 'Cursor padrão', + pointer: 'Cursor de ponteiro', + cell: 'Cursor de célula', + text: 'Cursor de texto', + grab: 'Cursor de arrastar', + hide: 'Ocultar cursor', + mode: 'Modo do mouse', + absolute: 'Modo absoluto', + relative: 'Modo relativo', + speed: 'Velocidade da roda', + fast: 'Rápido', + slow: 'Lento', + requestPointer: 'Usando modo relativo. Por favor, clique na área de trabalho para obter o ponteiro do mouse.', + resetHid: 'Redefinir HID', + hidOnly: { + title: 'Modo Somente-HID', + desc: 'Se o seu mouse e teclado pararem de responder e a redefinição de HID não ajudar, pode ser um problema de compatibilidade entre o NanoKVM e o dispositivo. Tente habilitar o modo Somente-HID para melhor compatibilidade.', + tip1: 'Habilitar o modo Somente-HID irá desmontar o U-disk virtual e a rede virtual', + tip2: 'No modo Somente-HID, a montagem de imagem está desativada', + tip3: 'NanoKVM será reiniciado automaticamente após a troca de modos', + enable: 'Habilitar modo Somente-HID', + disable: 'Desabilitar modo Somente-HID' + } + }, + image: { + title: 'Imagens', + loading: 'Carregando...', + empty: 'Nada Encontrado', + cdrom: 'Montar a imagem no modo CD-ROM', + mountFailed: 'Falha na Montagem', + mountDesc: 'Em alguns sistemas, é necessário ejetar o disco virtual no host remoto antes de montar a imagem.', + refresh: 'Atualizar a lista de imagens', + tips: { + title: 'Como fazer upload', + usb1: 'Conecte o NanoKVM ao seu computador via USB.', + usb2: 'Certifique-se de que o disco virtual está montado (Configurações - Disco Virtual).', + usb3: 'Abra o disco virtual no seu computador e copie o arquivo de imagem para o diretório raiz do disco virtual.', + scp1: 'Certifique-se de que o NanoKVM e seu computador estão na mesma rede local.', + scp2: 'Abra um terminal no seu computador e use o comando SCP para fazer upload do arquivo de imagem para o diretório /data no NanoKVM.', + scp3: 'Exemplo: scp seu-caminho-da-imagem root@seu-ip-nanokvm:/data', + tfCard: 'Cartão TF', + tf1: 'Este método é suportado em sistemas Linux', + tf2: 'Remova o cartão TF do NanoKVM (para a versão FULL, desmonte a caixa primeiro).', + tf3: 'Insira o cartão TF em um leitor de cartão e conecte-o ao seu computador.', + tf4: 'Copie o arquivo de imagem para o diretório /data no cartão TF.', + tf5: 'Insira o cartão TF no NanoKVM.' + } + }, + script: { + title: 'Scripts', + upload: 'Upload', + run: 'Executar', + runBackground: 'Executar em segundo plano', + runFailed: 'Falha na execução', + attention: 'Atenção', + delDesc: 'Tem certeza de que deseja excluir este arquivo?', + confirm: 'Sim', + cancel: 'Não', + delete: 'Excluir', + close: 'Fechar' + }, + terminal: { + title: 'Terminal', + nanokvm: 'Terminal NanoKVM', + serial: 'Terminal de Porta Serial', + serialPort: 'Porta Serial', + serialPortPlaceholder: 'Por favor, digite a porta serial', + baudrate: 'Taxa de transmissão', + parity: 'Paridade', + parityNone: 'Nenhum', + parityEven: 'Par', + parityOdd: 'Ímpar', + flowControl: 'Controle de fluxo', + flowControlNone: 'Nenhum', + flowControlSoft: 'Software', + flowControlHard: 'Hardware', + dataBits: 'Bits de dados', + stopBits: 'Bits de parada', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Enviando comando...', + sent: 'Comando enviado', + input: 'Por favor, digite o MAC', + ok: 'Ok' + }, + download: { + title: 'Baixador de Imagens', + input: 'Por favor, digite uma URL de imagem remota', + ok: 'Ok', + disabled: 'A partição /data é RO, então não podemos baixar a imagem' + }, + power: { + title: 'Energia', + showConfirm: 'Confirmação', + showConfirmTip: 'Operações de energia requerem uma confirmação extra', + reset: 'Redefinir', + power: 'Energia', + powerShort: 'Energia (clique curto)', + powerLong: 'Energia (clique longo)', + resetConfirm: 'Prosseguir com a operação de redefinição?', + powerConfirm: 'Prosseguir com a operação de energia?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + settings: { + title: 'Configurações', + about: { + title: 'Sobre o NanoKVM', + information: 'Informação', + ip: 'IP', + mdns: 'mDNS', + application: 'Versão do Aplicativo', + applicationTip: 'Versão do aplicativo web NanoKVM', + image: 'Versão da Imagem', + imageTip: 'Versão da imagem do sistema NanoKVM', + deviceKey: 'Chave do Dispositivo', + community: 'Comunidade', + hostname: 'Nome do Host', + hostnameUpdated: 'Nome do host atualizado. Reinicie para aplicar.', + ipType: { + Wired: 'Com Fio', + Wireless: 'Sem Fio', + Other: 'Outro' + } + }, + appearance: { + title: 'Aparência', + display: 'Exibição', + language: 'Idioma', + menuBar: 'Barra de Menu', + menuBarDesc: 'Exibir ícones na barra de menu', + webTitle: 'Título da Web', + webTitleDesc: 'Personalizar o título da página web' + }, + device: { + title: 'Dispositivo', + oled: { + title: 'OLED', + description: 'Desligar tela OLED após', + 0: 'Nunca', + 15: '15 seg', + 30: '30 seg', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hora' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi', + setBtn: 'Configurar' + }, + ssh: { + description: 'Habilitar acesso remoto SSH', + tip: 'Defina uma senha forte antes de habilitar (Conta - Mudar Senha)' + }, + tls: { + description: 'Habilitar protocolo HTTPS', + tip: 'Atenção: O uso de HTTPS pode aumentar a latência, especialmente com o modo de vídeo MJPEG.' + }, + advanced: 'Configurações Avançadas', + swap: { + title: 'Swap', + disable: 'Desativar', + description: 'Defina o tamanho do arquivo de swap', + tip: 'Habilitar esta função pode encurtar a vida útil do seu cartão SD!' + }, + mouseJiggler: { + title: 'Movimentador de Mouse', + description: 'Impedir que o host remoto entre em suspensão', + disable: 'Desativar', + absolute: 'Modo Absoluto', + relative: 'Modo Relativo' + }, + mdns: { + description: 'Habilitar serviço de descoberta mDNS', + tip: 'Desligue se não for necessário' + }, + hdmi: { + description: 'Habilitar saída HDMI/monitor' + }, + hidOnly: 'Modo Somente-HID', + disk: 'Disco Virtual', + diskDesc: 'Montar U-disk virtual no host remoto', + network: 'Rede Virtual', + networkDesc: 'Montar placa de rede virtual no host remoto', + reboot: 'Reiniciar', + rebootDesc: 'Tem certeza de que deseja reiniciar o NanoKVM?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: 'Otimização de memória', + tip: 'Quando o uso de memória excede o limite, a coleta de lixo é realizada de forma mais agressiva para tentar liberar memória. Recomenda-se definir para 75MB se estiver usando Tailscale. É necessário reiniciar o Tailscale para que a alteração tenha efeito.', + disable: 'Desativar' + }, + restart: 'Reiniciar Tailscale?', + stop: 'Parar Tailscale?', + stopDesc: 'Sair do Tailscale e desabilitar a inicialização automática no boot.', + loading: 'Carregando...', + notInstall: 'Tailscale não encontrado! Por favor, instale.', + install: 'Instalar', + installing: 'Instalando', + failed: 'Falha na instalação', + retry: 'Por favor, atualize e tente novamente. Ou tente instalar manualmente', + download: 'Baixar o', + package: 'pacote de instalação', + unzip: 'e descompacte-o', + upTailscale: 'Fazer upload do tailscale para o diretório NanoKVM /usr/bin/', + upTailscaled: 'Fazer upload do tailscaled para o diretório NanoKVM /usr/sbin/', + refresh: 'Atualizar página atual', + notLogin: 'O dispositivo ainda não foi vinculado. Por favor, faça login e vincule este dispositivo à sua conta.', + urlPeriod: 'Esta URL é válida por 10 minutos', + login: 'Login', + loginSuccess: 'Login Bem-sucedido', + enable: 'Habilitar Tailscale', + deviceName: 'Nome do Dispositivo', + deviceIP: 'IP do Dispositivo', + account: 'Conta', + logout: 'Sair', + logoutDesc: 'Tem certeza de que deseja sair?', + uninstall: 'Desinstalar Tailscale', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + update: { + title: 'Verificar Atualizações', + queryFailed: 'Falha ao obter a versão', + updateFailed: 'Falha na atualização. Por favor, tente novamente.', + isLatest: 'Você já tem a versão mais recente.', + available: 'Uma atualização está disponível. Tem certeza de que deseja atualizar agora?', + updating: 'Atualização iniciada. Por favor, aguarde...', + confirm: 'Confirmar', + cancel: 'Cancelar', + preview: 'Prévia das Atualizações', + previewDesc: 'Tenha acesso antecipado a novos recursos e melhorias', + previewTip: 'Esteja ciente de que as versões de prévia podem conter bugs ou funcionalidade incompleta!' + }, + account: { + title: 'Conta', + webAccount: 'Nome da Conta Web', + password: 'Senha', + updateBtn: 'Alterar', + logoutBtn: 'Sair', + logoutDesc: 'Tem certeza de que deseja sair?', + okBtn: 'Sim', + cancelBtn: 'Não' + } + }, + error: { + title: 'Encontramos um problema', + refresh: 'Atualizar' + }, + fullscreen: { + toggle: 'Alternar Tela Cheia' + }, + menu: { + collapse: 'Recolher Menu', + expand: 'Expandir Menu' + } + } +} \ No newline at end of file From 7f3e039d1b8a13a2b1a98914f6835c9e2d0a0e29 Mon Sep 17 00:00:00 2001 From: chiconws Date: Fri, 13 Jun 2025 16:05:05 -0300 Subject: [PATCH 18/88] Add PT-BR translation --- web/src/i18n/locales/pt_br.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts index a3534ab..509170f 100644 --- a/web/src/i18n/locales/pt_br.ts +++ b/web/src/i18n/locales/pt_br.ts @@ -1,4 +1,4 @@ -const en = { +const pt_br = { translation: { head: { desktop: 'Área de Trabalho Remota', @@ -342,4 +342,6 @@ const en = { expand: 'Expandir Menu' } } -} \ No newline at end of file +} + +export default pt_br; From 61ea4bd38522085d8b5ed5be71e1a65ea936d0e2 Mon Sep 17 00:00:00 2001 From: jingyuexing <19589872+jingyuexing@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:52:48 +0800 Subject: [PATCH 19/88] chore: cherry pick --- server/config/hardware.go | 37 ++++++++--- server/router/application.go | 4 ++ server/service/application/settings.go | 63 +++++++++++++++++++ web/src/api/application.ts | 8 +++ web/src/jotai/settings.ts | 2 + web/src/pages/desktop/menu/index.tsx | 9 ++- web/src/pages/desktop/menu/screen/index.tsx | 9 +-- .../menu/settings/device/advanced/index.tsx | 3 +- .../desktop/menu/settings/device/index.tsx | 23 ++++--- 9 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 server/service/application/settings.go diff --git a/server/config/hardware.go b/server/config/hardware.go index 79d4e2f..1913707 100644 --- a/server/config/hardware.go +++ b/server/config/hardware.go @@ -11,8 +11,10 @@ type HWVersion int const ( HWVersionAlpha HWVersion = iota + HWVersionPro HWVersionBeta HWVersionPcie + HWVersionATX HWVersionFile = "/etc/kvm/hw" ) @@ -41,6 +43,14 @@ var HWPcie = Hardware{ GPIOHDDLed: "", } +var HWPro = Hardware{ + Version: HWVersionPro, + GPIOReset: "/sys/class/gpio/gpio35/value", + GPIOPower: "/sys/class/gpio/gpio7/value", + GPIOPowerLED: "/sys/class/gpio/gpio75/value", + GPIOHDDLed: "/sys/class/gpio/gpio74/value", +} + func (h HWVersion) String() string { switch h { case HWVersionAlpha: @@ -49,29 +59,38 @@ func (h HWVersion) String() string { return "Beta" case HWVersionPcie: return "PCIE" + case HWVersionPro: + return "Pro" default: return "Unknown" } } -func getHwVersion() HWVersion { +func GetHwVersion() HWVersion { content, err := os.ReadFile(HWVersionFile) if err != nil { return HWVersionAlpha } version := strings.ReplaceAll(string(content), "\n", "") - if version == "beta" { + switch version { + case "alpha": + return HWVersionAlpha + case "beta": return HWVersionBeta - } else if version == "pcie" { + case "pcie": return HWVersionPcie + case "atx": + return HWVersionATX + case "pro": + return HWVersionPro + default: + return HWVersionAlpha } - - return HWVersionAlpha } func getHardware() (h Hardware) { - version := getHwVersion() + version := GetHwVersion() switch version { case HWVersionAlpha: @@ -83,10 +102,12 @@ func getHardware() (h Hardware) { case HWVersionPcie: h = HWPcie + case HWVersionPro: + h = HWPro + default: h = HWAlpha log.Errorf("Unsupported hardware version: %s", version) } - - return + return h } diff --git a/server/router/application.go b/server/router/application.go index 1c4d269..351e0ce 100644 --- a/server/router/application.go +++ b/server/router/application.go @@ -16,4 +16,8 @@ func applicationRouter(r *gin.Engine) { api.GET("/application/preview", service.GetPreview) // get preview updates state api.POST("/application/preview", service.SetPreview) // set preview updates state + + api.GET("/application/settings/disabled-items", service.GetDisableItems) // get disable items list + api.GET("/application/menu/disabled-items", service.GetDisableMenus) + } diff --git a/server/service/application/settings.go b/server/service/application/settings.go new file mode 100644 index 0000000..9b8ef48 --- /dev/null +++ b/server/service/application/settings.go @@ -0,0 +1,63 @@ +package application + +import ( + "NanoKVM-Server/config" + "NanoKVM-Server/proto" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + DeviceTLS = "device:tls" + DeviceSSH = "device:ssh" + DeviceMDNS = "device:mdns" + DeviceHDMI = "device:hdmi" + DeviceWIFI = "device:wifi" + DeviceMouse = "device:mouse" + DeviceReboot = "device:reboot" + DeviceOLED = "device:oled" + DeviceAdvance = "device:advance" + DeviceAdvancedSwap = "device:advance:swap" +) + +const ( + MenuScript = "script" + MenuImage = "image" + MenuDownload = "download" + MenuTerminal = "terminal" + MenuScreen = "screen" + MenuScreenResolution = "screen:resolution" +) + +func (s *Service) GetDisableItems(ctx *gin.Context) { + var rsp proto.Response + switch config.GetHwVersion() { + case config.HWVersionPcie, config.HWVersionATX: + rsp.OkRspWithData(ctx, []string{ + DeviceAdvancedSwap, + DeviceAdvance, + MenuScreenResolution, + }) + log.Debugf("disable menus items %s", DeviceAdvancedSwap) + return + default: + rsp.OkRspWithData(ctx, []string{}) + } +} + +func (s *Service) GetDisableMenus(ctx *gin.Context) { + var rsp proto.Response + switch config.GetHwVersion() { + case config.HWVersionPcie, config.HWVersionATX: + rsp.OkRspWithData(ctx, []string{ + DeviceAdvancedSwap, + DeviceAdvance, + MenuScreenResolution, + }) + log.Debugf("disable menus items %s", DeviceAdvancedSwap) + return + default: + rsp.OkRspWithData(ctx, []string{}) + } +} diff --git a/web/src/api/application.ts b/web/src/api/application.ts index dcb1385..4c69108 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -26,3 +26,11 @@ export function setPreviewUpdates(enable: boolean) { export function getPreviewUpdates() { return http.get('/api/application/preview'); } + +export function getSettingsDisabledItems() { + return http.get('/api/application/settings/disabled-items'); +} + +export function getMenuDisableItems(){ + return http.get("/api/application/menu/disabled-items") +} diff --git a/web/src/jotai/settings.ts b/web/src/jotai/settings.ts index 82f9e49..6b79508 100644 --- a/web/src/jotai/settings.ts +++ b/web/src/jotai/settings.ts @@ -3,5 +3,7 @@ import { atom } from 'jotai'; // menu bar disabled items export const menuDisabledItemsAtom = atom([]); + // web title export const webTitleAtom = atom(''); + diff --git a/web/src/pages/desktop/menu/index.tsx b/web/src/pages/desktop/menu/index.tsx index 97cc63c..9f651b7 100644 --- a/web/src/pages/desktop/menu/index.tsx +++ b/web/src/pages/desktop/menu/index.tsx @@ -20,6 +20,7 @@ import { Script } from './script'; import { Settings } from './settings'; import { Terminal } from './terminal'; import { Wol } from './wol'; +import { getMenuDisableItems } from '@/api/application.ts'; export const Menu = () => { const { t } = useTranslation(); @@ -34,7 +35,13 @@ export const Menu = () => { // disabled menu items const items = getMenuDisabledItems(); setMenuDisabledItems(items); - + getMenuDisableItems().then(res=>{ + let disableItems = new Set(items) + if(res.code === 0){ + (res.data as string[]).forEach((ele)=>disableItems.add(ele)) + } + setMenuDisabledItems([...disableItems.values()]) + }) // react-draggable bounds const handleResize = () => { if (!nodeRef.current) return; diff --git a/web/src/pages/desktop/menu/screen/index.tsx b/web/src/pages/desktop/menu/screen/index.tsx index 1d149c8..390ae60 100644 --- a/web/src/pages/desktop/menu/screen/index.tsx +++ b/web/src/pages/desktop/menu/screen/index.tsx @@ -16,13 +16,14 @@ import { Quality } from './quality'; import { Reset } from './reset.tsx'; import { Resolution } from './resolution'; import { VideoMode } from './video-mode.tsx'; +import { menuDisabledItemsAtom } from '@/jotai/settings.ts'; export const Screen = () => { const { t } = useTranslation(); const videoMode = useAtomValue(videoModeAtom); const resolution = useAtomValue(resolutionAtom); - + const menuDisabledItems = useAtomValue(menuDisabledItemsAtom); const [fps, setFps] = useState(30); const [quality, setQuality] = useState(2); const [gop, setGop] = useState(30); @@ -71,9 +72,9 @@ export const Screen = () => { const content = (
- - - + {!menuDisabledItems.includes('screen:resolution') && } + {!menuDisabledItems.includes('screen:quality') && } + {!menuDisabledItems.includes('screen:fps') && } {videoMode === 'mjpeg' ? : }
diff --git a/web/src/pages/desktop/menu/settings/device/advanced/index.tsx b/web/src/pages/desktop/menu/settings/device/advanced/index.tsx index 7dfb16a..6fafb6c 100644 --- a/web/src/pages/desktop/menu/settings/device/advanced/index.tsx +++ b/web/src/pages/desktop/menu/settings/device/advanced/index.tsx @@ -9,12 +9,13 @@ const children = ( ); -export const Advanced = () => { +export const Advanced = ({disable = false}:{disable?:boolean}) => { const { t } = useTranslation(); return ( diff --git a/web/src/pages/desktop/menu/settings/device/index.tsx b/web/src/pages/desktop/menu/settings/device/index.tsx index cb818d9..787f518 100644 --- a/web/src/pages/desktop/menu/settings/device/index.tsx +++ b/web/src/pages/desktop/menu/settings/device/index.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { Divider } from 'antd'; -import { useAtom } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import { useTranslation } from 'react-i18next'; import * as api from '@/api/hid.ts'; @@ -17,10 +17,11 @@ import { Ssh } from './ssh.tsx'; import { Tls } from './tls.tsx'; import { VirtualDevices } from './virtual-devices.tsx'; import { Wifi } from './wifi.tsx'; +import { menuDisabledItemsAtom } from '@/jotai/settings.ts'; export const Device = () => { const { t } = useTranslation(); - + const disableMenus = useAtomValue(menuDisabledItemsAtom) const [hidMode, setHidMode] = useAtom(hidModeAtom); useEffect(() => { @@ -37,23 +38,25 @@ export const Device = () => {
- - - - + {!disableMenus.includes('device:tls') && } + {!disableMenus.includes('device:ssh') && } + {!disableMenus.includes('device:mdns') && } + {!disableMenus.includes('device:hdmi') && } + {hidMode === 'normal' ? : }
- - - + {!disableMenus.includes('device:oled') && } + {!disableMenus.includes('device:wifi') && } + {!disableMenus.includes('device:mouse') && } +
- + From b6d87b9799584a1ad5da309e6112cc0795c72d6c Mon Sep 17 00:00:00 2001 From: chiconws Date: Fri, 13 Jun 2025 16:03:07 -0300 Subject: [PATCH 20/88] Add PT-BR translation --- web/src/i18n/locales/pt_br.ts | 345 ++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 web/src/i18n/locales/pt_br.ts diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts new file mode 100644 index 0000000..a3534ab --- /dev/null +++ b/web/src/i18n/locales/pt_br.ts @@ -0,0 +1,345 @@ +const en = { + translation: { + head: { + desktop: 'Área de Trabalho Remota', + login: 'Login', + changePassword: 'Mudar Senha', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Login', + placeholderUsername: 'Nome de usuário', + placeholderPassword: 'Senha', + placeholderPassword2: 'Por favor, digite a senha novamente', + noEmptyUsername: 'Nome de usuário é obrigatório', + noEmptyPassword: 'Senha é obrigatória', + noAccount: 'Falha ao obter informações do usuário, por favor atualize a página ou redefina a senha', + invalidUser: 'Nome de usuário ou senha inválidos', + error: 'Erro inesperado', + changePassword: 'Mudar Senha', + changePasswordDesc: 'Para a segurança do seu dispositivo, por favor, mude a senha!', + differentPassword: 'Senhas não conferem', + illegalUsername: 'Nome de usuário contém caracteres inválidos', + illegalPassword: 'Senha contém caracteres inválidos', + forgetPassword: 'Esqueci a senha', + ok: 'Ok', + cancel: 'Cancelar', + loginButtonText: 'Login', + tips: { + reset1: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM por 10 segundos.', + reset2: 'Para etapas detalhadas, por favor, consulte este documento:', + reset3: 'Conta padrão da Web:', + reset4: 'Conta padrão SSH:', + change1: 'Por favor, note que esta ação irá alterar as seguintes senhas:', + change2: 'Senha de login da Web', + change3: 'Senha root do sistema (senha de login SSH)', + change4: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi para o NanoKVM', + success: 'Por favor, verifique o status da rede do NanoKVM e visite o novo endereço IP.', + failed: 'Operação falhou, por favor, tente novamente.', + confirmBtn: 'Ok', + finishBtn: 'Finalizado' + }, + screen: { + title: 'Tela', + video: 'Modo de Vídeo', + videoDirectTips: 'Ative HTTPS em "Configurações > Dispositivo" para usar este modo', + resolution: 'Resolução', + auto: 'Automático', + autoTips: 'Rasgos na tela ou desvio do mouse podem ocorrer em resoluções específicas. Considere ajustar a resolução do host remoto ou desativar o modo automático.', + fps: 'FPS', + customizeFps: 'Personalizar', + quality: 'Qualidade', + qualityLossless: 'Sem perdas', + qualityHigh: 'Alta', + qualityMedium: 'Média', + qualityLow: 'Baixa', + frameDetect: 'Detecção de Quadros', + frameDetectTip: 'Calcular a diferença entre os quadros. Parar a transmissão de vídeo quando nenhuma alteração for detectada na tela do host remoto.', + resetHdmi: 'Redefinir HDMI' + }, + keyboard: { + title: 'Teclado', + paste: 'Colar', + tips: 'Apenas letras e símbolos de teclado padrão são suportados', + placeholder: 'Por favor, digite', + submit: 'Enviar', + virtual: 'Teclado', + ctrlaltdel: 'Ctrl+Alt+Del' + }, + mouse: { + title: 'Mouse', + cursor: 'Estilo do cursor', + default: 'Cursor padrão', + pointer: 'Cursor de ponteiro', + cell: 'Cursor de célula', + text: 'Cursor de texto', + grab: 'Cursor de arrastar', + hide: 'Ocultar cursor', + mode: 'Modo do mouse', + absolute: 'Modo absoluto', + relative: 'Modo relativo', + speed: 'Velocidade da roda', + fast: 'Rápido', + slow: 'Lento', + requestPointer: 'Usando modo relativo. Por favor, clique na área de trabalho para obter o ponteiro do mouse.', + resetHid: 'Redefinir HID', + hidOnly: { + title: 'Modo Somente-HID', + desc: 'Se o seu mouse e teclado pararem de responder e a redefinição de HID não ajudar, pode ser um problema de compatibilidade entre o NanoKVM e o dispositivo. Tente habilitar o modo Somente-HID para melhor compatibilidade.', + tip1: 'Habilitar o modo Somente-HID irá desmontar o U-disk virtual e a rede virtual', + tip2: 'No modo Somente-HID, a montagem de imagem está desativada', + tip3: 'NanoKVM será reiniciado automaticamente após a troca de modos', + enable: 'Habilitar modo Somente-HID', + disable: 'Desabilitar modo Somente-HID' + } + }, + image: { + title: 'Imagens', + loading: 'Carregando...', + empty: 'Nada Encontrado', + cdrom: 'Montar a imagem no modo CD-ROM', + mountFailed: 'Falha na Montagem', + mountDesc: 'Em alguns sistemas, é necessário ejetar o disco virtual no host remoto antes de montar a imagem.', + refresh: 'Atualizar a lista de imagens', + tips: { + title: 'Como fazer upload', + usb1: 'Conecte o NanoKVM ao seu computador via USB.', + usb2: 'Certifique-se de que o disco virtual está montado (Configurações - Disco Virtual).', + usb3: 'Abra o disco virtual no seu computador e copie o arquivo de imagem para o diretório raiz do disco virtual.', + scp1: 'Certifique-se de que o NanoKVM e seu computador estão na mesma rede local.', + scp2: 'Abra um terminal no seu computador e use o comando SCP para fazer upload do arquivo de imagem para o diretório /data no NanoKVM.', + scp3: 'Exemplo: scp seu-caminho-da-imagem root@seu-ip-nanokvm:/data', + tfCard: 'Cartão TF', + tf1: 'Este método é suportado em sistemas Linux', + tf2: 'Remova o cartão TF do NanoKVM (para a versão FULL, desmonte a caixa primeiro).', + tf3: 'Insira o cartão TF em um leitor de cartão e conecte-o ao seu computador.', + tf4: 'Copie o arquivo de imagem para o diretório /data no cartão TF.', + tf5: 'Insira o cartão TF no NanoKVM.' + } + }, + script: { + title: 'Scripts', + upload: 'Upload', + run: 'Executar', + runBackground: 'Executar em segundo plano', + runFailed: 'Falha na execução', + attention: 'Atenção', + delDesc: 'Tem certeza de que deseja excluir este arquivo?', + confirm: 'Sim', + cancel: 'Não', + delete: 'Excluir', + close: 'Fechar' + }, + terminal: { + title: 'Terminal', + nanokvm: 'Terminal NanoKVM', + serial: 'Terminal de Porta Serial', + serialPort: 'Porta Serial', + serialPortPlaceholder: 'Por favor, digite a porta serial', + baudrate: 'Taxa de transmissão', + parity: 'Paridade', + parityNone: 'Nenhum', + parityEven: 'Par', + parityOdd: 'Ímpar', + flowControl: 'Controle de fluxo', + flowControlNone: 'Nenhum', + flowControlSoft: 'Software', + flowControlHard: 'Hardware', + dataBits: 'Bits de dados', + stopBits: 'Bits de parada', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Enviando comando...', + sent: 'Comando enviado', + input: 'Por favor, digite o MAC', + ok: 'Ok' + }, + download: { + title: 'Baixador de Imagens', + input: 'Por favor, digite uma URL de imagem remota', + ok: 'Ok', + disabled: 'A partição /data é RO, então não podemos baixar a imagem' + }, + power: { + title: 'Energia', + showConfirm: 'Confirmação', + showConfirmTip: 'Operações de energia requerem uma confirmação extra', + reset: 'Redefinir', + power: 'Energia', + powerShort: 'Energia (clique curto)', + powerLong: 'Energia (clique longo)', + resetConfirm: 'Prosseguir com a operação de redefinição?', + powerConfirm: 'Prosseguir com a operação de energia?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + settings: { + title: 'Configurações', + about: { + title: 'Sobre o NanoKVM', + information: 'Informação', + ip: 'IP', + mdns: 'mDNS', + application: 'Versão do Aplicativo', + applicationTip: 'Versão do aplicativo web NanoKVM', + image: 'Versão da Imagem', + imageTip: 'Versão da imagem do sistema NanoKVM', + deviceKey: 'Chave do Dispositivo', + community: 'Comunidade', + hostname: 'Nome do Host', + hostnameUpdated: 'Nome do host atualizado. Reinicie para aplicar.', + ipType: { + Wired: 'Com Fio', + Wireless: 'Sem Fio', + Other: 'Outro' + } + }, + appearance: { + title: 'Aparência', + display: 'Exibição', + language: 'Idioma', + menuBar: 'Barra de Menu', + menuBarDesc: 'Exibir ícones na barra de menu', + webTitle: 'Título da Web', + webTitleDesc: 'Personalizar o título da página web' + }, + device: { + title: 'Dispositivo', + oled: { + title: 'OLED', + description: 'Desligar tela OLED após', + 0: 'Nunca', + 15: '15 seg', + 30: '30 seg', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hora' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi', + setBtn: 'Configurar' + }, + ssh: { + description: 'Habilitar acesso remoto SSH', + tip: 'Defina uma senha forte antes de habilitar (Conta - Mudar Senha)' + }, + tls: { + description: 'Habilitar protocolo HTTPS', + tip: 'Atenção: O uso de HTTPS pode aumentar a latência, especialmente com o modo de vídeo MJPEG.' + }, + advanced: 'Configurações Avançadas', + swap: { + title: 'Swap', + disable: 'Desativar', + description: 'Defina o tamanho do arquivo de swap', + tip: 'Habilitar esta função pode encurtar a vida útil do seu cartão SD!' + }, + mouseJiggler: { + title: 'Movimentador de Mouse', + description: 'Impedir que o host remoto entre em suspensão', + disable: 'Desativar', + absolute: 'Modo Absoluto', + relative: 'Modo Relativo' + }, + mdns: { + description: 'Habilitar serviço de descoberta mDNS', + tip: 'Desligue se não for necessário' + }, + hdmi: { + description: 'Habilitar saída HDMI/monitor' + }, + hidOnly: 'Modo Somente-HID', + disk: 'Disco Virtual', + diskDesc: 'Montar U-disk virtual no host remoto', + network: 'Rede Virtual', + networkDesc: 'Montar placa de rede virtual no host remoto', + reboot: 'Reiniciar', + rebootDesc: 'Tem certeza de que deseja reiniciar o NanoKVM?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: 'Otimização de memória', + tip: 'Quando o uso de memória excede o limite, a coleta de lixo é realizada de forma mais agressiva para tentar liberar memória. Recomenda-se definir para 75MB se estiver usando Tailscale. É necessário reiniciar o Tailscale para que a alteração tenha efeito.', + disable: 'Desativar' + }, + restart: 'Reiniciar Tailscale?', + stop: 'Parar Tailscale?', + stopDesc: 'Sair do Tailscale e desabilitar a inicialização automática no boot.', + loading: 'Carregando...', + notInstall: 'Tailscale não encontrado! Por favor, instale.', + install: 'Instalar', + installing: 'Instalando', + failed: 'Falha na instalação', + retry: 'Por favor, atualize e tente novamente. Ou tente instalar manualmente', + download: 'Baixar o', + package: 'pacote de instalação', + unzip: 'e descompacte-o', + upTailscale: 'Fazer upload do tailscale para o diretório NanoKVM /usr/bin/', + upTailscaled: 'Fazer upload do tailscaled para o diretório NanoKVM /usr/sbin/', + refresh: 'Atualizar página atual', + notLogin: 'O dispositivo ainda não foi vinculado. Por favor, faça login e vincule este dispositivo à sua conta.', + urlPeriod: 'Esta URL é válida por 10 minutos', + login: 'Login', + loginSuccess: 'Login Bem-sucedido', + enable: 'Habilitar Tailscale', + deviceName: 'Nome do Dispositivo', + deviceIP: 'IP do Dispositivo', + account: 'Conta', + logout: 'Sair', + logoutDesc: 'Tem certeza de que deseja sair?', + uninstall: 'Desinstalar Tailscale', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + update: { + title: 'Verificar Atualizações', + queryFailed: 'Falha ao obter a versão', + updateFailed: 'Falha na atualização. Por favor, tente novamente.', + isLatest: 'Você já tem a versão mais recente.', + available: 'Uma atualização está disponível. Tem certeza de que deseja atualizar agora?', + updating: 'Atualização iniciada. Por favor, aguarde...', + confirm: 'Confirmar', + cancel: 'Cancelar', + preview: 'Prévia das Atualizações', + previewDesc: 'Tenha acesso antecipado a novos recursos e melhorias', + previewTip: 'Esteja ciente de que as versões de prévia podem conter bugs ou funcionalidade incompleta!' + }, + account: { + title: 'Conta', + webAccount: 'Nome da Conta Web', + password: 'Senha', + updateBtn: 'Alterar', + logoutBtn: 'Sair', + logoutDesc: 'Tem certeza de que deseja sair?', + okBtn: 'Sim', + cancelBtn: 'Não' + } + }, + error: { + title: 'Encontramos um problema', + refresh: 'Atualizar' + }, + fullscreen: { + toggle: 'Alternar Tela Cheia' + }, + menu: { + collapse: 'Recolher Menu', + expand: 'Expandir Menu' + } + } +} \ No newline at end of file From 105f77d9ec1b2dbe345a88fa217c9f962f813f0d Mon Sep 17 00:00:00 2001 From: chiconws Date: Fri, 13 Jun 2025 16:05:05 -0300 Subject: [PATCH 21/88] Add PT-BR translation --- web/src/i18n/locales/pt_br.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts index a3534ab..509170f 100644 --- a/web/src/i18n/locales/pt_br.ts +++ b/web/src/i18n/locales/pt_br.ts @@ -1,4 +1,4 @@ -const en = { +const pt_br = { translation: { head: { desktop: 'Área de Trabalho Remota', @@ -342,4 +342,6 @@ const en = { expand: 'Expandir Menu' } } -} \ No newline at end of file +} + +export default pt_br; From fce974c1469bb5078af3c234966dec7ffbb32455 Mon Sep 17 00:00:00 2001 From: jingyuexing <19589872+jingyuexing@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:04:44 +0800 Subject: [PATCH 22/88] update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd5919..bf09252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0 [a82aa79](https://github.com/sipeed/NanoKVM/commit/a82aa792d87337268ab644b37fcbacdae4f0ce33) (2025-07-15) + +* feat: support server side disable menu +* Add PT-BR translation (thanks to [@chiconws](https://github.com/chiconws)) + ## 2.2.9 [c77981c](https://github.com/sipeed/NanoKVM/commit/c77981cc0ceebd8f6705b6c5d8c3cf4edf4f6717) (2025-06-13) * fix(security): resolve parameter injection in serial port terminal From ba242be4272187e62fd0797ec87efa902177b375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bystr=C3=B6m?= <1126336+acidflash@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:25:57 +0000 Subject: [PATCH 23/88] Add swedish in translation --- web/src/i18n/languages.ts | 3 +- web/src/i18n/locales/se.ts | 353 +++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 web/src/i18n/locales/se.ts diff --git a/web/src/i18n/languages.ts b/web/src/i18n/languages.ts index ff2b501..38a0ca8 100644 --- a/web/src/i18n/languages.ts +++ b/web/src/i18n/languages.ts @@ -18,7 +18,8 @@ const languages = [ { key: 'cz', name: 'Česky' }, { key: 'uk', name: 'Українська' }, { key: 'nb', name: 'Norsk, bokmål' }, - { key: 'th', name: 'ภาษาไทย' } + { key: 'th', name: 'ภาษาไทย' }, + { key: 'se', name: "Svenska"} ]; languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })); diff --git a/web/src/i18n/locales/se.ts b/web/src/i18n/locales/se.ts new file mode 100644 index 0000000..decb12d --- /dev/null +++ b/web/src/i18n/locales/se.ts @@ -0,0 +1,353 @@ +const se = { + translation: { + head: { + desktop: 'Fjärrskrivbord', + login: 'Logga in', + changePassword: 'Byt lösenord', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Logga in', + placeholderUsername: 'Användarnamn', + placeholderPassword: 'Lösenord', + placeholderPassword2: 'Vänligen ange lösenordet igen', + noEmptyUsername: 'Användarnamn krävs', + noEmptyPassword: 'Lösenord krävs', + noAccount: 'Kunde inte hämta användarinformation, uppdatera sidan eller återställ lösenordet', + invalidUser: 'Ogiltigt användarnamn eller lösenord', + error: 'Oväntat fel', + changePassword: 'Byt lösenord', + changePasswordDesc: 'För din enhets säkerhet, byt lösenord!', + differentPassword: 'Lösenorden matchar inte', + illegalUsername: 'Användarnamnet innehåller ogiltiga tecken', + illegalPassword: 'Lösenordet innehåller ogiltiga tecken', + forgetPassword: 'Glömt lösenord', + ok: 'Ok', + cancel: 'Avbryt', + loginButtonText: 'Logga in', + tips: { + reset1: + 'För att återställa lösenordet, håll in BOOT-knappen på NanoKVM i 10 sekunder.', + reset2: 'För detaljerade steg, se detta dokument:', + reset3: 'Standardkonto för webben:', + reset4: 'Standardkonto för SSH:', + change1: 'Observera att denna åtgärd ändrar följande lösenord:', + change2: 'Webbinloggningslösenord', + change3: 'Systemets root-lösenord (SSH-lösenord)', + change4: 'För att återställa lösenordet, håll in BOOT-knappen på NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Konfigurera Wi-Fi för NanoKVM', + success: 'Kontrollera nätverksstatusen för NanoKVM och besök den nya IP-adressen.', + failed: 'Åtgärden misslyckades, försök igen.', + confirmBtn: 'Ok', + finishBtn: 'Färdig' + }, + screen: { + title: 'Skärm', + video: 'Videoläge', + videoDirectTips: 'Aktivera HTTPS i "Inställningar > Enhet" för att använda detta läge', + resolution: 'Upplösning', + auto: 'Automatisk', + autoTips: + 'Skärmtear eller musförskjutning kan förekomma vid vissa upplösningar. Överväg att justera fjärrvärdens upplösning eller inaktivera automatiskt läge.', + fps: 'FPS', + customizeFps: 'Anpassa', + quality: 'Kvalitet', + qualityLossless: 'Förlustfri', + qualityHigh: 'Hög', + qualityMedium: 'Medel', + qualityLow: 'Låg', + frameDetect: 'Ramdetection', + frameDetectTip: + "Beräkna skillnaden mellan ramar. Sluta skicka videoström när inga förändringar upptäcks på fjärrvärdens skärm.", + resetHdmi: 'Återställ HDMI' + }, + keyboard: { + title: 'Tangentbord', + paste: 'Klistra in', + tips: 'Endast standardbokstäver och symboler på tangentbordet stöds', + placeholder: 'Ange text', + submit: 'Skicka', + virtual: 'Tangentbord', + ctrlaltdel: 'Ctrl+Alt+Del' + }, + mouse: { + title: 'Mus', + cursor: 'Markörstil', + default: 'Standardmarkör', + pointer: 'Pekmarkör', + cell: 'Cellmarkör', + text: 'Textmarkör', + grab: 'Greppmarkör', + hide: 'Dölj markör', + mode: 'Musläge', + absolute: 'Absolut läge', + relative: 'Relativt läge', + speed: 'Scrollhastighet', + fast: 'Snabb', + slow: 'Långsam', + requestPointer: 'Använder relativt läge. Klicka på skrivbordet för att få muspekaren.', + resetHid: 'Återställ HID', + hidOnly: { + title: 'Endast-HID-läge', + desc: 'Om din mus och ditt tangentbord slutar svara och återställning av HID inte hjälper, kan det bero på kompatibilitetsproblem mellan NanoKVM och enheten. Prova att aktivera Endast-HID-läge för bättre kompatibilitet.', + tip1: 'Aktivering av Endast-HID-läge avmonterar den virtuella U-disken och nätverket', + tip2: 'I Endast-HID-läge är avbildningsmontering inaktiverat', + tip3: 'NanoKVM kommer automatiskt att starta om efter lägesbyte', + enable: 'Aktivera Endast-HID-läge', + disable: 'Inaktivera Endast-HID-läge' + } + }, + image: { + title: 'Avbildningar', + loading: 'Laddar...', + empty: 'Inget hittades', + cdrom: 'Montera avbildning i CD-ROM-läge', + mountFailed: 'Montering misslyckades', + mountDesc: + "I vissa system måste den virtuella disken avmonteras på fjärrvärden innan avbildningen monteras.", + refresh: 'Uppdatera avbildningslistan', + tips: { + title: 'Hur man laddar upp', + usb1: 'Anslut NanoKVM till din dator via USB.', + usb2: 'Säkerställ att den virtuella disken är monterad (Inställningar - Virtuell Disk).', + usb3: 'Öppna den virtuella disken på din dator och kopiera avbildningsfilen till rotkatalogen.', + scp1: 'Säkerställ att NanoKVM och din dator är på samma lokala nätverk.', + scp2: 'Öppna en terminal på din dator och använd SCP-kommandot för att ladda upp avbildningen till /data på NanoKVM.', + scp3: 'Exempel: scp din-avbildningssökväg root@din-nanokvm-ip:/data', + tfCard: 'TF-kort', + tf1: 'Denna metod stöds på Linux-system', + tf2: 'Ta ut TF-kortet från NanoKVM (för FULL-versionen, öppna chassit först).', + tf3: 'Sätt in TF-kortet i en kortläsare och anslut till din dator.', + tf4: 'Kopiera avbildningsfilen till /data på TF-kortet.', + tf5: 'Sätt in TF-kortet i NanoKVM.' + } + }, + script: { + title: 'Skript', + upload: 'Ladda upp', + run: 'Kör', + runBackground: 'Kör i bakgrunden', + runFailed: 'Körning misslyckades', + attention: 'Observera', + delDesc: 'Är du säker på att du vill ta bort denna fil?', + confirm: 'Ja', + cancel: 'Nej', + delete: 'Ta bort', + close: 'Stäng' + }, + terminal: { + title: 'Terminal', + nanokvm: 'NanoKVM Terminal', + serial: 'Serieport-terminal', + serialPort: 'Serieport', + serialPortPlaceholder: 'Ange serieport', + baudrate: 'Baudhastighet', + parity: 'Paritet', + parityNone: 'Ingen', + parityEven: 'Jämn', + parityOdd: 'Udda', + flowControl: 'Flödeskontroll', + flowControlNone: 'Ingen', + flowControlSoft: 'Mjuk', + flowControlHard: 'Hård', + dataBits: 'Databitar', + stopBits: 'Stoppbitar', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Skickar kommando...', + sent: 'Kommando skickat', + input: 'Ange MAC-adress', + ok: 'Ok' + }, + download: { + title: 'Avbildningshämtare', + input: 'Ange en fjärravbildnings-URL', + ok: 'Ok', + disabled: '/data partitionen är skrivskyddad, kan inte hämta avbildning' + }, + power: { + title: 'Ström', + showConfirm: 'Bekräftelse', + showConfirmTip: 'Strömätgärder kräver extra bekräftelse', + reset: 'Starta om', + power: 'Ström', + powerShort: 'Ström (kort tryck)', + powerLong: 'Ström (långt tryck)', + resetConfirm: 'Utföra omstart?', + powerConfirm: 'Utföra strömåtgärd?', + okBtn: 'Ja', + cancelBtn: 'Nej' + }, + settings: { + title: 'Inställningar', + about: { + title: 'Om NanoKVM', + information: 'Information', + ip: 'IP', + mdns: 'mDNS', + application: 'Applikationsversion', + applicationTip: 'NanoKVM webbapplikationsversion', + image: 'Systemversion', + imageTip: 'NanoKVM systemavbildningsversion', + deviceKey: 'Enhetsnyckel', + community: 'Community', + hostname: 'Värdnamn', + hostnameUpdated: 'Värdnamn uppdaterat. Starta om för att tillämpa.', + ipType: { + Wired: 'Trådbundet', + Wireless: 'Trådlöst', + Other: 'Annat' + } + }, + appearance: { + title: 'Utseende', + display: 'Display', + language: 'Språk', + menuBar: 'Menyfält', + menuBarDesc: 'Visa ikoner i menyfältet', + webTitle: 'Webbtitel', + webTitleDesc: 'Anpassa webbsidans titel' + }, + device: { + title: 'Enhet', + oled: { + title: 'OLED', + description: 'Stäng av OLED-skärmen efter', + 0: 'Aldrig', + 15: '15 sek', + 30: '30 sek', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 timme' + }, + wifi: { + title: 'Wi-Fi', + description: 'Konfigurera Wi-Fi', + setBtn: 'Konfigurera' + }, + ssh: { + description: 'Aktivera SSH-fjärråtkomst', + tip: 'Ställ in ett starkt lösenord innan du aktiverar (Konto - Byt lösenord)' + }, + tls: { + description: 'Aktivera HTTPS-protokoll', + tip: 'Observera: Användning av HTTPS kan öka fördröjningen, särskilt med MJPEG-läge.' + }, + advanced: 'Avancerade inställningar', + swap: { + title: 'Swap', + disable: 'Inaktivera', + description: 'Ange swap-filens storlek', + tip: 'Aktivering av denna funktion kan förkorta livslängden på ditt SD-kort!' + }, + mouseJiggler: { + title: 'Musvickare', + description: 'Förhindra att fjärrvärden går i viloläge', + disable: 'Inaktivera', + absolute: 'Absolut läge', + relative: 'Relativt läge' + }, + mdns: { + description: 'Aktivera mDNS-upptäckningstjänst', + tip: 'Stäng av om det inte behövs' + }, + hdmi: { + description: 'Aktivera HDMI/monitorutgång' + }, + hidOnly: 'Endast-HID-läge', + disk: 'Virtuell disk', + diskDesc: 'Montera virtuell U-disk på fjärrvärden', + network: 'Virtuellt nätverk', + networkDesc: 'Montera virtuell nätverkskort på fjärrvärden', + reboot: 'Starta om', + rebootDesc: 'Är du säker på att du vill starta om NanoKVM?', + okBtn: 'Ja', + cancelBtn: 'Nej' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: 'Minnesoptimering', + tip: 'När minnesanvändningen överskrider gränsen utförs aggressivare skräpsamling för att frigöra minne. Rekommenderas att sättas till 75 MB om du använder Tailscale. Omstart krävs för att det ska gälla.', + disable: 'Inaktivera' + }, + restart: 'Starta om Tailscale?', + stop: 'Stoppa Tailscale?', + stopDesc: 'Logga ut från Tailscale och inaktivera autostart vid uppstart.', + loading: 'Laddar...', + notInstall: 'Tailscale hittades inte! Installera först.', + install: 'Installera', + installing: 'Installerar', + failed: 'Installationen misslyckades', + retry: 'Uppdatera sidan och försök igen. Eller installera manuellt', + download: 'Ladda ner', + package: 'installationspaketet', + unzip: 'och packa upp det', + upTailscale: 'Ladda upp tailscale till NanoKVM-katalogen /usr/bin/', + upTailscaled: 'Ladda upp tailscaled till katalogen /usr/sbin/', + refresh: 'Uppdatera sidan', + notLogin: + 'Enheten är ännu inte bunden. Logga in och bind enheten till ditt konto.', + urlPeriod: 'Denna URL är giltig i 10 minuter', + login: 'Logga in', + loginSuccess: 'Inloggning lyckades', + enable: 'Aktivera Tailscale', + deviceName: 'Enhetsnamn', + deviceIP: 'Enhets-IP', + account: 'Konto', + logout: 'Logga ut', + logoutDesc: 'Är du säker på att du vill logga ut?', + uninstall: 'Avinstallera Tailscale', + okBtn: 'Ja', + cancelBtn: 'Nej' + }, + update: { + title: 'Sök efter uppdateringar', + queryFailed: 'Kunde inte hämta version', + updateFailed: 'Uppdatering misslyckades. Försök igen.', + isLatest: 'Du har redan den senaste versionen.', + available: 'En uppdatering finns tillgänglig. Vill du uppdatera nu?', + updating: 'Uppdatering påbörjad. Vänta...', + confirm: 'Bekräfta', + cancel: 'Avbryt', + preview: 'Förhandsvisning av uppdateringar', + previewDesc: 'Få tidig tillgång till nya funktioner och förbättringar', + previewTip: + 'Observera att förhandsversioner kan innehålla buggar eller ofullständig funktionalitet!' + }, + account: { + title: 'Konto', + webAccount: 'Webbkonto-namn', + password: 'Lösenord', + updateBtn: 'Byt', + logoutBtn: 'Logga ut', + logoutDesc: 'Är du säker på att du vill logga ut?', + okBtn: 'Ja', + cancelBtn: 'Nej' + } + }, + error: { + title: 'Vi stötte på ett problem', + refresh: 'Uppdatera' + }, + fullscreen: { + toggle: 'Växla fullskärm' + }, + menu: { + collapse: 'Fäll ihop menyn', + expand: 'Expandera menyn' + } + } +}; + +export default se; From 1b5ca8eef69bb521de235abec327945d651e77a3 Mon Sep 17 00:00:00 2001 From: T3rr0rZ0n3 Date: Thu, 28 Aug 2025 20:37:33 +0200 Subject: [PATCH 24/88] Add catalan translation --- web/src/i18n/languages.ts | 1 + web/src/i18n/locales/ca.ts | 347 +++++++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 web/src/i18n/locales/ca.ts diff --git a/web/src/i18n/languages.ts b/web/src/i18n/languages.ts index ff2b501..cd0d133 100644 --- a/web/src/i18n/languages.ts +++ b/web/src/i18n/languages.ts @@ -1,4 +1,5 @@ const languages = [ + { key: 'ca', name: 'Català' }, { key: 'nl', name: 'Nederlands' }, { key: 'da', name: 'Danish' }, { key: 'de', name: 'Deutsch' }, diff --git a/web/src/i18n/locales/ca.ts b/web/src/i18n/locales/ca.ts new file mode 100644 index 0000000..acdd820 --- /dev/null +++ b/web/src/i18n/locales/ca.ts @@ -0,0 +1,347 @@ +const ca = { + translation: { + head: { + desktop: "Escriptori remot", + login: "Inici de sessió", + changePassword: "Canviar contrasenya", + terminal: "Terminal", + wifi: "Xarxa Sense fils" + }, + auth: { + login: "Inici de sessió", + placeholderUsername: "Nom d'usuari", + placeholderPassword: "Contrasenya", + placeholderPassword2: "Torna a introduir la contrasenya", + noEmptyUsername: "Cal introduir el nom d'usuari", + noEmptyPassword: "Cal introduir la contrasenya", + noAccount: "No s'ha pogut obtenir la informació de l'usuari, actualitza la pàgina web o restableix la contrasenya", + invalidUser: "Nom d'usuari o contrasenya invàlids", + error: "Error inesperat", + changePassword: "Canviar la contrasenya", + changePasswordDesc: "Per a la seguretat del dispositiu, canvia la contrasenya!", + differentPassword: "Les contrasenyes no coincideixen", + illegalUsername: "El nom d'usuari conté caràcters no permesos", + illegalPassword: "La contrasenya conté caràcters no permesos", + forgetPassword: "Has oblidat la contrasenya", + ok: "D'acord", + cancel: "Cancel·la", + loginButtonText: "Inicia sessió", + tips: { + reset1: "Per restablir les contrasenyes, mantingues premut el botó BOOT del NanoKVM durant 10 segons.", + reset2: "Per veure els passos detallats, consulta aquest document:", + reset3: "Compte web per defecte:", + reset4: "Compte SSH per defecte:", + change1: "Aquesta acció canviarà les següents contrasenyes:", + change2: "Contrasenya d'inici de sessió web", + change3: "Contrasenya root del sistema (inici de sessió SSH)", + change4: "Per restablir les contrasenyes, mantingues premut el botó BOOT del NanoKVM." + } + }, + wifi: { + title: "Xarxa Sense fils", + description: "Configura la xarxa sense fils per al NanoKVM", + success: "Comprova l'estat de la xarxa del NanoKVM i visita la nova adreça IP.", + failed: "L'operació ha fallat, torna-ho a intentar.", + confirmBtn: "D'acord", + finishBtn: "Fet" + }, + screen: { + title: "Pantalla", + video: "Mode de vídeo", + videoDirectTips: "Activa HTTPS a 'Configuració > Dispositiu' per utilitzar aquest mode", + resolution: "Resolució", + auto: "Automàtic", + autoTips: "Poden aparèixer talls o desajustos del ratolí en certes resolucions. Prova a canviar la resolució de l'amfitrió remot o desactiva el mode automàtic.", + fps: "FPS", + customizeFps: "Personalitzat", + quality: "Qualitat", + qualityLossless: "Sense pèrdua", + qualityHigh: "Alta", + qualityMedium: "Mitjana", + qualityLow: "Baixa", + frameDetect: "Detecció de fotogrames", + frameDetectTip: "Calcula la diferència entre fotogrames. S'atura la transmissió si no hi ha canvis a la pantalla de l'amfitrió remot.", + resetHdmi: "Restablir HDMI" + }, + keyboard: { + title: "Teclat", + paste: "Enganxa", + tips: "Només es permeten lletres i símbols estàndard", + placeholder: "Escriu aquí", + submit: "Envia", + virtual: "Teclat", + ctrlaltdel: "Ctrl+Alt+Del" + }, + mouse: { + title: "Ratolí", + cursor: "Estil del cursor", + default: "Cursor per defecte", + pointer: "Cursor punter", + cell: "Cursor de cel·la", + text: "Cursor de text", + grab: "Cursor de mà", + hide: "Amaga el cursor", + mode: "Mode de ratolí", + absolute: "Mode absolut", + relative: "Mode relatiu", + speed: "Velocitat de la roda", + fast: "Ràpida", + slow: "Lenta", + requestPointer: "Estàs usant el mode relatiu. Fes clic a l'escriptori per obtenir el punter.", + resetHid: "Restablir HID", + hidOnly: { + title: "Mode només HID", + desc: "Si el teclat o ratolí deixen de funcionar i restablir HID no ajuda, pot ser un problema de compatibilitat. Prova d'activar el mode només HID.", + tip1: "Activar aquest mode desmuntarà el disc virtual i la xarxa virtual", + tip2: "En mode només HID, no es pot muntar imatges", + tip3: "El NanoKVM es reiniciarà automàticament en canviar de mode", + enable: "Activa mode només HID", + disable: "Desactiva mode només HID" + } + }, + image: { + title: "Imatges", + loading: "Carregant...", + empty: "No s'ha trobat res", + cdrom: "Munta la imatge en mode CD-ROM", + mountFailed: "Error en muntar", + mountDesc: "En alguns sistemes cal expulsar el disc virtual abans de muntar la imatge.", + refresh: "Actualitza la llista", + tips: { + title: "Com pujar imatges", + usb1: "Connecta el NanoKVM al teu ordinador via USB.", + usb2: "Assegura't que el disc virtual està muntat (Configuració - Disc Virtual).", + usb3: "Obre el disc virtual i copia la imatge a l'arrel.", + scp1: "Comprova que el NanoKVM i el teu ordinador estan a la mateixa xarxa.", + scp2: "Obre un terminal i usa SCP per pujar la imatge al directori /data del NanoKVM.", + scp3: "Exemple: scp ruta-de-la-imatge root@ip-del-nanokvm:/data", + tfCard: "Targeta TF", + tf1: "Mètode disponible a sistemes GNU/Linux", + tf2: "Extreu la targeta TF del NanoKVM (versió FULL, cal obrir la carcassa).", + tf3: "Introdueix la targeta en un lector i connecta'l a l'ordinador.", + tf4: "Copia la imatge al directori /data de la targeta.", + tf5: "Reintrodueix la targeta al NanoKVM." + } + }, + script: { + title: "Scripts", + upload: "Puja", + run: "Executa", + runBackground: "Executa en segon pla", + runFailed: "Error en l'execució", + attention: "Atenció", + delDesc: "Estàs segur que vols eliminar aquest fitxer?", + confirm: "Sí", + cancel: "No", + delete: "Esborra", + close: "Tanca" + }, + terminal: { + title: "Terminal", + nanokvm: "Terminal NanoKVM", + serial: "Terminal de port sèrie", + serialPort: "Port sèrie", + serialPortPlaceholder: "Introdueix el port sèrie", + baudrate: "Velocitat (baudrate)", + parity: "Paritat", + parityNone: "Cap", + parityEven: "Parell", + parityOdd: "Senar", + flowControl: "Control de flux", + flowControlNone: "Cap", + flowControlSoft: "Suau", + flowControlHard: "Dur", + dataBits: "Bits de dades", + stopBits: "Bits de parada", + confirm: "D'acord" + }, + wol: { + title: "Wake-on-LAN", + sending: "Enviant comanda...", + sent: "Comanda enviada", + input: "Introdueix la MAC", + ok: "D'acord" + }, + download: { + title: "Descarregador d'imatges", + input: "Introdueix la URL de la imatge", + ok: "D'acord", + disabled: "La partició /data és només lectura. No es pot descarregar la imatge." + }, + power: { + title: "Alimentació", + showConfirm: "Confirmació", + showConfirmTip: "Les operacions d'alimentació requereixen confirmació", + reset: "Reinicia", + power: "Encén", + powerShort: "Clic curt", + powerLong: "Clic llarg", + resetConfirm: "Vols realment reiniciar?", + powerConfirm: "Vols realment encendre/apagar?", + okBtn: "Sí", + cancelBtn: "No" + }, + settings: { + title: "Configuració", + about: { + title: "Sobre NanoKVM", + information: "Informació", + ip: "IP", + mdns: "mDNS", + application: "Versió aplicació", + applicationTip: "Versió de la interfície web de NanoKVM", + image: "Versió de la imatge", + imageTip: "Versió del sistema NanoKVM", + deviceKey: "Clau del dispositiu", + community: "Comunitat", + hostname: "Nom del dispositiu", + hostnameUpdated: "Nom actualitzat. Reinicia per aplicar.", + ipType: { + Wired: "Cablejada", + Wireless: "Sense fil", + Other: "Altra" + } + }, + appearance: { + title: "Aparença", + display: "Pantalla", + language: "Idioma", + menuBar: "Barra de menú", + menuBarDesc: "Mostra icones a la barra de menú", + webTitle: "Títol web", + webTitleDesc: "Personalitza el títol de la pàgina" + }, + device: { + title: "Dispositiu", + oled: { + title: "OLED", + description: "Apagar pantalla OLED després de", + 0: "Mai", + 15: "15 s", + 30: "30 s", + 60: "1 min", + 180: "3 min", + 300: "5 min", + 600: "10 min", + 1800: "30 min", + 3600: "1 h" + }, + wifi: { + title: "Xarxa sense fils", + description: "Configura la xarxa sense fils", + setBtn: "Configura" + }, + ssh: { + description: "Activa accés remot per SSH", + tip: "Configura una contrasenya segura abans (Compte - Canvia contrasenya)" + }, + tls: { + description: "Activa el protocol HTTPS", + tip: "Atenció: Usar HTTPS pot augmentar la latència, sobretot amb vídeo MJPEG." + }, + advanced: "Configuració avançada", + swap: { + title: "Swap", + disable: "Desactiva", + description: "Defineix la mida del fitxer swap", + tip: "Pot reduir la vida útil de la targeta SD!" + }, + mouseJiggler: { + title: "Mou-ratolí automàtic", + description: "Evita que el dispositiu remot entri en repòs", + disable: "Desactiva", + absolute: "Mode absolut", + relative: "Mode relatiu" + }, + mdns: { + description: "Activa descobriment mDNS", + tip: "Desactiva-ho si no és necessari" + }, + hdmi: { + description: "Activa la sortida HDMI" + }, + hidOnly: "Mode només HID", + disk: "Disc virtual", + diskDesc: "Munta un disc U virtual al dispositiu remot", + network: "Xarxa virtual", + networkDesc: "Munta una targeta de xarxa virtual al dispositiu remot", + reboot: "Reinicia", + rebootDesc: "Segur que vols reiniciar el NanoKVM?", + okBtn: "Sí", + cancelBtn: "No" + }, + tailscale: { + title: "Tailscale", + memory: { + title: "Optimització de memòria", + tip: "Quan es supera el límit de memòria, es fa una neteja més agressiva. Recomanat: 75MB si uses Tailscale. Requereix reiniciar Tailscale.", + disable: "Desactiva" + }, + restart: "Reiniciar Tailscale?", + stop: "Aturar Tailscale?", + stopDesc: "Tanca la sessió i desactiva l'inici automàtic", + loading: "Carregant...", + notInstall: "Tailscale no instal·lat! Instal·la-ho.", + install: "Instal·la", + installing: "Instal·lant", + failed: "Error en la instal·lació", + retry: "Actualitza i torna-ho a provar. O instal·la manualment", + download: "Descarrega el", + package: "paquet d'instal·lació", + unzip: "i descomprimeix-lo", + upTailscale: "Puja tailscale a /usr/bin/", + upTailscaled: "Puja tailscaled a /usr/sbin/", + refresh: "Actualitza la pàgina", + notLogin: "El dispositiu no està vinculat. Inicia sessió per vincular-lo.", + urlPeriod: "Aquesta URL és vàlida durant 10 minuts", + login: "Inicia sessió", + loginSuccess: "Sessió iniciada correctament", + enable: "Activa Tailscale", + deviceName: "Nom del dispositiu", + deviceIP: "IP del dispositiu", + account: "Compte", + logout: "Tanca sessió", + logoutDesc: "Segur que vols tancar sessió?", + uninstall: "Desinstal·la Tailscale", + okBtn: "Sí", + cancelBtn: "No" + }, + update: { + title: "Comprova actualitzacions", + queryFailed: "Error en obtenir la versió", + updateFailed: "Error en actualitzar. Torna-ho a intentar.", + isLatest: "Ja tens la darrera versió.", + available: "Hi ha una actualització disponible. Vols actualitzar ara?", + updating: "Actualitzant... espera", + confirm: "Confirma", + cancel: "Cancel·la", + preview: "Versió de prova", + previewDesc: "Prova noves funcions abans que ningú", + previewTip: "Compte: aquestes versions poden tenir errors o funcions inacabades!" + }, + account: { + title: "Compte", + webAccount: "Nom del compte web", + password: "Contrasenya", + updateBtn: "Canvia", + logoutBtn: "Tanca sessió", + logoutDesc: "Segur que vols tancar sessió?", + okBtn: "Sí", + cancelBtn: "No" + } + }, + error: { + title: "Hi ha hagut un error", + refresh: "Actualitza" + }, + fullscreen: { + toggle: "Pantalla completa" + }, + menu: { + collapse: "Amaga menú", + expand: "Mostra menú" + } + } +}; + +export default ca; \ No newline at end of file From 350dbdfe4b60557a2406d68ab748f1b4e0b1b248 Mon Sep 17 00:00:00 2001 From: BuGu <62454025+Z2Z-GuGu@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:51:48 +0800 Subject: [PATCH 25/88] Add NanoKVM EDID Update tool --- tools/nanokvm_update_edid/E21_NanoKVM.bin | Bin 0 -> 256 bytes tools/nanokvm_update_edid/Makefile | 22 + tools/nanokvm_update_edid/nanokvm_update_edid | Bin 0 -> 24240 bytes .../nanokvm_update_edid/nanokvm_update_edid.c | 749 ++++++++++++++++++ .../nanokvm_update_edid/nanokvm_update_edid.h | 41 + 5 files changed, 812 insertions(+) create mode 100644 tools/nanokvm_update_edid/E21_NanoKVM.bin create mode 100644 tools/nanokvm_update_edid/Makefile create mode 100755 tools/nanokvm_update_edid/nanokvm_update_edid create mode 100644 tools/nanokvm_update_edid/nanokvm_update_edid.c create mode 100644 tools/nanokvm_update_edid/nanokvm_update_edid.h diff --git a/tools/nanokvm_update_edid/E21_NanoKVM.bin b/tools/nanokvm_update_edid/E21_NanoKVM.bin new file mode 100644 index 0000000000000000000000000000000000000000..005cad48ffef12727d945e7835cd60df346cbbd5 GIT binary patch literal 256 zcmZSh4+ac=ER4pY3=9l%%8bkn2GSK;nW~GU0~V_b281m2X1I8uv4If@Fj+N76k6yy zMCiCO+~E-fY6EKh&)}Dsm+u|s%cTGUK#@NfB7YeyeB^=!86Zj&7{4+x3mUkw$cqRw g3ZydgFbFVU8qFxnP!xDUP9aYt6k!?AbZUTG0NVs90RR91 literal 0 HcmV?d00001 diff --git a/tools/nanokvm_update_edid/Makefile b/tools/nanokvm_update_edid/Makefile new file mode 100644 index 0000000..9b7b9ca --- /dev/null +++ b/tools/nanokvm_update_edid/Makefile @@ -0,0 +1,22 @@ +# CROSS_COMPILE = aarch64-none-linux-gnu- +# CROSS_COMPILE = riscv64-unknown-linux-gnu- +CROSS_COMPILE = riscv64-unknown-linux-musl- +# CROSS_COMPILE = riscv64-unknown-elf- + +CFLAGS += -I/usr/local/RISC-V-toolchain/riscv64-linux-musl-x86_64/sysroot/usr/include + +CC = $(CROSS_COMPILE)gcc + +SRC = nanokvm_update_edid.c + +TARGET = nanokvm_update_edid + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) -march=rv64gc -o $@ $^ + +clean: + rm -f $(TARGET) + +.PHONY: all clean \ No newline at end of file diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid b/tools/nanokvm_update_edid/nanokvm_update_edid new file mode 100755 index 0000000000000000000000000000000000000000..8b2489ed51c72931ed65bfee671abce24b44a39c GIT binary patch literal 24240 zcmeHve|%KMx%X@~$r6wxuqsfs9Eb?EO*SAPpg;nI$Sn{=AllZuY<4%vYO=fRZou?P zNeG)2D+HQa)M^s}5pofz^%t}Xt-r7JhTdw`_f=U13tDBPQX`N}-tRL%cF!I{?e+8C zf9}W0CwtEM&humDnP;AvGv}OHQ!;;Un#sgeN@JfheszJsie!d=nK;S7p@>ak7W|H4 z8q36WI>$tQk>EzI;J-s0m^uy+WXvr2UXuAQ$ug+pweW%z@A?lyF5>%VlT^nR zgOBWazH+`P^{J!0WVRYlg{{VwUt1Tf$q#sf&XtoV76g3-<7EMc;5ZWh7s>0PIF7*o zF#KPN|BLZI82?mesNFTuZ6Vs!X6{#BdN^+5dR;9r@Q zFiOMmPi0c+LYycYP@TvI#xw{3@hIOR{SAlaE+6{5`-f|%d^o>>wQKBhb_g^mDl}in z4iPPtpJ?^vr}|JGC{g)IKiPonL3W`!P~FG|b4_(Q*4s1p7CyCh@JD}+Wv_bUm$&cV z@M-p)$4*|l=<%JotA85InQ^-BceaJozgT?NxKY14>&-db+ZDUKZtdWypZ)aGQJq=w z_O@L)Bft1juKgFSm*)Q4{kK;wb>#l=zH4&&K3;cW(SiqVx7b2C<1V}F(qSL1?1jqn zraj2f`pJFaJa_|eYU{#5%2RNGG!g%VZ!+LxCBDdjUkiLR{*8%jMsf}t=tlwTkDkH^ z(eE3WTAp@^M-4dnE72DjW;_>q0?R>GQk2 ztg_0DbGygu39(vtZO|PeVviTU0cX(fW%f$sQI@mT&k*0Z+)y z2u3so0SfD`@<4jPUKw!Ix>3AJ!uvx}<%At5o~d|BxM~-|DHf`v{D*LASCw)XiT*;d zjAE5a!*#fdXOySuaEiYwjnm=!I9;g2DF&)EMTe{SOnF5*Tpw4j)8T5YA^Jr+T=fmY zZ`9#5R;aXGhf}Opsal8YBZyyzQ=eApHXW`XZyI#Cer&o!htqhY(k30QpMTt=!}a4} zyAG!qsnSjzuAd+5)8YED?k^JC6TwX&yVpwk}U|6`R&J5KZ{m;vuc!#!Z4UygBojqTRZ@*!1+l#}7_jk=Zl) z%LbNr?e?C$DUbE!O?$YX0qIHiFr~wC=W(EBGxSyb8#!Ed-66NXOPev>zes~W9f|W`~F#>QI53@ z^(!(@jqYN@N#CAG-X*7cn#x`AOClDsOs+Gho4sCTell{5BvUye2d0H%U44Ivm%Fz@ zZoKJ53w!G*orNuQJ|og<>B@0_t2>jh^?EEo$<#og&dw^@%}+MPz&-Fj@O!0y#! z!@C*LJ;1Teov&9lp`6b&-#j;5LYA>S9qU^2X1v_Abz5e7Z{D!<-iz9s%8&J$!E;~> zmDODOTJQe0ZJDQf2@j9W8z#;~?W2~Xr;qnEf#$cCt|Cqa&elk`#SC7;Tf4h+oDIj* zlxAyYdz#Q4c?R}B9527VJrBBJ$pIw$T>Z~Mf8P1@=Lr4Jas8Z1=+6=QpX*2eKBfQn z(En1rd`LUB^;Umcs03|YJS6kj=qMY&{pPt?SKAZsk)3Wqd+y^FdlCNixWW$ZH6?sk zc}YiPPxFMxwwzNvPek(Ydmyqc`&3Wfh3UQK7aLoeN>9bPO&X7r0(k-Hey%W$5pSmrvT+RC+8f_+IOmukR5( zpXHCSt|L2)wY_Xb^T%6H>^Rntn>XdTGkFtVJJZp;E$6i}dz$m`duQ_#>(FD)+3>#}#vt@q4ezp1|1WF0<(O>SzOZC&Ug!>&SP`GRe3`kCb_)2BsuMxKe}O+3{jt#g)JXC1fBS+WlNw`X;EUc+(n z^3@wm9pkGO=vK0;}4^3El(Rn!`+Y0*9zUN3tJt9yCm!Z(M-2&Y}GSZT=NB+COcLCRzvTc{Z*Q1n2 z{wng6tRoMLyvrW6W)`7l3b*eN_*CE|OW{Yh3;dlu)UNwvIoE6x=<_=S^&wUM2L=A# z4zj^viMF*1bip2!UnhHHo4`H5^*Y-g5cs?uPd;p67+r>TN$4_^Z2N&p)Nw?a0qW-dnp<>tNq3@YTTec0SS~@SC?A=r;V0d? zT_QJ3THpbyb^7iU_=CI2-U@A7FVOXnlHxCS2t0o~#?oGzpPGXn1d{-doxl1iGN3AH6x< zX~Gi9hxWz=_4Qkcno2``mdc>~spTxn@JhUV!&c!>iZkbo;M}!U?|pl}6zD}A&~rhu zzuLM5KA!Nbr1kdxUEo6?UGJ5)Qv&~cdq1_v?q;d&T=prbJLBc6cUm(WbUxo16H;z& zPnBMFQsDJmdU)(ML?|=rPc39!)ty5&I9l2LZfY+$BK_h)#P~!nNbhFx@_Qja>F>T1 zLV5;Dll1qT;{v^Mr;2?`Rj+v`))m`H^FA7(X?#{A8lo%B@vWG#C8KMPimnt_m8@bF zN8gQg*`&9t9_2eCG(WNxW9peC0x=GuHBOfDaq^!XAzPYp z4}kkp+^5Z*(cFmW%m$*pyF|H0Z4-X^R;iQjv`T-DNPTzd`QmhEW4!#WU9ql~K6(ET zJ@_}#4$5z0zZG&zKxI2^)Y>V%eBQ4AhSunsGx72;MoODuthYTbbZthS?=x$rrt8H% zIS&{^qoppU#C|P!OuHxoHON+Kk#m4gX#CU70dn>V>Q^z|XgZ&1k+Xmg3D;@Ok@El= zN69;tRa!cP9M7IuSLg9mp3a>j?`iNX*U2iA^MD5oIyxT`^!s<{`-M-=0>((qs$aym z3hIoM@xDdQ0%#m9)alHT^MF?|is|ds@+%?b!`+GTK05M`BGRoVkIF*pJR@js-mM}X zRtWgYf*9||tH>3NMeXmz%j@|FsW)(L%$GJ2|Z_caN8j2xp& z&l#i38U@7&;!M^odZbU_al}2H&qwXO0)LCgz4MHJw{)LKv3Imjj`$^z)}HjV=o%Rp z9i&Bv_sKCoyff9i%A|iJ$5D7!VjS%nEIlUL_qiw~jiU)aine_w(0<3!=$gm=JL70F z`t~!9Mi1^y>G{#VkA&t2Fpk<#CY{ekXMQO1cA*9IHaI9p{lR=39RW}3%1n4(^vDN- zYY2H*((|JBV*>v>wCJTBd0*hK>&MaPn)d{H8un+fzx11EpB(pZmgDF-eiJ<+NB`?d zPckM(?Q-;g+^AoU`*(Bw((|Kh-V*xv^Kmi7^D&AF&sSrpFDz*P;TY;o9YZ}UGQSx8 z8T(cT)+!$RctRwv;MB)ht+?#e$4z6O`-EoJSdkh@>z*PyCFWJ{#iDCkFJ34`o*M_MQWw+Z?WjeW+i3KdaNPrR#f_$A8ArFGb8cxD|cs<#G);q ziZb&v%!+9 zcoCVDoj#}fDtv*OAAf=RewKGddhh;rZ7U~FZ&F_PN?3C*`5&!9Ux7OZoQYNFuAHN<9Y3S=Ce}B;1pP;OmkYgSta~?& zUDw;1ncnvVR<xi8BrQo;R`g zm(Ox#oVRQ(&vLHI!T*PT$}vo?QeT1mLtGO|Ty`iaHGf6s$Z24l>MX*HHiss&}XP6nSRQT8lwA6`h=Jek6rPO8{O35dTYi+px5^HZ*QOEz3 z^fSEN&;k*m;N2g9YScc4~hu}&F>Rnn+Nomls}`>67BwOVMI z4Eo=h>B(1g?-SwD2lj~b*yB2_Q(%urze;Q8li|`ad!Y5SUaJpt;H1{oY92Zj<#|kK zD%%?_HSG}REql;TXOrzj_Xe_^)>(RxcRHEZxks0GM)6s$;qCkH9g(o8D|*vSDwbS> z_IU))3}N}F$s@QX%XH$1(TrcLI)lTZIHtqi9U}TD`p8U9Pji0VDz)D%pNSR0o)3ph zr*Bu5#{IjBaI>NRp>S!|ZdDV@T#{v@ng6-E=fc0YQUsdUuIMAzary`JwNqua%maNJ zY6p*Mq}p*?S-wvkIrr%~)aqa4`zX)-@I-J-6{{S0x|baPA`6gzU$}I`R*G6Q#*Exg zXCiutIEE1f_pZ3heF1eiirztE%LzHgo4WDjj>pcc(@)&*l#zN7x8EJ%(kr*-T>?*^ zl76DqvhG9u2$!>=2^;wpMaehB|*4wq)MtGKV^v^=eB+V%*=^e_C&LL(3@=$(o{1^wb3 zLx-ov?b1R%&4+iNw30dV=4h3GL@r|-a5+K_t;X%G3RP>Kpyu_3v@tHNqCVsfs&{8N zT{YfmatDaS<6Y^f@wf)E+0}06t--oltjh`?@@CrhwFA#7$LsC!*ebDXn zd0qdI!R@T}X@%Mh&4zccY|a`-Flejtdwl$7;-v95l%o!{;dJA_nl5P%{sxxjtE|Ke zU}T?AwOjK{aB3BG!4$g*IwQdxoyqWSY)GqcYgKsX3omd%lEbSN7VsJy^{Mx{G>6L- za0i1%%}IKCflR9m_-a#1mdtm19ThchZT^zUQ^${AdgE+i;AFnb6YPhe9PgcZyj9#0 z3XoL$F7*cM{C-~`1ZQzpd;Ho;cOdBTc~w4de_;xzuhw7V1|j7wMk#gj6git+(z<5;O2Jt_t z!8&fm%DS4GdMO?J)4y&K6`$vo{a=)!)={rH9d$u;wP3K$9VDfwVUUW-lLubysquvB zsR7}_{u*$2Y8_SX;P*7Rodc}3K9{GmUR&isxPq_{+?0LYQcjmO{rdHs&ddG&eeJQQ zS~RV^c=5t{3rnYI*Es^NRSx*4>`0mmUB*e>3$>kHSK+qJ#~YxdWVOG3`GtB`%$=kX z@v$1+@q1dmuTERA^oAu`t;1XAK#OXAdOcO+zQZF5I-bXwD8M3NS~tP~%1qo&^k08< zebD3N>}U%FYcHyxc|t)U?R$(ZEL)=8Lh&!)uJwi7&=YW1_%MKYLZhg1U%Opo!@Pfg zSQD;O=Z6EkbzaJCC(Hd6wIpY9`28rI6R{b-M6WUPG{?s)yUO9^g8-_C@L1=bsg1kI zw(!bnnj~Wb=Xvqcug2f?EznAC<53h+R7aU721E>uAS=)k=A^EBlZK$QP^b(@fi`!@ zX}fi0t*zQs>#<88L7rr+^H&8h)~eB=1~rF`#p}mhr6K;D6S9-?S8;!@ph=g&_JJ|Lm7n37(O7zU92d!oB#JzH>8uiav1%4p;SLemdFW;(==@F(FS$XU zHceYJd!A09F`ticsG6~eDGorRSdGu=ph$%o2k(A7(9=vzs|(TqI(ke{v-usNYFo%> zBjXfQc)X(%9CTqzI{VYw7*ZAOAsnK3cGdO=z54u97r;4J17|nPu zOq7Rg`c3-A0^eBR8w-46fp0AEjRn53z}K^5&qu2FrHPVc$Z|Afifi@F z)jei$ttLuuOFH$f1bgp2E{GdE99c`V_ugUaNW> zmi+2lI;CH-|BoH|WKs$WWjpu`(d zapzAhEYxani+NlAqyi93)ac84t;*@tmiT-%xIOiFwPh0uCKrs?X5~*PoG_tqYT@LB ztl|k!#6QDlrg1pU2;h5s&jf#=y_S9)^ixScF8Xoehv6oj2`3OfFC^dDW-Un_P=eo# z0A!i6GmYgi-TyLjGyTZBh|VZk=<{S#ZswIZ-~&Krsix~8S)DjEyk!AW( z#^AfB@5&Z0jI%_9fX%hYd01qA#Ug&IS#d zF~|%PndNCVcyoZxv!cuerkNwm+0zDHGid6du_K04v!+3o zWg=6g0vND99Y0+j<|M&H(N2FsC)2lW@LGj>1_63 z{PGY)Mzhs=L$+YhWxK`gz18Cl+C9EdaFva(bJ|w<0=EYJ4yW6ea3otrodYHbAZi{Ix1aGEx$_3FPos{%6NxURQpI zR_TKRas=zW&gy{A>#GZ%lV4fqbPN$19du8LS}aRZEfm8g_U%M zfI?dgq$H!XH1=a=6qg2ZDNMLfeBVx}{PBP`b)i(7*pJkyDn%xhB5%NX%c1Cu`o8KK zeU+(warCrij2CL&lZu-Y^Pf~aovC?GDxSgA{3sRAWNMz0id&eP&!pl5n3^}F;)qAm zmZ|ta*6>{(mh>>9>LB*^r2g;=n3^Z*8O+$ZM$HdX@#H=fsdx@E&Ic&pOgqrXd0i^~ z5Z2W%K9u$Ji(~k>zQ6LT*`Z!K={3$WMZTHuNRvuSS7yFTO@Da*vqm%D(WXCoHM>sD zGxMEqQZc$R^PPhF!}U8`h*KV^`=TFMz9+ZXc}o@kb1!su%B&xe5bSKr@q`XTH~ z=|8GG=d;fXoWB2l(VzU+IQ_YP{tm~_?Wf%w&t)eJ{yZ23qkTvg8TN&-ay*Mo7$Hjj zAF={tam-CayWc1Q@KTzJ%l`I-DFQwec7BZnBK@npVOuKkA8P`CK^#~W#~Cy3+sGIkocn)6AB zANnNy&M`v3{W23%M&4dUqXk|qZ-_T@dhEYuD5u8h&)uhK9B@sqP@We_`E#xi@@c;) zN(+Jax1SN+ca#4Z$Co7%S7Z5TA%<1Ra-OrF5tpBg=W1QS#pR%^hJA}}1)hr?PNo>* z{3=ObXrNy$@gf7>#N`ZSrw#qCMba-a(3isARNsZv(U1ycL4P6XmmBCG;CL3hPulY_ zX@}oPdcT4G0PxY0Mt?XYI3S202kl|Fa>2{y!!CN#N9u*BSPQ`n#lG zBI7`jq&M^WGS+U8p9MU(pYeLQq~B+tAIa&5oMY!JIDT$BewWM1V#|k$@}H9BnI`2Q zGRXglw8LQoJ|Fa&i>gqq z9Xnfj-PlIU?(*5IYJ3%r8oLX(8$r7RkBSqo2w|VVeku4CTXshv;HbB|y`exoHq4@3 zuUvJtwe`$baSQHUuykUVn_+R7@7R^X<8^p_c!q8lPmk^Vsi~9sYFu{lS{L}|E-qeB zVlP=Z$8Kl#IX5jVUNCPqvoBg)HoIib(#0kA`STWjzqqu-zNC28{1T9q&M%u)Jl|e6 zckT@(OB5pdU0K)zB$ zM!}|Mi91nejR%dcc3899XWejvtdra}jo$r&Z7YI7!K3zRll!SrvGER3z|IR3z%4ay zhoK}u8@7qv+ZMt#zQ1gN9Z&Dw_Uq;>m}g(?7CXI(J>JL?)Pmd)R6e}fk*Ftsvrg{v zX1AxdjBE!i7~sVM_^yi^H6e<6gmJUC{&#UpZ40GazO{z{9IUU!(g2R3fH+pGGwx}A zR)BS#f@-|*!wOvWUXY7pD8LE=?ivSy^2}coVg(`hZTQ9O5>)U4ceUtRtbq2x6TONR z(8hP_oajgpSh!$Et;Y$SJ_rd489}rUE8suq@U_&W1$s;BtX{qqS)_>v@5<3G*EDXHr`oPW{R=TrRZIm12)9OKkUdc`kc zmX8y9#-RAs{rEkI@03o+3@RfPN%5;YyGg)^RPn3(_6GT2Lp{d;2~WoQ(`#r{7sapc z@0ZI56W0m0M176=OC|p-NvGBUc1nJlk5M8Wm_?sc{`tVDOtQF%^@2kAAm*^4z$Al` zI%2!B6#m4#Pd;e6eIzGO{HS^=pj`4RT&+Jed{Z#VF(b&9vCVUU!c zh^uq2J;=a{HE%```(TS_%wZa>bK_-^&me`+i zisV;*X5?4b8*xHyr}))6irOD}pKL#QJC!JpYQHVu)$32Jw-g}*sY@IZzpC8oNcSOn zes#asRU}w&Cy0c6vI8l5G9(5jm7X@Y{jsGTpjQn(MC?Y4m zL-J3N@sGmjc>JsStGGb@OZ6SjD1Q!7U`A>%WE%Ak0gZu#^A#=S2WB%c7%tiW1Kv>q Ao&W#< literal 0 HcmV?d00001 diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid.c b/tools/nanokvm_update_edid/nanokvm_update_edid.c new file mode 100644 index 0000000..4a829ee --- /dev/null +++ b/tools/nanokvm_update_edid/nanokvm_update_edid.c @@ -0,0 +1,749 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "nanokvm_update_edid.h" + + +int client; +// old offset set to 0xff first +static uint8_t old_offset = 0xff; + +// ======================================================================================================= + +int get_edid_from_file(const char *filename, uint8_t *edid_data, uint16_t *edid_size) { + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open EDID file"); + return -1; + } + + *edid_size = fread(edid_data, 1, EDID_BUFFER_SIZE, file); + fclose(file); + + return 0; +} + +int check_edid(uint8_t *edid_data, uint16_t edid_size) +{ + uint16_t i; + // Check if the EDID data length is valid + if (edid_size != EDID_BUFFER_SIZE) { + fprintf(stderr, "EDID data length is not %d bytes\n", EDID_BUFFER_SIZE); + return -1; // EDID data length is not enough + } + + // Check EDID header + if (edid_data[0] != 0x00 || edid_data[1] != 0xFF || + edid_data[2] != 0xFF || edid_data[3] != 0xFF || + edid_data[4] != 0xFF || edid_data[5] != 0xFF || + edid_data[6] != 0xFF || edid_data[7] != 0x00) { + fprintf(stderr, "EDID header is invalid\n"); + return -1; // EDID header is invalid + } + + // First 128 Bytes checksum + uint8_t checksum1 = 0; + for (i = 0; i < 127; i++) { + checksum1 += edid_data[i]; + } + checksum1 = 0x100 - checksum1; // Reverse + if (checksum1 != edid_data[127]) { + // Checksum for first 128 bytes is incorrect + fprintf(stderr, "Checksum for first 128 bytes is incorrect\n"); + return -1; + } + + // Second 128 Bytes checksum + uint8_t checksum2 = 0; + for (i = 128; i < 255; i++) { + checksum2 += edid_data[i]; + } + checksum2 = 0x100 - checksum2; // Reverse + if (checksum2 != edid_data[255]) { + // Checksum for second 128 bytes is incorrect + fprintf(stderr, "Checksum for second 128 bytes is incorrect\n"); + return -1; + } + + return 0; // EDID is valid +} + +// ======================================================================================================= + +void NanoKVM_PCIe_HDMI_Reset(void) +{ + system("echo 0 > /sys/class/gpio/gpio451/value"); + usleep(100000); + system("echo 1 > /sys/class/gpio/gpio451/value"); + usleep(100000); +} + +// ======================================================================================================= + +// I2C write function +int i2c_write_byte(uint8_t offset, uint8_t reg, uint8_t data) +{ + // reg buffer + uint8_t reg_buf[2] = {0}; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the data to the i2c bus + reg_buf[0] = reg; // Set the register to write + reg_buf[1] = data; // Set the data to write + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write to the i2c bus"); + return -1; + } + return 0; +} + +int i2c_write_bytes(uint8_t offset, uint8_t reg, const uint8_t *data, size_t len) +{ + // check len + if (len == 0) { + fprintf(stderr, "Data length must be greater than 0.\n"); + return -1; + } + + // reg buffer + uint8_t reg_buf[1 + len]; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the data to the i2c bus + reg_buf[0] = reg; + for (size_t i = 0; i < len; i++) { + reg_buf[i + 1] = data[i]; + } + + // write to the I2C bus + if (write(client, reg_buf, 1 + len) != 1 + len) { + perror("Failed to write to the i2c bus"); + return -1; + } + + return 0; +} + +// I2C read function +int i2c_read_byte(uint8_t offset, uint8_t reg, uint8_t *data) +{ + // reg buffer + uint8_t reg_buf[2] = {0}; + + // if offset is changed, write it first + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // Set the offset register + reg_buf[1] = offset; // Set the register to read + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // write the register address to read + reg_buf[0] = reg; // Set the register to read + if (write(client, reg_buf, 1) != 1) { + perror("Failed to write register address to the i2c bus"); + return -1; + } + + // read the data from the i2c bus + if (read(client, data, 1) != 1 ) { + perror("Failed to read from the i2c bus"); + return -1; + } + + return 0; +} + +int i2c_read_bytes(uint8_t offset, uint8_t reg, uint8_t *data, size_t len) +{ + // 检查数据长度 + if (len == 0) { + fprintf(stderr, "Data length must be greater than 0.\n"); + return -1; + } + + uint8_t reg_buf[2]; // 创建寄存器缓冲区 + + // 如果偏移量改变,先写入偏移量 + if (offset != old_offset) { + old_offset = offset; + reg_buf[0] = LT6911_REG_OFFSET; // 设置偏移寄存器 + reg_buf[1] = offset; // 设置偏移值 + if (write(client, reg_buf, 2) != 2) { + perror("Failed to write offset to the i2c bus"); + return -1; + } + } + + // 写入要读取的寄存器地址 + reg_buf[0] = reg; // 设置要读取的寄存器 + if (write(client, reg_buf, 1) != 1) { + perror("Failed to write register address to the i2c bus"); + return -1; + } + + // 从 I2C 总线读取数据 + if (read(client, data, len) != len) { + perror("Failed to read from the i2c bus"); + return -1; + } + + return 0; +} + +// ======================================================================================================= + +int lt6911_enable(void) { + // Enable the LT6911UXC by writing to the appropriate register + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xEE, 0x01) != 0) { + fprintf(stderr, "Failed to enable LT6911UXC\n"); + return -1; + } + return 0; +} + +int lt6911_disable(void) { + // Disable the LT6911UXC by writing to the appropriate register + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xEE, 0x00) != 0) { + fprintf(stderr, "Failed to disable LT6911UXC\n"); + return -1; + } + return 0; +} + +// ======================================================================================================= + +int lt6911uxc_edid_write(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911UXC_WR_SIZE + 1; + uint8_t version_str[32] = {0}; + + fprintf(stdout, "Writing EDID....\n"); + + // Start + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x59, 0x51) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x10) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x81) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + // Waiting for erasure + usleep(500000); + + if (i2c_read_byte(LT6911_SYS3_OFFSET, 0x08, chip_data) != 0) return -1; + if (chip_data[0] != 0xEE) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xAE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xEE) != 0) return -1; + // Write + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x20) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, edid_data+(LT6911UXC_WR_SIZE*i), LT6911UXC_WR_SIZE) != 0) return -1; + } else { + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, version_str, LT6911UXC_WR_SIZE) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911UXC_WR_SIZE*i)) != 0) return -1; + } else { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x81) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xC0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x90) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i != wr_count-1) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + } else { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x88) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + } + if (i2c_read_byte(LT6911_SYS3_OFFSET, 0x08, chip_data) != 0) return -1; + if (chip_data[0] != 0xEE) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xAE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS3_OFFSET, 0x08, 0xEE) != 0) return -1; + + fprintf(stdout, "EDID write completed\n"); + + return 0; +} + +int lt6911uxc_edid_read(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911UXC_WR_SIZE; + + // Read EDID data from LT6911UXC + fprintf(stdout, "Reading EDID...\n"); + // Read + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x84) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0x5F) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911UXC_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x90) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x21) != 0) return -1; + if (i2c_read_bytes(LT6911_SYS_OFFSET, 0x5F, edid_data+(LT6911UXC_WR_SIZE*i), LT6911UXC_WR_SIZE) != 0) return -1; + } + + return 0; +} + +int lt6911c_edid_write(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911C_WR_SIZE; + uint8_t version_str[32] = {0}; + + fprintf(stdout, "Writing EDID....\n"); + + // Start + lt6911_enable(); + if (i2c_read_bytes(LT6911_SYS4_OFFSET, 0x00, chip_data, 2) != 0) return -1; + if (chip_data[0] != 0x16 || chip_data[1] != 0x05) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + lt6911_disable(); + + usleep(100000); + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_read_bytes(LT6911_SYS4_OFFSET, 0x00, chip_data, 2) != 0) return -1; + if (chip_data[0] != 0x16 || chip_data[1] != 0x05) { + fprintf(stderr, "Unsupported chip version\n"); + return -1; + } + lt6911_disable(); + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xC0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x59, 0x51) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x83) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + // waiting to clean + usleep(500000); + if (i2c_read_byte(LT6911_SYS2_OFFSET, 0x02, chip_data) != 0) return -1; + if (chip_data[0] != 0xFF) { + fprintf(stderr, "Clean Error\n"); + return -1; + } + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xFF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xEF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA2) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x01) != 0) return -1; + if (i2c_write_bytes(LT6911_SYS_OFFSET, 0x59, edid_data+(LT6911C_WR_SIZE*i), LT6911C_WR_SIZE) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911C_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0xE0) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + } + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x8A) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_read_byte(LT6911_SYS2_OFFSET, 0x02, chip_data) != 0) return -1; + if (chip_data[0] != 0xFF) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xDF) != 0) return -1; + if (i2c_write_byte(LT6911_SYS2_OFFSET, 0x02, 0xFF) != 0) return -1; + + fprintf(stdout, "EDID write completed\n"); + + return 0; +} + +int lt6911c_edid_read(uint8_t *edid_data, uint16_t edid_size) +{ + uint8_t i; + int ret; + uint8_t chip_data[16] = {0}; + uint8_t wr_count = edid_size / LT6911C_WR_SIZE; + + // Read EDID data from LT6911UXC + fprintf(stdout, "Reading EDID...\n"); + // Read + + if (i2c_write_byte(LT6911_SYS_OFFSET, 0xFF, 0x80) != 0) return -1; + lt6911_enable(); + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x86) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + + for (i = 0; i < wr_count; i++) { + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5E, 0x6F) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0xA2) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5B, 0x01) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5C, 0x80) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5D, 0x00+(LT6911C_WR_SIZE*i)) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x92) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x5A, 0x82) != 0) return -1; + if (i2c_write_byte(LT6911_SYS_OFFSET, 0x58, 0x01) != 0) return -1; + if (i2c_read_bytes(LT6911_SYS_OFFSET, 0x5F, edid_data+(LT6911C_WR_SIZE*i), LT6911C_WR_SIZE) != 0) return -1; + } + + return 0; +} + +// ======================================================================================================= + +int lt6911_edid_config(chip_version_t chip_version, uint8_t *edid_data, uint16_t edid_size) +{ + if (chip_version == CHIP_UNKNOWN) + { + fprintf(stderr, "Unknown chip version\n"); + return -1; + } + + // open i2c device + if ((client = open(I2C_DEVICE, O_RDWR)) < 0) { + perror("Failed to open the i2c bus"); + return -1; + } + + // // set the lt6911uxc address + if (ioctl(client, I2C_SLAVE, I2C_ADDRESS) < 0) { + perror("Failed to acquire bus access and/or talk to slave"); + close(client); + return -1; + } + + // edid write + if (chip_version == CHIP_LT6911UXC) { + if (lt6911uxc_edid_write(edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to write EDID data to LT6911UXC\n"); + close(client); + return -1; + } + } else if (chip_version == CHIP_LT6911C) { + if (lt6911c_edid_write(edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to write EDID data to LT6911C\n"); + close(client); + return -1; + } + } else { + fprintf(stderr, "Unknown chip version\n"); + close(client); + return -1; + } + // sleep 1s + sleep(1); + + // read edid data back + uint8_t edid_read_data[EDID_BUFFER_SIZE] = {0}; + if (chip_version == CHIP_LT6911UXC) { + if (lt6911uxc_edid_read(edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "Failed to read EDID data from LT6911UXC\n"); + close(client); + return -1; + } + } else if (chip_version == CHIP_LT6911C) { + if (lt6911c_edid_read(edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "Failed to read EDID data from LT6911C\n"); + close(client); + return -1; + } + } else { + fprintf(stderr, "Unknown chip version\n"); + close(client); + return -1; + } + + // check if the read data matches the written data + if (memcmp(edid_data, edid_read_data, EDID_BUFFER_SIZE) != 0) { + fprintf(stderr, "EDID data mismatch after write/read cycle\n"); + // print edid with 16*16 hex format + fprintf(stderr, "Written EDID data:\n"); + for (int i = 0; i < EDID_BUFFER_SIZE; i++) { + fprintf(stderr, "%02X ", edid_data[i]); + if ((i + 1) % 16 == 0) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\nRead EDID data:\n"); + for (int i = 0; i < EDID_BUFFER_SIZE; i++) { + fprintf(stderr, "%02X ", edid_read_data[i]); + if ((i + 1) % 16 == 0) { + fprintf(stderr, "\n"); + } + } + close(client); + return -1; + } else { + fprintf(stdout, "EDID data verified successfully\n"); + } + + // close client + close(client); + return 0; +} + +// ======================================================================================================= + +void print_warning(product_version_t product_version) { + printf("\n=========================================================\n"); + printf("Incorrect EDID may cause issues such as \n"); + printf("inability to display images, please modify with caution\n"); + printf("=========================================================\n\n"); + + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + printf("\n==========================================================\n"); + printf("⚠️ WARNING: Hardware version detected as Cube/Lite!\n"); + printf("==========================================================\n"); + printf("After flashing, you MUST manually power cycle the device!\n"); + printf("Please ensure you can physically disconnect its power,\n"); + printf("NOT just remotely reboot it!!\n"); + printf("==========================================================\n\n"); + } +} + +void print_success(product_version_t product_version) { + printf("\n=========================================================\n"); + printf("✅ EDID update successful!\n"); + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + printf("Please manually power cycle the device to apply changes.\n"); + } + printf("=========================================================\n\n"); +} + +int get_user_confirmation() { + char input[256]; + printf("Do you want to continue? (Y/N): \n"); + + while (1) { + + if (fgets(input, sizeof(input), stdin) == NULL) { + printf("\nInput error. Exiting.\n"); + return 0; + } + + // 去除换行符 + input[strcspn(input, "\n")] = '\0'; + + if (strlen(input) == 0) { + continue; // 空输入,重新提示 + } + + // 转换为小写方便比较 + char choice = tolower(input[0]); + + if (choice == 'y') { + return 1; + } else if (choice == 'n') { + return 0; + } else { + printf("Invalid input. Please enter Y or N.\n"); + } + } +} + +// ======================================================================================================= + +int main(int argc, char *argv[]) { + + uint8_t counter = 0; + + // Check version + chip_version_t chip_version = CHIP_UNKNOWN; + product_version_t product_version = PRODUCT_UNKNOWN; + char chip_version_str[32] = {0}; + char product_version_str[32] = {0}; + + FILE *file = fopen(VERSION_PATH, "r"); + if (file == NULL) { + fprintf(stderr, "Please upgrade to the latest system\n"); + return 1; + } + + if (fgets(chip_version_str, sizeof(chip_version_str), file) == NULL) { + fprintf(stderr, "Failed to read chip version\n"); + fclose(file); + return 1; + } + fclose(file); + + file = fopen(PRODUCT_PATH, "r"); + if (file == NULL) { + fprintf(stderr, "Please upgrade to the latest system\n"); + return 1; + } + + if (fgets(product_version_str, sizeof(product_version_str), file) == NULL) { + fprintf(stderr, "Failed to read product version\n"); + fclose(file); + return 1; + } + fclose(file); + + chip_version_str[strcspn(chip_version_str, "\n")] = '\0'; + product_version_str[strcspn(product_version_str, "\n")] = '\0'; + + if (strcmp(chip_version_str, "c") == 0) { + fprintf(stdout, "Chip Version: LT6911C\n"); + chip_version = CHIP_LT6911C; + } else if (strcmp(chip_version_str, "ux") == 0) { + fprintf(stdout, "Chip Version: LT6911UXC\n"); + chip_version = CHIP_LT6911UXC; + } else if (strcmp(chip_version_str, "ue") == 0) { + fprintf(stderr, "Chip Version Error: UE version's edid can't be updated\n"); + return 1; + } else { + fprintf(stderr, "Chip Version Error: Unknown version\n"); + return 1; + } + + if (strcmp(product_version_str, "alpha") == 0) { + fprintf(stdout, "Product Version: CUBE_A\n"); + product_version = PRODUCT_CUBE_A; + } else if (strcmp(product_version_str, "beta") == 0) { + fprintf(stdout, "Product Version: CUBE_B\n"); + product_version = PRODUCT_CUBE_B; + } else if (strcmp(product_version_str, "pcie") == 0) { + fprintf(stdout, "Product Version : PCIE_A\n"); + product_version = PRODUCT_PCIE_A; + } else { + fprintf(stderr, "Product Version Error: Unknown version\n"); + return 1; + } + + print_warning(product_version); + if (product_version == PRODUCT_CUBE_A || product_version == PRODUCT_CUBE_B) { + if (get_user_confirmation() == 0) { + return EXIT_FAILURE; + } + } + + // check command line arguments + if (argc != 2) { + fprintf(stderr, "Please enter the location of the EDID file using \"%s /path/to/edid.bin\"\n", argv[0]); + return EXIT_FAILURE; + } + + uint16_t edid_size = 0; + uint8_t edid_data[EDID_BUFFER_SIZE] = {0}; + if (get_edid_from_file(argv[1], edid_data, &edid_size) != 0) { + fprintf(stderr, "Failed to read EDID data from file %s\n", argv[1]); + return EXIT_FAILURE; + } + if (check_edid(edid_data, edid_size) != 0) { + fprintf(stderr, "EDID data is invalid\n"); + return EXIT_FAILURE; + } + fprintf(stdout, "EDID data loaded successfully from %s\n", argv[1]); + + if (product_version == PRODUCT_PCIE_A) NanoKVM_PCIe_HDMI_Reset(); + + // configure lt6911 edid + if (lt6911_edid_config(chip_version, edid_data, edid_size) != 0) { + fprintf(stderr, "Failed to configure LT6911 EDID\n"); + return EXIT_FAILURE; + } + + if (product_version == PRODUCT_PCIE_A) NanoKVM_PCIe_HDMI_Reset(); + + print_success(product_version); + + return EXIT_SUCCESS; +} diff --git a/tools/nanokvm_update_edid/nanokvm_update_edid.h b/tools/nanokvm_update_edid/nanokvm_update_edid.h new file mode 100644 index 0000000..ed743fb --- /dev/null +++ b/tools/nanokvm_update_edid/nanokvm_update_edid.h @@ -0,0 +1,41 @@ +#ifndef NANOKVM_UPDATE_EDID_H +#define NANOKVM_UPDATE_EDID_H + +#define I2C_DEVICE "/dev/i2c-4" // I2C 设备文件 +#define EDID_BUFFER_SIZE 256 // 最大支持的字节数 + +#define I2C_ADDRESS 0x2b // I2C 设备地址(根据实际设备调整) +#define LT6911_REG_OFFSET 0xFF // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS_OFFSET 0x80 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS2_OFFSET 0x90 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS3_OFFSET 0x81 // LT6911UXC 寄存器偏移地址 +#define LT6911_SYS4_OFFSET 0xA0 // LT6911UXC 寄存器偏移地址 +#define LT6911_CSI_INFO_OFFSET 0x85 // LT6911UXC CSI接口信息寄存器偏移地址 +#define LT6911_HDMI_INFO_OFFSET 0x86 // LT6911UXC HDMI信息寄存器偏移地址 +#define LT6911_CSI_TOTAL_OFFSET 0xD4 // LT6911UXC CSI总线统计信息 +#define LT6911_AUDIO_INFO_OFFSET 0xB0 // LT6911UXC 音频信息寄存器偏移地址 +#define LT6911C_HDMI_INFO_OFFSET 0xD2 // LT6911C HDMI信息寄存器偏移地址 +#define LT6911C_AUDIO_INFO_OFFSET 0xD1 // LT6911C 音频信息寄存器偏移地址 +#define LT6911C_CSI_INFO_OFFSET 0xC2 // LT6911C CSI信息寄存器偏移地址 + +#define EDID_BUFFER_SIZE 256 // 最大支持的字节数 +#define LT6911UXC_WR_SIZE 32 // LT6911UXC单次读写最大字节数 +#define LT6911C_WR_SIZE 16 // LT6911C单次读写最大字节数 + +#define VERSION_PATH "/etc/kvm/hdmi_version" +#define PRODUCT_PATH "/etc/kvm/hw" + +typedef enum { + CHIP_LT6911UXC = 0, + CHIP_LT6911C, + CHIP_UNKNOWN +} chip_version_t; + +typedef enum { + PRODUCT_CUBE_A = 0, + PRODUCT_CUBE_B, + PRODUCT_PCIE_A, + PRODUCT_UNKNOWN, +} product_version_t; + +#endif // NANOKVM_UPDATE_EDID_H \ No newline at end of file From bab729ed18eb99124e31fd0b632a06ab60ba0614 Mon Sep 17 00:00:00 2001 From: BuGu <62454025+Z2Z-GuGu@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:34:12 +0800 Subject: [PATCH 26/88] Add PID/VID Setting Function --- kvmapp/system/init.d/S03usbdev | 16 ++++++++++++++-- kvmapp/system/init.d/S03usbhid | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev index c650e6b..3536cf5 100755 --- a/kvmapp/system/init.d/S03usbdev +++ b/kvmapp/system/init.d/S03usbdev @@ -9,8 +9,20 @@ start_usb_dev(){ mkdir g0 cd g0 - echo 0x3346 > idVendor - echo 0x1009 > idProduct + # echo 0x3346 > idVendor + # echo 0x1009 > idProduct + if [ -e /boot/usb.vid ] + then + cat /boot/usb.vid > idVendor + else + echo 0x3346 > idVendor + fi + if [ -e /boot/usb.pid ] + then + cat /boot/usb.pid > idProduct + else + echo 0x1009 > idProduct + fi mkdir strings/0x409 echo '0123456789ABCDEF' > strings/0x409/serialnumber echo 'sipeed' > strings/0x409/manufacturer diff --git a/kvmapp/system/init.d/S03usbhid b/kvmapp/system/init.d/S03usbhid index 6d5d79f..a8bcc45 100755 --- a/kvmapp/system/init.d/S03usbhid +++ b/kvmapp/system/init.d/S03usbhid @@ -11,8 +11,20 @@ start_usb_dev(){ echo 0x0101 > bcdUSB echo 0x0623 > bcdDevice - echo 0x3346 > idVendor - echo 0x1009 > idProduct + # echo 0x3346 > idVendor + # echo 0x1009 > idProduct + if [ -e /boot/usb.vid ] + then + cat /boot/usb.vid > idVendor + else + echo 0x3346 > idVendor + fi + if [ -e /boot/usb.pid ] + then + cat /boot/usb.pid > idProduct + else + echo 0x1009 > idProduct + fi mkdir strings/0x409 # echo '0' > strings/0x409/serialnumber echo 'sipeed' > strings/0x409/manufacturer From 755cbe113dd8cb9c49d69e5ed055854a17b33179 Mon Sep 17 00:00:00 2001 From: Luccas Ferreira <44102810+Luccas-LF@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:17:37 -0300 Subject: [PATCH 27/88] Create pt-br.ts --- web/src/i18n/locales/pt-br.ts | 352 ++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 web/src/i18n/locales/pt-br.ts diff --git a/web/src/i18n/locales/pt-br.ts b/web/src/i18n/locales/pt-br.ts new file mode 100644 index 0000000..b0b254a --- /dev/null +++ b/web/src/i18n/locales/pt-br.ts @@ -0,0 +1,352 @@ +const ptBR = { + translation: { + head: { + desktop: 'Área de Trabalho Remota', + login: 'Login', + changePassword: 'Alterar Senha', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Login', + placeholderUsername: 'Nome de usuário', + placeholderPassword: 'Senha', + placeholderPassword2: 'Por favor, digite a senha novamente', + noEmptyUsername: 'Nome de usuário obrigatório', + noEmptyPassword: 'Senha obrigatória', + noAccount: 'Falha ao obter informações do usuário, por favor atualize a página ou redefina a senha', + invalidUser: 'Nome de usuário ou senha inválidos', + error: 'Erro inesperado', + changePassword: 'Alterar Senha', + changePasswordDesc: 'Para a segurança do seu dispositivo, por favor altere a senha!', + differentPassword: 'As senhas não coincidem', + illegalUsername: 'Nome de usuário contém caracteres inválidos', + illegalPassword: 'Senha contém caracteres inválidos', + forgetPassword: 'Esqueceu a senha', + ok: 'Ok', + cancel: 'Cancelar', + loginButtonText: 'Entrar', + tips: { + reset1: + 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM por 10 segundos.', + reset2: 'Para instruções detalhadas, consulte este documento:', + reset3: 'Conta padrão para web:', + reset4: 'Conta padrão para SSH:', + change1: 'Note que esta ação irá alterar as seguintes senhas:', + change2: 'Senha de login da web', + change3: 'Senha root do sistema (senha de login SSH)', + change4: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi para NanoKVM', + success: 'Por favor, verifique o status da rede do NanoKVM e acesse o novo endereço IP.', + failed: 'Operação falhou, por favor tente novamente.', + confirmBtn: 'Ok', + finishBtn: 'Concluído' + }, + screen: { + title: 'Tela', + video: 'Modo de Vídeo', + videoDirectTips: 'Ative HTTPS em "Configurações > Dispositivo" para usar este modo', + resolution: 'Resolução', + auto: 'Automático', + autoTips: + "Pode ocorrer tearing da tela ou desvio do mouse em determinadas resoluções. Considere ajustar a resolução do host remoto ou desative o modo automático.", + fps: 'FPS', + customizeFps: 'Personalizar', + quality: 'Qualidade', + qualityLossless: 'Sem perda', + qualityHigh: 'Alta', + qualityMedium: 'Média', + qualityLow: 'Baixa', + frameDetect: 'Detecção de quadros', + frameDetectTip: + "Calcula a diferença entre quadros. Para de transmitir o vídeo quando não há mudanças na tela do host remoto.", + resetHdmi: 'Resetar HDMI' + }, + keyboard: { + title: 'Teclado', + paste: 'Colar', + tips: 'Apenas letras e símbolos padrão do teclado são suportados', + placeholder: 'Digite aqui', + submit: 'Enviar', + virtual: 'Teclado', + ctrlaltdel: 'Ctrl+Alt+Del' + }, + mouse: { + title: 'Mouse', + cursor: 'Estilo do cursor', + default: 'Cursor padrão', + pointer: 'Cursor de ponteiro', + cell: 'Cursor de célula', + text: 'Cursor de texto', + grab: 'Cursor de agarrar', + hide: 'Ocultar cursor', + mode: 'Modo do mouse', + absolute: 'Modo absoluto', + relative: 'Modo relativo', + speed: 'Velocidade da roda', + fast: 'Rápido', + slow: 'Lento', + requestPointer: 'Usando modo relativo. Por favor, clique na área de trabalho para ativar o ponteiro do mouse.', + resetHid: 'Resetar HID', + hidOnly: { + title: 'Modo somente HID', + desc: "Se seu mouse e teclado pararem de responder e resetar HID não ajudar, pode ser um problema de compatibilidade entre o NanoKVM e o dispositivo. Tente ativar o modo somente HID para melhor compatibilidade.", + tip1: 'Ativar modo somente HID irá desmontar o disco virtual e a rede virtual', + tip2: 'No modo somente HID, o montagem de imagens é desativado', + tip3: 'NanoKVM irá reiniciar automaticamente após a troca de modo', + enable: 'Ativar modo somente HID', + disable: 'Desativar modo somente HID' + } + }, + image: { + title: 'Imagens', + loading: 'Carregando...', + empty: 'Nada encontrado', + cdrom: 'Montar imagem no modo CD-ROM', + mountFailed: 'Falha ao montar', + mountDesc: + "Em alguns sistemas, é necessário ejetar o disco virtual no host remoto antes de montar a imagem.", + refresh: 'Atualizar lista de imagens', + tips: { + title: 'Como enviar', + usb1: 'Conecte o NanoKVM ao computador via USB.', + usb2: 'Certifique-se que o disco virtual está montado (Configurações - Disco Virtual).', + usb3: 'Abra o disco virtual no computador e copie o arquivo de imagem para a raiz do disco virtual.', + scp1: 'Certifique-se que o NanoKVM e seu computador estão na mesma rede local.', + scp2: 'Abra o terminal no computador e use o comando SCP para enviar o arquivo de imagem para o diretório /data no NanoKVM.', + scp3: 'Exemplo: scp caminho-da-imagem root@ip-do-nanokvm:/data', + tfCard: 'Cartão TF', + tf1: 'Método suportado no sistema Linux', + tf2: 'Remova o cartão TF do NanoKVM (para a versão FULL, desmonte o cartão primeiro).', + tf3: 'Insira o cartão TF em um leitor de cartões e conecte ao computador.', + tf4: 'Copie o arquivo de imagem para o diretório /data no cartão TF.', + tf5: 'Insira o cartão TF no NanoKVM.' + } + }, + script: { + title: 'Scripts', + upload: 'Enviar', + run: 'Executar', + runBackground: 'Executar em Segundo Plano', + runFailed: 'Falha ao executar', + attention: 'Atenção', + delDesc: 'Tem certeza que deseja deletar este arquivo?', + confirm: 'Sim', + cancel: 'Não', + delete: 'Deletar', + close: 'Fechar' + }, + terminal: { + title: 'Terminal', + nanokvm: 'Terminal NanoKVM', + serial: 'Terminal Porta Serial', + serialPort: 'Porta Serial', + serialPortPlaceholder: 'Por favor, digite a porta serial', + baudrate: 'Baud Rate', + parity: 'Paridade', + parityNone: 'Nenhuma', + parityEven: 'Paridade Par', + parityOdd: 'Paridade Ímpar', + flowControl: 'Controle de Fluxo', + flowControlNone: 'Nenhum', + flowControlSoft: 'Soft', + flowControlHard: 'Hard', + dataBits: 'Bits de dados', + stopBits: 'Bits de parada', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Enviando comando...', + sent: 'Comando enviado', + input: 'Por favor, digite o MAC', + ok: 'Ok' + }, + download: { + title: 'Download de Imagem', + input: 'Por favor, digite uma URL de imagem remota', + ok: 'Ok', + disabled: 'A partição /data está somente leitura, não é possível baixar a imagem' + }, + power: { + title: 'Energia', + showConfirm: 'Confirmação', + showConfirmTip: 'Operações de energia exigem confirmação adicional', + reset: 'Reiniciar', + power: 'Energia', + powerShort: 'Energia (clique curto)', + powerLong: 'Energia (clique longo)', + resetConfirm: 'Deseja realmente reiniciar?', + powerConfirm: 'Deseja realmente realizar a operação de energia?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + settings: { + title: 'Configurações', + about: { + title: 'Sobre o NanoKVM', + information: 'Informações', + ip: 'IP', + mdns: 'mDNS', + application: 'Versão do Aplicativo', + applicationTip: 'Versão do aplicativo web do NanoKVM', + image: 'Versão da Imagem', + imageTip: 'Versão da imagem do sistema NanoKVM', + deviceKey: 'Chave do Dispositivo', + community: 'Comunidade', + hostname: 'Nome do Host', + hostnameUpdated: 'Nome do host atualizado. Reinicie para aplicar.', + ipType: { + Wired: 'Com fio', + Wireless: 'Sem fio', + Other: 'Outro' + } + }, + appearance: { + title: 'Aparência', + display: 'Tela', + language: 'Idioma', + menuBar: 'Barra de Menu', + menuBarDesc: 'Exibir ícones na barra de menu', + webTitle: 'Título da Web', + webTitleDesc: 'Personalizar o título da página web' + }, + device: { + title: 'Dispositivo', + oled: { + title: 'OLED', + description: 'Desligar a tela OLED após', + 0: 'Nunca', + 15: '15 segundos', + 30: '30 segundos', + 60: '1 minuto', + 180: '3 minutos', + 300: '5 minutos', + 600: '10 minutos', + 1800: '30 minutos', + 3600: '1 hora' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configurar Wi-Fi', + setBtn: 'Configurar' + }, + ssh: { + description: 'Ativar acesso SSH remoto', + tip: 'Configure uma senha forte antes de habilitar (Conta - Alterar Senha)' + }, + tls: { + description: 'Ativar protocolo HTTPS', + tip: 'Aviso: Usar HTTPS pode aumentar a latência, especialmente no modo de vídeo MJPEG.' + }, + advanced: 'Configurações Avançadas', + swap: { + title: 'Swap', + disable: 'Desativar', + description: 'Definir o tamanho do arquivo swap', + tip: 'Ativar essa função pode reduzir a vida útil do seu cartão SD!' + }, + mouseJiggler: { + title: 'Agitador de Mouse', + description: 'Impede que o host remoto entre em modo de suspensão', + disable: 'Desativar', + absolute: 'Modo Absoluto', + relative: 'Modo Relativo' + }, + mdns: { + description: 'Ativar serviço de descoberta mDNS', + tip: 'Desative se não for necessário' + }, + hdmi: { + description: 'Ativar saída HDMI/monitor' + }, + hidOnly: 'Modo somente HID', + disk: 'Disco Virtual', + diskDesc: 'Montar disco virtual no host remoto', + network: 'Rede Virtual', + networkDesc: 'Montar placa de rede virtual no host remoto', + reboot: 'Reiniciar', + rebootDesc: 'Tem certeza que deseja reiniciar o NanoKVM?', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + tailscale: { + title: 'Tailscale', + memory: { + title: 'Otimização de memória', + tip: 'Quando o uso de memória ultrapassa o limite, a coleta de lixo é mais agressiva para tentar liberar memória. Recomenda-se configurar para 75MB se usar Tailscale. É necessário reiniciar o Tailscale para aplicar a alteração.', + disable: 'Desabilitar' + }, + restart: 'Reiniciar Tailscale?', + stop: 'Parar Tailscale?', + stopDesc: 'Sair do Tailscale e desativar inicialização automática na inicialização.', + loading: 'Carregando...', + notInstall: 'Tailscale não encontrado! Por favor, instale.', + install: 'Instalar', + installing: 'Instalando', + failed: 'Falha na instalação', + retry: 'Por favor, atualize e tente novamente. Ou tente instalar manualmente', + download: 'Baixar o', + package: 'pacote de instalação', + unzip: 'e descompacte', + upTailscale: 'Envie o tailscale para o diretório NanoKVM /usr/bin/', + upTailscaled: 'Envie o tailscaled para o diretório NanoKVM /usr/sbin/', + refresh: 'Atualizar página atual', + notLogin: + 'Dispositivo ainda não está vinculado. Faça login e vincule este dispositivo à sua conta.', + urlPeriod: 'Este URL é válido por 10 minutos', + login: 'Login', + loginSuccess: 'Login bem-sucedido', + enable: 'Habilitar Tailscale', + deviceName: 'Nome do Dispositivo', + deviceIP: 'IP do Dispositivo', + account: 'Conta', + logout: 'Sair', + logoutDesc: 'Tem certeza que deseja sair?', + uninstall: 'Desinstalar Tailscale', + okBtn: 'Sim', + cancelBtn: 'Não' + }, + update: { + title: 'Verificar Atualizações', + queryFailed: 'Falha ao obter versão', + updateFailed: 'Falha na atualização. Por favor, tente novamente.', + isLatest: 'Já está na versão mais recente.', + available: 'Atualização disponível. Deseja atualizar agora?', + updating: 'Atualização iniciada. Por favor aguarde...', + confirm: 'Confirmar', + cancel: 'Cancelar', + preview: 'Visualizar Atualizações', + previewDesc: 'Obtenha acesso antecipado a novos recursos e melhorias', + previewTip: + 'Esteja ciente de que versões de visualização podem conter bugs ou funcionalidades incompletas!' + }, + account: { + title: 'Conta', + webAccount: 'Nome da conta web', + password: 'Senha', + updateBtn: 'Alterar', + logoutBtn: 'Sair', + logoutDesc: 'Tem certeza que deseja sair?', + okBtn: 'Sim', + cancelBtn: 'Não' + } + }, + error: { + title: 'Encontramos um problema', + refresh: 'Atualizar' + }, + fullscreen: { + toggle: 'Alternar Tela Cheia' + }, + menu: { + collapse: 'Recolher Menu', + expand: 'Expandir Menu' + } + } +}; +export default ptBR; From 3299c202b01f91acd4941b9af434f7dd068dcade Mon Sep 17 00:00:00 2001 From: Luccas Ferreira <44102810+Luccas-LF@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:19:50 -0300 Subject: [PATCH 28/88] Update languages.ts --- web/src/i18n/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/i18n/languages.ts b/web/src/i18n/languages.ts index ff2b501..335fe33 100644 --- a/web/src/i18n/languages.ts +++ b/web/src/i18n/languages.ts @@ -8,6 +8,7 @@ const languages = [ { key: 'id', name: 'Indonesia' }, { key: 'it', name: 'Italian' }, { key: 'pl', name: 'Polski' }, + { key: 'pt-br', name: 'Português (Brasil)' }, { key: 'ru', name: 'Русский' }, { key: 'ko', name: '한국어' }, { key: 'zh', name: '简体中文' }, From 396aee7d0a2b57c176ede0e4adcfd8a12176f5d2 Mon Sep 17 00:00:00 2001 From: Luccas Ferreira <44102810+Luccas-LF@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:22:33 -0300 Subject: [PATCH 29/88] Update languages.ts --- web/src/i18n/languages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/i18n/languages.ts b/web/src/i18n/languages.ts index 335fe33..f7116b4 100644 --- a/web/src/i18n/languages.ts +++ b/web/src/i18n/languages.ts @@ -8,7 +8,7 @@ const languages = [ { key: 'id', name: 'Indonesia' }, { key: 'it', name: 'Italian' }, { key: 'pl', name: 'Polski' }, - { key: 'pt-br', name: 'Português (Brasil)' }, + { key: 'pt_br', name: 'Português (Brasil)' }, { key: 'ru', name: 'Русский' }, { key: 'ko', name: '한국어' }, { key: 'zh', name: '简体中文' }, From e82069dea219c26fde40af44b77457462f7498ca Mon Sep 17 00:00:00 2001 From: Luccas Ferreira <44102810+Luccas-LF@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:22:57 -0300 Subject: [PATCH 30/88] Delete web/src/i18n/locales/pt-br.ts --- web/src/i18n/locales/pt-br.ts | 352 ---------------------------------- 1 file changed, 352 deletions(-) delete mode 100644 web/src/i18n/locales/pt-br.ts diff --git a/web/src/i18n/locales/pt-br.ts b/web/src/i18n/locales/pt-br.ts deleted file mode 100644 index b0b254a..0000000 --- a/web/src/i18n/locales/pt-br.ts +++ /dev/null @@ -1,352 +0,0 @@ -const ptBR = { - translation: { - head: { - desktop: 'Área de Trabalho Remota', - login: 'Login', - changePassword: 'Alterar Senha', - terminal: 'Terminal', - wifi: 'Wi-Fi' - }, - auth: { - login: 'Login', - placeholderUsername: 'Nome de usuário', - placeholderPassword: 'Senha', - placeholderPassword2: 'Por favor, digite a senha novamente', - noEmptyUsername: 'Nome de usuário obrigatório', - noEmptyPassword: 'Senha obrigatória', - noAccount: 'Falha ao obter informações do usuário, por favor atualize a página ou redefina a senha', - invalidUser: 'Nome de usuário ou senha inválidos', - error: 'Erro inesperado', - changePassword: 'Alterar Senha', - changePasswordDesc: 'Para a segurança do seu dispositivo, por favor altere a senha!', - differentPassword: 'As senhas não coincidem', - illegalUsername: 'Nome de usuário contém caracteres inválidos', - illegalPassword: 'Senha contém caracteres inválidos', - forgetPassword: 'Esqueceu a senha', - ok: 'Ok', - cancel: 'Cancelar', - loginButtonText: 'Entrar', - tips: { - reset1: - 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM por 10 segundos.', - reset2: 'Para instruções detalhadas, consulte este documento:', - reset3: 'Conta padrão para web:', - reset4: 'Conta padrão para SSH:', - change1: 'Note que esta ação irá alterar as seguintes senhas:', - change2: 'Senha de login da web', - change3: 'Senha root do sistema (senha de login SSH)', - change4: 'Para redefinir as senhas, pressione e segure o botão BOOT no NanoKVM.' - } - }, - wifi: { - title: 'Wi-Fi', - description: 'Configurar Wi-Fi para NanoKVM', - success: 'Por favor, verifique o status da rede do NanoKVM e acesse o novo endereço IP.', - failed: 'Operação falhou, por favor tente novamente.', - confirmBtn: 'Ok', - finishBtn: 'Concluído' - }, - screen: { - title: 'Tela', - video: 'Modo de Vídeo', - videoDirectTips: 'Ative HTTPS em "Configurações > Dispositivo" para usar este modo', - resolution: 'Resolução', - auto: 'Automático', - autoTips: - "Pode ocorrer tearing da tela ou desvio do mouse em determinadas resoluções. Considere ajustar a resolução do host remoto ou desative o modo automático.", - fps: 'FPS', - customizeFps: 'Personalizar', - quality: 'Qualidade', - qualityLossless: 'Sem perda', - qualityHigh: 'Alta', - qualityMedium: 'Média', - qualityLow: 'Baixa', - frameDetect: 'Detecção de quadros', - frameDetectTip: - "Calcula a diferença entre quadros. Para de transmitir o vídeo quando não há mudanças na tela do host remoto.", - resetHdmi: 'Resetar HDMI' - }, - keyboard: { - title: 'Teclado', - paste: 'Colar', - tips: 'Apenas letras e símbolos padrão do teclado são suportados', - placeholder: 'Digite aqui', - submit: 'Enviar', - virtual: 'Teclado', - ctrlaltdel: 'Ctrl+Alt+Del' - }, - mouse: { - title: 'Mouse', - cursor: 'Estilo do cursor', - default: 'Cursor padrão', - pointer: 'Cursor de ponteiro', - cell: 'Cursor de célula', - text: 'Cursor de texto', - grab: 'Cursor de agarrar', - hide: 'Ocultar cursor', - mode: 'Modo do mouse', - absolute: 'Modo absoluto', - relative: 'Modo relativo', - speed: 'Velocidade da roda', - fast: 'Rápido', - slow: 'Lento', - requestPointer: 'Usando modo relativo. Por favor, clique na área de trabalho para ativar o ponteiro do mouse.', - resetHid: 'Resetar HID', - hidOnly: { - title: 'Modo somente HID', - desc: "Se seu mouse e teclado pararem de responder e resetar HID não ajudar, pode ser um problema de compatibilidade entre o NanoKVM e o dispositivo. Tente ativar o modo somente HID para melhor compatibilidade.", - tip1: 'Ativar modo somente HID irá desmontar o disco virtual e a rede virtual', - tip2: 'No modo somente HID, o montagem de imagens é desativado', - tip3: 'NanoKVM irá reiniciar automaticamente após a troca de modo', - enable: 'Ativar modo somente HID', - disable: 'Desativar modo somente HID' - } - }, - image: { - title: 'Imagens', - loading: 'Carregando...', - empty: 'Nada encontrado', - cdrom: 'Montar imagem no modo CD-ROM', - mountFailed: 'Falha ao montar', - mountDesc: - "Em alguns sistemas, é necessário ejetar o disco virtual no host remoto antes de montar a imagem.", - refresh: 'Atualizar lista de imagens', - tips: { - title: 'Como enviar', - usb1: 'Conecte o NanoKVM ao computador via USB.', - usb2: 'Certifique-se que o disco virtual está montado (Configurações - Disco Virtual).', - usb3: 'Abra o disco virtual no computador e copie o arquivo de imagem para a raiz do disco virtual.', - scp1: 'Certifique-se que o NanoKVM e seu computador estão na mesma rede local.', - scp2: 'Abra o terminal no computador e use o comando SCP para enviar o arquivo de imagem para o diretório /data no NanoKVM.', - scp3: 'Exemplo: scp caminho-da-imagem root@ip-do-nanokvm:/data', - tfCard: 'Cartão TF', - tf1: 'Método suportado no sistema Linux', - tf2: 'Remova o cartão TF do NanoKVM (para a versão FULL, desmonte o cartão primeiro).', - tf3: 'Insira o cartão TF em um leitor de cartões e conecte ao computador.', - tf4: 'Copie o arquivo de imagem para o diretório /data no cartão TF.', - tf5: 'Insira o cartão TF no NanoKVM.' - } - }, - script: { - title: 'Scripts', - upload: 'Enviar', - run: 'Executar', - runBackground: 'Executar em Segundo Plano', - runFailed: 'Falha ao executar', - attention: 'Atenção', - delDesc: 'Tem certeza que deseja deletar este arquivo?', - confirm: 'Sim', - cancel: 'Não', - delete: 'Deletar', - close: 'Fechar' - }, - terminal: { - title: 'Terminal', - nanokvm: 'Terminal NanoKVM', - serial: 'Terminal Porta Serial', - serialPort: 'Porta Serial', - serialPortPlaceholder: 'Por favor, digite a porta serial', - baudrate: 'Baud Rate', - parity: 'Paridade', - parityNone: 'Nenhuma', - parityEven: 'Paridade Par', - parityOdd: 'Paridade Ímpar', - flowControl: 'Controle de Fluxo', - flowControlNone: 'Nenhum', - flowControlSoft: 'Soft', - flowControlHard: 'Hard', - dataBits: 'Bits de dados', - stopBits: 'Bits de parada', - confirm: 'Ok' - }, - wol: { - title: 'Wake-on-LAN', - sending: 'Enviando comando...', - sent: 'Comando enviado', - input: 'Por favor, digite o MAC', - ok: 'Ok' - }, - download: { - title: 'Download de Imagem', - input: 'Por favor, digite uma URL de imagem remota', - ok: 'Ok', - disabled: 'A partição /data está somente leitura, não é possível baixar a imagem' - }, - power: { - title: 'Energia', - showConfirm: 'Confirmação', - showConfirmTip: 'Operações de energia exigem confirmação adicional', - reset: 'Reiniciar', - power: 'Energia', - powerShort: 'Energia (clique curto)', - powerLong: 'Energia (clique longo)', - resetConfirm: 'Deseja realmente reiniciar?', - powerConfirm: 'Deseja realmente realizar a operação de energia?', - okBtn: 'Sim', - cancelBtn: 'Não' - }, - settings: { - title: 'Configurações', - about: { - title: 'Sobre o NanoKVM', - information: 'Informações', - ip: 'IP', - mdns: 'mDNS', - application: 'Versão do Aplicativo', - applicationTip: 'Versão do aplicativo web do NanoKVM', - image: 'Versão da Imagem', - imageTip: 'Versão da imagem do sistema NanoKVM', - deviceKey: 'Chave do Dispositivo', - community: 'Comunidade', - hostname: 'Nome do Host', - hostnameUpdated: 'Nome do host atualizado. Reinicie para aplicar.', - ipType: { - Wired: 'Com fio', - Wireless: 'Sem fio', - Other: 'Outro' - } - }, - appearance: { - title: 'Aparência', - display: 'Tela', - language: 'Idioma', - menuBar: 'Barra de Menu', - menuBarDesc: 'Exibir ícones na barra de menu', - webTitle: 'Título da Web', - webTitleDesc: 'Personalizar o título da página web' - }, - device: { - title: 'Dispositivo', - oled: { - title: 'OLED', - description: 'Desligar a tela OLED após', - 0: 'Nunca', - 15: '15 segundos', - 30: '30 segundos', - 60: '1 minuto', - 180: '3 minutos', - 300: '5 minutos', - 600: '10 minutos', - 1800: '30 minutos', - 3600: '1 hora' - }, - wifi: { - title: 'Wi-Fi', - description: 'Configurar Wi-Fi', - setBtn: 'Configurar' - }, - ssh: { - description: 'Ativar acesso SSH remoto', - tip: 'Configure uma senha forte antes de habilitar (Conta - Alterar Senha)' - }, - tls: { - description: 'Ativar protocolo HTTPS', - tip: 'Aviso: Usar HTTPS pode aumentar a latência, especialmente no modo de vídeo MJPEG.' - }, - advanced: 'Configurações Avançadas', - swap: { - title: 'Swap', - disable: 'Desativar', - description: 'Definir o tamanho do arquivo swap', - tip: 'Ativar essa função pode reduzir a vida útil do seu cartão SD!' - }, - mouseJiggler: { - title: 'Agitador de Mouse', - description: 'Impede que o host remoto entre em modo de suspensão', - disable: 'Desativar', - absolute: 'Modo Absoluto', - relative: 'Modo Relativo' - }, - mdns: { - description: 'Ativar serviço de descoberta mDNS', - tip: 'Desative se não for necessário' - }, - hdmi: { - description: 'Ativar saída HDMI/monitor' - }, - hidOnly: 'Modo somente HID', - disk: 'Disco Virtual', - diskDesc: 'Montar disco virtual no host remoto', - network: 'Rede Virtual', - networkDesc: 'Montar placa de rede virtual no host remoto', - reboot: 'Reiniciar', - rebootDesc: 'Tem certeza que deseja reiniciar o NanoKVM?', - okBtn: 'Sim', - cancelBtn: 'Não' - }, - tailscale: { - title: 'Tailscale', - memory: { - title: 'Otimização de memória', - tip: 'Quando o uso de memória ultrapassa o limite, a coleta de lixo é mais agressiva para tentar liberar memória. Recomenda-se configurar para 75MB se usar Tailscale. É necessário reiniciar o Tailscale para aplicar a alteração.', - disable: 'Desabilitar' - }, - restart: 'Reiniciar Tailscale?', - stop: 'Parar Tailscale?', - stopDesc: 'Sair do Tailscale e desativar inicialização automática na inicialização.', - loading: 'Carregando...', - notInstall: 'Tailscale não encontrado! Por favor, instale.', - install: 'Instalar', - installing: 'Instalando', - failed: 'Falha na instalação', - retry: 'Por favor, atualize e tente novamente. Ou tente instalar manualmente', - download: 'Baixar o', - package: 'pacote de instalação', - unzip: 'e descompacte', - upTailscale: 'Envie o tailscale para o diretório NanoKVM /usr/bin/', - upTailscaled: 'Envie o tailscaled para o diretório NanoKVM /usr/sbin/', - refresh: 'Atualizar página atual', - notLogin: - 'Dispositivo ainda não está vinculado. Faça login e vincule este dispositivo à sua conta.', - urlPeriod: 'Este URL é válido por 10 minutos', - login: 'Login', - loginSuccess: 'Login bem-sucedido', - enable: 'Habilitar Tailscale', - deviceName: 'Nome do Dispositivo', - deviceIP: 'IP do Dispositivo', - account: 'Conta', - logout: 'Sair', - logoutDesc: 'Tem certeza que deseja sair?', - uninstall: 'Desinstalar Tailscale', - okBtn: 'Sim', - cancelBtn: 'Não' - }, - update: { - title: 'Verificar Atualizações', - queryFailed: 'Falha ao obter versão', - updateFailed: 'Falha na atualização. Por favor, tente novamente.', - isLatest: 'Já está na versão mais recente.', - available: 'Atualização disponível. Deseja atualizar agora?', - updating: 'Atualização iniciada. Por favor aguarde...', - confirm: 'Confirmar', - cancel: 'Cancelar', - preview: 'Visualizar Atualizações', - previewDesc: 'Obtenha acesso antecipado a novos recursos e melhorias', - previewTip: - 'Esteja ciente de que versões de visualização podem conter bugs ou funcionalidades incompletas!' - }, - account: { - title: 'Conta', - webAccount: 'Nome da conta web', - password: 'Senha', - updateBtn: 'Alterar', - logoutBtn: 'Sair', - logoutDesc: 'Tem certeza que deseja sair?', - okBtn: 'Sim', - cancelBtn: 'Não' - } - }, - error: { - title: 'Encontramos um problema', - refresh: 'Atualizar' - }, - fullscreen: { - toggle: 'Alternar Tela Cheia' - }, - menu: { - collapse: 'Recolher Menu', - expand: 'Expandir Menu' - } - } -}; -export default ptBR; From 815770fabf94160d07452d2cd39a44a7bb51d201 Mon Sep 17 00:00:00 2001 From: BuGu <62454025+Z2Z-GuGu@users.noreply.github.com> Date: Fri, 5 Sep 2025 18:19:32 +0800 Subject: [PATCH 31/88] Update S30eth --- kvmapp/system/init.d/S30eth | 156 ++++++++++++------------------------ 1 file changed, 52 insertions(+), 104 deletions(-) diff --git a/kvmapp/system/init.d/S30eth b/kvmapp/system/init.d/S30eth index 17db97f..604a673 100755 --- a/kvmapp/system/init.d/S30eth +++ b/kvmapp/system/init.d/S30eth @@ -1,119 +1,67 @@ #!/bin/sh -. /etc/profile +RESERVE_INET="192.168.90.1/22" -# /boot/eth.nodhcp for example -# ipaddr/net gw[optional] -# 192.168.0.101/24 192.168.0.1 -# 192.168.3.116/22 +clean_line() { + echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/\r//g' +} -RESERVE_INET="192.168.0.1/24" +calc_gw() { + addr="$1" + netid="$2" + IFS=. read a b c d <>24)&255 )).$(( (gwnum>>16)&255 )).$(( (gwnum>>8)&255 )).$(( gwnum&255 ))" +} start() { - printf "start ethernet: " - if [ -e /boot/eth.nodhcp ] - then - [ -e /boot/eth.nodhcp ] && - cat /boot/eth.nodhcp | while read inet gw - do - addr=${inet%/*} - netid=${inet#*/} - [ -z $gw ] && - gw=$( echo $addr| ( IFS='.' read a b c d; echo $(( - (((((($a<<8)+$b)<<8)+$c)<<8)+$d) - & (((1<<$netid)-1)<<(32-$netid)) - )) - )) && - gw=$(($gw>>24&0xff)).$(($gw>>16&0xff)).$(($gw>>8&0xff)).$((1+( $gw>>0&0xff ))) + if [ -e /boot/eth.nodhcp ]; then + while IFS= read -r line || [ -n "$line" ]; do + line=$(clean_line "$line") + [ -z "$line" ] && continue + set -- $line + inet="$1" + gw="$2" + [ -z "$inet" ] && continue + addr=${inet%/*} + netid=${inet#*/} + [ -z "$gw" ] && gw=$(calc_gw "$addr" "$netid") + arping -Dqc2 -Ieth0 "$addr" || continue + ip a add "$inet" brd + dev eth0 + ip r add default via "$gw" dev eth0 + echo -e "nameserver $gw" >> /etc/resolv.conf + break + done < /boot/eth.nodhcp - arping -Dqc2 -Ieth0 $addr || continue - ip a add $inet brd + dev eth0 - ip r add default via $gw dev eth0 - cp -f /etc/resolv.conf /etc/resolv.conf.old - cat > /etc/resolv.conf << EOF -nameserver $gw -nameserver 8.8.4.4 -nameserver 8.8.8.8 -nameserver 114.114.114.114 -nameserver 119.29.29.29 -nameserver 223.5.5.5 -EOF - break - done && - ip a show dev eth0|grep inet || ( - udhcpc -i eth0 -t 3 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid &>/dev/null - ip a show dev eth0|grep inet - ) || ( - # failed to apply dynamic addr, need a available static addr to visit the LAN - inet=$RESERVE_INET - addr=${inet%/*} - ip a add $inet brd + dev eth0 - ) || exit 1 + ip a show dev eth0 | grep inet > /dev/null || { + udhcpc -i eth0 -t 3 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid &>/dev/null + ip a show dev eth0 | grep inet > /dev/null + } || { + inet=$RESERVE_INET + addr=${inet%/*} + ip a add "$inet" brd + dev eth0 + } || exit 1 else - (udhcpc -i eth0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid) & + udhcpc -i eth0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.eth0.pid & fi - - echo "OK" } + stop() { - [[ ! -e "/run/udhcpc.eth0.pid" ]] && echo "udhcpc is not running..." && exit 1 - kill `cat /run/udhcpc.eth0.pid` + [ ! -e "/run/udhcpc.eth0.pid" ] && exit 1 + kill "$(cat /run/udhcpc.eth0.pid)" rm /run/udhcpc.eth0.pid } case "$1" in - start) - start - ;; - stop) - stop - ;; - restart|reload) - $0 stop - $0 start - ;; - doc) - cat < Date: Sun, 14 Sep 2025 11:58:35 +0200 Subject: [PATCH 32/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 2349619..b98cdd1 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -46,6 +46,7 @@ export const VirtualKeyboard = () => { const languages = [ { value: 'en', label: 'English' }, { value: 'fr', label: 'French' }, + { value: 'de', label: 'Deutsch' }, { value: 'ru', label: 'Russian' } ]; From 759abc92c9d7c40c1282906d57e13af8d690cbf6 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:02:37 +0200 Subject: [PATCH 33/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index b98cdd1..09b0e33 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -127,6 +127,13 @@ export const VirtualKeyboard = () => { // all other labels use their own code return KeyboardCodes.get(base); } + if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { + const base = key.replace('_qwertz', ''); + if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); + if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + // all other labels use their own code + return KeyboardCodes.get(base); + } const specialKey = specialKeyMap.get(key); if (specialKey) { From 309b9665cb761a233a3cfa4d70f536068c4ffbab Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:06:30 +0200 Subject: [PATCH 34/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index de75c14..006d17a 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -29,6 +29,14 @@ export const keyboardOptions = { '{shiftleft} RusZ RusX RusC RusV RusB RusN RusM RusComma RusPeriod RusSlash {shiftright}', '{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}' ], + qwertz: [ + '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', + 'Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal {backspace}', + '{tab} KeyQ KeyW KeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP BracketLeft BracketRight Backslash', + '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon Quote {enter}', + '{shiftleft} KeyY KeyX KeyC KeyV KeyB KeyN KeyM Comma Period Slash {shiftright}', + '{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}' + ], azerty: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', 'Backquote_azerty Digit1_azerty Digit2_azerty Digit3_azerty Digit4_azerty Digit5_azerty Digit6_azerty Digit7_azerty Digit8_azerty Digit9_azerty Digit0_azerty Minus_azerty Equal_azerty {backspace}', From 00d30efa457e8b6e06967c97bedab1b88282c603 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:12:44 +0200 Subject: [PATCH 35/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 006d17a..4b0e8ce 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -32,8 +32,8 @@ export const keyboardOptions = { qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', 'Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal {backspace}', - '{tab} KeyQ KeyW KeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP BracketLeft BracketRight Backslash', - '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon Quote {enter}', + '{tab} KeyQ KeyW KeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP KeyUE BracketLeft BracketRight Backslash', + '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL KeyOE KeyAE Semicolon Quote {enter}', '{shiftleft} KeyY KeyX KeyC KeyV KeyB KeyN KeyM Comma Period Slash {shiftright}', '{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}' ], From e9cccd58ea1a3e881bd60cf6a4fb6c6eb5a4b8ee Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:26:06 +0200 Subject: [PATCH 36/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 4b0e8ce..d77e9d9 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -63,6 +63,10 @@ export const keyboardOptions = { Equal: '+
=', '{backspace}': 'Backspace', + KeyUE: 'Ü', + KeyOE: 'Ö', + KeyAE: 'Ä', + '{tab}': 'Tab', KeyQ: 'Q', KeyW: 'W', From 196b9555ab94d3e02dc9f7e195c6f5b78e54f128 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:58:59 +0200 Subject: [PATCH 37/88] Update virtual-keys.ts --- .../pages/desktop/keyboard/virtual-keys.ts | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index d77e9d9..5121064 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -31,11 +31,11 @@ export const keyboardOptions = { ], qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', - 'Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal {backspace}', - '{tab} KeyQ KeyW KeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP KeyUE BracketLeft BracketRight Backslash', - '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL KeyOE KeyAE Semicolon Quote {enter}', - '{shiftleft} KeyY KeyX KeyC KeyV KeyB KeyN KeyM Comma Period Slash {shiftright}', - '{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}' + 'DEBackquote DEDigit1 DEDigit2 DEDigit3 DEDigit4 DEDigit5 DEDigit6 DEDigit7 DEDigit8 DEDigit9 DEDigit0 DEDigit00 DEDigit000 {backspace}', + '{tab} DEKeyQ KeyW DEKeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP DEKeyUE DEPlus', + '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL DEKeyOE DEKeyAE DEHashtag {enter}', + '{shiftleft} DEaufrechtstrich KeyY KeyX KeyC KeyV KeyB KeyN KeyM DEComma DEPunkt DEMinus {shiftright}', + '{DEcontrolleft} {winleft} {altleft} {space} {DEaltright} {winright} {menu} {DEcontrolright}' ], azerty: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', @@ -63,9 +63,35 @@ export const keyboardOptions = { Equal: '+
=', '{backspace}': 'Backspace', - KeyUE: 'Ü', - KeyOE: 'Ö', - KeyAE: 'Ä', + DEBackquote: '^
°', + DEDigit1: '1 !', + DEDigit2: '2 "', + DEDigit3: '3 §', + DEDigit4: '4 $', + DEDigit5: '5 %', + DEDigit6: '6 &', + DEDigit7: '7{/', + DEDigit8: '8[(', + DEDigit9: '9])', + DEDigit0: '0}=', + DEDigit00: 'ß\\?', + DEDigit000: '´ `', + + DEKeyQ: 'Q @', + DEKeyE: 'E €', + DEPlus: '+~*', + DEHashtag: '# \'', + DEaufrechtstrich: '<|>', + DEComma: ', ;', + DEPunkt: '. :', + DEMinus: '- _', + '{DEcontrolleft}': 'STRG', + '{DEaltright}': 'ALT GR', + '{DEcontrolright}': 'STRG', + + DEKeyUE: 'Ü', + DEKeyOE: 'Ö', + DEKeyAE: 'Ä', '{tab}': 'Tab', KeyQ: 'Q', From b9c85fad7b5ef71aa0b8fd290d5ca8b4c74c6fed Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 13:00:06 +0200 Subject: [PATCH 38/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 09b0e33..59289b2 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -66,6 +66,7 @@ export const VirtualKeyboard = () => { const layoutMap = new Map([ ['en', 'default'], ['ru', 'rus'], + ['de', 'qwertz'], ['fr', 'azerty'] ]); From acf366d61ee850084b993d11ca4855e474040288 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:03:09 +0200 Subject: [PATCH 39/88] Update mappings.ts --- web/src/pages/desktop/keyboard/mappings.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/pages/desktop/keyboard/mappings.ts b/web/src/pages/desktop/keyboard/mappings.ts index 11f2b18..f4daa53 100644 --- a/web/src/pages/desktop/keyboard/mappings.ts +++ b/web/src/pages/desktop/keyboard/mappings.ts @@ -153,6 +153,11 @@ export const KeyboardCodes: Map = new Map([ ['ShiftRight', 229], ['AltRight', 230], ['MetaRight', 231] + + // DE-spezifische Tokens (wichtig für qwertz-Layout) + ['{DEcontrolleft}', 'ControlLeft'], // wird in display als 'STRG' gezeigt + ['{DEcontrolright}', 'ControlRight'], + ['{DEaltright}', 'AltRight'] // AltGr ]); export const ModifierCodes: Map = new Map([ From d8c4de9a01d3302943c6aa9b6f800cf16d47c18c Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:04:10 +0200 Subject: [PATCH 40/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 5121064..1e3987f 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -327,7 +327,12 @@ export const modifierKeys = [ '{controlright}', '{altright}', '{metaright}', - '{winright}' + '{winright}', + + // DE-spezifische Modifier-Tokens (für qwertz-Layout) + '{DEcontrolleft}', + '{DEcontrolright}', + '{DEaltright}' ]; // double line display buttons From 3de21f295b2b70ee8801ba4f9a92f7a55dc511be Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:04:26 +0200 Subject: [PATCH 41/88] Update mappings.ts --- web/src/pages/desktop/keyboard/mappings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/desktop/keyboard/mappings.ts b/web/src/pages/desktop/keyboard/mappings.ts index f4daa53..b701ebf 100644 --- a/web/src/pages/desktop/keyboard/mappings.ts +++ b/web/src/pages/desktop/keyboard/mappings.ts @@ -152,7 +152,7 @@ export const KeyboardCodes: Map = new Map([ ['ControlRight', 228], ['ShiftRight', 229], ['AltRight', 230], - ['MetaRight', 231] + ['MetaRight', 231], // DE-spezifische Tokens (wichtig für qwertz-Layout) ['{DEcontrolleft}', 'ControlLeft'], // wird in display als 'STRG' gezeigt From 0e30b628948d2cc4421d3971478ae1b8ef99eebd Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:07:28 +0200 Subject: [PATCH 42/88] Update mappings.ts --- web/src/pages/desktop/keyboard/mappings.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/src/pages/desktop/keyboard/mappings.ts b/web/src/pages/desktop/keyboard/mappings.ts index b701ebf..11f2b18 100644 --- a/web/src/pages/desktop/keyboard/mappings.ts +++ b/web/src/pages/desktop/keyboard/mappings.ts @@ -152,12 +152,7 @@ export const KeyboardCodes: Map = new Map([ ['ControlRight', 228], ['ShiftRight', 229], ['AltRight', 230], - ['MetaRight', 231], - - // DE-spezifische Tokens (wichtig für qwertz-Layout) - ['{DEcontrolleft}', 'ControlLeft'], // wird in display als 'STRG' gezeigt - ['{DEcontrolright}', 'ControlRight'], - ['{DEaltright}', 'AltRight'] // AltGr + ['MetaRight', 231] ]); export const ModifierCodes: Map = new Map([ From 8310e43b31aa4855c563552701c677d604490baa Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:08:53 +0200 Subject: [PATCH 43/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 1e3987f..8565675 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -313,7 +313,12 @@ export const specialKeyMap = new Map([ ['{arrowright}', 'ArrowRight'], ['{arrowleft}', 'ArrowLeft'], ['{arrowdown}', 'ArrowDown'], - ['{arrowup}', 'ArrowUp'] + ['{arrowup}', 'ArrowUp'], + + //DE + ['{DEcontrolleft}', 'ControlLeft'], + ['{DEcontrolright}', 'ControlRight'], + ['{DEaltright}', 'AltRight'] ]); // modifier keys From 8fc71a2e362b0e353a2f66c012d99d16019ae542 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:14:48 +0200 Subject: [PATCH 44/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 59289b2..907b203 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -130,8 +130,10 @@ export const VirtualKeyboard = () => { } if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { const base = key.replace('_qwertz', ''); + // Y ↔ Z Tausch if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + // all other labels use their own code return KeyboardCodes.get(base); } From 90068a3c2d83406f334a6905fecff2f4c56fe728 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:16:22 +0200 Subject: [PATCH 45/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 907b203..288fc01 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -131,8 +131,8 @@ export const VirtualKeyboard = () => { if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { const base = key.replace('_qwertz', ''); // Y ↔ Z Tausch - if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); - if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + //if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); + //if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); // all other labels use their own code return KeyboardCodes.get(base); From 1016853d25a44a8b820801d27e4e6bed06785650 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:54:13 +0200 Subject: [PATCH 46/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 288fc01..907b203 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -131,8 +131,8 @@ export const VirtualKeyboard = () => { if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { const base = key.replace('_qwertz', ''); // Y ↔ Z Tausch - //if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); - //if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); + if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); // all other labels use their own code return KeyboardCodes.get(base); From 48c941fb86f335243fcbec8502233640e8c670b9 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:01:34 +0200 Subject: [PATCH 47/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 907b203..c0956dc 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -118,6 +118,9 @@ export const VirtualKeyboard = () => { } function getKeyCode(key: string) { + console.log('Key: ', key); + console.log('KeyboardLanguage: ', keyboardLanguage); + // AZERTY: swap A↔Q and Z↔W on French physical positions if (keyboardLanguage === 'fr' && key.endsWith('_azerty')) { const base = key.replace('_azerty', ''); From c3a9e9d4020da0dc196bf2944088ef916a8a2c9d Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:05:44 +0200 Subject: [PATCH 48/88] Update virtual-keys.ts --- .../pages/desktop/keyboard/virtual-keys.ts | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 8565675..775557f 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -31,11 +31,11 @@ export const keyboardOptions = { ], qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', - 'DEBackquote DEDigit1 DEDigit2 DEDigit3 DEDigit4 DEDigit5 DEDigit6 DEDigit7 DEDigit8 DEDigit9 DEDigit0 DEDigit00 DEDigit000 {backspace}', - '{tab} DEKeyQ KeyW DEKeyE KeyR KeyT KeyZ KeyU KeyI KeyO KeyP DEKeyUE DEPlus', - '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL DEKeyOE DEKeyAE DEHashtag {enter}', - '{shiftleft} DEaufrechtstrich KeyY KeyX KeyC KeyV KeyB KeyN KeyM DEComma DEPunkt DEMinus {shiftright}', - '{DEcontrolleft} {winleft} {altleft} {space} {DEaltright} {winright} {menu} {DEcontrolright}' + 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Digit00_qwertz Digit000_qwertz {backspace}', + '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ KeyU KeyI KeyO KeyP KeyUE_qwertz Plus_qwertz', + '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL KeyOE_qwertz KeyAE_qwertz Hashtag_qwertz {enter}', + '{shiftleft} aufrechtstrich_qwertz KeyY KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Punkt_qwertz Minus_qwertz {shiftright}', + '{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}' ], azerty: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', @@ -63,35 +63,35 @@ export const keyboardOptions = { Equal: '+
=', '{backspace}': 'Backspace', - DEBackquote: '^
°', - DEDigit1: '1 !', - DEDigit2: '2 "', - DEDigit3: '3 §', - DEDigit4: '4 $', - DEDigit5: '5 %', - DEDigit6: '6 &', - DEDigit7: '7{/', - DEDigit8: '8[(', - DEDigit9: '9])', - DEDigit0: '0}=', - DEDigit00: 'ß\\?', - DEDigit000: '´ `', + Backquote_qwertz: '^
°', + Digit1_qwertz: '1 !', + Digit2_qwertz: '2 "', + Digit3_qwertz: '3 §', + Digit4_qwertz: '4 $', + Digit5_qwertz: '5 %', + Digit6_qwertz: '6 &', + Digit7_qwertz: '7{/', + Digit8_qwertz: '8[(', + Digit9_qwertz: '9])', + Digit0_qwertz: '0}=', + Digit00_qwertz: 'ß\\?', + Digit000_qwertz: '´ `', - DEKeyQ: 'Q @', - DEKeyE: 'E €', - DEPlus: '+~*', - DEHashtag: '# \'', - DEaufrechtstrich: '<|>', - DEComma: ', ;', - DEPunkt: '. :', - DEMinus: '- _', - '{DEcontrolleft}': 'STRG', - '{DEaltright}': 'ALT GR', - '{DEcontrolright}': 'STRG', + KeyQ_qwertz: 'Q @', + KeyE_qwertz: 'E €', + Plus_qwertz: '+~*', + Hashtag_qwertz: '# \'', + aufrechtstrich_qwertz: '<|>', + Comma_qwertz: ', ;', + Punkt_qwertz: '. :', + Minus_qwertz: '- _', + '{controlleft_qwertz}': 'STRG', + '{altright_qwertz}': 'ALT GR', + '{controlright_qwertz}': 'STRG', - DEKeyUE: 'Ü', - DEKeyOE: 'Ö', - DEKeyAE: 'Ä', + KeyUE_qwertz: 'Ü', + KeyOE_qwertz: 'Ö', + KeyAE_qwertz: 'Ä', '{tab}': 'Tab', KeyQ: 'Q', @@ -316,9 +316,9 @@ export const specialKeyMap = new Map([ ['{arrowup}', 'ArrowUp'], //DE - ['{DEcontrolleft}', 'ControlLeft'], - ['{DEcontrolright}', 'ControlRight'], - ['{DEaltright}', 'AltRight'] + ['{controlleft_qwertz}', 'ControlLeft'], + ['{controlright_qwertz}', 'ControlRight'], + ['{altright_qwertz}', 'AltRight'] ]); // modifier keys @@ -335,9 +335,9 @@ export const modifierKeys = [ '{winright}', // DE-spezifische Modifier-Tokens (für qwertz-Layout) - '{DEcontrolleft}', - '{DEcontrolright}', - '{DEaltright}' + '{controlleft_qwertz}', + '{controlright_qwertz}', + '{altright_qwertz}' ]; // double line display buttons From 305a75d1a7f2a07f0fa85fbac0cd726b7e4cab04 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:10:40 +0200 Subject: [PATCH 49/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 775557f..ad1e272 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -32,9 +32,9 @@ export const keyboardOptions = { qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Digit00_qwertz Digit000_qwertz {backspace}', - '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ KeyU KeyI KeyO KeyP KeyUE_qwertz Plus_qwertz', + '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP KeyUE_qwertz Plus_qwertz', '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL KeyOE_qwertz KeyAE_qwertz Hashtag_qwertz {enter}', - '{shiftleft} aufrechtstrich_qwertz KeyY KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Punkt_qwertz Minus_qwertz {shiftright}', + '{shiftleft} aufrechtstrich_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Punkt_qwertz Minus_qwertz {shiftright}', '{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}' ], azerty: [ @@ -77,6 +77,9 @@ export const keyboardOptions = { Digit00_qwertz: 'ß\\?', Digit000_qwertz: '´ `', + KeyY_qwertz: 'Y', + KeyZ_qwertz: 'Z', + KeyQ_qwertz: 'Q @', KeyE_qwertz: 'E €', Plus_qwertz: '+~*', From f8d8b04de35676b0b3d3558d94d4298b57660856 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:15:39 +0200 Subject: [PATCH 50/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index c0956dc..2d397e1 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -136,6 +136,7 @@ export const VirtualKeyboard = () => { // Y ↔ Z Tausch if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); + if (base === 'KeyUE') return KeyboardCodes.get('KeyBracketLeft'); // all other labels use their own code return KeyboardCodes.get(base); From 6ca0fa76264746afde6c647b013c2a726d76703a Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:25:05 +0200 Subject: [PATCH 51/88] Update virtual-keys.ts --- .../pages/desktop/keyboard/virtual-keys.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index ad1e272..0fe9030 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -32,9 +32,9 @@ export const keyboardOptions = { qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Digit00_qwertz Digit000_qwertz {backspace}', - '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP KeyUE_qwertz Plus_qwertz', - '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL KeyOE_qwertz KeyAE_qwertz Hashtag_qwertz {enter}', - '{shiftleft} aufrechtstrich_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Punkt_qwertz Minus_qwertz {shiftright}', + '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz Plus_qwertz', + '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon_qwertz Quote_qwertz Backslash_qwertz {enter}', + '{shiftleft} IntlBackslash_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Period_qwertz Slash_qwertz {shiftright}', '{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}' ], azerty: [ @@ -83,18 +83,18 @@ export const keyboardOptions = { KeyQ_qwertz: 'Q @', KeyE_qwertz: 'E €', Plus_qwertz: '+~*', - Hashtag_qwertz: '# \'', - aufrechtstrich_qwertz: '<|>', + Backslash_qwertz: '# \'', + IntlBackslash_qwertz: '<|>', Comma_qwertz: ', ;', - Punkt_qwertz: '. :', - Minus_qwertz: '- _', + Period_qwertz: '. :', + Slash_qwertz: '- _', '{controlleft_qwertz}': 'STRG', '{altright_qwertz}': 'ALT GR', '{controlright_qwertz}': 'STRG', - KeyUE_qwertz: 'Ü', - KeyOE_qwertz: 'Ö', - KeyAE_qwertz: 'Ä', + BracketLeft_qwertz: 'Ü', + Semicolon_qwertz: 'Ö', + Quote_qwertz: 'Ä', '{tab}': 'Tab', KeyQ: 'Q', From e86d385521c6f6c3cf624515f44d404a9bf81f7e Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:25:08 +0200 Subject: [PATCH 52/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index 2d397e1..d5a7d34 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -133,10 +133,9 @@ export const VirtualKeyboard = () => { } if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) { const base = key.replace('_qwertz', ''); - // Y ↔ Z Tausch + // Tausch if (base === 'KeyZ') return KeyboardCodes.get('KeyY'); if (base === 'KeyY') return KeyboardCodes.get('KeyZ'); - if (base === 'KeyUE') return KeyboardCodes.get('KeyBracketLeft'); // all other labels use their own code return KeyboardCodes.get(base); From bc640845168e383ac1d83f82081775a8228487b0 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:28:16 +0200 Subject: [PATCH 53/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 0fe9030..4fcfaf4 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -32,7 +32,7 @@ export const keyboardOptions = { qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Digit00_qwertz Digit000_qwertz {backspace}', - '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz Plus_qwertz', + '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz BracketRight_qwertz', '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon_qwertz Quote_qwertz Backslash_qwertz {enter}', '{shiftleft} IntlBackslash_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Period_qwertz Slash_qwertz {shiftright}', '{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}' @@ -82,7 +82,7 @@ export const keyboardOptions = { KeyQ_qwertz: 'Q @', KeyE_qwertz: 'E €', - Plus_qwertz: '+~*', + BracketRight_qwertz: '+~*', Backslash_qwertz: '# \'', IntlBackslash_qwertz: '<|>', Comma_qwertz: ', ;', From fb9f127289c0e76c20a8e148eef281366130d12d Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:31:41 +0200 Subject: [PATCH 54/88] Update virtual-keys.ts --- web/src/pages/desktop/keyboard/virtual-keys.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 4fcfaf4..1355ff5 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -31,7 +31,7 @@ export const keyboardOptions = { ], qwertz: [ '{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12', - 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Digit00_qwertz Digit000_qwertz {backspace}', + 'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Minus_qwertz Equal_qwertz {backspace}', '{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz BracketRight_qwertz', '{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon_qwertz Quote_qwertz Backslash_qwertz {enter}', '{shiftleft} IntlBackslash_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Period_qwertz Slash_qwertz {shiftright}', @@ -74,8 +74,8 @@ export const keyboardOptions = { Digit8_qwertz: '8[(', Digit9_qwertz: '9])', Digit0_qwertz: '0}=', - Digit00_qwertz: 'ß\\?', - Digit000_qwertz: '´ `', + Minus_qwertz: 'ß\\?', + Equal_qwertz: '´ `', KeyY_qwertz: 'Y', KeyZ_qwertz: 'Z', From 7d3df10915d76cd8b1d4cf53e0890c155f02a2e9 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:41:45 +0200 Subject: [PATCH 55/88] Update virtual-keys.ts --- .../pages/desktop/keyboard/virtual-keys.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keys.ts b/web/src/pages/desktop/keyboard/virtual-keys.ts index 1355ff5..cc1a233 100644 --- a/web/src/pages/desktop/keyboard/virtual-keys.ts +++ b/web/src/pages/desktop/keyboard/virtual-keys.ts @@ -64,30 +64,30 @@ export const keyboardOptions = { '{backspace}': 'Backspace', Backquote_qwertz: '^
°', - Digit1_qwertz: '1 !', - Digit2_qwertz: '2 "', - Digit3_qwertz: '3 §', - Digit4_qwertz: '4 $', - Digit5_qwertz: '5 %', - Digit6_qwertz: '6 &', + Digit1_qwertz: '1
!', + Digit2_qwertz: '2
"', + Digit3_qwertz: '3
§', + Digit4_qwertz: '4
$', + Digit5_qwertz: '5
%', + Digit6_qwertz: '6
&', Digit7_qwertz: '7{/', Digit8_qwertz: '8[(', Digit9_qwertz: '9])', Digit0_qwertz: '0}=', Minus_qwertz: 'ß\\?', - Equal_qwertz: '´ `', + Equal_qwertz: '´
`', KeyY_qwertz: 'Y', KeyZ_qwertz: 'Z', - KeyQ_qwertz: 'Q @', - KeyE_qwertz: 'E €', + KeyQ_qwertz: 'Q
@', + KeyE_qwertz: 'E
€', BracketRight_qwertz: '+~*', - Backslash_qwertz: '# \'', + Backslash_qwertz: '#
\'', IntlBackslash_qwertz: '<|>', - Comma_qwertz: ', ;', - Period_qwertz: '. :', - Slash_qwertz: '- _', + Comma_qwertz: ',
;', + Period_qwertz: '.
:', + Slash_qwertz: '-
_', '{controlleft_qwertz}': 'STRG', '{altright_qwertz}': 'ALT GR', '{controlright_qwertz}': 'STRG', From f209cb1e0e6ee4c714e0e84c0c6566eac2c50b21 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:42:35 +0200 Subject: [PATCH 56/88] Update virtual-keyboard.tsx --- web/src/pages/desktop/keyboard/virtual-keyboard.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx index d5a7d34..5a586a9 100644 --- a/web/src/pages/desktop/keyboard/virtual-keyboard.tsx +++ b/web/src/pages/desktop/keyboard/virtual-keyboard.tsx @@ -118,9 +118,6 @@ export const VirtualKeyboard = () => { } function getKeyCode(key: string) { - console.log('Key: ', key); - console.log('KeyboardLanguage: ', keyboardLanguage); - // AZERTY: swap A↔Q and Z↔W on French physical positions if (keyboardLanguage === 'fr' && key.endsWith('_azerty')) { const base = key.replace('_azerty', ''); From 5fcdf888b3c32dc9cc3c626796fe3f974fea8932 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:14:52 +0200 Subject: [PATCH 57/88] Update paste.go --- server/service/hid/paste.go | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/server/service/hid/paste.go b/server/service/hid/paste.go index df64468..76bb633 100644 --- a/server/service/hid/paste.go +++ b/server/service/hid/paste.go @@ -16,11 +16,41 @@ type Char struct { type PasteReq struct { Content string `form:"content" validate:"required"` + Langue string `form:"langue"` +} + +func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { + // wenn kein lang angegeben → Standardmap zurück + if lang == "" { + return base + } + + // immer Kopie erstellen + m := copyMap(base) + + switch lang { + case "de": + // Z/Y tauschen + m['z'], m['y'] = m['y'], m['z'] + m['Z'], m['Y'] = m['Y'], m['Z'] + + // deutsche Sonderzeichen hinzufügen oder remappen + m['ä'] = Char{0, 52} // auf US-Taste ' (Apostroph) + m['Ä'] = Char{2, 52} + m['ö'] = Char{0, 51} // auf US-Taste ; + m['Ö'] = Char{2, 51} + m['ü'] = Char{0, 47} // auf US-Taste [ + m['Ü'] = Char{2, 47} + m['ß'] = Char{0, 45} // auf US-Taste - + // usw. + } + return m } func (s *Service) Paste(c *gin.Context) { var req PasteReq var rsp proto.Response + charMapLocal := LangueSwitch(charMap, req.Langue) if err := proto.ParseFormRequest(c, &req); err != nil { rsp.ErrRsp(c, -1, "invalid arguments") return @@ -34,7 +64,7 @@ func (s *Service) Paste(c *gin.Context) { keyUp := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} for _, char := range req.Content { - key, ok := charMap[char] + key, ok := charMapLocal[char] if !ok { log.Debugf("unknown key '%c' (rune: %d)", char, char) continue @@ -48,6 +78,15 @@ func (s *Service) Paste(c *gin.Context) { rsp.OkRsp(c) log.Debugf("hid paste success, total %d characters processed", len(req.Content)) } + +func copyMap(src map[rune]Char) map[rune]Char { + dst := make(map[rune]Char, len(src)) + for k, v := range src { + dst[k] = v + } + return dst +} + var charMap = map[rune]Char{ // Lowercase letters 'a': {0, 4}, 'b': {0, 5}, 'c': {0, 6}, 'd': {0, 7}, 'e': {0, 8}, From 0dc0f9ea73251601096a0919773256fde8b29ba6 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:55:14 +0200 Subject: [PATCH 58/88] Update paste.go --- server/service/hid/paste.go | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/server/service/hid/paste.go b/server/service/hid/paste.go index 76bb633..4a0c260 100644 --- a/server/service/hid/paste.go +++ b/server/service/hid/paste.go @@ -30,19 +30,29 @@ func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { switch lang { case "de": - // Z/Y tauschen - m['z'], m['y'] = m['y'], m['z'] - m['Z'], m['Y'] = m['Y'], m['Z'] + // Y tauschen + m['y'] = Char{0, 29} + m['Y'] = Char{2, 29} + + // Z tauschen + m['z'] = Char{0, 28} + m['Z'] = Char{2, 28} // deutsche Sonderzeichen hinzufügen oder remappen - m['ä'] = Char{0, 52} // auf US-Taste ' (Apostroph) - m['Ä'] = Char{2, 52} - m['ö'] = Char{0, 51} // auf US-Taste ; - m['Ö'] = Char{2, 51} - m['ü'] = Char{0, 47} // auf US-Taste [ - m['Ü'] = Char{2, 47} - m['ß'] = Char{0, 45} // auf US-Taste - - // usw. + m['\u00E4'] = Char{0, 52} // ä + m['\u00C4'] = Char{2, 52} // Ä + m['\u00F6'] = Char{0, 51} // ö + m['\u00D6'] = Char{2, 51} // Ö + m['\u00FC'] = Char{0, 47} // ü + m['\u00DC'] = Char{2, 47} // Ü + m['\u00DF'] = Char{0, 45} // ß + + //Tauschen + m['^'] = Char{0, 53} + + //neu + m['\u1FFD'] = Char{0, 53} + } return m } @@ -50,11 +60,11 @@ func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { func (s *Service) Paste(c *gin.Context) { var req PasteReq var rsp proto.Response - charMapLocal := LangueSwitch(charMap, req.Langue) if err := proto.ParseFormRequest(c, &req); err != nil { rsp.ErrRsp(c, -1, "invalid arguments") return } + charMapLocal := LangueSwitch(charMap, req.Langue) if len(req.Content) > 1024 { rsp.ErrRsp(c, -2, "content too long") return @@ -66,6 +76,7 @@ func (s *Service) Paste(c *gin.Context) { for _, char := range req.Content { key, ok := charMapLocal[char] if !ok { + rsp.ErrRsp(c, -1, "unknown character: "+string(char)) log.Debugf("unknown key '%c' (rune: %d)", char, char) continue } From 044292ccb4f67f0c19c1b07376876f810d606adc Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 28 Sep 2025 11:36:54 +0200 Subject: [PATCH 59/88] Update paste.go --- server/service/hid/paste.go | 55 +++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/server/service/hid/paste.go b/server/service/hid/paste.go index 4a0c260..7b1686a 100644 --- a/server/service/hid/paste.go +++ b/server/service/hid/paste.go @@ -48,10 +48,40 @@ func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { m['\u00DF'] = Char{0, 45} // ß //Tauschen - m['^'] = Char{0, 53} + m['^'] = Char{0, 53} // muss doppelt sein + m['/'] = Char{2, 36} // Shift + 7 + m['('] = Char{2, 37} // Shift + 8 + m['&'] = Char{2, 35} // Shift + 6 + m[')'] = Char{2, 38} // Shift + 9 + m['`'] = Char{2, 46} // Grave Accent / Backtick + m['"'] = Char{2, 31} // Shift + 2 + m['?'] = Char{2, 45} // Shift + ß + m['{'] = Char{0x40, 36} // ALt Gr + 7 + m['['] = Char{0x40, 37} // ALt Gr + 8 + m[']'] = Char{0x40, 38} // ALt Gr + 6 + m['}'] = Char{0x40, 39} // ALt Gr + 0 + m['\\'] = Char{0x40, 45} // ALt Gr + ß + m['@'] = Char{0x40, 20} // ALt Gr + q + m['+'] = Char{0, 48} // Shift + + + m['*'] = Char{2, 48} // Shift + + + m['~'] = Char{0x40, 48} // Shift + + + m['#'] = Char{0, 49} // Shift + # + m['\''] = Char{2, 49} // Shift + # + m['<'] = Char{0, 100} // Shift + < + m['>'] = Char{2, 100} // Shift + < + m['|'] = Char{0x40, 100} // ALt Gr + < + m[';'] = Char{2, 54} // Shift + , + m[':'] = Char{2, 55} // Shift + . + m['-'] = Char{0, 56} // Shift + - + m['_'] = Char{2, 56} // Shift + - //neu - m['\u1FFD'] = Char{0, 53} + m['\u00B4'] = Char{0, 46} // ´ + m['\u00B0'] = Char{2, 53} // ° + m['\u00A7'] = Char{2, 32} // § + m['\u20AC'] = Char{0x40, 8} // € + m['\u00B2'] = Char{0x40, 31} // ² + m['\u00B3'] = Char{0x40, 32} // ³ } return m @@ -76,7 +106,26 @@ func (s *Service) Paste(c *gin.Context) { for _, char := range req.Content { key, ok := charMapLocal[char] if !ok { - rsp.ErrRsp(c, -1, "unknown character: "+string(char)) + + hex := string(char) + + code := int(char) + + for i := 12; i >= 0; i -= 4 { + digit := (code >> i) & 0xF + var c byte + if digit < 10 { + c = byte('0' + digit) + } else { + c = byte('A' + (digit - 10)) + } + hex += string(c) + } + + + + + rsp.ErrRsp(c, -1, "unknown character: "+hex) log.Debugf("unknown key '%c' (rune: %d)", char, char) continue } From 310d550ff6e557da05bf9d68e5d556b7a4ba6634 Mon Sep 17 00:00:00 2001 From: Alexander-Ger-Reich <50119493+Alexander-Ger-Reich@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:03:51 +0200 Subject: [PATCH 60/88] Update paste.tsx --- web/src/pages/desktop/menu/keyboard/paste.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/web/src/pages/desktop/menu/keyboard/paste.tsx b/web/src/pages/desktop/menu/keyboard/paste.tsx index 382d66e..cd6da91 100644 --- a/web/src/pages/desktop/menu/keyboard/paste.tsx +++ b/web/src/pages/desktop/menu/keyboard/paste.tsx @@ -8,6 +8,9 @@ import { useTranslation } from 'react-i18next'; import { paste } from '@/api/hid'; import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts'; +import { Button, Input, Modal, Select, type InputRef } from 'antd'; +const { Option } = Select; + const { TextArea } = Input; export const Paste = () => { @@ -19,6 +22,7 @@ export const Paste = () => { const [status, setStatus] = useState<'' | 'error'>(''); const [isLoading, setIsLoading] = useState(false); const [errMsg, setErrMsg] = useState(''); + const [langue, setLangue] = useState('de'); const inputRef = useRef(null); @@ -88,6 +92,15 @@ export const Paste = () => { >
{t('keyboard.tips')}
+ +